feat: warn on legacy coverage drift

This commit is contained in:
Shakker 2026-05-04 22:59:23 +01:00
parent 2214750347
commit 03b3aa67e5
No known key found for this signature in database
2 changed files with 46 additions and 0 deletions

View File

@ -48,6 +48,7 @@ export function resolveCoverageObligations({ profile, entries, surfaces, targetP
const stateResult = stateSatisfiesRequirement(state, requirement);
const targetResult = targetSatisfiesRequirement(targetPlan, requirement);
warnings.push(...legacyCompatibilityWarnings({ entry, surface, requirement }));
const status = entry.skipReason
? "skipped"
: !stateResult.ok
@ -80,6 +81,49 @@ export function resolveCoverageObligations({ profile, entries, surfaces, targetP
};
}
function legacyCompatibilityWarnings({ entry, surface, requirement }) {
const warnings = [];
const scenario = entry.scenario;
const state = entry.state;
if (!scenario || !state || !surface || !requirement) {
return warnings;
}
if ((state.compatibleSurfaces ?? []).length > 0 && !state.compatibleSurfaces.includes(surface.id)) {
warnings.push({
kind: "legacy-compatibility-disagreement",
surface: surface.id,
scenario: scenario.id,
state: state.id,
requirement: requirement.id,
message: `state '${state.id}' does not list surface '${surface.id}' in compatibleSurfaces`
});
}
if ((state.incompatibleSurfaces ?? []).includes(surface.id)) {
warnings.push({
kind: "legacy-compatibility-disagreement",
surface: surface.id,
scenario: scenario.id,
state: state.id,
requirement: requirement.id,
message: `state '${state.id}' lists surface '${surface.id}' in incompatibleSurfaces`
});
}
if ((surface.requiredStates ?? []).length > 0 &&
(scenario.states ?? []).length === 0 &&
!surface.requiredStates.includes(state.id)) {
warnings.push({
kind: "legacy-compatibility-disagreement",
surface: surface.id,
scenario: scenario.id,
state: state.id,
requirement: requirement.id,
message: `surface '${surface.id}' requiredStates does not include state '${state.id}'`
});
}
return warnings;
}
export function assertResolvedCoverageIsRunnable(resolved) {
const invalid = (resolved?.obligations ?? []).filter((obligation) =>
["invalid", "missing-proof", "unsupported-state", "unsupported-target"].includes(obligation.status)

View File

@ -148,6 +148,7 @@ export async function runSelfCheck(flags = {}) {
assertArrayNotEmpty(data.entries, "matrix entries");
assertEqual(data.resolvedCoverage?.schemaVersion, "kova.resolvedCoverage.v1", "resolved coverage schema");
assertEqual(data.resolvedCoverage?.statuses?.planned, 1, "resolved planned obligation count");
assertEqual(data.resolvedCoverage?.warnings?.length, 0, "resolved coverage migration warnings");
assertEqual(data.resolvedCoverage?.obligations?.[0]?.surface, "fresh-install", "resolved obligation surface");
assertEqual(data.resolvedCoverage?.obligations?.[0]?.requirement, "baseline", "resolved obligation requirement");
assertEqual(data.entries.length, 1, "matrix include filter count");
@ -5171,6 +5172,7 @@ function validateReport(report) {
assertEqual(report.schemaVersion, "kova.report.v1", "report schema");
assertEqual(report.mode, "dry-run", "report mode");
assertEqual(report.summary?.statuses?.["DRY-RUN"], 2, "report dry-run count");
assertEqual(Object.hasOwn(report, "resolvedCoverage"), false, "report does not include planner-only resolved coverage");
assertEqual(report.performance?.repeat, 2, "report repeat count");
assertEqual(report.performance?.groupCount, 1, "report performance group count");
assertArrayNotEmpty(report.records, "report records");