fix(#3132): realign retired covered/backstop-as-status vocab to resolved+verification (#3138)

* 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 <sim@local>
This commit is contained in:
Tom Boucher
2026-08-07 04:45:30 -04:00
committed by GitHub
parent a731a45cd6
commit 3146ff36aa
11 changed files with 110 additions and 38 deletions

View File

@@ -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 <intel_surface_hint> 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: <the check>, 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: <the check>, 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.
</downstream_consumer>
@@ -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)
</quality_gate>
```

View File

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

View File

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