fix(#4217): reconcile artifacts before classifying an abnormally-ended executor (#4442)

* 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 <sim@local>
This commit is contained in:
Tom Boucher
2026-09-06 18:57:38 -04:00
committed by GitHub
parent 19b66c3ec8
commit 2cf119f57e
25 changed files with 352 additions and 29 deletions

View File

@@ -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)

View File

@@ -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",

View File

@@ -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

View File

@@ -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.

View File

@@ -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('<runtime_compatibility>');
const to = content.indexOf('</runtime_compatibility>', 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');
});
});
});

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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) => {