Add migration planning tools and step899-908 test suite

This commit is contained in:
Bill
2026-02-22 19:21:19 -07:00
parent 18c9660baf
commit 33103b5d71
23 changed files with 1125 additions and 4 deletions

View File

@@ -38,6 +38,10 @@
#include "graduation/UpgradePriorityPolicy.h"
#include "graduation/RuntimeAssumptionExtractor.h"
#include "graduation/RuntimeProfileStore.h"
#include "graduation/MigrationStepSequencer.h"
#include "graduation/MigrationFeasibilityEngine.h"
#include "graduation/MigrationPathCandidate.h"
#include "graduation/MigrationProgressTracker.h"
#include "TaskCompletionMetrics.h"
#include "ABTestComparison.h"
#include "LanguageCapabilityMatrix.h"
@@ -282,6 +286,7 @@ private:
std::string recordingSessionId_;
PairUpgradeQueue upgradeQueue_;
RuntimeProfileStore runtimeProfileStore_;
MigrationProgressTracker migrationTracker_;
// ---------------------------------------------------------------
// Protocol handlers
@@ -747,4 +752,5 @@ private:
#include "mcp/RegisterCostPlanningTools.h"
#include "mcp/RegisterUpgradeQueueTools.h"
#include "mcp/RegisterRuntimePackTools.h"
#include "mcp/RegisterMigrationPlanningTools.h"
#include "mcp/RegisterOnboardingAndAllTools.h"

View File

@@ -0,0 +1,63 @@
#pragma once
// Step 904: Migration audit log — append-only event log for migration activity.
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
struct MigrationAuditEntry {
std::string entryId; // "AE-<N>"
std::string pairId;
std::string event;
std::string timestamp; // ISO-8601 string supplied by caller
std::string detail;
};
class MigrationAuditLog {
std::vector<MigrationAuditEntry> entries_;
int nextId_ = 1;
public:
void append(const MigrationAuditEntry& entry) {
entries_.push_back(entry);
}
const std::vector<MigrationAuditEntry>& entries() const { return entries_; }
std::size_t count() const { return entries_.size(); }
void clear() {
entries_.clear();
nextId_ = 1;
}
std::vector<MigrationAuditEntry> filterByPair(const std::string& pairId) const {
std::vector<MigrationAuditEntry> out;
for (const auto& e : entries_)
if (e.pairId == pairId) out.push_back(e);
return out;
}
// Factory: creates a new entry with a generated entryId.
// The caller is responsible for inserting into the log via append().
MigrationAuditEntry makeEntry(const std::string& pairId,
const std::string& event,
const std::string& detail) {
MigrationAuditEntry e;
e.entryId = "AE-" + std::to_string(nextId_++);
e.pairId = pairId;
e.event = event;
e.timestamp = "1970-01-01T00:00:00Z"; // caller should override
e.detail = detail;
return e;
}
static nlohmann::json toJson(const MigrationAuditEntry& e) {
return {
{"entry_id", e.entryId},
{"pair_id", e.pairId},
{"event", e.event},
{"timestamp", e.timestamp},
{"detail", e.detail}
};
}
};

View File

@@ -0,0 +1,71 @@
#pragma once
// Step 906: Migration checkpoint store — saves and restores migration checkpoints.
#include <string>
#include <unordered_map>
#include <nlohmann/json.hpp>
struct MigrationCheckpoint {
std::string checkpointId; // "CP-<N>"
std::string pairId;
int stepIndex = 0;
std::string state; // "saved" | "restored" | "expired"
};
class MigrationCheckpointStore {
std::unordered_map<std::string, MigrationCheckpoint> store_;
int nextId_ = 1;
public:
// Persist a checkpoint for pairId at stepIndex. Returns the created checkpoint.
MigrationCheckpoint save(const std::string& pairId, int stepIndex) {
MigrationCheckpoint cp;
cp.checkpointId = "CP-" + std::to_string(nextId_++);
cp.pairId = pairId;
cp.stepIndex = stepIndex;
cp.state = "saved";
store_[cp.checkpointId] = cp;
return cp;
}
// Restore a checkpoint by ID: sets state to "restored". Returns false if not found.
bool restore(const std::string& checkpointId) {
auto it = store_.find(checkpointId);
if (it == store_.end()) return false;
if (it->second.state == "expired") return false;
it->second.state = "restored";
return true;
}
// Expire a checkpoint by ID. Returns false if not found.
bool expire(const std::string& checkpointId) {
auto it = store_.find(checkpointId);
if (it == store_.end()) return false;
it->second.state = "expired";
return true;
}
bool has(const std::string& checkpointId) const {
return store_.count(checkpointId) > 0;
}
const MigrationCheckpoint* get(const std::string& checkpointId) const {
auto it = store_.find(checkpointId);
return (it != store_.end()) ? &it->second : nullptr;
}
std::size_t count() const { return store_.size(); }
void clear() {
store_.clear();
nextId_ = 1;
}
static nlohmann::json toJson(const MigrationCheckpoint& cp) {
return {
{"checkpoint_id", cp.checkpointId},
{"pair_id", cp.pairId},
{"step_index", cp.stepIndex},
{"state", cp.state}
};
}
};

View File

@@ -0,0 +1,74 @@
#pragma once
// Step 901: Migration feasibility engine — scores a runtime pair and emits a verdict.
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
struct FeasibilityResult {
std::string pairId;
float score = 0.0f; // 0.0 1.0
std::string verdict; // "feasible" | "marginal" | "infeasible"
std::vector<std::string> blockers; // reasons that reduce feasibility
};
class MigrationFeasibilityEngine {
public:
// Assess migration feasibility for a runtime pair.
// riskScore : normalised risk [0.0, 1.0]; higher means more risk
// hasSourcePack : true when a RuntimeSemanticsPack exists for srcRuntime
// hasTargetPack : true when a RuntimeSemanticsPack exists for tgtRuntime
static FeasibilityResult assess(const std::string& pairId,
const std::string& srcRuntime,
const std::string& tgtRuntime,
float riskScore,
bool hasSourcePack,
bool hasTargetPack) {
FeasibilityResult r;
r.pairId = pairId;
// Base score starts at 1.0 and is reduced by risk and missing data.
float score = 1.0f;
// Clamp riskScore to [0, 1].
if (riskScore < 0.0f) riskScore = 0.0f;
if (riskScore > 1.0f) riskScore = 1.0f;
score -= riskScore * 0.5f; // risk contributes up to 0.5 penalty
if (!hasSourcePack) {
score -= 0.2f;
r.blockers.push_back("missing_pack:" + srcRuntime);
}
if (!hasTargetPack) {
score -= 0.2f;
r.blockers.push_back("missing_pack:" + tgtRuntime);
}
if (srcRuntime.empty()) {
score -= 0.1f;
r.blockers.push_back("empty_source_runtime");
}
if (tgtRuntime.empty()) {
score -= 0.1f;
r.blockers.push_back("empty_target_runtime");
}
if (score < 0.0f) score = 0.0f;
r.score = score;
if (score >= 0.6f) r.verdict = "feasible";
else if (score >= 0.35f) r.verdict = "marginal";
else r.verdict = "infeasible";
return r;
}
static nlohmann::json toJson(const FeasibilityResult& r) {
nlohmann::json blockerArr = nlohmann::json::array();
for (const auto& b : r.blockers) blockerArr.push_back(b);
return {
{"pair_id", r.pairId},
{"score", r.score},
{"verdict", r.verdict},
{"blockers", blockerArr}
};
}
};

View File

