feat(voyage): emit MANIFEST_PROFILE_DRIFT warning in plan-validator strict mode — brief assumption 7
Step 20 of v4.1 — implements drift detection in plan-validator.mjs per
brief Assumptions block 7: "Mismatch (e.g. korrupt manuell endring)
emitterer MANIFEST_PROFILE_DRIFT-warning fra plan-validator i --strict-modus."
Logic (after validateAllManifests in validatePlanContent):
1. Strict-mode only — soft mode never emits drift warnings.
2. Plan frontmatter must declare 'profile: <name>' to establish baseline.
3. For each step manifest, if profile_used is set AND differs from plan
profile, emit warning (NOT error) with code MANIFEST_PROFILE_DRIFT
and location 'step N: profile_used = X, plan profile = Y'.
Forward-compat preserved: drift is a warning, plan remains valid:true.
Operators see the drift in --strict mode without parsing breaking.
New files:
tests/validators/plan-validator-profile-drift.test.mjs — 4 tests
tests/fixtures/plan-profile-drift.md — drift fixture
Tests verify:
1. drift detected in strict mode → MANIFEST_PROFILE_DRIFT in warnings
2. drift NOT detected in soft mode → strict gate honored
3. matching profile → no drift warning
4. no plan-level profile → drift detection silent (no baseline)
Tests: 479 pass + 2 skipped (Docker not installed).
This commit is contained in:
parent
93c6b82f62
commit
e98eba88c9
3 changed files with 149 additions and 0 deletions
|
|
@ -5,6 +5,7 @@
|
|||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { sliceSteps, validatePlanHeadings, extractPlanVersion } from '../parsers/plan-schema.mjs';
|
||||
import { validateAllManifests } from '../parsers/manifest-yaml.mjs';
|
||||
import { parseDocument } from '../util/frontmatter.mjs';
|
||||
import { issue, fail } from '../util/result.mjs';
|
||||
|
||||
export function validatePlanContent(text, opts = {}) {
|
||||
|
|
@ -33,6 +34,29 @@ export function validatePlanContent(text, opts = {}) {
|
|||
warnings.push(issue('PLAN_VERSION_MISMATCH', `plan_version=${planVersion}, current target is 1.7`));
|
||||
}
|
||||
|
||||
// v4.1 SC #20 — MANIFEST_PROFILE_DRIFT detection. Strict-mode only.
|
||||
// If plan frontmatter declares `profile: <name>` and any step manifest
|
||||
// declares `profile_used: <other>`, emit a warning (NOT an error) so
|
||||
// operators see drift but parsing remains forward-compat.
|
||||
if (strict) {
|
||||
const planFm = parseDocument(text).parsed?.frontmatter;
|
||||
const planProfile =
|
||||
planFm && typeof planFm.profile === 'string' ? planFm.profile : null;
|
||||
if (planProfile) {
|
||||
for (const m of manRes.parsed) {
|
||||
const stepProfile = m.manifest && m.manifest.profile_used;
|
||||
if (typeof stepProfile === 'string' && stepProfile !== planProfile) {
|
||||
warnings.push(issue(
|
||||
'MANIFEST_PROFILE_DRIFT',
|
||||
`step ${m.n}: profile_used = ${stepProfile}, plan profile = ${planProfile}`,
|
||||
'A step manifest declares a different profile than the plan frontmatter; ' +
|
||||
'verify whether this is intentional (manual override) or accidental drift.',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue