fix: remove destructive migrate rewrite, add replay source attribution and warning drain

This commit is contained in:
John Dvorak
2026-05-22 11:41:43 -07:00
parent 1de735ee08
commit 6e4656add5
6 changed files with 50 additions and 31 deletions
@@ -2,7 +2,7 @@
* Route rewriter for APOPHIS migrate command.
*
* Responsibilities:
* - Rewrite route schema annotations (e.g., x-validate-runtime → runtime)
* - Rewrite route schema annotations
* - Preserve schema structure and formatting
* - Handle annotations in Fastify route definitions
* - Detect ambiguous annotations and require manual choice
@@ -40,10 +40,12 @@ export interface AmbiguousRoutePattern {
/**
* Mapping of deprecated route schema annotations to their modern equivalents.
* x-validate-runtime is intentionally NOT here — it is the current, active annotation
* used by contract.ts and hook-validator.ts for per-route runtime validation opt-out.
* Rewriting it to 'runtime' would break it, since 'runtime' is a top-level plugin
* config option, not a valid route schema annotation.
*/
export const LEGACY_ROUTE_ANNOTATIONS: Record<string, string> = {
'x-validate-runtime': 'runtime',
};
export const LEGACY_ROUTE_ANNOTATIONS: Record<string, string> = {};
/**
* Ambiguous route patterns that require manual choice.
+34 -13
View File
@@ -73,33 +73,44 @@ function formatHumanOutput(result: ReplayResult, artifact: Artifact): string {
w.includes('Artifact cwd no longer exists')
)
function formatFailure(label: string, failure: FailureRecord | undefined): void {
if (!failure) return
lines.push(label)
lines.push(` Route: ${failure.route}`)
if (failure.source && failure.source !== 'route') {
lines.push(` Source: ${failure.source}`)
}
if (failure.contract) {
lines.push(` Contract: ${failure.contract}`)
}
if (failure.expected) {
lines.push(` Expected: ${failure.expected}`)
}
if (failure.observed) {
lines.push(` Observed: ${failure.observed}`)
}
}
if (result.reproduced) {
lines.push('Replay reproduced the original failure.')
lines.push('')
lines.push('Original failure')
lines.push(` Route: ${result.originalFailure?.route}`)
lines.push(` Contract: ${result.originalFailure?.contract}`)
lines.push(` Expected: ${result.originalFailure?.expected}`)
lines.push(` Observed: ${result.originalFailure?.observed}`)
formatFailure('Original failure', result.originalFailure)
lines.push(` Seed: ${artifact.seed}`)
} else if (result.newFailure) {
lines.push('Replay produced a different result.')
lines.push('')
lines.push('Original failure')
lines.push(` Route: ${result.originalFailure?.route}`)
lines.push(` Contract: ${result.originalFailure?.contract}`)
formatFailure('Original failure', result.originalFailure)
lines.push('')
lines.push('New result')
lines.push(` Route: ${result.newFailure.route}`)
lines.push(` Contract: ${result.newFailure.contract}`)
lines.push(` Expected: ${result.newFailure.expected}`)
lines.push(` Observed: ${result.newFailure.observed}`)
formatFailure('New result', result.newFailure)
lines.push(` Seed: ${artifact.seed}`)
} else {
lines.push('Replay passed — failure no longer reproduces.')
lines.push('')
lines.push('Original failure')
lines.push(` Route: ${result.originalFailure?.route}`)
if (result.originalFailure?.source && result.originalFailure.source !== 'route') {
lines.push(` Source: ${result.originalFailure.source}`)
}
lines.push(` Contract: ${result.originalFailure?.contract}`)
lines.push(` Seed: ${artifact.seed}`)
}
@@ -299,6 +310,14 @@ async function executeReplay(
pluginContractRegistry,
})
// Drain plugin contract registry warnings (missing extensions, etc.)
if (pluginContractRegistry?.drainWarnings) {
const pcrWarnings = pluginContractRegistry.drainWarnings()
if (pcrWarnings.length > 0) {
warnings.push(...pcrWarnings)
}
}
// If no routes matched, or route found but no contracts (plugin not registered before routes),
// try direct contract execution
if (runResult.noRoutesMatched || runResult.noContractsFound) {
@@ -403,6 +422,7 @@ async function executeReplay(
observed: newFailure.observed,
seed: artifact.seed || 42,
replayCommand: `apophis replay --artifact ${artifactPath}`,
source: newFailure.source,
},
warnings,
}, artifact),
@@ -416,6 +436,7 @@ async function executeReplay(
observed: newFailure.observed,
seed: artifact.seed || 42,
replayCommand: `apophis replay --artifact ${artifactPath}`,
source: newFailure.source,
},
}
}
+1
View File
@@ -123,6 +123,7 @@ function buildArtifact(
seed: options.seed,
replayCommand: `apophis replay --artifact ${f.artifactPath || '<artifact-path-unavailable>'}`,
category: f.category ?? (f.observed ? classifyError(f.observed) : ErrorTaxonomy.RUNTIME),
source: f.source,
}
})
+4
View File
@@ -40,6 +40,8 @@ export interface VerifyFailure {
artifactPath?: string
formula?: string
category?: string
/** Source of the contract: 'route' or 'plugin:name' */
source?: string
}
export interface VerifyRunResult {
@@ -649,6 +651,7 @@ export async function runVerify(deps: VerifyRunnerDeps): Promise<VerifyRunResult
observed: diagnostic.observed,
formula: diagnostic.formula,
category: diagnostic.category,
source: route.formulaSources?.[formula],
})
} else {
passedCount++
@@ -664,6 +667,7 @@ export async function runVerify(deps: VerifyRunnerDeps): Promise<VerifyRunResult
observed: diagnostic.observed,
formula: diagnostic.formula,
category: diagnostic.category,
source: route.formulaSources?.[formula],
})
}
}
+2
View File
@@ -195,6 +195,8 @@ export interface FailureRecord {
category?: string;
diff?: string;
actual?: string;
/** Source of the contract: 'route' or 'plugin:name' */
source?: string;
}
/**