@@ -0,0 +1,46 @@
#pragma once
// Step 899: Migration path candidate — source/target runtime pair with feasibility score.
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
struct MigrationPathCandidate {
std::string pairId;
std::string sourceRuntime;
std::string targetRuntime;
std::vector<std::string> steps;
float feasibility = 0.0f; // 0.0 (impossible) to 1.0 (fully feasible)
std::string rationale;
};
class MigrationPathCandidateFactory {
public:
static MigrationPathCandidate make(const std::string& pairId,
const std::string& sourceRuntime,
const std::string& targetRuntime,
const std::vector<std::string>& steps,
float feasibility,
const std::string& rationale) {
MigrationPathCandidate c;
c.pairId = pairId;
c.sourceRuntime = sourceRuntime;
c.targetRuntime = targetRuntime;
c.steps = steps;
c.feasibility = feasibility < 0.0f ? 0.0f : (feasibility > 1.0f ? 1.0f : feasibility);
c.rationale = rationale;
return c;
}
static nlohmann::json toJson(const MigrationPathCandidate& c) {
nlohmann::json stepArr = nlohmann::json::array();
for (const auto& s : c.steps) stepArr.push_back(s);
return {
{"pair_id", c.pairId},
{"source_runtime", c.sourceRuntime},
{"target_runtime", c.targetRuntime},
{"steps", stepArr},
{"feasibility", c.feasibility},
{"rationale", c.rationale}
};
}
};

View File

@@ -0,0 +1,27 @@
#pragma once
// Step 900: Migration path ranker — sorts candidates by feasibility descending.
#include <string>
#include <vector>
#include <algorithm>
#include "MigrationPathCandidate.h"
class MigrationPathRanker {
public:
// Returns a new vector sorted by feasibility descending (highest first).
static std::vector<MigrationPathCandidate> rank(
std::vector<MigrationPathCandidate> candidates) {
std::stable_sort(candidates.begin(), candidates.end(),
[](const MigrationPathCandidate& a, const MigrationPathCandidate& b) {
return a.feasibility > b.feasibility;
});
return candidates;
}
// Returns the top N candidates after ranking. If n >= size, returns all.
static std::vector<MigrationPathCandidate> topN(
std::vector<MigrationPathCandidate> candidates, std::size_t n) {
auto ranked = rank(std::move(candidates));
if (n < ranked.size()) ranked.resize(n);
return ranked;
}
};

View File

@@ -0,0 +1,78 @@
#pragma once
// Step 905: Migration progress tracker — tracks per-pair step completion state.
#include <string>
#include <unordered_map>
#include <nlohmann/json.hpp>
struct MigrationProgress {
std::string pairId;
int totalSteps = 0;
int completedSteps = 0;
std::string status; // "pending" | "in_progress" | "complete" | "failed"
};
class MigrationProgressTracker {
std::unordered_map<std::string, MigrationProgress> records_;
public:
// Initialise tracking for a pair. Status becomes "pending".
void start(const std::string& pairId, int totalSteps) {
MigrationProgress p;
p.pairId = pairId;
p.totalSteps = totalSteps > 0 ? totalSteps : 1;
p.completedSteps = 0;
p.status = "pending";
records_[pairId] = p;
}
// Increment completedSteps by 1. Sets status to "in_progress" if not already complete.
void advance(const std::string& pairId) {
auto it = records_.find(pairId);
if (it == records_.end()) return;
auto& p = it->second;
if (p.status == "complete" || p.status == "failed") return;
p.completedSteps++;
p.status = "in_progress";
if (p.completedSteps >= p.totalSteps) p.status = "complete";
}
// Force status to "complete".
void complete(const std::string& pairId) {
auto it = records_.find(pairId);
if (it == records_.end()) return;
it->second.completedSteps = it->second.totalSteps;
it->second.status = "complete";
}
// Force status to "failed".
void fail(const std::string& pairId) {
auto it = records_.find(pairId);
if (it == records_.end()) return;
it->second.status = "failed";
}
// Returns pointer to progress record, or nullptr if pair not tracked.
const MigrationProgress* get(const std::string& pairId) const {
auto it = records_.find(pairId);
return (it != records_.end()) ? &it->second : nullptr;
}
// Returns completion percentage [0.0, 100.0], or -1.0 if pair not tracked.
float percentComplete(const std::string& pairId) const {
const auto* p = get(pairId);
if (!p || p->totalSteps == 0) return -1.0f;
return (static_cast<float>(p->completedSteps) /
static_cast<float>(p->totalSteps)) * 100.0f;
}
std::size_t count() const { return records_.size(); }
static nlohmann::json toJson(const MigrationProgress& p) {
return {
{"pair_id", p.pairId},
{"total_steps", p.totalSteps},
{"completed_steps", p.completedSteps},
{"status", p.status}
};
}
};

