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

@@ -107,6 +107,8 @@ Clarify WHAT a phase delivers through Socratic questioning with quantitative amb
**Edge Coverage (Step 5.5):** After the ambiguity gate passes, spec-phase runs an edge-completeness probe over each requirement. It raises only applicable categories from a closed 8-category taxonomy (boundary, adjacency, empty, encoding, ordering, precision, idempotency, concurrency), proposes one concrete candidate edge per category, and records each as `covered` / `dismissed` (reason required) / `backstop` / `unresolved` in a `## Edge Coverage` SPEC section. Unresolved applicable edges soft-gate the spec (Resolve / Write-anyway-flagged / Keep-probing); `covered` and `backstop` edges are later lifted into plan-phase `must_haves`. Under `--auto` the probe **never auto-dismisses** — it auto-covers where a defensible acceptance criterion exists, otherwise auto-backstops.
**Prohibition Coverage (Step 5.6):** After the edge probe, spec-phase runs a prohibition-completeness probe — a two-stage prose pass (adversarial recall → precision classifier) that surfaces the unwritten *must-NOT* constraints (values/safety/ethics) the spec never forbids. Each is resolved to `resolved` (a NEGATIVE acceptance criterion, carrying a `test` or `judgment` verification tier) / `dismissed` (reason required) / `unresolved`, recorded in a `## Prohibitions (must-NOT)` SPEC section. Resolved prohibitions are lifted into plan-phase `must_haves.prohibitions`; judgment-tier items soft-gate at verify time (never silent, never hard-halt) and unwired test-tier items fail closed. Under `--auto` the probe **never auto-dismisses**; canon-bound concerns (OWASP / GDPR / fairness) are referred to `/gsd:secure-phase`.
**Prerequisites:** `.planning/ROADMAP.md` exists
**Produces:** `{phase}-SPEC.md` (with a `## Edge Coverage` section)

View File

