feat(spec-phase): prohibition probe — surface "must-NOT" constraints (#644) (#1149)

Adds the spec-time prohibition probe (spec-phase Step 5.6) — the second adapter of the probe-core resolution model. Surfaces unwritten must-NOT constraints as negative SPEC acceptance criteria with test/judgment verification tiers; fail-closed at verify time. Per ADR-550. Closes #644.
This commit is contained in:
Rezolv
2026-06-14 21:29:11 -04:00
committed by GitHub
parent 9a4a448c27
commit 395fb519e7
29 changed files with 1503 additions and 11 deletions

View File

@@ -892,6 +892,7 @@ Output consumed by /gsd:execute-phase. Plans need:
- 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`, and every `backstop` edge into `must_haves.truths` as a non-inferable check (note it needs a held-out/property-based test). `unresolved` edges are explicit assumptions — surface them in the plan, do not silently drop them.
- If the SPEC has a `## Prohibitions` section, lift every resolved prohibition into the `must_haves.prohibitions:` sibling block (NOT `truths` — ADR-550 D3) carrying `statement` + `status` + `verification`; unresolved prohibitions are explicit assumptions — surface them in the plan, do not silently drop them. A prohibition is a must-NOT (negative) check that belongs in its own `must_haves.prohibitions` block. Never place a must-NOT under `must_haves.truths` — that block keeps positive-observable semantics only.
- **"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>
@@ -938,6 +939,7 @@ Every task MUST include these fields — they are NOT optional:
- [ ] 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 SPEC ## Prohibitions resolved item is represented in a plan's must_haves.prohibitions (no silent drops)
</quality_gate>
```

View File

@@ -312,11 +312,73 @@ failure being eliminated). Log: `[auto] edge coverage: C covered, B backstop, U
Populate the `## Edge Coverage` section of SPEC.md from the resolved edges.
## Step 5.6: Prohibition-Completeness Probe (must-NOT)
Run AFTER Step 5.5 (you probe the must-NOT axis of clear requirements, over the same
requirement list). Reference: @~/.claude/gsd-core/references/prohibition-probe.md — the
portable two-stage protocol, the canon-referral rule, and the status×verification schema
live there (size-cap discipline; keep this step lean).
**D1 — no compiled engine (ADR-550 D7b).** Unlike Step 5.5, the prohibition probe has NO
compiled recall engine and runs NO `node` invocation here. The recall stage is an LLM prose
pass: the closed eight-category edge taxonomy a classifier can apply does not exist for the
open values/safety/ethics must-NOT axis. Do NOT copy the Step 5.5 engine-resolution block.
Only the schema/projection layer is real code; the recall is prose.
For each Requirement gathered so far, run the two-stage recall→precision pass:
1. **Stage 1 — Recall (adversarial prose probe).** Ask the single adversarial question of the
requirement: *"What could this feature silently become that the author would NOT want, but
the spec does not forbid?"* Over-produce (~10 raw must-NOT candidates) — recall first.
2. **Stage 2 — Precision (one-pass classifier).** Filter the raw list in a single pass:
**DROP routine-engineering** items (normal correctness/hygiene — "must not mutate input",
"must not throw on empty" — owned by the edge probe or code review); **KEEP
values / safety / ethics** items (manipulative framing, protected-attribute proxies, raw
PII in plaintext). This collapses ~10 → ~2–3 genuine prohibitions.
3. **Canon-referral (ADR-550 D6, PROB-13).** A kept candidate that is canon security/compliance
(OWASP / prototype-pollution / path-traversal / injection / GDPR / generic fairness) is
NOT minted here — emit a one-line breadcrumb (*"prototype-pollution is canon — owned by
/gsd:secure-phase + eslint; not minted here"*) and DROP it. Minting canon items duplicates
/gsd:secure-phase and drowns the bespoke signal.
4. **Resolve each surfaced (non-canon) prohibition** (AskUserQuestion; text mode → numbered list):
- **Keep it** → write a NEGATIVE acceptance criterion (a must-NOT line) into Acceptance
Criteria AND mark the prohibition `resolved` with a verification tier: `test` (a
mechanical negative test/lint/assertion exists) or `judgment` (real but not mechanically
checkable — routes to judgment review).
- **Dismiss (reason)** → mark `dismissed` with a REQUIRED non-empty reason (PROB-05). The
reason string is the audit trail; silence is not a valid dismissal.
- **Defer** → leave `unresolved`.
**Soft gate (after resolving) — PROB-06:**
- All applicable prohibitions resolved → proceed to Step 6.
- Any `unresolved` → AskUserQuestion:
- header: "Prohibitions"
- question: "[N] prohibition(s) are unresolved: [list]. What do you want to do?"
- options: "Resolve now" (loop back) / "Write SPEC.md anyway — flag unresolved" /
"Keep probing"
- On "anyway": write SPEC.md with those rows marked `⚠ Prohibition unresolved — planner
must treat as assumption`. This is a soft gate (write-anyway-with-flags), never a silent
skip — the soft gate IS the control.
**`--auto` mode:** auto-`resolved` where a defensible negative acceptance criterion can be
written (test or judgment tier); otherwise leave `unresolved`. **`--auto` NEVER auto-dismisses
a prohibition** — a wrong dismissal is the exact silent failure this probe eliminates (PROB-06,
the load-bearing safety property). Log: `[auto] prohibitions: R resolved, U unresolved`.
**Text mode (PROB-09):** per Step 5's text-mode rule, replace the AskUserQuestion menus above
with plain-text numbered lists — there is NO hard AskUserQuestion dependency, so the probe
runs identically for non-Claude / text-mode hosts.
Populate the `## Prohibitions` section of SPEC.md from the resolved prohibitions (each
`resolved`/`test` row is a checkable negative acceptance criterion; `resolved`/`judgment`
rows route to judgment review; `⚠ UNRESOLVED` rows are flagged as assumptions).
## Step 6: Generate SPEC.md
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 **Prohibitions** section from Step 5.6 (resolved/dismissed/unresolved rows with the test|judgment tier).
**Requirements for every requirement entry:**
- One specific, testable statement
@@ -377,6 +439,7 @@ Next: /gsd:discuss-phase {X}
- Scout the codebase BEFORE the first question — grounded questions only
- Max 2–3 questions per round — do not frontload all questions at once
- Step 5.5 edge probe runs after the ambiguity gate; dismissals require a reason; --auto never auto-dismisses
- Step 5.6 prohibition probe runs after the edge probe; dismissals require a reason; --auto never auto-dismisses a prohibition
</critical_rules>
<success_criteria>
@@ -389,4 +452,5 @@ Next: /gsd:discuss-phase {X}
- SPEC.md committed atomically (when commit_docs is true)
- User directed to /gsd:discuss-phase as next step
- Edge-completeness probe run; Edge Coverage section populated; unresolved edges flagged as assumptions
- Prohibition-completeness probe run; Prohibitions section populated; unresolved prohibitions flagged as assumptions
</success_criteria>

View File

@@ -63,10 +63,15 @@ for plan in "$PHASE_DIR"/*-PLAN.md; do
done
```
Returns JSON: `{ truths: [...], artifacts: [...], key_links: [...] }`
Returns JSON: `{ truths: [...], artifacts: [...], key_links: [...], prohibitions: [...] }`
Aggregate all must_haves across plans for phase-level verification.
**Prohibitions (`must_haves.prohibitions`, ADR-550 D3 — the must-NOT sibling block):** When a plan carries `must_haves.prohibitions`, extract each `{ statement, status, verification }` item and route it by `verification` tier in verdict assembly (ADR-550 D4, "B-with-guard", 2026-06-12 maintainer decision). These are NEGATIVE checks (the must-NOT must NOT have happened), distinct from positive `truths`:
- **judgment-tier → mode-dependent soft-gate.** Interactive verify defers each item to the end-of-phase human checkpoint (`human_verify_mode: end-of-phase`). Autonomous verify records a NON-AUTHORITATIVE LLM-judge verdict + a prominent `unverified-prohibition — human review recommended` flag (autonomous completion reads "complete with N flagged prohibitions"). NEVER a silent pass; NEVER a hard halt of an AFK run.
- **test-tier → FAIL CLOSED (accept-and-flag).** Accept the `verification: test` value (the SPEC↔must_haves.prohibitions projection contract holds — no forced schema change later), but a well-formed test-tier item reaching verify with NO wired enforcement disposes as UNVERIFIED, flagged like an unresolved judgment item, NEVER green. The deterministic fail-closed default is `dispositionForProhibition()` in probe-core (`status: 'unverified'`, `flagged: true` on empty `enforcementEvidence`). The real fail-first negative-test enforcement MECHANISM defers to a follow-up PR (#644's corpus is entirely judgment-tier; a contrived test-tier fixture here would be the gold-plating failure mode).
**Option B: Use Success Criteria from ROADMAP.md**
If no must_haves in frontmatter (MUST_HAVES returns error or empty), check for Success Criteria:
@@ -465,13 +470,18 @@ Classify status using this decision tree IN ORDER (most restrictive first):
1. IF any truth FAILED, artifact MISSING/STUB, key link NOT_WIRED, blocker found, **or test quality audit found blockers (disabled requirement tests, circular tests)**:
→ **gaps_found**
2. IF the previous step produced ANY human verification items:
2. IF any `must_haves.prohibitions` item disposes as flagged-unverified (ADR-550 D4):
- **test-tier, fail-closed** (no wired enforcement — `dispositionForProhibition()` returns `status: 'unverified'`, `flagged: true`): → **gaps_found** (never green; the unwired test-tier item is an unverified gap).
- **judgment-tier, autonomous run** (non-authoritative LLM-judge verdict): emit the `unverified-prohibition — human review recommended` flag and classify → **human_needed** (autonomous completion reads "complete with N flagged prohibitions"; never a silent pass, never a hard halt).
- **judgment-tier, interactive run**: route to the end-of-phase human checkpoint → **human_needed**.
3. IF the previous step produced ANY human verification items:
→ **human_needed** (even if all truths VERIFIED and score is N/N)
3. IF all checks pass AND no human verification items:
4. IF all checks pass AND no human verification items AND no flagged prohibitions:
→ **passed**
**passed is ONLY valid when no human verification items exist.**
**passed is ONLY valid when no human verification items AND no flagged prohibitions exist.** A prohibition (must-NOT) can never be silently absorbed into a `passed` verdict — that is the core failure mode ADR-550 D4 forbids.
**Score:** `verified_truths / total_truths`
</step>