From 2cf119f57e0f812099a96b4e3a89b2c7d2d31105 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 6 Sep 2026 18:57:38 -0400 Subject: [PATCH] fix(#4217): reconcile artifacts before classifying an abnormally-ended executor (#4442) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(#4217): reconcile artifacts before classifying abnormal ends * test(#4217): pin the completion-reconciliation contract * chore(#4217): regen derived inventory and install-tree fixtures * test(#4217): follow the #4003 anchoring pins into the reconciliation fragment Emitted-Drift-Ack-Growth: execute-phase.md — the runtime-neutral completion-reconciliation pointer, the two Codex wait-rule bindings, and the step-7 reconcile-first gate net +33 bytes over the extracted fallback block (#4217) * chore(#4217): add changeset fragment * chore(#4217): backfill PR number in changeset fragment --------- Co-authored-by: sim --- .changeset/silly-lemurs-munch.md | 5 + docs/INVENTORY-MANIFEST.json | 1 + gsd-core/workflows/execute-phase.md | 34 +-- .../steps/completion-reconciliation.md | 53 ++++ ...e-phase-completion-reconciliation.test.cjs | 256 ++++++++++++++++++ tests/fixtures/install-tree/antigravity.json | 1 + tests/fixtures/install-tree/augment.json | 1 + tests/fixtures/install-tree/claude-local.json | 1 + tests/fixtures/install-tree/claude.json | 1 + tests/fixtures/install-tree/cline.json | 1 + tests/fixtures/install-tree/codebuddy.json | 1 + tests/fixtures/install-tree/codex.json | 1 + tests/fixtures/install-tree/copilot.json | 1 + tests/fixtures/install-tree/cursor.json | 1 + tests/fixtures/install-tree/hermes.json | 1 + tests/fixtures/install-tree/kilo.json | 1 + tests/fixtures/install-tree/kimi-code.json | 1 + tests/fixtures/install-tree/kimi.json | 1 + tests/fixtures/install-tree/opencode.json | 1 + tests/fixtures/install-tree/pi.json | 1 + tests/fixtures/install-tree/qwen.json | 1 + tests/fixtures/install-tree/trae.json | 1 + tests/fixtures/install-tree/windsurf.json | 1 + tests/fixtures/install-tree/zcode.json | 1 + tests/safe-resume-gate-anchoring.test.cjs | 13 +- 25 files changed, 352 insertions(+), 29 deletions(-) create mode 100644 .changeset/silly-lemurs-munch.md create mode 100644 gsd-core/workflows/execute-phase/steps/completion-reconciliation.md create mode 100644 tests/execute-phase-completion-reconciliation.test.cjs diff --git a/.changeset/silly-lemurs-munch.md b/.changeset/silly-lemurs-munch.md new file mode 100644 index 000000000..973b5e297 --- /dev/null +++ b/.changeset/silly-lemurs-munch.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 4442 +--- +**`/gsd-execute-phase` no longer closes a finished executor as `turn_aborted`** — an executor whose plan SUMMARY and matching commits are already on disk is now reconciled as complete when its session ends abnormally, instead of waiting indefinitely for a terminal response and failing. (#4217) diff --git a/docs/INVENTORY-MANIFEST.json b/docs/INVENTORY-MANIFEST.json index 421097462..5e3b0415a 100644 --- a/docs/INVENTORY-MANIFEST.json +++ b/docs/INVENTORY-MANIFEST.json @@ -616,6 +616,7 @@ "discuss-phase-assumptions/steps/auto-advance-dispatch.md", "docs-update/steps/dispatch-monorepo-packages.md", "execute-phase/steps/codebase-drift-gate.md", + "execute-phase/steps/completion-reconciliation.md", "execute-phase/steps/executor-isolation-dispatch.md", "execute-phase/steps/executor-progress-policy.md", "execute-phase/steps/gap-closure-artifacts.md", diff --git a/gsd-core/workflows/execute-phase.md b/gsd-core/workflows/execute-phase.md index 025b34a63..a5ef4ac59 100644 --- a/gsd-core/workflows/execute-phase.md +++ b/gsd-core/workflows/execute-phase.md @@ -25,6 +25,9 @@ Orchestrator coordinates, not executes. Each subagent loads the full execute-pla instead of spawning parallel agents. Only attempt parallel spawning if the user explicitly requests it — and in that case, rely on the spot-check fallback in step 3 to detect completion. +- **Codex:** native subagent sessions can end abnormally (`turn_aborted`) after the plan + work is already committed. Completion is decided by the step-4 artifact reconciliation + (SUMMARY + matching recent commits), not by the session's terminal state (#4217). - **Other runtimes:** If `Agent`/`agent` tool is genuinely unavailable (e.g. a backgrounded Claude Code agent per #853, or a non-Claude runtime), use sequential inline execution as the fallback for executor parallelization only. If `Agent` IS available (top-level Claude @@ -806,7 +809,7 @@ increases monotonically across waves. `{status}` is `complete` (success), > **Worktree recovery policy (#48 + #1292):** See `execute-phase/steps/worktree-recovery-policy.md` — FAIL-CLOSED rule for base/HEAD-namespace mismatches AND isolated-run fail-safe recovery. - > **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above to spawn executor agent(s), stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available. + > **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above to spawn executor agent(s), stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available. While waiting, run the step-4 completion surveillance; if the child's session ends abnormally — including `turn_aborted` — reconcile artifacts per `execute-phase/steps/completion-reconciliation.md` before classifying the plan (#4217). **Orchestrator-managed worktree dispatch** (`ISOLATION=orchestrator-worktree`): read and execute `execute-phase/steps/executor-isolation-dispatch.md`. GSD creates each worktree (`worktree create`) and spawns the executor into it; the orchestrator performs every git operation. Merge-back and cleanup are the existing manifest-scoped gauntlet, unchanged. @@ -848,28 +851,9 @@ increases monotonically across waves. `{status}` is `complete` (success), [checkpoint] phase {PHASE_NUMBER} wave {N}/{M} plan {plan_id} checkpoint ({P}/{Q} plans done) ``` - **Completion signal fallback (Copilot and runtimes where Agent() may not return):** + **Completion reconciliation (EVERY runtime — any spawn whose terminal response may not arrive):** - If a spawned agent does not return a completion signal but appears to have finished - its work, do NOT block indefinitely. Instead, verify completion via spot-checks: - - ```bash - # For each plan in this wave, check if the executor finished: - SUMMARY_EXISTS=$(test -f "{phase_dir}/{plan_number}-{plan_padded}-SUMMARY.md" && echo "true" || echo "false") - # #4003: anchored, zero-pad-tolerant scope (see safe_resume_gate); --since stays. - SPOT_PHASE_N=$((10#{phase_number})) - SPOT_PLAN_N=$((10#{plan_padded})) - COMMITS_FOUND=$(git log --oneline --all -E --grep="^[a-z]+\((0*${SPOT_PHASE_N})-(0*${SPOT_PLAN_N})\):" --since="1 hour ago" | head -1) - COMMITS_SINCE_DISPATCH=$(git log "${EXPECTED_BRANCH}" --since="${DISPATCH_TS}" --oneline | head -1) - ``` - - **If SUMMARY.md exists AND commits are found:** The agent completed successfully — - treat as done and proceed to step 5. Log: `"✓ {Plan ID} completed (verified via spot-check — completion signal not received)"` - - **If SUMMARY.md does NOT exist after a reasonable wait:** The agent may still be - running or may have failed silently. Check `git log --oneline -5` for recent - activity. If commits are still appearing, wait longer. If no activity, report - the plan as failed and route to the failure handler in step 6. + If a spawned agent does not return a normal terminal completion response — or its session ends abnormally (interrupted, aborted, closed, killed, timed out, `turn_aborted`, including ends the orchestrator itself initiated) — do NOT block indefinitely and do NOT classify the plan as failed yet. Read and execute `gsd-core/workflows/execute-phase/steps/completion-reconciliation.md` — reconcile the plan artifacts FIRST, classify SECOND: SUMMARY present AND matching recent commits → complete (proceed to step 5, do NOT re-dispatch); no completion evidence → the failure handler. Verify, never wait. **Configurable stall surveillance (#3212):** Every `${EXECUTOR_STALL_INTERVAL_MINUTES}` minutes while waiting, inspect `git log "${EXPECTED_BRANCH}" --since="${DISPATCH_TS}"` @@ -882,9 +866,6 @@ increases monotonically across waves. `{status}` is `complete` (success), PROGRESS, not total runtime. Before treating an executor as stalled — and before sending it any message — read and execute `execute-phase/steps/executor-progress-policy.md`. - **This fallback applies to all runtimes.** Claude Code's Agent() backgrounds by - default: the completion signal may never arrive. Verify, never wait. - 5. **Post-wave hook validation (parallel mode only):** Hooks run on every executor commit by default (#2924); this post-wave run only fires when `workflow.worktree_skip_hooks=true` opted out of per-commit hooks: ```bash SKIP_HOOKS=$(gsd_run query config-get workflow.worktree_skip_hooks --raw 2>/dev/null || echo "false") @@ -1117,6 +1098,7 @@ increases monotonically across waves. `{status}` is `complete` (success), if [ -n "$RETRY_AFTER" ]; then RETRY_HINT=" Provider hinted retry-after: ${RETRY_AFTER}s"; else RETRY_HINT=""; fi ``` One classifier branch handles sentinels across Claude/Copilot/Codex/Gemini. Reference: `docs/research/provider-rate-limit-signals.md`. + **Abnormal ends reconcile first (#4217):** an abnormal session end (`turn_aborted`-class) routes through the step-4 artifact reconciliation BEFORE classifying the failure — artifacts decide. **Step 7.1 — `class == "quota-exceeded"`:** follow the quota-recovery fragment below. **Step 7.2 — `class == "classify-handoff-bug"`:** If error contains `classifyHandoffIfNeeded is not defined`, treat as Claude runtime bug. Run the same step-5 spot-checks; PASS => treat as success, FAIL => fall through. @@ -1320,7 +1302,7 @@ ${VERIFIER_SKILLS}", ) ``` -> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available. +> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available. If the session ends abnormally (`turn_aborted`), reconcile via the `verification.status` query below — the session's terminal state is not evidence of failure (#4217). Read status via the canonical query (scoped to frontmatter, covers missing/unknown cases): ```bash diff --git a/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md b/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md new file mode 100644 index 000000000..42bfbd0fa --- /dev/null +++ b/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md @@ -0,0 +1,53 @@ +# Completion reconciliation (#4217, split A of #3754) + +Read and follow this fragment from `execute-phase.md` step 4 whenever an executor's +completion is in question. It owns the whole reconciliation policy — both arms — so the +host wait step stays inside the ADR-857 Phase 6 byte ceiling (#1168). + +**Reconcile FIRST, classify SECOND.** How the child's session ended is bookkeeping +about the transport; what it wrote to disk and to git is the evidence about the work. + +## When this runs + +1. **No terminal response** — a spawned agent does not return a normal terminal + completion signal but appears to have finished its work (or may still be running). +2. **Abnormal end** — the child's session ended without a normal terminal completion + response: interrupted, aborted, closed, killed, timed out, or ended `turn_aborted` — + INCLUDING ends the orchestrator itself initiated. **An abnormally-ended child is + not evidence of failure (#4217):** the orchestrator's own interrupt/close says + nothing about whether the work completed; only the artifacts do. + +This policy applies to EVERY runtime and every isolation path — harness `Agent()` +dispatches, orchestrator-worktree process spawns, and sequential dispatch alike. Never +block indefinitely waiting for a signal; verify via filesystem and git state. + +## Probes (per plan in the wave) + +```bash +# For each plan in this wave, check if the executor finished: +SUMMARY_EXISTS=$(test -f "{phase_dir}/{plan_number}-{plan_padded}-SUMMARY.md" && echo "true" || echo "false") +# #4003: anchored, zero-pad-tolerant scope (see safe_resume_gate); --since stays. +SPOT_PHASE_N=$((10#{phase_number})) +SPOT_PLAN_N=$((10#{plan_padded})) +COMMITS_FOUND=$(git log --oneline --all -E --grep="^[a-z]+\((0*${SPOT_PHASE_N})-(0*${SPOT_PLAN_N})\):" --since="1 hour ago" | head -1) +COMMITS_SINCE_DISPATCH=$(git log "${EXPECTED_BRANCH}" --since="${DISPATCH_TS}" --oneline | head -1) +``` + +## Verdicts + +**If SUMMARY.md exists AND matching commits are found:** the agent completed +successfully — treat the plan as complete WITHOUT requiring another terminal child +response, proceed to step 5, and do NOT re-dispatch a fresh executor for this plan: +the work is already committed, and a second executor would redo it on top of itself. +Log: `"✓ {Plan ID} completed (verified via spot-check — completion signal not received)"`. + +**If SUMMARY.md does NOT exist after a reasonable wait:** the agent may still be +running or may have failed silently. Check `git log --oneline -5` for recent +activity. If commits are still appearing, wait longer. If no activity, report the +plan as failed and route to the failure handler in step 6. + +Evidence is BOTH probes or neither: a SUMMARY without matching commits, and matching +commits without a SUMMARY, are each incomplete evidence — never auto-complete on one +of them. When an abnormal end reconciles to no completion evidence, it stays failed: +route to the failure handler exactly as a normal failure would, and let the +safe-resume gate handle any un-summarized commits on the next run. diff --git a/tests/execute-phase-completion-reconciliation.test.cjs b/tests/execute-phase-completion-reconciliation.test.cjs new file mode 100644 index 000000000..3e50bd001 --- /dev/null +++ b/tests/execute-phase-completion-reconciliation.test.cjs @@ -0,0 +1,256 @@ +/** + * Regression tests for #4217 (split A of #3754): artifact-complete executor + * not reconciled — SUMMARY + matching commits present yet closed as turn_aborted. + * + * The supervision contract lives in shipped workflow text + * (gsd-core/workflows/execute-phase.md + its completion-reconciliation step + * fragment), which is the instruction the orchestrator executes at runtime. + * Reading the .md and asserting on its clauses tests the deployed contract + * (the source-text-is-the-product category; .md reads are outside + * no-source-grep's .cjs-only scope). + * + * Red on `next` before the fix: rows 1-7 of + * .gsd/bug/fix-4217-codex-artifact-complete-reconcile/50-test-matrix.md. + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const WORKFLOW_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'execute-phase.md'); +const FRAGMENT_PATH = path.join( + __dirname, '..', 'gsd-core', 'workflows', 'execute-phase', 'steps', 'completion-reconciliation.md' +); + +function readWorkflow() { + return fs.readFileSync(WORKFLOW_PATH, 'utf-8'); +} + +function readFragment() { + return fs.readFileSync(FRAGMENT_PATH, 'utf-8'); +} + +/** + * Slice the step-4 "Wait for all agents in wave to complete" region — the + * supervision surface that governs executor outcomes while/after waiting. + */ +function waitStepRegion(content) { + const from = content.indexOf('4. **Wait for all agents in wave to complete.**'); + assert.ok(from !== -1, 'execute-phase.md must contain the step-4 wait region'); + const to = content.indexOf('5. **Post-wave hook validation', from); + assert.ok(to !== -1, 'execute-phase.md step 4 must be followed by step 5'); + return content.slice(from, to); +} + +/** The abnormal-termination shapes the contract must name (#3754 / #4217). */ +const ABNORMAL_SHAPES = ['interrupted', 'aborted', 'closed', 'killed', 'timed out', 'turn_aborted']; + +describe('execute-phase completion reconciliation (#4217 — split A of #3754)', () => { + test('workflow file exists', () => { + assert.ok(fs.existsSync(WORKFLOW_PATH), 'workflows/execute-phase.md should exist'); + }); + + // ── Row 1: the #4217 lifecycle regression ──────────────────────────────── + describe('row 1 — artifact-complete executor with an abnormal end is reconciled, not failed', () => { + test('step 4 carries an abnormal-end clause covering every termination shape', () => { + const region = waitStepRegion(readWorkflow()); + for (const shape of ABNORMAL_SHAPES) { + assert.ok( + region.includes(shape), + `step 4 must name the abnormal-termination shape "${shape}" so no runtime reads its own case as unlisted (#4217)` + ); + } + }); + + test('step 4 requires reconciliation BEFORE classifying an abnormally-ended executor', () => { + const region = waitStepRegion(readWorkflow()); + assert.match( + region, + /reconcil[\s\S]{0,400}FIRST[\s\S]{0,200}classify[\s\S]{0,20}SECOND/i, + 'step 4 must state the order outright: reconcile the artifacts FIRST, classify SECOND (#4217)' + ); + }); + + test('step 4 covers ends the orchestrator itself initiated (interrupt/close)', () => { + const region = waitStepRegion(readWorkflow()); + assert.match( + region, + /orchestrator itself (interrupted|closed|initiated)/i, + 'the abnormal-end clause must survive an orchestrator-initiated close — the exact reported case (#4217)' + ); + }); + + test('step 7 requires reconciliation before classifying an abnormal end as failure', () => { + const content = readWorkflow(); + const from = content.indexOf('7. **Handle failures:**'); + assert.ok(from !== -1, 'execute-phase.md must contain the step-7 failure handler'); + const step7 = content.slice(from, content.indexOf('@~/.claude/gsd-core/references/execute-phase-quota-recovery.md', from)); + assert.match( + step7, + /reconcil[\s\S]{0,200}BEFORE classifying|BEFORE classifying[\s\S]{0,200}reconcil/i, + 'step 7 must route abnormal session ends through artifact reconciliation BEFORE classifying the failure (#4217 D4 gap)' + ); + }); + + test('fragment verdict: SUMMARY AND matching commits resolve complete without another terminal response', () => { + const fragment = readFragment(); + assert.match( + fragment, + /SUMMARY[\s\S]{0,200}(AND|and)[\s\S]{0,200}matching[\s\S]{0,300}complete/i, + 'the fragment must spell out the complete verdict: SUMMARY present AND matching commits present => complete (#4217)' + ); + assert.match( + fragment, + /(without requiring|no) another terminal|terminal child response/i, + 'the complete verdict must not require another terminal child response (#4217)' + ); + }); + + test('fragment verdict forbids re-dispatch on the reconciled-complete path', () => { + const fragment = readFragment(); + assert.match( + fragment, + /do NOT re-?dispatch/i, + 'a reconciled-complete plan must not be re-dispatched — a second executor would redo committed work on top of itself (#4217)' + ); + }); + }); + + // ── Row 2: runtime-neutral fallback scope ──────────────────────────────── + describe('row 2 — the fallback is scoped to EVERY runtime, not Copilot', () => { + test('step-4 fallback heading is runtime-neutral', () => { + const region = waitStepRegion(readWorkflow()); + assert.match( + region, + /Completion reconciliation \(EVERY runtime/i, + 'the fallback heading must not scope to Copilot or to runtimes where Agent() may not return (#4217)' + ); + assert.doesNotMatch( + region, + /Completion signal fallback \(Copilot and runtimes where Agent\(\) may not return\)/, + 'the Copilot-scoped heading was the scope gap that kept Codex out (#4217)' + ); + }); + + test('step 4 names the completion-reconciliation fragment by exact path', () => { + const region = waitStepRegion(readWorkflow()); + assert.ok( + region.includes('gsd-core/workflows/execute-phase/steps/completion-reconciliation.md'), + 'step 4 must direct the orchestrator to the reconciliation fragment by exact path (also proves response-language inheritance)' + ); + }); + }); + + // ── Rows 3-5: negative space — the reconciliation must NOT over-complete ── + describe('rows 3-5 — evidence-less or partial-evidence ends are never auto-completed', () => { + test('fragment: SUMMARY alone is not sufficient evidence', () => { + const fragment = readFragment(); + assert.match( + fragment, + /BOTH probes or neither/i, + 'the reconciliation must take BOTH probes together — never one alone (#4217 negative space)' + ); + assert.match( + fragment, + /SUMMARY without matching commits[\s\S]{0,160}commits without a SUMMARY[\s\S]{0,160}incomplete evidence/i, + 'SUMMARY-without-commits and commits-without-SUMMARY must each be named as incomplete evidence' + ); + }); + + test('fragment: commits without SUMMARY keep the wait-longer arm', () => { + const fragment = readFragment(); + assert.match( + fragment, + /commits are still appearing, wait longer|wait longer/i, + 'commits-without-SUMMARY must keep the existing wait-longer arm (still working / closeout incomplete)' + ); + assert.match( + fragment, + /If SUMMARY\.md does NOT exist/i, + 'the incomplete arm must remain: SUMMARY missing after a reasonable wait routes to activity check / failure handler' + ); + }); + + test('fragment: the abnormal-end clause never converts an evidence-less abnormal end into success', () => { + const fragment = readFragment(); + assert.match( + fragment, + /not evidence of failure/i, + 'the clause must say an abnormally-ended child is not evidence of failure — and the converse holds: no evidence, no completion (#4217)' + ); + assert.match( + fragment, + /route to the failure handler/i, + 'when reconciliation finds no completion evidence, the abnormal end still routes to the failure handler (stays failed)' + ); + }); + }); + + // ── Row 6: the Codex wait rule is bounded and linked ───────────────────── + describe('row 6 — the Codex orchestrator wait rule is bound to the reconciliation', () => { + test('every CODEX RUNTIME wait rule references the reconciliation/surveillance surface', () => { + const content = readWorkflow(); + const blocks = [...content.matchAll(/ORCHESTRATOR RULE — CODEX RUNTIME([\s\S]{0,700}?)(?=\n\s*\n)/g)]; + assert.ok(blocks.length >= 2, 'both CODEX RUNTIME wait rules must exist (dispatch + verify dispatch)'); + for (const [, body] of blocks) { + assert.match( + body, + /completion reconciliation|reconcil|spot-check|surveillance/i, + 'a CODEX RUNTIME wait rule must bind the wait to the step-4 reconciliation — an unbounded wait is the #4217 deadlock' + ); + } + }); + }); + + // ── Row 7: runtime_compatibility names Codex ───────────────────────────── + describe('row 7 — runtime_compatibility declares Codex covered', () => { + test('runtime_compatibility names Codex and defers completion to artifact reconciliation', () => { + const content = readWorkflow(); + const from = content.indexOf(''); + const to = content.indexOf('', from); + assert.ok(from !== -1 && to !== -1, 'runtime_compatibility block must exist'); + const rtc = content.slice(from, to); + assert.match(rtc, /\*\*Codex:/, 'runtime_compatibility must name Codex (#4217)'); + assert.match( + rtc, + /SUMMARY[\s\S]{0,160}(and|\+)[\s\S]{0,160}commits|spot-check/i, + 'the Codex entry must point completion decisions at the artifact reconciliation (SUMMARY + matching commits)' + ); + }); + }); + + // ── Rows 8-10: preserved-behavior guards ───────────────────────────────── + describe('rows 8-10 — preserved behavior and seam boundaries', () => { + test('host still carries the spot-check vocabulary (agent-frontmatter contract)', () => { + const content = readWorkflow(); + assert.ok(content.includes('spot-check'), 'execute-phase must keep spot-check fallback vocabulary'); + assert.ok( + content.includes('sequential inline execution'), + 'execute-phase must keep the Copilot sequential inline fallback wording' + ); + }); + + test('stall surveillance block is untouched (#4218 seam)', () => { + const content = readWorkflow(); + const from = content.indexOf('**Configurable stall surveillance (#3212):**'); + assert.ok(from !== -1, 'the #3212 stall surveillance block must remain in step 4'); + const block = content.slice(from, content.indexOf('If the stalled executor', from)); + assert.match(block, /EXECUTOR_STALL_INTERVAL_MINUTES/, 'stall interval config unchanged'); + assert.match(block, /EXECUTOR_STALL_THRESHOLD_MINUTES/, 'stall threshold config unchanged'); + }); + + test('host stays under the frozen ADR-857 Phase 6 ceiling (#1168)', () => { + const { lfByteCount } = require('../scripts/workflow-size.cjs'); + const bytes = lfByteCount(WORKFLOW_PATH); + assert.ok(bytes < 93600, `execute-phase.md must stay below the frozen pre-phase-6 ceiling (93600); got ${bytes}`); + }); + + test('fragment keeps the #4003 anchored commit-scope probe and dispatch bound', () => { + const fragment = readFragment(); + assert.match(fragment, /0\*\$\{SPOT_PHASE_N\}/, 'probe keeps the zero-pad-tolerant anchored scope (#4003)'); + assert.match(fragment, /--since="\$\{DISPATCH_TS\}"/, 'probe keeps the dispatch-time bound'); + assert.match(fragment, /SUMMARY_EXISTS/, 'probe keeps the SUMMARY existence check'); + }); + }); +}); diff --git a/tests/fixtures/install-tree/antigravity.json b/tests/fixtures/install-tree/antigravity.json index e8a7e42de..8943408be 100644 --- a/tests/fixtures/install-tree/antigravity.json +++ b/tests/fixtures/install-tree/antigravity.json @@ -383,6 +383,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/augment.json b/tests/fixtures/install-tree/augment.json index 1e5d20c0a..588734b0b 100644 --- a/tests/fixtures/install-tree/augment.json +++ b/tests/fixtures/install-tree/augment.json @@ -455,6 +455,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/claude-local.json b/tests/fixtures/install-tree/claude-local.json index 4778a6df5..3cf79d7da 100644 --- a/tests/fixtures/install-tree/claude-local.json +++ b/tests/fixtures/install-tree/claude-local.json @@ -348,6 +348,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/claude.json b/tests/fixtures/install-tree/claude.json index a2f013c83..95195ce26 100644 --- a/tests/fixtures/install-tree/claude.json +++ b/tests/fixtures/install-tree/claude.json @@ -383,6 +383,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/cline.json b/tests/fixtures/install-tree/cline.json index d9899d85a..6149f0fd9 100644 --- a/tests/fixtures/install-tree/cline.json +++ b/tests/fixtures/install-tree/cline.json @@ -385,6 +385,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/codebuddy.json b/tests/fixtures/install-tree/codebuddy.json index d51cf2b3a..18f097b05 100644 --- a/tests/fixtures/install-tree/codebuddy.json +++ b/tests/fixtures/install-tree/codebuddy.json @@ -455,6 +455,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/codex.json b/tests/fixtures/install-tree/codex.json index 89bef7a38..c0b65addc 100644 --- a/tests/fixtures/install-tree/codex.json +++ b/tests/fixtures/install-tree/codex.json @@ -419,6 +419,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/copilot.json b/tests/fixtures/install-tree/copilot.json index 1cb27a87c..92efd5ee9 100644 --- a/tests/fixtures/install-tree/copilot.json +++ b/tests/fixtures/install-tree/copilot.json @@ -384,6 +384,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/cursor.json b/tests/fixtures/install-tree/cursor.json index f54509b21..eb4503859 100644 --- a/tests/fixtures/install-tree/cursor.json +++ b/tests/fixtures/install-tree/cursor.json @@ -383,6 +383,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/hermes.json b/tests/fixtures/install-tree/hermes.json index 0e7ba27f5..d0a1b9ac0 100644 --- a/tests/fixtures/install-tree/hermes.json +++ b/tests/fixtures/install-tree/hermes.json @@ -383,6 +383,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/kilo.json b/tests/fixtures/install-tree/kilo.json index 7b6df1af2..d43d47aa4 100644 --- a/tests/fixtures/install-tree/kilo.json +++ b/tests/fixtures/install-tree/kilo.json @@ -455,6 +455,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/kimi-code.json b/tests/fixtures/install-tree/kimi-code.json index 71a4948b6..b0970bd63 100644 --- a/tests/fixtures/install-tree/kimi-code.json +++ b/tests/fixtures/install-tree/kimi-code.json @@ -384,6 +384,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/kimi.json b/tests/fixtures/install-tree/kimi.json index 951b389f2..dcc0bac5c 100644 --- a/tests/fixtures/install-tree/kimi.json +++ b/tests/fixtures/install-tree/kimi.json @@ -420,6 +420,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/opencode.json b/tests/fixtures/install-tree/opencode.json index 863d5be00..366cf8ec7 100644 --- a/tests/fixtures/install-tree/opencode.json +++ b/tests/fixtures/install-tree/opencode.json @@ -455,6 +455,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/pi.json b/tests/fixtures/install-tree/pi.json index 17ab254b6..d4fb2bc2d 100644 --- a/tests/fixtures/install-tree/pi.json +++ b/tests/fixtures/install-tree/pi.json @@ -243,6 +243,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/qwen.json b/tests/fixtures/install-tree/qwen.json index 1989d7bce..99ac81efa 100644 --- a/tests/fixtures/install-tree/qwen.json +++ b/tests/fixtures/install-tree/qwen.json @@ -383,6 +383,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/trae.json b/tests/fixtures/install-tree/trae.json index 78368cd79..b1e4423f6 100644 --- a/tests/fixtures/install-tree/trae.json +++ b/tests/fixtures/install-tree/trae.json @@ -383,6 +383,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/windsurf.json b/tests/fixtures/install-tree/windsurf.json index 0257661eb..d067cde15 100644 --- a/tests/fixtures/install-tree/windsurf.json +++ b/tests/fixtures/install-tree/windsurf.json @@ -311,6 +311,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/fixtures/install-tree/zcode.json b/tests/fixtures/install-tree/zcode.json index b117fe29b..9b4bc9ae2 100644 --- a/tests/fixtures/install-tree/zcode.json +++ b/tests/fixtures/install-tree/zcode.json @@ -455,6 +455,7 @@ "gsd-core/workflows/eval-review.md", "gsd-core/workflows/execute-phase.md", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md", + "gsd-core/workflows/execute-phase/steps/completion-reconciliation.md", "gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md", "gsd-core/workflows/execute-phase/steps/executor-progress-policy.md", "gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md", diff --git a/tests/safe-resume-gate-anchoring.test.cjs b/tests/safe-resume-gate-anchoring.test.cjs index 3426c8da1..3ca931342 100644 --- a/tests/safe-resume-gate-anchoring.test.cjs +++ b/tests/safe-resume-gate-anchoring.test.cjs @@ -78,12 +78,19 @@ describe('#4003 — safe_resume_gate commit-scope greps', () => { }); test('completion spot-check uses the anchored scope and keeps its time bound', () => { + // #4217 moved the completion spot-check probes (with the whole reconciliation + // policy, both arms) into execute-phase/steps/completion-reconciliation.md — + // "extract, not bump" against the frozen host ceiling. The anchoring contract + // travels with them: negative shape against the host, positives against the + // fragment that now owns the probes. const w = fs.readFileSync(WORKFLOW, 'utf8'); - assert.ok(!w.includes('--grep="{phase_number}-{plan_padded}"'), + const frag = fs.readFileSync(path.join(__dirname, '..', 'gsd-core', 'workflows', + 'execute-phase', 'steps', 'completion-reconciliation.md'), 'utf8'); + assert.ok(!w.includes('--grep="{phase_number}-{plan_padded}"') && !frag.includes('--grep="{phase_number}-{plan_padded}"'), 'the raw padded placeholder substring grep must not remain'); - assert.ok(w.includes('SPOT_PHASE_N=$((10#{phase_number}))') && w.includes('SPOT_PLAN_N=$((10#{plan_padded}))'), + assert.ok(frag.includes('SPOT_PHASE_N=$((10#{phase_number}))') && frag.includes('SPOT_PLAN_N=$((10#{plan_padded}))'), 'the spot-check derives zero-stripped components'); - assert.ok(w.includes('--since="1 hour ago"'), 'the spot-check keeps its temporal bound'); + assert.ok(frag.includes('--since="1 hour ago"'), 'the spot-check keeps its temporal bound'); }); test('the gate pipeline separates same-scope commits across a milestone tag (behavioral)', (t) => {