fix: remove destructive migrate rewrite, add replay source attribution and warning drain
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +195,8 @@ export interface FailureRecord {
|
||||
category?: string;
|
||||
diff?: string;
|
||||
actual?: string;
|
||||
/** Source of the contract: 'route' or 'plugin:name' */
|
||||
source?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user