From 7b50026075180934ba536e0ee384bdfbe1df2d54 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Sun, 11 Oct 2026 01:44:30 +0200 Subject: [PATCH] test: align pinned workflow text with the uat canonicalize seam verify-work now calls verification.canonicalize-uat instead of hand-rolling the status flip, and progress's Route V.stale re-verifies in place. The flip's behavior is covered in tests/uat-evidence.test.cjs; these tests keep the workflow's side of the contract. Refreshes the compact-content benchmark baseline for the moved bytes. --- .../compact-content-benchmark-baseline.json | 18 +++---- tests/progress-forensic.test.cjs | 14 +++-- tests/verify-work-auto-transition.test.cjs | 54 ++++++++----------- 3 files changed, 42 insertions(+), 44 deletions(-) diff --git a/tests/fixtures/compact-content-benchmark-baseline.json b/tests/fixtures/compact-content-benchmark-baseline.json index 0c8551f62..cfeb39bce 100644 --- a/tests/fixtures/compact-content-benchmark-baseline.json +++ b/tests/fixtures/compact-content-benchmark-baseline.json @@ -18,9 +18,9 @@ "reductionPct": 16.74 }, "execute-phase": { - "offTokens": 24960, - "onTokens": 22854, - "reductionPct": 8.44 + "offTokens": 25132, + "onTokens": 23026, + "reductionPct": 8.38 }, "new-project": { "offTokens": 14038, @@ -33,14 +33,14 @@ "reductionPct": 11.06 }, "verify-work": { - "offTokens": 13192, - "onTokens": 11223, - "reductionPct": 14.93 + "offTokens": 13136, + "onTokens": 11167, + "reductionPct": 14.99 } }, "aggregate": { - "offTokens": 107146, - "onTokens": 91020, - "reductionPct": 15.05 + "offTokens": 107262, + "onTokens": 91136, + "reductionPct": 15.03 } } diff --git a/tests/progress-forensic.test.cjs b/tests/progress-forensic.test.cjs index 71e2deccd..98943b628 100644 --- a/tests/progress-forensic.test.cjs +++ b/tests/progress-forensic.test.cjs @@ -209,7 +209,7 @@ describe('#1107: progress routing consults verification.status before reporting ); }); - test('stale verification routes to verify-work (Route V.stale)', () => { + test('stale verification is re-verified in place, as one action (Route V.stale)', () => { const workflow = readWorkflow(); assert.ok(workflow.includes('**Route V.stale:'), 'must define a Route V.stale section'); const route = workflow.slice( @@ -217,8 +217,16 @@ describe('#1107: progress routing consults verification.status before reporting workflow.indexOf('**Route V.gaps:') ); assert.ok( - route.includes('verify-work'), - 'Route V.stale must route to /msd:verify-work {phase}' + route.includes('msd-core/references/stale-reverification.md'), + 'Route V.stale must follow the shared stale re-verification procedure' + ); + assert.ok( + route.includes('.reverify_phases'), + 'Route V.stale must batch every stale-but-executed phase from init.progress, not just the current one' + ); + assert.ok( + !/`\/msd:execute-phase \{phase\}/.test(route), + 'Route V.stale must not send the user to execute-phase for a phase whose plans are all summarized' ); }); diff --git a/tests/verify-work-auto-transition.test.cjs b/tests/verify-work-auto-transition.test.cjs index a15c1cd1e..a40b588ed 100644 --- a/tests/verify-work-auto-transition.test.cjs +++ b/tests/verify-work-auto-transition.test.cjs @@ -98,14 +98,18 @@ describe('verify-work.md — auto-transition after UAT passes with 0 issues', () test('human_needed verification is promoted to passed only after successful human UAT', () => { const content = fs.readFileSync(VERIFY_WORK, 'utf-8'); const statusIdx = content.indexOf('VERIFICATION_STATUS=$(msd_run query verification.status "$PHASE_DIR"'); - const humanNeededIdx = content.indexOf('if [ "$VERIFICATION_STATUS_VALUE" = "human_needed" ]; then'); - const setPassedIdx = content.indexOf('msd_run query frontmatter.set "$VERIFICATION_FILE" --field status --value passed'); + const staleIdx = content.indexOf('If `PHASE_VERIFICATION_STATUS` is `stale`'); + const canonIdx = content.indexOf('CANON=$(msd_run query verification.canonicalize-uat "$PHASE_DIR"'); const predicateIdx = content.indexOf('PHASE_COMPLETE=$(msd_run phase uat-passed "{phase}" --require-verification)'); assert.ok(statusIdx !== -1, 'verify-work.md must inspect canonical verification status'); - assert.ok(humanNeededIdx > statusIdx, 'status=passed promotion must be restricted to human_needed'); - assert.ok(setPassedIdx > humanNeededIdx, 'human_needed verification must be promoted after status check'); - assert.ok(setPassedIdx < predicateIdx, 'verification must be canonicalized before the required predicate runs'); + assert.ok(canonIdx > statusIdx, 'the promotion must go through the shared canonicalize-uat seam, after the status read'); + assert.ok(staleIdx < canonIdx, 'a stale report must be re-verified BEFORE canonicalization, or a passed UAT can never be recorded'); + assert.ok(canonIdx < predicateIdx, 'verification must be canonicalized before the required predicate runs'); + assert.ok( + !content.includes('frontmatter.set "$VERIFICATION_FILE" --field status --value passed'), + 'the workflow must not hand-roll the status flip — verification.canonicalize-uat owns it' + ); }); test('stale verification blocks before phase transition', () => { @@ -136,47 +140,33 @@ describe('verify-work.md — auto-transition after UAT passes with 0 issues', () // phase-close uses in its --uat-only form (the verification-status blocker // is exactly what the flip removes, so the full predicate could never pass // at pre-check time); the flagged call stays the later transition gate. +// The flip itself — gated on the uat-only row predicate, refused for any +// status other than human_needed, fail-closed on missing evidence — is owned +// by `verification.canonicalize-uat` and tested behaviorally in +// tests/uat-evidence.test.cjs. What stays pinned here is the workflow's side +// of that contract: it calls the seam, and it reports a refusal. describe('verify-work.md — canonicalize flip is gated by the UAT predicate (#4663)', () => { - test('canonicalize flips to passed only when the uat-passed predicate reports passed (#4663)', () => { + test('the flagged predicate remains the later transition gate (#4663)', () => { const content = fs.readFileSync(VERIFY_WORK, 'utf-8'); - const humanNeededIdx = content.indexOf('if [ "$VERIFICATION_STATUS_VALUE" = "human_needed" ]; then'); - const precheckIdx = content.indexOf('UAT_PRECHECK=$(msd_run phase uat-passed "{phase}" --uat-only 2>/dev/null)'); - const flipGuardIdx = content.indexOf('if [ "$UAT_PRECHECK_PASSED" = "true" ]; then'); - const setPassedIdx = content.indexOf('msd_run query frontmatter.set "$VERIFICATION_FILE" --field status --value passed'); - - assert.ok(precheckIdx !== -1, 'the canonicalize block must run the uat-passed predicate before flipping'); - assert.ok(humanNeededIdx !== -1 && precheckIdx > humanNeededIdx, 'the pre-check must sit inside the human_needed branch'); - assert.ok(content.includes(".passed // false"), 'the verdict must be extracted from the typed report with a false default'); - assert.ok(flipGuardIdx !== -1 && flipGuardIdx > precheckIdx, 'the flip must be guarded on the extracted passed verdict'); - assert.ok(setPassedIdx > flipGuardIdx, 'frontmatter.set must sit INSIDE the passed==true guard'); - }); - - test('the canonicalize pre-check runs uat-passed without --require-verification (#4663)', () => { - const content = fs.readFileSync(VERIFY_WORK, 'utf-8'); - const precheckIdx = content.indexOf('UAT_PRECHECK=$(msd_run phase uat-passed "{phase}" --uat-only 2>/dev/null)'); + const canonIdx = content.indexOf('CANON=$(msd_run query verification.canonicalize-uat "$PHASE_DIR"'); const flaggedIdx = content.indexOf('PHASE_COMPLETE=$(msd_run phase uat-passed "{phase}" --require-verification)'); - assert.ok(precheckIdx !== -1, 'the --uat-only pre-check must exist'); - assert.ok( - !content.slice(precheckIdx, precheckIdx + 120).includes('--require-verification'), - 'the pre-check is the unflagged predicate - requiring verification there would evaluate the very report being written' - ); - assert.ok(flaggedIdx !== -1 && flaggedIdx > precheckIdx, 'the flagged predicate remains the later transition gate'); + assert.ok(canonIdx !== -1, 'the canonicalize call must exist'); + assert.ok(flaggedIdx !== -1 && flaggedIdx > canonIdx, 'the flagged predicate remains the later transition gate'); }); test('refused canonicalization says the verification stays human_needed (#4663)', () => { const content = fs.readFileSync(VERIFY_WORK, 'utf-8'); + assert.match(content, /"\$CANON_REASON" = "uat_not_passed"/, 'the refusal must branch on the typed reason'); assert.match(content, /stays human_needed/, 'the refusal message must say verification stays human_needed'); assert.match(content, /blockers \| length/, 'the refusal must carry the blocking-row count'); }); - test('an indeterminate pre-check must not flip the report (fail closed) (#4663)', () => { + test('an indeterminate canonicalize call reads as no reason, never as a flip (#4663)', () => { const content = fs.readFileSync(VERIFY_WORK, 'utf-8'); - // jq -r '.passed // false' with an `|| echo "false"` fallback: empty or - // failed msd_run output must yield no-flip, never a flip. assert.ok( - content.includes(`jq -r '.passed // false' 2>/dev/null || echo "false"`), - 'the extraction must default to false on empty/failed output' + content.includes(`jq -r '.reason // empty' 2>/dev/null || echo ""`), + 'the extraction must default to empty on empty/failed output' ); }); });