From e1495214140c2929524e3dd45e1f725f3d70c6f1 Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 3 Oct 2026 01:32:48 +0000 Subject: [PATCH] test(W7c): ship-control-flow keeps step routing, tokens and gates, drops rule prose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit W7c (t09 F1, F2, F3, F4). One test file. Removed assertion groups (invariant -> remaining protection): - Work-list prose ("Expand a repair into individual steps ...", "This replaces the current item ...", "Add its destination only if ...", "The saved list takes precedence ...", "For another repair, repeat rule 2 ...", "The unchanged release steps follow", "Follow the saved work list", "Keep CYCLES and scoped approvals across this repeat", "Keep the findings and their approvals", "any further repair inserts its checks ...") -> repairs insert ordered steps ahead of pending work: kept free checks on the example sequences (`5 → … → 11.5`, `9 → 10 → 11` before 11.5, `5–11.5 → 12–14 → 16`, `14.5 → 15 → 16`, `9 → 10 → 11 → 11.5`), insert-before- pending routing lines, no unlisted steps, same attempt counts, no reset of the one-repair limit; judge `ship/SKILL.md workflow` (gate lane); gate E2Es ship-docsync-recovery, ship-docsync-stale-after (gate). - Queue-qualification sentence pins duplicated in ship-version-sync (t09 F4: "require successful utility output ...", "`offline:false` qualifies ...", "Offline output without that fallback ...") -> kept in test/ship-version-sync.test.ts next to its script-behavior tests (free); here the Qualify < Usable < No-usable order, `NEW_VERSION=CANDIDATE_VERSION`, `>= NEW_VERSION`, `BUMP_LEVEL`, `currentVersion` and the approval line stay. - Distribution prose ("List candidate distribution paths", untracked files, manifest sentences) -> kept the New-artifact branch label, AskUserQuestion and the no-release-during-/ship safety line; gate E2E ship-local-workflow (gate). - Dispatch/dashboard/coverage prose ("Omitting the flag runs the subagent in the background", "keeping a fresh context", "Use one row for each entry in step 1", "including its undetermined-percentage and test-only rules") -> kept the `run_in_background: false` contract at all three sites, the dashboard header/verdict tokens, no inline Skill fallback, coverage gate not bypassed; gate E2E ship-coverage-audit (gate). - Recovery/docs/test sentences ("If an attempt remains and either ...", "a concrete launch/input/permission correction ...", "restart Step 16 stage 1 to regenerate ...", "This recovery also applies ... stage 5", "Content- preserving commits keep valid evidence", "Retain queued fixes and restore coverage", "If this pass made edits, resume at the next decision ...", "Undispatched host-unsupported/gated specialists do not block", "count it in the invocation record before launch", "Keep Step 9.4's incomplete flags", "saved handle, original token and source", "unknown or missing status is unavailable", "Finish the saved replies ...", "fetch and inspect the remote, then merge ...") -> judge `ship/SKILL.md workflow` (gate lane); gate E2Es ship-docsync-* (gate) and ship-skipped-queued-finding (gate); the adjacent stop/approval lines stay free. Kept as case-insensitive meaning checks (t09 F1/F3; stop/consent/approval and safety lines): STOP and AskUserQuestion gates apply during repairs, a repair never resets or expands approvals, children return evidence not permission, prior-invocation audits never qualify, one repair/re-audit and no third attempt (initial-plus-one, once), Otherwise -> stop unless the user accepts the named docs risk, never silently refresh hashes, never run a third audit, dispatched-reviewer-missing -> stop, CYCLES >= 3 -> stop with `converged:false`, no fourth cycle, missing coverage cannot be replaced, failed probes never clean, required-probe gate before Step 10, cannot waive reviewer/test/security gates, native recovery stops the task first and remains blocked, outside output cannot replace native, never attach new tokens to old work, failed build stops shipping, never edit the child's counts, test failure stops publication, push failure stops with no Step 19, never rewrite history, never bypass failed guards, both redaction scans before publishing, no release publish during /ship, only approval changes the version, declining bootstrap is not test approval. Kept unchanged: the executed plan-tune nudge shell test (3 roots), section/ step ordering checks, Step 11.5 placement and `review_binding.*` fields, branch-label order in the adversarial finish (every host), all stale- wording bans, `(recommended)` marker on Add tests. Cosmetic pins dropped: STOP / ONE capitals (t09 F1, F3). Mutation log: .context/mutation-ship-control-flow.md (21 mutations, 0 unexpected: 3 reorders, 3 markers, 9 safety red; 6 rewordings green). --- test/ship-control-flow.test.ts | 239 ++++++++++++++------------------- 1 file changed, 101 insertions(+), 138 deletions(-) diff --git a/test/ship-control-flow.test.ts b/test/ship-control-flow.test.ts index 5c5350197..129a0ce63 100644 --- a/test/ship-control-flow.test.ts +++ b/test/ship-control-flow.test.ts @@ -22,49 +22,40 @@ const gate = compact(entry.slice(entry.indexOf('## Step 16:'), entry.indexOf('## describe('ship source controller', () => { test('documentation freshness covers selected code paths as well as release metadata', () => { const docs = compact(read('ship/sections/documentation.md.tmpl')); - expect(docs).toContain('hashes of the selected release paths, generated outputs and docs/templates'); + expect(docs).toContain('selected release paths, generated outputs and docs/templates'); const freshness = gate.slice(gate.indexOf('### 3.'), gate.indexOf('### 4.')); expect(freshness).toContain('selected release paths, generated outputs and docs/templates'); - expect(freshness).toContain("A prior invocation's audit or risk decision never qualifies"); - expect(freshness).toContain('the same approved scope and exact content'); + expect(freshness).toMatch(/a prior invocation's audit or risk decision never qualifies/i); }); test('changed documentation inputs permit only the remaining bounded re-audit', () => { const docs = compact(read('ship/sections/documentation.md.tmpl')); - const recovery = docs.slice(docs.indexOf('## Blocked recovery')); - expect(recovery).toContain('If an attempt remains and either the audited inputs changed'); - expect(recovery).toContain('a concrete launch/input/permission correction or reviewed patch repair is available'); - expect(docs).toContain('an initial audit plus ONE repair/re-audit'); - expect(docs).toContain('never a third attempt, even after Step 16 changes'); + expect(docs.indexOf('## Blocked recovery')).toBeGreaterThan(-1); + expect(docs).toMatch(/initial[- ]audit plus one repair\/re-audit/i); + expect(docs).toMatch(/never a third attempt/i); const freshness = gate.slice(gate.indexOf('### 3.'), gate.indexOf('### 4.')); expect(freshness).toContain('**An attempt remains, with changed inputs or an available repair:**'); - expect(freshness).toContain('**Otherwise:** STOP unless the user accepts'); + expect(freshness).toMatch(/\*\*Otherwise:\*\* stop unless the user accepts/i); expect(freshness).not.toContain('**No attempt remains or no repair is available:**'); }); test('repairs use one ordered work list without a return stack', () => { const text = compact(controller); - expect(compact(entry)).toContain('**Next steps:** one ordered work list, with the current step marked'); - expect(text).toContain('Expand a repair into individual steps and insert them before the still-pending work'); - expect(text).toContain('This replaces the current item, whose actual result stays in the record'); - expect(text).toContain('Add its destination only if not already the next pending step'); - expect(text).toContain('The saved list takes precedence over ordinary next-step sentences inside a repair'); + expect(compact(entry)).toContain('**Next steps:**'); expect(text).toContain('5 → 6 → 7 → 8 → 9 → 10 → 11 → 11.5'); expect(text).not.toContain('finish the inner repair, then resume the unfinished outer range'); - expect(text).toContain('Keep the same attempt counts throughout the invocation'); - expect(text).toContain('initial-plus-ONE limit never resets'); + expect(text).toMatch(/initial-plus-one limit never resets/i); }); test('the roadmap and recovery groups define ownership and zero-edit eligibility', () => { const text = compact(entry); expect(text).toContain('integrate (1–3) → test and review (4–11.5) → prepare the release (12–15) → verify frozen content (16) → push and publish (17–21)'); - expect(text).toContain('children return evidence, not permission to proceed'); - expect(review).toContain('**No edits in this pass:** Resolve the required-probe gate below'); - expect(review).toContain('Only after it clears may you continue to Step 10'); - expect(review).toContain('With completed checklist and dispatched reviewers, failed/unavailable required probes block continuation'); - expect(review).toContain('This cannot waive missing reviewer output, recurring fixes or independent test/security gates'); - expect(review).toContain('Undispatched gated/unsupported specialists do not block independently'); - expect(text).toContain('Reuse it only for that same scope; a repair never resets approvals or expands them'); + expect(text).toMatch(/children return evidence, not permission to proceed/i); + expect(review).toContain('**No edits in this pass:**'); + expect(review).toMatch(/only after it clears may you continue to Step 10/i); + expect(review).toMatch(/failed\/unavailable required probes block continuation/i); + expect(review).toMatch(/cannot waive missing reviewer output, recurring fixes or independent test\/security gates/i); + expect(text).toMatch(/a repair never resets approvals or expands them/i); expect(text).not.toContain('eligible zero-edit pass'); expect(text).not.toContain('the same waiver'); expect(text).not.toContain("the controller's detour"); @@ -72,13 +63,9 @@ describe('ship source controller', () => { test('distribution discovery is a shortlist, not a manifest-only artifact decision', () => { const distribution = compact(entry.slice(entry.indexOf('## Step 2:'), entry.indexOf('## Step 3:'))); - expect(distribution).toContain('List candidate distribution paths'); - expect(distribution).toContain("Also inspect matching untracked files from Step 1's status"); - expect(distribution).toContain('a new `package.json` or `Cargo.toml` alone does not establish a publishable artifact'); - expect(distribution).toContain('inspect existing manifests for newly declared binaries or package exports'); - expect(distribution).toContain('New artifact without a pipeline'); + expect(distribution).toContain('**New artifact without a pipeline'); expect(distribution).toContain('AskUserQuestion'); - expect(distribution).toContain('Do not publish a release during `/ship`'); + expect(distribution).toMatch(/do not publish a release during `\/ship`/i); }); test('queue qualification exhaustively distinguishes online, git fallback and unusable output', () => { @@ -89,13 +76,11 @@ describe('ship source controller', () => { expect(qualify).toBeGreaterThan(0); expect(usable).toBeGreaterThan(qualify); expect(missing).toBeGreaterThan(usable); - expect(version).toContain('require successful utility output and a nonempty valid version'); - expect(version).toContain('`offline:false` qualifies; `offline:true` qualifies only with `fallback:"git"`'); - expect(version).toContain('Offline output without that fallback, failure, malformed output or an empty version is unusable, even if it contains a version-looking string'); - expect(version).toContain('FRESH sets `NEW_VERSION=CANDIDATE_VERSION`'); - expect(version).toContain('Only approval changes the existing version'); - expect(version).toContain('a sibling holding `>= NEW_VERSION` requires a choice: advance past it, or stop this attempt and sync'); - expect(version.slice(missing)).toContain('FRESH uses local `BUMP_LEVEL` arithmetic; ALREADY_BUMPED keeps `currentVersion`'); + expect(version).toContain('`NEW_VERSION=CANDIDATE_VERSION`'); + expect(version).toMatch(/only approval changes the existing version/i); + expect(version).toContain('`>= NEW_VERSION`'); + expect(version.slice(missing)).toContain('`BUMP_LEVEL`'); + expect(version.slice(missing)).toContain('`currentVersion`'); }); test.each(['home', 'override', 'plugin'])('the actual nudge shell honors %s roots, repeat suppression and enabled tuning', mode => { @@ -142,11 +127,8 @@ describe('ship source controller', () => { test('nested repairs resume the unfinished outer range before its destination', () => { const text = compact(controller); - expect(text).toContain('Expand a repair into individual steps and insert them before the still-pending work'); - expect(text).toContain('For another repair, repeat rule 2 without discarding pending work'); - expect(text).toContain('Step 11 fixes insert `9 → 10 → 11` before 11.5'); - expect(text).toContain('A further Step 9 fix affecting 6–8 makes the list `5 → 6 → 7 → 8 → 9 → 10 → 11 → 11.5`'); - expect(text).toContain('The unchanged release steps follow'); + expect(text).toContain('`9 → 10 → 11` before 11.5'); + expect(text).toContain('`5 → 6 → 7 → 8 → 9 → 10 → 11 → 11.5`'); expect(text).not.toContain('Enter Step 9 before REVIEW_START capture and full review'); }); @@ -157,10 +139,8 @@ describe('ship source controller', () => { expect(shared).toBeLessThan(coverage.indexOf('Dispatch the audit through Agent')); const contract = compact(coverage.slice(shared, coverage.indexOf('Dispatch the audit through Agent'))); expect(contract).toContain('use the Agent tool with `run_in_background: false`'); - expect(contract).toContain('Omitting the flag runs the subagent in the background'); - expect(contract).toContain('keeping a fresh context'); - expect(contract).toContain('Do not invoke the target as a Skill or run it inline instead'); - expect(contract).toContain("only under that section's documented fallback, after a failed subagent has stopped"); + expect(contract).toMatch(/do not invoke the target as a Skill or run it inline instead/i); + expect(contract).toMatch(/after a failed subagent has stopped/i); for (const section of ['test-coverage', 'plan-completion', 'greptile']) { const text = read(`ship/sections/${section}.md.tmpl`); expect(text).toContain('shared foreground-dispatch rule'); @@ -178,8 +158,7 @@ describe('ship source controller', () => { const ship = generateReviewDashboard(ctx); expect(ship).toContain('REVIEW READINESS DASHBOARD'); expect(ship).toContain('| Review | Runs | Last run | Status | Required |'); - expect(ship).toContain('Use one row for each entry in step 1'); - expect(ship).toContain('Only Eng Review is marked required'); + expect(ship).toMatch(/only Eng Review is marked required/i); expect(ship).toContain('{actual status and reason}'); expect(ship).toContain('VERDICT: {CLEARED or NOT CLEARED} — {reason}'); expect(ship).not.toContain('+====================================================================+'); @@ -190,18 +169,17 @@ describe('ship source controller', () => { test('coverage fallback settles the child and still applies the coverage gate', () => { const text = compact(read('ship/sections/test-coverage.md.tmpl')); const fallback = text.slice(text.indexOf('**Audit failure:**'), text.indexOf('{{TEST_COVERAGE_GATE_SHIP}}')); - expect(fallback).toContain('confirm it stopped before running the same audit inline'); - expect(fallback).toContain('does not pass or bypass the coverage gate'); - expect(fallback).toContain('including its undetermined-percentage and test-only rules'); + expect(fallback).toMatch(/confirm it stopped before running the same audit inline/i); + expect(fallback).toMatch(/does not pass or bypass the coverage gate/i); expect(text).not.toContain('partial results are better than none'); }); test('no-plan gate skips only the audit and retains verification and the remaining section', () => { const text = generatePlanCompletionGateShip({ host: 'claude', skillName: 'ship', tmplPath: '', paths: HOST_PATHS.claude }); const noPlan = compact(text.slice(text.indexOf('**No plan file found:**'))); - expect(noPlan).toContain('Skip only the plan completion audit'); - expect(noPlan).toContain('Continue with Step 8.1, Scope Drift and Prior Learnings'); - expect(noPlan).toContain('Step 9 QA still runs'); + expect(noPlan).toMatch(/skip only the plan completion audit/i); + expect(noPlan).toContain('Step 8.1'); + expect(noPlan).toMatch(/Step 9 QA still runs/i); expect(noPlan).not.toContain('Skip entirely'); }); @@ -211,8 +189,8 @@ describe('ship source controller', () => { const positions = steps.map(step => text.indexOf(step)); expect(positions.every(position => position >= 0)).toBe(true); expect(positions).toEqual([...positions].sort((a,b) => a-b)); - expect(text).toContain('All three snapshots must match'); - expect(text).toContain('does not mean the failed or unrun probes passed'); + expect(text).toMatch(/all three snapshots must match/i); + expect(text).toMatch(/does not mean the failed or unrun probes passed/i); expect(text).not.toContain('equal snapshots do not pass waived probes'); }); @@ -221,9 +199,8 @@ describe('ship source controller', () => { expect(text).toContain('**Behavior, tests or build inputs changed:**'); expect(text).toContain('**Only authored docs or release metadata changed:**'); expect(text).toContain('**No changes, or the docs-only checks still support the plan:**'); - expect(text).toContain('accepted audit matches all inputs | Continue to stage 4'); - expect(text).toContain("Use Blocked recovery with the existing count"); - expect(text).toContain('Use this example only after confirming that every allowed edit is release metadata'); + expect(text).toContain('| Continue to stage 4'); + expect(text).toMatch(/Blocked recovery with the existing count/i); expect(text).not.toContain('Every listed change below is metadata:'); }); @@ -233,26 +210,24 @@ describe('ship source controller', () => { expect(end).toBeLessThan(entry.indexOf('{{BASE_BRANCH_DETECT}}')); expect(entry.match(/### Ship control flow/g)).toHaveLength(1); const text = compact(controller); - expect(text).toContain('You, the **parent** running /ship, own advancement'); - expect(text).toContain('Follow the saved work list'); - expect(text).toContain('STOP and AskUserQuestion gates still apply during repairs'); - expect(text).toContain('The saved list takes precedence over ordinary next-step sentences inside a repair. A range never adds unlisted steps'); - expect(text).toContain('Keep the same attempt counts throughout the invocation'); + expect(text).toMatch(/the \*\*parent\*\* running \/ship, own advancement/i); + expect(text).toMatch(/stop and AskUserQuestion gates still apply during repairs/i); + expect(text).toMatch(/a range never adds unlisted steps/i); + expect(text).toMatch(/keep the same attempt counts throughout the invocation/i); expect(text).toContain('A range ending at Step 14 does not enter Step 14.5'); - expect(text).toContain('its initial-plus-ONE limit never resets'); expect(text).not.toContain('| At step |'); expect(entry.match(/Ship control flow/g)).toHaveLength(1); - expect(review).toContain('Every repeat starts before the checklist read and captures a fresh REVIEW_START'); + expect(review).toMatch(/captures a fresh REVIEW_START/); }); test('distribution and bootstrap detours end at their exact forward resume', () => { const merge = compact(entry.slice(entry.indexOf('## Step 3:'), entry.indexOf('{{SECTION:tests}}'))); - expect(merge).toContain('repeat Step 2 on the merged content, including its decisions, then continue to Step 4'); - expect(merge).toContain('Otherwise continue to Step 4 directly'); + expect(merge).toMatch(/repeat Step 2 on the merged content[^.]*continue to Step 4/i); + expect(merge).toMatch(/otherwise continue to Step 4 directly/i); const tests = compact(read('ship/sections/tests.md.tmpl')); - expect(tests).toContain('A runs Step 4 with this new bootstrap choice, then returns here to run the tests'); + expect(tests).toMatch(/A runs Step 4 with this new bootstrap choice, then returns here/i); expect(tests).toContain('A) Add tests (recommended)'); - expect(tests).toContain('declining bootstrap alone is not that approval'); + expect(tests).toMatch(/declining bootstrap alone is not that approval/i); }); test('missing dispatched output outranks the cycle cap, fixes and zero-edit continuation', () => { @@ -262,49 +237,40 @@ describe('ship source controller', () => { expect(positions.every(position => position >= 0)).toBe(true); expect(positions).toEqual([...positions].sort((a, b) => a - b)); const missing = decisions.slice(positions[0], positions[1]); - expect(missing).toContain('STOP and name each failed specialist or Red Team'); - expect(missing).toContain('Retain queued fixes and restore coverage'); - expect(missing).toContain('If this pass made edits, resume at the next decision; otherwise run a fresh complete Step 9'); - expect(missing).toContain('A successful peer or a QA exception cannot replace missing dispatched coverage'); + expect(missing).toMatch(/^1\. \*\*Dispatched reviewer output missing:\*\* stop\b/i); + expect(missing).toMatch(/cannot replace missing dispatched coverage/i); const cap = decisions.slice(positions[1], positions[2]); - expect(cap).toContain('STOP'); - expect(cap).toContain('`converged:false`; do not run a fourth fixing cycle'); + expect(cap).toMatch(/\bstop\b/i); + expect(cap).toContain('`converged:false`'); + expect(cap).toMatch(/do not run a fourth fixing cycle/i); const fixes = decisions.slice(positions[2], positions[3]); - expect(fixes).toContain('Insert Step 5, affected Steps 6–8 and all of Step 9 before the pending Step 10 in the work list'); - expect(fixes).toContain('Tests must pass or retain approval for the same verified pre-existing failures and scope'); - expect(fixes).toContain('Keep CYCLES and scoped approvals across this repeat'); - expect(decisions.slice(positions[3])).toContain('Only after it clears may you continue to Step 10'); - expect(review).toContain('Undispatched host-unsupported/gated specialists do not block'); - expect(review).toContain('Failed, blocked, inconclusive or not-run required probes mean false, never clean'); - expect(review).toContain('This cannot waive missing reviewer output, recurring fixes or independent test/security gates'); + expect(fixes).toContain('Insert Step 5, affected Steps 6–8 and all of Step 9 before the pending Step 10'); + expect(fixes).toMatch(/retain approval for the same verified pre-existing failures and scope/i); + expect(decisions.slice(positions[3])).toMatch(/only after it clears may you continue to Step 10/i); + expect(review).toMatch(/failed, blocked, inconclusive or not-run required probes mean false, never clean/i); }); test('comment fixes resume triage and preserve settled approvals and replies', () => { const section = compact(read('ship/sections/greptile.md.tmpl')); - expect(section).toContain('If fixes were approved, save their approvals and comment references'); - expect(section).toContain("Run Step 9's full review/fix loop, then return here"); - expect(review).toContain('Finish the complete review and QA before applying any fix in Step 9.4'); - expect(section).toContain('Finish the saved replies without asking again about completed fixes, and classify new comments'); - expect(section).toContain('With no queued fixes, continue to Step 11'); - expect(section).toContain('This optional triage does not block ship'); - expect(section).toContain('unknown or missing status is unavailable'); + expect(section).toMatch(/save their approvals and comment references/i); + expect(section).toMatch(/Run Step 9's full review\/fix loop, then return here/i); + expect(review).toMatch(/finish the complete review and QA before applying any fix in Step 9\.4/i); + expect(section).toMatch(/with no queued fixes, continue to Step 11/i); + expect(section).toMatch(/this optional triage does not block ship/i); expect(section).not.toContain('return to Step 9'); }); test('native recovery has one corrected attempt and cannot borrow outside completion', () => { const finish = adversarial.slice(adversarial.indexOf('### Finish the adversarial phase')); const recovery = finish.slice(finish.indexOf('1. **Required native'), finish.indexOf('2. **Fixes queued')); - expect(recovery).toContain('STOP and confirm the native task stopped'); - expect(recovery).toContain('Outside-provider output cannot replace this pass'); - expect(recovery).toContain('One recovery retry is allowed only after a concrete prerequisite correction and restored access'); - expect(recovery).toContain('count it in the invocation record before launch'); - expect(recovery).toContain('Capture a fresh PASS_START and persist the new attempt separately'); - expect(recovery).toContain('Without that correction, or if the recovery fails, ask for repair and remain blocked'); + expect(recovery).toMatch(/stop and confirm the native task stopped/i); + expect(recovery).toMatch(/outside-provider output cannot replace this pass/i); + expect(recovery).toMatch(/one recovery retry is allowed only after a concrete prerequisite correction/i); + expect(recovery).toContain('PASS_START'); + expect(recovery).toMatch(/ask for repair and remain blocked/i); const queued = finish.slice(finish.indexOf('2. **Fixes queued'), finish.indexOf('3. **Native complete')); - expect(queued).toContain('Keep the findings and their approvals'); - expect(queued).toContain('Insert Steps 9, 10 and 11 before the pending Step 11.5 in the work list. Step 9 completes full review before fixes'); - expect(queued).toContain('any further repair inserts its checks ahead of the remaining items'); - expect(queued).toContain('not recovery retries'); + expect(queued).toContain('Insert Steps 9, 10 and 11 before the pending Step 11.5'); + expect(queued).toMatch(/not recovery retries/i); expect(finish.slice(finish.indexOf('3. **Native complete'))).toContain('then continue to Step 11.5'); }); @@ -312,19 +278,19 @@ describe('ship source controller', () => { const ctx = { host, skillName: 'ship', tmplPath: '', paths: HOST_PATHS[host] }; const ship = generateAdversarialStep(ctx); const finish = compact(ship.slice(ship.indexOf('### Finish the adversarial phase'))); - expect(finish).toContain('Apply these decisions in order before leaving Step 11'); - expect(finish).toContain('Required native review incomplete'); - expect(finish).toContain('Outside-provider output cannot replace this pass'); - expect(finish).toContain('Fixes queued after native completion'); - expect(finish).toContain('Native complete with no queued fixes'); + const branches = ['Required native review incomplete', 'Fixes queued after native completion', 'Native complete with no queued fixes'] + .map(label => finish.indexOf(label)); + expect(branches.every(index => index >= 0)).toBe(true); + expect(branches).toEqual([...branches].sort((a, b) => a - b)); + expect(finish).toMatch(/outside-provider output cannot replace this pass/i); expect(finish).toContain('Step 11.5'); expect(finish).not.toContain('proceed to Step 12'); expect(finish).not.toContain('return to Step 9'); const review = generateAdversarialStep({ ...ctx, skillName: 'review' }); expect(review).not.toContain('Ship control flow'); expect(review).not.toContain('Step 11.5'); - expect(review).toContain('Return all findings and structured-review decisions to Step 5'); - expect(review).toContain('do not start an inner repair loop'); + expect(review).toMatch(/return all findings and structured-review decisions to Step 5/i); + expect(review).toMatch(/do not start an inner repair loop/i); }); test('Step 11.5 verifies original record identity before any release write', () => { @@ -332,59 +298,56 @@ describe('ship source controller', () => { expect(bindingStart).toBeGreaterThan(entry.indexOf('{{SECTION:adversarial}}')); expect(bindingStart).toBeLessThan(entry.indexOf('## Step 12:')); const binding = compact(entry.slice(bindingStart, entry.indexOf('## Step 12:'))); - for (const field of ['saved handle, original token and source', 'skill:"review"', 'via:"ship"', 'skill:"adversarial-review"', + for (const field of ['skill:"review"', 'via:"ship"', 'skill:"adversarial-review"', 'review_binding.state', 'verified', 'review_binding.start_wtree', 'review_binding.end_wtree']) expect(binding).toContain(field); - expect(binding).toContain('Never attach new tokens to old work'); - expect(binding).toContain('Keep Step 9.4\'s incomplete flags'); - expect(binding).toContain('does not mean the failed or unrun probes passed'); - expect(binding).toContain('insert `9 → 10 → 11 → 11.5` before Step 12. Bind the new records at 11.5'); + expect(binding).toMatch(/never attach new tokens to old work/i); + expect(binding).toMatch(/does not mean the failed or unrun probes passed/i); + expect(binding).toContain('`9 → 10 → 11 → 11.5` before Step 12'); }); test('late build and behavior changes complete bounded ranges before docs', () => { const build = gate.slice(gate.indexOf('### 1.'), gate.indexOf('### 2.')); - expect(build).toContain('A missing prerequisite or failed build stops shipping'); - expect(build).toContain('Repair the prerequisite or build, then repeat stage 1'); - expect(build).toContain('After it passes, continue to stage 2; treat any content repair as a behavioral change there'); + expect(build).toMatch(/a missing prerequisite or failed build stops shipping/i); + expect(build).toMatch(/repeat stage 1/i); + expect(build).toMatch(/continue to stage 2/i); const behavior = gate.slice(gate.indexOf('1. **Behavior'), gate.indexOf('2. **Only authored')); expect(behavior).toContain('Insert `5–11.5 → 12–14 → 16` before the pending Step 17'); - expect(behavior).toContain('before the pending Step 17, then stop this step. This repair excludes Step 14.5'); - expect(behavior).toContain('rebuild and compare again before stage 3 decides documentation freshness'); + expect(behavior).toMatch(/excludes Step 14\.5/i); + expect(behavior).toMatch(/rebuild and compare again before stage 3/i); const plan = gate.slice(gate.indexOf('2. **Only authored'), gate.indexOf('3. **No changes')); - expect(plan).toContain("run Step 8's audit and decision gates only, then return to Step 16 stage 1"); - expect(plan).toContain("Never edit the child's counts yourself"); + expect(plan).toMatch(/run Step 8's audit and decision gates only, then return to Step 16 stage 1/i); + expect(plan).toMatch(/never edit the child's counts yourself/i); }); test('docs freshness has bounded re-audit and exact-content exception routes', () => { const docs = gate.slice(gate.indexOf('### 3.'), gate.indexOf('### 4.')); - expect(docs).toContain("Use Blocked recovery with the existing count"); + expect(docs).toMatch(/Blocked recovery with the existing count/i); expect(docs).toContain('**An attempt remains, with changed inputs or an available repair:** insert `14.5 → 15 → 16` before Step 17'); - expect(docs).toContain('restart Step 16 stage 1 to regenerate and compare again'); - expect(docs).toContain('STOP unless the user accepts the specific named documentation risk and all unwaivable gates clear'); - expect(docs).toContain('Never run a third audit'); - expect(docs).toContain('Unchanged approved content goes to stage 4; repaired content goes to stage 1'); - expect(docs).toContain('retain `Documentation: blocked`'); - expect(docs).toContain('Missing, stale or blocked | Use recovery below. Never silently refresh hashes'); + expect(docs).toMatch(/stop unless the user accepts the specific named documentation risk and all unwaivable gates clear/i); + expect(docs).toMatch(/never run a third audit/i); + expect(docs).toMatch(/unchanged approved content goes to stage 4; repaired content goes to stage 1/i); + expect(docs).toContain('`Documentation: blocked`'); + expect(docs).toMatch(/never silently refresh hashes/i); }); test('tests, absent test approval and new writes reopen final verification', () => { const tests = gate.slice(gate.indexOf('### 4.'), gate.indexOf('### 5.')); - expect(tests).toContain("**New, changed or unwaived test failure:** STOP publication. Run Steps 5–15, starting with Step 5's triage, then return to Step 16 stage 1"); - expect(tests).toContain('This recovery also applies if a failure appears while reporting in stage 5'); - expect(tests).toContain('run Steps 5–15, including the no-tests decision, then return to Step 16 stage 1'); - expect(tests).toContain('it does not authorize a third attempt'); - expect(gate).toContain('If content changes during or after verification, restart at stage 1 and complete all five stages before Step 17'); - expect(gate).toContain('Content-preserving commits keep valid evidence'); + expect(tests).toMatch(/\*\*New, changed or unwaived test failure:\*\* stop publication\. Run Steps 5–15/i); + expect(tests).toMatch(/run Steps 5–15, including the no-tests decision, then return to Step 16 stage 1/i); + expect(tests).toMatch(/does not authorize a third attempt/i); + expect(gate).toMatch(/restart at stage 1 and complete all five stages before Step 17/i); }); test('push failure kinds and publication races have distinct resumes', () => { const push = compact(entry.slice(entry.indexOf('## Step 17:'), entry.indexOf('## Step 18:'))); - expect(push).toContain('**If the push fails, STOP.** No Step 19 or publication claim'); - expect(push).toContain("fetch and inspect the remote, then merge under Step 3's conflict rules"); - expect(push).toContain('Run Steps 5–16 before returning to Step 17. Never rewrite history'); - expect(push).toContain('repeat Step 16 even if content is unchanged before returning to Step 17'); - expect(push).toContain('Never bypass failed guards'); + expect(push).toMatch(/push fails[^.]*\bstop\b[\s\S]{0,20}no Step 19 or publication claim/i); + expect(push).toMatch(/merge under Step 3's conflict rules/i); + expect(push).toContain('Run Steps 5–16 before returning to Step 17'); + expect(push).toMatch(/never rewrite history/i); + expect(push).toMatch(/repeat Step 16 even if content is unchanged/i); + expect(push).toMatch(/never bypass failed guards/i); const publication = compact(read('ship/sections/pr-body.md.tmpl')); - expect(publication).toContain("If the open PR/MR or title changed, repeat Step 18's identity/title preparation"); - expect(publication).toContain('then return here for a new lookup, fresh body and both redaction scans before publishing'); + expect(publication).toMatch(/repeat Step 18's identity\/title preparation/i); + expect(publication).toMatch(/both redaction scans before publishing/i); }); });