View File

@@ -0,0 +1,53 @@
#pragma once
// Step 903: Migration rollback policy — defines what happens when a migration step fails.
#include <string>
#include <nlohmann/json.hpp>
struct RollbackPolicy {
std::string pairId;
std::string triggerCondition; // human-readable description of when policy fires
std::string action; // "abort" | "checkpoint" | "retry"
int maxRetries = 0;
};
class MigrationRollbackPolicy {
public:
// Default policy: checkpoint on failure, allow up to 3 retries.
static RollbackPolicy defaultPolicy(const std::string& pairId) {
RollbackPolicy p;
p.pairId = pairId;
p.triggerCondition = "any_step_failure";
p.action = "checkpoint";
p.maxRetries = 3;
return p;
}
// Strict policy: abort immediately on first failure, no retries.
static RollbackPolicy strict(const std::string& pairId) {
RollbackPolicy p;
p.pairId = pairId;
p.triggerCondition = "any_step_failure";
p.action = "abort";
p.maxRetries = 0;
return p;
}
// Retry-only policy: retry up to N times before aborting.
static RollbackPolicy retryPolicy(const std::string& pairId, int maxRetries) {
RollbackPolicy p;
p.pairId = pairId;
p.triggerCondition = "any_step_failure";
p.action = "retry";
p.maxRetries = maxRetries > 0 ? maxRetries : 1;
return p;
}
static nlohmann::json toJson(const RollbackPolicy& p) {
return {
{"pair_id", p.pairId},
{"trigger_condition", p.triggerCondition},
{"action", p.action},
{"max_retries", p.maxRetries}
};
}
};

View File

@@ -0,0 +1,69 @@
#pragma once
// Step 902: Migration step sequencer — produces an ordered list of migration steps.
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
struct MigrationStep {
std::string stepId;
std::string description;
std::string prerequisite; // empty string means no prerequisite
bool required = true;
};
class MigrationStepSequencer {
public:
// Generate a canonical step sequence for migrating srcRuntime -> tgtRuntime.
// Always includes at minimum: analyze_source, adapt_semantics, verify_target.
static std::vector<MigrationStep> sequence(const std::string& pairId,
const std::string& srcRuntime,
const std::string& tgtRuntime) {
std::vector<MigrationStep> steps;
steps.push_back({
pairId + ":analyze_source",
"Analyze source runtime (" + srcRuntime + ") semantics and dependencies",
"",
true
});
steps.push_back({
pairId + ":adapt_semantics",
"Adapt semantic constructs from " + srcRuntime + " to " + tgtRuntime,
pairId + ":analyze_source",
true
});
steps.push_back({
pairId + ":verify_target",
"Verify adapted code against target runtime (" + tgtRuntime + ") constraints",
pairId + ":adapt_semantics",
true
});
// Optional enrichment steps when both runtimes are non-empty.
if (!srcRuntime.empty() && !tgtRuntime.empty()) {
steps.push_back({
pairId + ":run_equivalence_check",
"Run equivalence checks to validate behavioral parity post-migration",
pairId + ":verify_target",
false
});
steps.push_back({
pairId + ":generate_migration_report",
"Generate migration report summarising findings and any residual risks",
pairId + ":run_equivalence_check",
false
});
}
return steps;
}
static nlohmann::json toJson(const MigrationStep& s) {
return {
{"step_id", s.stepId},
{"description", s.description},
{"prerequisite", s.prerequisite},
{"required", s.required}
};
}
};

View File

@@ -0,0 +1,87 @@
// Sprint 67: Migration Planning MCP tools (Steps 907)
// Included inside MCPServer class body.
void registerMigrationPlanningTools() {
tools_.push_back({"whetstone_plan_migration_path",
"Generate a cross-runtime migration path plan with step sequence and feasibility.",
{{"type", "object"}, {"properties", {
{"pair_id", {{"type", "string"}}},
{"source_runtime", {{"type", "string"}}},
{"target_runtime", {{"type", "string"}}},
{"risk_score", {{"type", "number"}}}
}}, {"required", nlohmann::json::array({"pair_id", "source_runtime", "target_runtime"})}}
});
toolHandlers_["whetstone_plan_migration_path"] =
[this](const nlohmann::json& args) { return runPlanMigrationPath(args); };
tools_.push_back({"whetstone_track_migration_progress",
"Track migration progress for a language pair (start/advance/complete/fail/get).",
{{"type", "object"}, {"properties", {
{"pair_id", {{"type", "string"}}},
{"action", {{"type", "string"}}},
{"total_steps", {{"type", "integer"}}}
}}, {"required", nlohmann::json::array({"pair_id", "action"})}}
});
toolHandlers_["whetstone_track_migration_progress"] =
[this](const nlohmann::json& args) { return runTrackMigrationProgress(args); };
}
nlohmann::json runPlanMigrationPath(const nlohmann::json& args) {
if (!args.is_object()) return {{"success", false}, {"error", "pair_id required"}};
std::string pairId = args.value("pair_id", "");
std::string sourceRuntime = args.value("source_runtime", "");
std::string targetRuntime = args.value("target_runtime", "");
if (pairId.empty()) return {{"success", false}, {"error", "pair_id required"}};
if (sourceRuntime.empty()) return {{"success", false}, {"error", "source_runtime required"}};
if (targetRuntime.empty()) return {{"success", false}, {"error", "target_runtime required"}};
float riskScore = args.value("risk_score", 0.2f);
// Generate step sequence
auto seqSteps = MigrationStepSequencer::sequence(pairId, sourceRuntime, targetRuntime);
std::vector<std::string> stepIds;
for (const auto& s : seqSteps) stepIds.push_back(s.stepId);
// Assess feasibility
auto feas = MigrationFeasibilityEngine::assess(pairId, sourceRuntime, targetRuntime,
riskScore, true, true);
// Build candidate
auto candidate = MigrationPathCandidateFactory::make(
pairId, sourceRuntime, targetRuntime, stepIds, feas.score, feas.verdict);
nlohmann::json stepsArr = nlohmann::json::array();
for (const auto& s : seqSteps) stepsArr.push_back(MigrationStepSequencer::toJson(s));
return {{"success", true}, {"pair_id", pairId},
{"source_runtime", sourceRuntime}, {"target_runtime", targetRuntime},
{"feasibility", feas.score}, {"verdict", feas.verdict},
{"steps", stepsArr}, {"step_count", (int)seqSteps.size()}};
}
nlohmann::json runTrackMigrationProgress(const nlohmann::json& args) {
if (!args.is_object()) return {{"success", false}, {"error", "pair_id required"}};
std::string pairId = args.value("pair_id", "");
std::string action = args.value("action", "");
if (pairId.empty()) return {{"success", false}, {"error", "pair_id required"}};
if (action.empty()) return {{"success", false}, {"error", "action required"}};
if (action == "start") {
int totalSteps = args.value("total_steps", 5);
migrationTracker_.start(pairId, totalSteps);
return {{"success", true}, {"pair_id", pairId}, {"action", "start"},
{"total_steps", totalSteps}};
} else if (action == "advance") {
migrationTracker_.advance(pairId);
} else if (action == "complete") {
migrationTracker_.complete(pairId);
} else if (action == "fail") {
migrationTracker_.fail(pairId);
} else if (action != "get") {
return {{"success", false}, {"error", "unknown action"}};
}
auto* pg = migrationTracker_.get(pairId);
if (!pg) return {{"success", false}, {"error", "pair not tracked"}};
auto j = MigrationProgressTracker::toJson(*pg);
j["success"] = true;
return j;
}

View File

@@ -62,6 +62,7 @@
registerCostPlanningTools();
registerUpgradeQueueTools();
registerRuntimePackTools();
registerMigrationPlanningTools();
registerOnboardingTools();
}
};