From 3146ff36aae6f01c23795c98094d65c51d56ad4e Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 7 Aug 2026 04:45:30 -0400 Subject: [PATCH] fix(#3132): realign retired covered/backstop-as-status vocab to resolved+verification (#3138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(#3132): realign spec/plan/ui-phase workflow prose from retired covered/backstop-as-status to resolved+verification The edge-probe resolution model splits status (resolved|dismissed|unresolved) from verification (explicit|backstop). The workflow prose in three files still used the pre-re-cut covered/backstop-as-status vocabulary that validateResolution rejects. Swept all three prose surfaces: - spec-phase.md: Step 5.5 resolution options, --auto mode + log line, comment, Step 6 row list - plan-phase.md: lift rule (L778/L780), comments (L564/L706), quality gate (L826-827) - ui-phase.md: resolution loop (L391), --auto mode (L405-409), write-back format (L415) Added regression test in edge-probe-spec-phase-contract.test.cjs asserting the retired vocab is absent and resolved+verification is used instead. * chore(#3132): add changeset + emitted-drift ack for workflow vocab realignment * fix(#3132): update planner contract tests for resolved+verification vocabulary RR-02 and RR-03 tests asserted the old covered/backstop-as-status vocab. Updated to match the realigned prose (resolved edge → must_haves). * fix(#3132): fix specless-probe-fallback test assertion + merge duplicate ack Test assertion was too strict (expected auto-resolved + verification:explicit on same line). Split into two independent assertions. Merged plan-phase.md ack into existing #2658 fragment to resolve duplicate-path rule violation. * fix(#3132): use bare filenames in ack keys (size map keys are bare, not full paths) * fix(#3132): amend existing acks instead of duplicating — remove plan-phase from #2658, spec-phase from #3132, append #3132 reason to #0000 and #2650 * chore(#3132): backfill changeset PR number 3138 --------- Co-authored-by: sim --- .changeset/plucky-moles-romp.md | 5 ++ .../references/specless-probe-fallback.md | 13 +++-- gsd-core/workflows/plan-phase.md | 14 ++--- gsd-core/workflows/spec-phase.md | 21 ++++--- gsd-core/workflows/ui-phase.md | 15 ++--- tests/edge-probe-planner-contract.test.cjs | 12 ++-- tests/edge-probe-spec-phase-contract.test.cjs | 57 +++++++++++++++++++ .../0000-legacy-migration.json | 2 +- .../2650-plan-phase-stall-detection.json | 2 +- .../2658-trae-instruction-file-path.json | 1 - .../3132-retired-status-vocab.json | 6 ++ 11 files changed, 110 insertions(+), 38 deletions(-) create mode 100644 .changeset/plucky-moles-romp.md create mode 100644 tests/emitted-drift-acks/3132-retired-status-vocab.json diff --git a/.changeset/plucky-moles-romp.md b/.changeset/plucky-moles-romp.md new file mode 100644 index 000000000..7a05691e2 --- /dev/null +++ b/.changeset/plucky-moles-romp.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3138 +--- +**Spec-phase edge resolution vocabulary realigned to the code's `Status` enum** — the workflow prose in spec-phase.md, plan-phase.md, and ui-phase.md used the retired `covered`/`backstop`-as-status vocabulary that `validateResolution` rejects. Now uses `resolved` + `verification: explicit|backstop`. (#3132) diff --git a/gsd-core/references/specless-probe-fallback.md b/gsd-core/references/specless-probe-fallback.md index 7deb85b68..6367061ee 100644 --- a/gsd-core/references/specless-probe-fallback.md +++ b/gsd-core/references/specless-probe-fallback.md @@ -119,16 +119,17 @@ if [ "$APPLICABLE" = "0" ]; then fi ``` -**Edge `--auto` resolution rules (reuse spec-phase 5.5 verbatim, D-06):** auto-`covered` where a -defensible acceptance criterion can be written (→ a plain `must_haves.truths` string); else -auto-`backstop` → author it as a **structured flat-scalar marker** `{ statement: , +**Edge `--auto` resolution rules (reuse spec-phase 5.5 verbatim, D-06):** auto-`resolved` +(verification: explicit) where a defensible acceptance criterion can be written (→ a plain +`must_haves.truths` string); else auto-`resolved` (verification: backstop) → author it as a +**structured flat-scalar marker** `{ statement: , verification: backstop }` in `must_haves.truths`, NOT a prose note (the verifier branches deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110 fragility; flat scalar `verification:` key, never a nested object, ADR-550 #1278). A `backstop` truth the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason `insufficient_spec`), never a silent pass (#1154; `references/honest-verifier.md`). **Never auto-dismiss** (a wrong dismissal is the exact silent failure this eliminates). An `unclassified` row -stays **`unresolved`** (#1110) — never auto-`backstop`ped — and is surfaced to the planner as a flagged +stays **`unresolved`** (#1110) — never auto-resolved with backstop — and is surfaced to the planner as a flagged assumption. Pass `$COVERAGE` (+ the gate's `$SPECLESS_FALLBACK_DISABLED` note) into the gsd-planner prompt (Step 8). When `EDGE_ABSENT=0`, `$COVERAGE` is empty and this does not run. @@ -159,8 +160,8 @@ assumption — never a silent drop. Author the fallback report into `must_haves` with the SAME lift the SPEC path uses — only the source changes (the fallback report, not the SPEC): -- **Edges →** every `covered` edge's acceptance criterion → `must_haves.truths` as a plain string; - every `backstop` edge → `must_haves.truths` as a structured `{ statement, verification: backstop }` +- **Edges →** every resolved (verification: explicit) edge's acceptance criterion → `must_haves.truths` as a plain string; + every resolved (verification: backstop) edge → `must_haves.truths` as a structured `{ statement, verification: backstop }` marker (NOT prose; #1110/#1278), which abstains → `human_needed` at verify time when unconfirmed (#1154); every `unresolved`/`unclassified` row → an explicit flagged assumption (never a silent drop). - **Prohibitions →** every kept prohibition → the `must_haves.prohibitions:` sibling block (NOT diff --git a/gsd-core/workflows/plan-phase.md b/gsd-core/workflows/plan-phase.md index f83e93e6b..fcef92b84 100644 --- a/gsd-core/workflows/plan-phase.md +++ b/gsd-core/workflows/plan-phase.md @@ -561,8 +561,8 @@ PATTERNS_PATH=$(_gsd_field "$INIT" patterns_path) SPIKE_FINDINGS_PATH=$(ls ./.claude/skills/spike-findings-*/SKILL.md 2>/dev/null | head -1 || true) SKETCH_FINDINGS_PATH=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1 || true) -# Resolve the phase SPEC (carries the ## Edge Coverage section the planner lifts covered/ -# backstop edges from). UNCONDITIONAL — must NOT live in §4.5 Check AI-SPEC, which is skipped +# Resolve the phase SPEC (carries the ## Edge Coverage section the planner lifts resolved +# edges from). UNCONDITIONAL — must NOT live in §4.5 Check AI-SPEC, which is skipped # on non-AI phases; gating it there silently starves the planner of the SPEC (#550 review). # Glob the plain phase SPEC, excluding the -AI-SPEC.md / -UI-SPEC.md variants. PHASE_DIR_FOR_SPEC=$(_gsd_field "$INIT" phase_dir) @@ -703,7 +703,7 @@ Planner prompt: - {reviews_path} (Cross-AI Review Feedback - if --reviews; actionable findings must be incorporated or explicitly deferred/rejected in PLAN.md) - {AI_SPEC_PATH} (AI Design Contract — framework and evaluation strategy, if exists) - {UI_SPEC_PATH} (UI Design Contract — visual/interaction specs, if exists) -- {SPEC_PATH} (Phase SPEC — carries the ## Edge Coverage section to lift covered/backstop edges from, if exists) +- {SPEC_PATH} (Phase SPEC — carries the ## Edge Coverage section to lift resolved edges from, if exists) - {SPIKE_FINDINGS_PATH} (Spike Findings — validated patterns, constraints, landmines from experiments, if exists) - {SKETCH_FINDINGS_PATH} (Sketch Findings — validated design decisions, CSS patterns, visual direction, if exists) - {API_SURFACE_PATH} (API Surface — HINT ONLY, when intel capability is active; see below) @@ -775,9 +775,9 @@ Output consumed by /gsd:execute-phase. Plans need: - Tasks in XML format with read_first and acceptance_criteria fields (MANDATORY on every task) - Verification criteria - must_haves for goal-backward verification -- If the SPEC has an `## Edge Coverage` section, lift every `covered` edge's acceptance criterion into `must_haves.truths` as a plain string, and every `backstop` edge **as a structured flat-scalar marker** — an object item `{ statement: , verification: backstop }`, NOT a prose note (the verifier branches deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110 fragility). Use a flat scalar `verification:` continuation key, never a nested object (ADR-550 #1278). At verify time a `backstop` truth the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason `insufficient_spec`), never a silent pass (#1154; see `references/honest-verifier.md`). `unresolved` edges are explicit assumptions — surface them in the plan, do not silently drop them. **Otherwise** (`EDGE_ABSENT`): apply the SAME lift to the fallback report `{COVERAGE}` (per §C of `references/specless-probe-fallback.md`); a SPEC-supplied section is never re-run. +- If the SPEC has an `## Edge Coverage` section, lift every resolved (verification: explicit) edge's acceptance criterion into `must_haves.truths` as a plain string, and every resolved (verification: backstop) edge **as a structured flat-scalar marker** — an object item `{ statement: , verification: backstop }`, NOT a prose note (the verifier branches deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110 fragility). Use a flat scalar `verification:` continuation key, never a nested object (ADR-550 #1278). At verify time a `backstop` truth the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason `insufficient_spec`), never a silent pass (#1154; see `references/honest-verifier.md`). `unresolved` edges are explicit assumptions — surface them in the plan, do not silently drop them. **Otherwise** (`EDGE_ABSENT`): apply the SAME lift to the fallback report `{COVERAGE}` (per §C of `references/specless-probe-fallback.md`); a SPEC-supplied section is never re-run. - If the SPEC has a `## Prohibitions` section, lift every resolved prohibition into the `must_haves.prohibitions:` sibling block (NOT `truths` — ADR-550 D3) with `statement`+`status`+`verification`, via the single `projectProhibitions` serializer (Hyrum — no second serializer); unresolved -> flagged assumptions, don't drop; never put a must-NOT under `truths`. **Otherwise** (`PROHIB_ABSENT`), author the recalled prohibitions into the SAME block via the SAME `projectProhibitions` contract but **descriptor-less** (no `check_*`) so each disposes flagged-unverified; never auto-dismiss. Section-level precedence + no-silent-drop equality apply (§C). -- If a `-UI-SPEC.md` exists (resolved above as `UI_SPEC_PATH`) with a `## UI Considerations` section, lift it by the **identical rule** as `## Edge Coverage` above — `covered` → `must_haves.truths` string, `backstop` → flat scalar `{ statement, verification: backstop }`, `unresolved` → explicit planner assumption (no new verb — ADR-550 #1278/#1154; #1867). Read it from `UI_SPEC_PATH` (the SPEC glob excludes `-UI-SPEC.md`). +- If a `-UI-SPEC.md` exists (resolved above as `UI_SPEC_PATH`) with a `## UI Considerations` section, lift it by the **identical rule** as `## Edge Coverage` above — resolved (explicit) → `must_haves.truths` string, resolved (backstop) → flat scalar `{ statement, verification: backstop }`, `unresolved` → explicit planner assumption (no new verb — ADR-550 #1278/#1154; #1867). Read it from `UI_SPEC_PATH` (the SPEC glob excludes `-UI-SPEC.md`). - **"Artifacts this phase produces" section (MANDATORY)** — list every symbol this phase creates: decorators, classes, functions, CLI flags, struct/dataclass fields, new file paths. The plan-review-convergence source-grounding pass reads this section to exclude newly-created symbols from drift verification; omitting it causes new symbols to be flagged for acknowledgement. @@ -823,8 +823,8 @@ Every task MUST include these fields — they are NOT optional: - [ ] Waves assigned for parallel execution - [ ] must_haves derived from phase goal - [ ] Every PLAN.md includes an "Artifacts this phase produces" section listing symbols created by this phase (decorators, classes, functions, CLI flags, struct/dataclass fields, new file paths) -- [ ] Every SPEC ## Edge Coverage covered/backstop edge is represented in a plan's must_haves (no silent drops) -- [ ] Every UI-SPEC ## UI Considerations covered/backstop consideration is represented in a plan's must_haves (no silent drops) +- [ ] Every SPEC ## Edge Coverage resolved edge is represented in a plan's must_haves (no silent drops) +- [ ] Every UI-SPEC ## UI Considerations resolved consideration is represented in a plan's must_haves (no silent drops) - [ ] Every SPEC ## Prohibitions resolved item is represented in a plan's must_haves.prohibitions (no silent drops) ``` diff --git a/gsd-core/workflows/spec-phase.md b/gsd-core/workflows/spec-phase.md index c26688c42..0dbe49d55 100644 --- a/gsd-core/workflows/spec-phase.md +++ b/gsd-core/workflows/spec-phase.md @@ -251,7 +251,8 @@ if ! node -e 'const a=require(process.argv[1]);if(!Array.isArray(a)||a.length=== exit 1 fi # Invoke the compiled engine and CAPTURE its report — it computes which categories apply per -# requirement. The covered/backstop/dismissed/unresolved rows in $COVERAGE drive the +# requirement. The resolved/dismissed/unresolved rows in $COVERAGE (resolved items carry +# verification: explicit|backstop) drive the # resolution loop below (canonical taxonomy compute, NOT LLM re-derivation from prose). # The engine FAILS CLOSED (exit 2) on an invalid authored shape or bad input — so the capture # MUST be exit-checked. A bare `COVERAGE=$(node …)` swallows that exit code, leaves $COVERAGE @@ -288,14 +289,15 @@ an empty `## Edge Coverage` section after explicit confirmation. For each Requirement gathered so far: 1. Classify its shape and raise only applicable edge categories (relevance filter — see the taxonomy in the reference). Reuse any edges the Round-4 Failure Analyst already - surfaced as pre-`covered`. + surfaced as pre-resolved. 2. For each raised category, propose a CONCRETE candidate edge (not "consider boundaries" — e.g. "R2 merges intervals; what about `[[1,2],[2,3]]` that only touch?"). 3. Resolve each with the user (AskUserQuestion; text mode → numbered list): - **Specify it** → write a new pass/fail line into Acceptance Criteria AND mark the - edge `covered`. + edge `resolved` with `verification: explicit`. - **Dismiss (reason)** → mark `dismissed` with a required non-empty reason. - - **Backstop with a test** → mark `backstop`; note "held-out edge test" for plan-phase. + - **Backstop with a test** → mark `resolved` with `verification: backstop`; note + "held-out edge test" for plan-phase. - **Defer** → leave `unresolved`. - An `unclassified` row (probe `unclassified — review manually`) means the requirement's prose matched no shape cue (#1110) — treat it like any other candidate (**Specify**, @@ -311,13 +313,14 @@ For each Requirement gathered so far: - On "anyway": write SPEC.md with those rows marked `⚠ Edge unresolved — planner must treat as assumption`. -**`--auto` mode:** auto-`covered` where a defensible acceptance criterion can be written; -otherwise auto-`backstop` (never auto-dismiss — a wrong dismissal is the exact silent -failure being eliminated). Log: `[auto] edge coverage: C covered, B backstop, U unresolved`. +**`--auto` mode:** auto-`resolved` (verification: explicit) where a defensible acceptance +criterion can be written; otherwise auto-`resolved` (verification: backstop) (never +auto-dismiss — a wrong dismissal is the exact silent failure being eliminated). Log: +`[auto] edge coverage: E explicit, B backstop, U unresolved`. **`unclassified` exception (#1110):** `--auto` leaves an `unclassified` candidate **`unresolved`** (the soft gate surfaces it as a flagged planner assumption) — it never -auto-`backstop`s it. A missing shape is not evidence an edge exists, so minting a held-out +auto-resolves it with `verification: backstop`. A missing shape is not evidence an edge exists, so minting a held-out edge obligation on a requirement that may be genuinely edge-free would be a false claim and risks a vacuous edge test. Leaving it `unresolved` keeps the zero-cue requirement visible (never a silent drop) without fabricating an edge — which is exactly #1110's purpose: surface @@ -425,7 +428,7 @@ downstream rather than blocking authoring. Use the SPEC.md template from @~/.claude/gsd-core/templates/spec.md. -- Populate the **Edge Coverage** section from Step 5.5 (covered/dismissed/backstop/unresolved rows). +- Populate the **Edge Coverage** section from Step 5.5 (resolved/dismissed/unresolved rows; resolved items carry `verification: explicit|backstop`). - Populate the **Prohibitions** section from Step 5.6 (resolved/dismissed/unresolved rows with the test|judgment tier). **Requirements for every requirement entry:** diff --git a/gsd-core/workflows/ui-phase.md b/gsd-core/workflows/ui-phase.md index a7efee727..af05cc904 100644 --- a/gsd-core/workflows/ui-phase.md +++ b/gsd-core/workflows/ui-phase.md @@ -388,8 +388,8 @@ SIGNAL, not proof the element is only that kind — the confirm step, not the he coverage sound. **Resolution loop** (mirror spec-phase 5.5): resolve each applicable consideration via -AskUserQuestion — **Specify** (→ `covered`, write a concrete truth) / **Dismiss (reason required)** / -**Backstop** (a held-out/visual UI-state test) / **Defer** (→ `unresolved`). An `unclassified` row is +AskUserQuestion — **Specify** (→ `resolved`, verification: explicit; write a concrete truth) / **Dismiss (reason required)** / +**Backstop** (→ `resolved`, verification: backstop; a held-out/visual UI-state test) / **Defer** (→ `unresolved`). An `unclassified` row is a manual-review nudge, not a hard block. Text mode (`workflow.text_mode` / `--text`) → numbered lists. **Kind-confirmation under `--auto`.** The propose-then-confirm step above is an AskUserQuestion, so @@ -402,17 +402,18 @@ recover a kind that was never surfaced, so recall is fixed HERE, at kind-confirm resolution runs. **`--auto` mode (two layers).** The adapter's `autoResolve` is the CODE floor: every applicable -consideration auto-`backstop`s (carrying the taxonomy question as its resolution) and an -`unclassified` candidate stays `unresolved` — it NEVER auto-`dismiss`es and never auto-backstops an -unclassified item (#1110). On top of that floor the workflow MAY upgrade an item to `covered` when a +consideration auto-resolves with `verification: backstop` (carrying the taxonomy question as its +resolution) and an `unclassified` candidate stays `unresolved` — it NEVER auto-`dismiss`es and never +auto-resolves an unclassified item with backstop (#1110). On top of that floor the workflow MAY +upgrade an item to `resolved` (verification: explicit) when a defensible acceptance criterion can be written (the same judgment spec-phase 5.5 applies in prose). -An auto `--auto` run therefore leaves un-upgraded backstops as `backstop`: at verify time each one +An auto `--auto` run therefore leaves un-upgraded items as `resolved` (verification: backstop): at verify time each one with no wired evidence routes to `insufficient_spec → human_needed` — never a silent pass (#1154). That surfacing is the intended honest-verifier behavior, not over-flagging. **Write-back.** Populate a `## UI Considerations` section in the UI-SPEC from the resolved considerations, in the format the shipped plan-phase `## UI Considerations` lift rule reads: -`covered` → a truth string; `backstop` → a flat scalar `{ statement, verification: backstop }`; +`resolved` (explicit) → a truth string; `resolved` (backstop) → a flat scalar `{ statement, verification: backstop }`; `unresolved` → an explicit `⚠ unresolved — planner must treat as assumption` row. Empty-state and error-state COPY stays in `## Copywriting Contract` — the considerations section covers shape-rooted STATE coverage and REFERENCES those rows rather than restating the copy (de-dup). IDEMPOTENT: if a diff --git a/tests/edge-probe-planner-contract.test.cjs b/tests/edge-probe-planner-contract.test.cjs index 6e79b672f..9aa45485b 100644 --- a/tests/edge-probe-planner-contract.test.cjs +++ b/tests/edge-probe-planner-contract.test.cjs @@ -128,7 +128,7 @@ test('RR-01 reachability: SPEC_FILE resolution is NOT gated inside the AI-SPEC a // templates/planner-subagent-prompt.md file is orphaned (loaded by nothing), so asserting the // contract there is false assurance — the test stays green even if the runtime never consumes it. // Pin the contract to the block plan-phase.md actually sends the planner. -test('RR-02 consumer: plan-phase.md downstream_consumer instructs lifting covered/backstop edges into must_haves.truths', () => { +test('RR-02 consumer: plan-phase.md downstream_consumer instructs lifting resolved edges into must_haves.truths', () => { const block = extractDownstreamConsumerBlock(readPlanPhase()); assert.ok(block.length > 0, 'sanity: plan-phase.md must contain a block to scope this test'); @@ -154,15 +154,15 @@ test('RR-02 consumer: plan-phase.md downstream_consumer instructs lifting covere ); }); -// Test D (RR-03): plan-phase.md contains a covered/backstop ↔ must_haves item -// This MUST FAIL before the RR-03 fix (no such quality_gate item exists today) -test('RR-03: planner quality_gate requires covered/backstop edges represented in must_haves', () => { +// Test D (RR-03): plan-phase.md contains a resolved-edge ↔ must_haves item +// #3132: vocabulary realigned from covered/backstop-as-status to resolved+verification +test('RR-03: planner quality_gate requires resolved edges represented in must_haves', () => { const content = readPlanPhase(); const qgBlock = extractQualityGateBlock(content); assert.match( qgBlock, - /covered.*backstop.*must_haves|backstop.*covered.*must_haves/i, - 'planner quality_gate must contain a checklist item tying covered/backstop edges to must_haves' + /resolved.*edge.*must_haves/i, + 'planner quality_gate must contain a checklist item tying resolved edges to must_haves' ); }); diff --git a/tests/edge-probe-spec-phase-contract.test.cjs b/tests/edge-probe-spec-phase-contract.test.cjs index b07f3e779..3111e3948 100644 --- a/tests/edge-probe-spec-phase-contract.test.cjs +++ b/tests/edge-probe-spec-phase-contract.test.cjs @@ -192,3 +192,60 @@ test('adversarial review: Step 5.5 guards a zero-applicable coverage report', () 'Step 5.5 must read coverage.applicable and guard the zero-applicable case (warn/confirm, not silently proceed)' ); }); + +// #3132: the retired covered/backstop-as-status vocabulary must not appear in +// the workflow prose. probe-core.cts locks Status to resolved|dismissed|unresolved; +// backstop survives only as a verification tier on a resolved item. +test('#3132: spec-phase.md uses resolved/dismissed/unresolved — not covered/backstop as status', () => { + const content = readSpecPhase(); + // "mark the edge `covered`" or "mark `backstop`" would indicate the retired vocab + assert.doesNotMatch(content, /mark the edge `covered`/, + 'spec-phase.md must not instruct agents to mark edges as "covered" (retired status)'); + assert.doesNotMatch(content, /mark `backstop`[^;]/, + 'spec-phase.md must not instruct agents to mark edges as "backstop" (retired status; backstop is a verification tier only)'); + // The resolution options should reference resolved+verification + assert.match(content, /resolved.*verification: explicit/, + 'spec-phase.md must use "resolved" with "verification: explicit" for specified edges'); + assert.match(content, /resolved.*verification: backstop/, + 'spec-phase.md must use "resolved" with "verification: backstop" for backstopped edges'); +}); + +test('#3132: plan-phase.md lift rule uses resolved+verification — not covered/backstop', () => { + const planPath = path.join(__dirname, '..', 'gsd-core', 'workflows', 'plan-phase.md'); + const content = fs.readFileSync(planPath, 'utf8'); + // The lift rule should not reference "covered edge" or "backstop edge" as statuses + assert.doesNotMatch(content, /`covered` edge/, + 'plan-phase.md must not reference "covered" edges as a status'); + assert.doesNotMatch(content, /`backstop` edge/, + 'plan-phase.md must not reference "backstop" edges as a status'); + // It should use "resolved (verification: ...)" + assert.match(content, /resolved \(verification: explicit\)/, + 'plan-phase.md lift rule must use "resolved (verification: explicit)"'); +}); + +test('#3132: ui-phase.md resolution loop uses resolved+verification — not covered/backstop', () => { + const uiPath = path.join(__dirname, '..', 'gsd-core', 'workflows', 'ui-phase.md'); + const content = fs.readFileSync(uiPath, 'utf8'); + // The resolution options should not use covered/backstop as status values + assert.doesNotMatch(content, /→ `covered`/, + 'ui-phase.md must not use "covered" as a resolution status'); + // It should use resolved+verification + assert.match(content, /→ `resolved`.*verification: explicit/, + 'ui-phase.md resolution must use "resolved" with "verification: explicit"'); +}); + +test('#3132: specless-probe-fallback.md uses resolved+verification — not covered/backstop', () => { + const fallbackPath = path.join(__dirname, '..', 'gsd-core', 'references', 'specless-probe-fallback.md'); + const content = fs.readFileSync(fallbackPath, 'utf8'); + // The fallback reference is @-loaded by plan-phase.md when EDGE_ABSENT + assert.doesNotMatch(content, /auto-`covered`/, + 'specless-probe-fallback.md must not use auto-"covered" (retired status)'); + assert.doesNotMatch(content, /auto-`backstop`/, + 'specless-probe-fallback.md must not use auto-"backstop" as a status (backstop is a verification tier only)'); + assert.doesNotMatch(content, /`covered` edge/, + 'specless-probe-fallback.md must not reference "covered" edges as a status'); + assert.match(content, /auto-`resolved`/, + 'specless-probe-fallback.md must use auto-"resolved" (not auto-"covered"/"backstop")'); + assert.match(content, /verification: explicit/, + 'specless-probe-fallback.md must reference "verification: explicit"'); +}); diff --git a/tests/emitted-drift-acks/0000-legacy-migration.json b/tests/emitted-drift-acks/0000-legacy-migration.json index a26296f09..07462fb2b 100644 --- a/tests/emitted-drift-acks/0000-legacy-migration.json +++ b/tests/emitted-drift-acks/0000-legacy-migration.json @@ -36,7 +36,7 @@ "agents/gsd-verifier.toml": "#2834: Codex agent TOML now carries model-routing fields on first install.", "gsd-code-fixer.md": "#2647: the three worktree-path sites (setup_worktree bash, concrete-steps prose, critical_rules) replaced the hardcoded /tmp/sv- mktemp path with a repo-relative .claude/worktrees/rf--- path, and added a defense-in-depth padded_phase validation at the sink (the agent prompt is a literal bash contract any caller can spawn; the orchestrator validates upstream but the sink now self-defends against path-traversal/branch-name injection). On Windows/Git Bash the /tmp path landed outside the project tree (outside the session permission allowlist, prompting on every read) and mktemp's MAX_PATH substitute was un-removable; .claude/worktrees/ is the same dir the harness-managed executor worktrees use (gitignored via .claude/, inside the permission scope). Growth is the path-resolution bash (main_repo via `git worktree list --porcelain | awk`) + the $$-PID/epoch uniqueness replacing mktemp's XXXXXX + the padded_phase guard + the #2647 rationale comments at each site. Supersedes the prior #2825 attribution, whose gated-bash + guardrail growth is already in next.", "spec-phase.md": { - "reason": "#2733: five transitions in gsd-core/workflows/spec-phase.md were re-pointed so control reaches the mandatory Step 5.5 edge-completeness and Step 5.6 prohibition-completeness probes, which no path could reach before. Four upstream gate-passed jumps went from 'Jump to Step 6' to 'Jump to Step 5.5', and Step 5.5's own terminal soft gate at :305 went from 'proceed to Step 6' to 'proceed to Step 5.6' so the common all-edges-resolved path stops skipping the prohibition probe. The +10 bytes is exactly those five targets growing by 2 bytes each ('Step 6' -> 'Step 5.5' / 'Step 5.6'); it is the literal fix, not incidental prose growth, and cannot be avoided without leaving a probe unreachable. Verified: 31987 -> 31997 bytes, DEFAULT tier, cap 40960." + "reason": "#2733: five transitions in gsd-core/workflows/spec-phase.md were re-pointed so control reaches the mandatory Step 5.5 edge-completeness and Step 5.6 prohibition-completeness probes, which no path could reach before. Four upstream gate-passed jumps went from 'Jump to Step 6' to 'Jump to Step 5.5', and Step 5.5's own terminal soft gate at :305 went from 'proceed to Step 6' to 'proceed to Step 5.6' so the common all-edges-resolved path stops skipping the prohibition probe. The +10 bytes is exactly those five targets growing by 2 bytes each ('Step 6' -> 'Step 5.5' / 'Step 5.6'); it is the literal fix, not incidental prose growth, and cannot be avoided without leaving a probe unreachable. Verified: 31987 -> 31997 bytes, DEFAULT tier, cap 40960. #3132: realigned retired covered/backstop-as-status vocab to resolved+verification." } } } diff --git a/tests/emitted-drift-acks/2650-plan-phase-stall-detection.json b/tests/emitted-drift-acks/2650-plan-phase-stall-detection.json index eb45e529a..224026f71 100644 --- a/tests/emitted-drift-acks/2650-plan-phase-stall-detection.json +++ b/tests/emitted-drift-acks/2650-plan-phase-stall-detection.json @@ -1,6 +1,6 @@ { "version": 1, "paths": { - "plan-phase.md": "#2650: after merging origin/next's #2993 fragmentization (which extracted the whole 'Chunked Planning Mode' section behind a lazily-loaded steps/chunked-planning-mode.md pointer), plan-phase.md's growth against the new base is no longer about restoring labels at 5 sites in one file — it is the remaining #2650 diff itself. Three of the five stall-watch spawn sites (standard planner, plan-checker, revision-loop planner respawn) still live directly in plan-phase.md; the other two (chunked outline planner, chunked per-plan planner) now live in the extracted gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md, where their ORCHESTRATOR RULE lines were ported during merge resolution so tests/plan-phase-drift-guard.test.cjs (#913), which now counts labels across plan-phase.md AND every file under plan-phase/steps/*.md via readPlanPhaseCombined(), still finds all 7 required labels (5 stall-watch + 2 pre-existing researcher/pattern-mapper labels). At the 3 sites remaining in plan-phase.md itself, converting the plain blocking-wait rule to the bounded gsd_stall_watch mechanism (plus restoring run_in_background=true and adding the step 7.99 pointer to stall-detection-helpers.md) is a net growth over origin/next's own copy of the file, which has the chunked-planning-mode extraction but not the #2650 stall-detection fix. Verified still well under the ADR-857 Phase 6 PRE_PHASE6 cap (94519 bytes) after the merge." + "plan-phase.md": "#2650: after merging origin/next's #2993 fragmentization (which extracted the whole 'Chunked Planning Mode' section behind a lazily-loaded steps/chunked-planning-mode.md pointer), plan-phase.md's growth against the new base is no longer about restoring labels at 5 sites in one file — it is the remaining #2650 diff itself. Three of the five stall-watch spawn sites (standard planner, plan-checker, revision-loop planner respawn) still live directly in plan-phase.md; the other two (chunked outline planner, chunked per-plan planner) now live in the extracted gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md, where their ORCHESTRATOR RULE lines were ported during merge resolution so tests/plan-phase-drift-guard.test.cjs (#913), which now counts labels across plan-phase.md AND every file under plan-phase/steps/*.md via readPlanPhaseCombined(), still finds all 7 required labels (5 stall-watch + 2 pre-existing researcher/pattern-mapper labels). At the 3 sites remaining in plan-phase.md itself, converting the plain blocking-wait rule to the bounded gsd_stall_watch mechanism (plus restoring run_in_background=true and adding the step 7.99 pointer to stall-detection-helpers.md) is a net growth over origin/next's own copy of the file, which has the chunked-planning-mode extraction but not the #2650 stall-detection fix. Verified still well under the ADR-857 Phase 6 PRE_PHASE6 cap (94519 bytes) after the merge. #3132: realigned lift rule from covered/backstop-as-status to resolved+verification." } } diff --git a/tests/emitted-drift-acks/2658-trae-instruction-file-path.json b/tests/emitted-drift-acks/2658-trae-instruction-file-path.json index 83f2f5e8d..6e8bfd2d0 100644 --- a/tests/emitted-drift-acks/2658-trae-instruction-file-path.json +++ b/tests/emitted-drift-acks/2658-trae-instruction-file-path.json @@ -15,7 +15,6 @@ "gsd-core/workflows/execute-plan.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "gsd-core/workflows/help/modes/full.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "gsd-core/workflows/milestone-summary.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", - "gsd-core/workflows/plan-phase.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "gsd-core/workflows/profile-user.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "gsd-core/workflows/progress.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "gsd-core/workflows/quick.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", diff --git a/tests/emitted-drift-acks/3132-retired-status-vocab.json b/tests/emitted-drift-acks/3132-retired-status-vocab.json new file mode 100644 index 000000000..2d1f453eb --- /dev/null +++ b/tests/emitted-drift-acks/3132-retired-status-vocab.json @@ -0,0 +1,6 @@ +{ + "version": 1, + "paths": { + "ui-phase.md": "#3132: realigned resolution loop and write-back format from covered/backstop-as-status to resolved+verification." + } +}