@@ -168,6 +168,7 @@
- [Spec-Phase Edge-Completeness Probe](#144-spec-phase-edge-completeness-probe)
- [v1.43.0 Features](#v1430-features)
- [MemPalace Memory Capability](#145-mempalace-memory-capability)
- [Spec-Phase Prohibition Probe](#146-spec-phase-prohibition-probe)
---
@@ -3145,3 +3146,36 @@ The load-bearing wire is the `plan-phase` lift: `covered` and `backstop` edges b
**Configuration:** `mempalace.enabled`, `mempalace.memory_mode`, `mempalace.wing`, `mempalace.recall_on_discuss`, `mempalace.recall_on_plan`, `mempalace.capture_artifacts`, `mempalace.mirror_kg`, `mempalace.cross_project_tunnels`, `mempalace.diary_journal`, `mempalace.auto_capture_hooks`
See [Configuration Reference](CONFIGURATION.md#mempalace-settings) for full schema and [How to enable cross-session memory with MemPalace](how-to/enable-cross-session-memory-with-mempalace.md) for a setup walkthrough.
### 146. Spec-Phase Prohibition Probe
**Command:** `/gsd-spec-phase`
**Purpose:** Surface the unwritten *must-NOT* constraints — the values/safety/ethics interpretations a feature could silently become that the author would never want but the spec does not forbid — before any code is written. The edge probe reaches data-shape edges; it structurally cannot reach prohibitions. This is the missing instrument, running as `Step 5.6` of spec-phase, after the edge probe.
**Behavior:** A two-stage, prose-orchestrated pass per requirement (no compiled recall engine — recall is inherently model-driven, ADR-550 D7b):
1. **Recall (adversarial probe):** *"What could this feature silently become that the author would NOT want, but the spec does not forbid?"* — model-robust open-vocabulary elicitation across values/safety/ethics.
2. **Precision (one-pass classifier):** drop routine-engineering items, keep genuine values/safety/ethics prohibitions — collapses the raw list to the load-bearing few.
Each surfaced prohibition is resolved to exactly one of three states:
| State | Meaning | Downstream effect |
|-------|---------|-------------------|
| `resolved` | Confirmed a real must-NOT | NEGATIVE acceptance criterion written into the SPEC `## Prohibitions (must-NOT)` section; lifted into `plan-phase` `must_haves.prohibitions` (its own sibling block, never `truths`) |
| `dismissed` | Not a genuine prohibition (requires a non-empty reason) | Recorded with its reason; empty dismissals are rejected |
| `unresolved` | Deferred | Soft-gates the spec; surfaced as a planner assumption |
Each resolved prohibition carries a `verification` tier — `test` (a negative test can enforce it) or `judgment` (only human/LLM judgment can). At verify time, judgment-tier prohibitions route to a never-silent / never-hard-halt soft gate (autonomous emits an `unverified-prohibition — human review recommended` flag); test-tier prohibitions fail closed when unwired (never silently green). Under `--auto`, the probe **never auto-dismisses**. Canon-bound concerns (OWASP / GDPR / fairness) are referred to `/gsd:secure-phase` rather than minting SPEC prohibitions (ADR-550 D6).
The load-bearing wire is the `plan-phase` lift into `must_haves.prohibitions`, so the section is not merely documentation.
**Requirements:**
- REQ-PROHIB-01: The prohibition pass MUST run after the edge probe and emit a `## Prohibitions (must-NOT)` SPEC section.
- REQ-PROHIB-02: Stage 1 MUST ask the adversarial recall question; Stage 2 MUST drop routine-engineering items and keep values/safety/ethics prohibitions.
- REQ-PROHIB-03: A `dismissed` resolution MUST require a non-empty reason.
- REQ-PROHIB-04: `--auto` MUST never auto-dismiss.
- REQ-PROHIB-05: `plan-phase` MUST lift resolved prohibitions into `must_haves.prohibitions` (never `truths`).
- REQ-PROHIB-06: A well-formed but unwired `test`-tier prohibition MUST fail closed at verify time — never a silent pass.
**Reference:** [Prohibition Probe](../gsd-core/references/prohibition-probe.md)

View File

@@ -238,6 +238,7 @@
"planner-revision.md",
"planner-source-audit.md",
"planning-config.md",
"prohibition-probe.md",
"project-skills-discovery.md",
"questioning.md",
"research-documentation-lookup.md",

View File

@@ -304,6 +304,7 @@ Full roster at `gsd-core/references/*.md`. References are shared knowledge docum
| `continuation-format.md` | Session continuation/resume format. |
| `domain-probes.md` | Domain-specific probing questions for discuss-phase. |
| `edge-probe.md` | Spec-phase edge-completeness probe — 8-category edge taxonomy, shape classification, and the `requirements → checks → verifier` resolution model (Step 5.5). |
| `prohibition-probe.md` | Spec-phase prohibition-completeness probe — the two-stage adversarial-recall → precision protocol that surfaces the unwritten *must-NOT* constraints (values/safety/ethics), with status×verification (`test`/`judgment`) tiering and canon-referral breadcrumbs (Step 5.6); second adapter of the `probe-core` resolution model. |
| `gate-prompts.md` | Gate/checkpoint prompt templates. |
| `loop-hook-dispatch.md` | Generic dispatch contract for consuming `gsd_run loop render-hooks <point> --raw` output in any host-loop workflow — envelope shape, per-kind dispatch rules (contribution/step/gate), and liveness banner. |
| `scout-codebase.md` | Phase-type→codebase-map selection table for discuss-phase scout step (extracted via #2551). |

View File

@@ -19,6 +19,7 @@ Language versions: [English](README.md) · [Português (pt-BR)](pt-BR/README.md)
- [Install a minimal GSD and add skills later](how-to/install-minimal-and-add-skills.md) — install only the core skills, then grow the surface with profiles and `/gsd:surface`
- [Discuss a phase](how-to/discuss-a-phase.md) — capture implementation decisions before planning begins
- [Resolve edge-coverage findings](how-to/resolve-edge-coverage-findings.md) — turn the spec phase's surfaced domain-boundary edges into covered, dismissed, or backstopped spec decisions
- [Resolve prohibition findings](how-to/resolve-prohibition-findings.md) — turn the spec phase's surfaced must-NOT constraints into resolved, dismissed, or deferred spec decisions
- [Plan a phase](how-to/plan-a-phase.md) — run research, decompose work, and verify plan quality
- [Execute a phase](how-to/execute-a-phase.md) — run plans in parallel waves with fresh-context subagents
- [Verify and ship](how-to/verify-and-ship.md) — walk through completed work, diagnose failures, and create the PR

View File

@@ -84,3 +84,12 @@ The capability system (ADR-857) classifies **predicate-generation as core verifi
- **"Exogenous grading"** is the verify-time half of this ADR's **judgment-tier** (Decision 4): the autonomous verifier records an LLM-judge verdict **marked non-authoritative** and flags *"unverified prohibition — human review recommended"* — grading against the spec's explicit predicates, never a self-graded restatement (the prose-drift / self-graded-review failure modes reproduced on #664). The judgment-tier soft-gate **is** the contract's "never a silent pass" guarantee at the core boundary.
- **The probe *adapters* are the core-default *generator*** — `edge-probe`'s `classifyShape`/`proposeEdges` and the prohibition probe's adversarial LLM-propose, the surfaces that *propose* predicates — default-on and non-removable, but **independently versionable**. The classifier's measured recall gap (the prose→shape under-fire on terse prose) is the reason they stay their own modules under this ADR rather than being folded into the slow core rail. **`probe-core` is not the generator:** per Decision 7b it ingests already-proposed items, and its deterministic validators are the **contract**'s CI-testable surface (Decision 5) — so `probe-core` sits on the contract side, the adapters on the generator side. ADR-857 phase 6 wires these modules onto the core predicate rail; it does **not** relabel them as a `capabilities/edge-probe/` plug-in.
- Decision 5's rule — *the CI-testable surface is the contract, not the classifier* — extends to ADR-857's core rail: the deterministic conformance test is the contract shape the verifier consumes, never the LLM's judgment.
## Addendum (2026-06-12): test-tier disposition — fail-closed now, heavy enforcement deferred
Decision 4 describes the `test`-tier as a "**Hard gate in both interactive and autonomous modes.**" The #644 implementation revises that to a **fail-closed-now / deferred-enforcement** resolution (the "B-with-guard" maintainer decision of 2026-06-12), so the architecture-of-record matches the shipped code:
- A well-formed but **unwired** `test`-tier prohibition resolves via `dispositionForProhibition()` to `{ status: 'unverified', flagged: true }` — **provably never green** without explicit evidence (REQ-PROHIB-06). This is the load-bearing safety half and it holds today.
- The **heavy negative-test enforcement mechanism** — a contrived `test`-tier consumer that mechanically runs the negative test — is **deferred to a follow-up**, because the #644 corpus is entirely `judgment`-tier and wiring a synthetic test-tier consumer now would be gold-plating. The fail-closed disposition guards the gap in the meantime: the gate cannot silently pass, it can only report `unverified`/flagged until enforcement lands.
Net effect on D4: the *guarantee* ("a `test`-tier prohibition is never a silent pass") is preserved exactly; what is deferred is only the mechanical-pass half of the gate, not the never-green safety property. This addendum supersedes the unqualified "hard gate" wording of Decision 4 for `test`-tier items until the enforcement follow-up lands. The decision also lives in `src/probe-core.cts` comments, `verify-phase.md`, and the #644 changeset.

View File

@@ -0,0 +1,110 @@
# How to resolve prohibition findings while writing a spec
**Goal:** Turn each must-NOT the spec phase surfaces into an explicit, checkable spec decision — so a constraint the author assumed but never wrote (the reminder that must not shame the user, the model that must not proxy on protected attributes, the log that must not store raw PII) becomes an acceptance criterion the verifier can enforce *before* any code exists, instead of an unwritten intent a literal `✅ done` can silently violate.
**Prerequisites:** A phase whose `/gsd-spec-phase` run has passed the ambiguity gate and the edge-completeness probe (Step 5.5). The prohibition probe (Step 5.6) then runs automatically over the same requirements and presents its findings — you do not invoke it separately.
For the two-stage recall→precision protocol, the canon-referral rule, and the `status × verification` schema, see [Spec-Phase Prohibition Probe](../FEATURES.md#146-spec-phase-prohibition-probe). This guide covers only how to *act* on the findings.
---
## Read a finding
Each finding is one **bespoke** must-NOT for one requirement — a values, safety, fairness, privacy, or transparency constraint the probe kept after dropping routine-engineering noise. A finding is phrased as a must-NOT statement, for example:
> **R1 · values** — MUST NOT use shaming, guilt, or loss-aversion streak framing (e.g. "Don't lose your streak!") — the reminder must encourage without penalty framing
You resolve each finding into one of three states. Claude presents them as a numbered choice (or an `AskUserQuestion` menu).
---
## Keep it — write a negative acceptance criterion
**Choose this when the prohibition is genuine for this feature.** Claude writes a must-NOT line into the spec's **Acceptance Criteria**, marks the prohibition `resolved`, and you pick its **verification tier**:
- **`test`** — a mechanical check can prove it: a negative test, a lint rule, an assertion that the audit log contains no raw SSN. Choose this when a green/red check exists.
- **`judgment`** — the prohibition is real but cannot be reduced to a mechanical test (e.g. "the framing is not manipulative"). It records intent and routes to a judgment-based review rather than a passing test.
> - [ ] MUST NOT use shaming or loss-aversion framing in the reminder copy *(judgment)*
Pick the tier honestly — see [what happens downstream](#what-happens-to-resolved-findings-downstream) for why a `test`-tier prohibition is held to a stricter bar.
---
## Dismiss it — record why it does not apply
**Choose this when the prohibition genuinely does not apply** — and say why. A dismissal **requires a non-empty reason**; silence is rejected.
> ⛔ dismissed — pure integer utility; no user-facing surface, no values/safety/privacy dimension
A wrong dismissal is the exact silent failure this probe exists to prevent, so dismiss only when the reason is solid. The reason string is the audit trail.
---
## Defer it — leave it unresolved and flagged
**Choose this only when you are not ready to decide.** The prohibition stays `unresolved` and is flagged. Unlike a dismissal, deferring makes no claim that the prohibition is safe — it is an explicit, visible assumption the planner must surface, not silently drop.
---
## When a finding is canon, not yours
Some candidates are **canon** security/compliance constraints a dedicated tool already owns (OWASP / prototype-pollution / path-traversal / injection → `/gsd-secure-phase` + eslint; GDPR retention / consent → `/gsd-secure-phase`). The probe does not surface these as findings to resolve — it emits a one-line breadcrumb and drops them, so the list you triage stays the ~2–3 **bespoke** prohibitions no other tool would catch. You do not act on a breadcrumb here; pick it up in `/gsd-secure-phase`.
---
## Clear the soft gate
After you have worked through the findings, the probe runs a **soft gate**:
- **All applicable prohibitions resolved** → the spec proceeds to the next step.
- **One or more still `unresolved`** → Claude asks what to do:
- **Resolve now** — loop back and resolve the remaining prohibitions.
- **Write the spec anyway** — the spec is written with those rows marked `⚠ Prohibition unresolved — planner must treat as assumption`. Use this deliberately; you are choosing to ship a known gap.
- **Keep probing** — continue surfacing.
The gate is *soft*: it never blocks you, but every unresolved prohibition stays visible in the spec's `## Prohibitions` section.
---
## Let Claude resolve them for you
**If the prohibitions are low-stakes or already settled by earlier phases**, run the spec phase in auto mode:
```bash
/gsd-spec-phase 3 --auto
```
In `--auto`, Claude marks a prohibition `resolved` where it can write a defensible negative acceptance criterion (at the `test` or `judgment` tier), and leaves it `unresolved` otherwise. It **never auto-dismisses** — dismissing a prohibition requires a human reason, because a wrong auto-dismissal is precisely the silent failure being eliminated. Claude logs the tally, for example:
```
[auto] prohibitions: 2 resolved, 1 unresolved
```
Review the logged choices afterwards; auto mode is a fast first pass, not a substitute for judgement on a prohibition that carries risk.
---
## What happens to resolved findings downstream
When you next run `/gsd-plan-phase`, the planner reads the spec's `## Prohibitions` section and:
- lifts every confirmed prohibition into the plan's `must_haves.prohibitions`,
- carries the `test`-tier and `judgment`-tier distinction with it,
- surfaces every `unresolved` prohibition as an explicit assumption.
At verify time, a `test`-tier prohibition whose mechanical check is not yet wired is held **fail-closed** — it reports as flagged/unverified, never as a silent pass — so a prohibition can never quietly disappear between spec and verification. A `judgment`-tier prohibition routes to a judgment-based review rather than a green/red test.
This is the payoff: a resolved prohibition becomes a checkable negative the goal-backward verifier accounts for, extending its reach to the must-NOT the requirement prose never stated.
---
## Related
- [Spec-Phase Prohibition Probe](../FEATURES.md#146-spec-phase-prohibition-probe) — protocol, schema, host-mapping table, and the front-of-pipeline rationale
- [Resolve edge-coverage findings](resolve-edge-coverage-findings.md) — the sibling probe on the data/behavior-shape axis
- [`/gsd-spec-phase`](../COMMANDS.md#gsd-spec-phase) — command reference and flags
- [Plan a phase](plan-a-phase.md) — where resolved prohibitions become `must_haves.prohibitions`
- [docs index](../README.md)
</content>
</invoke>