enhance(#1279): project check_violation_fixture scalar — #1278 locate + #1279 proof compose end-to-end (#1346)
Delivers option (a) from the #1314 maintainer review: thread a fourth flat scalar check_violation_fixture through the projection so a prohibition authored at spec-phase machine-proves fail-first and greens through the deterministic path alone — zero hand-authoring at verify time. - src/probe-core.cts: Prohibition gains check_violation_fixture?; projectProhibitions emits it (both kinds) ONLY for a well-formed descriptor and ONLY when non-empty (blank/absent -> projects absent -> producer hard-gates, never a partial green). - src/prohibition-enforcement.cts: descriptorFromProjection reads it back into violationFixture via the same numeric-coercion-safe scalar() normalizer. - Tests (RED-first, proven non-vacuous by reverting both src edits): CHK-02(#1346) projection emit, CHK-08(#1346) read-back, CHK-03(D) example round-trip, the fast-check round-trip property extended to the 4th scalar (the contract trek-e blocked #1301 on), and a real-subprocess COMPOSE capstone greening end-to-end through project -> descriptorFromProjection -> default prover+runner. - Docs flipped from 'hard-gates until #1346' to 'composes end-to-end': verify-phase.md, prohibition-probe.md, spec-phase.md authoring, ADR-550 addendum, changeset. #1346 now tracks only the node-test causation residual. 190 affected-suite tests green; eslint + tsc clean; size baseline regenerated.
This commit is contained in:
@@ -365,11 +365,16 @@ For each Requirement gathered so far, run the two-stage recall→precision pass:
|
||||
- `check_target` — the negative-test file path (for `node-test`), or the path to lint
|
||||
(for `lint-rule`).
|
||||
- `check_rule` — the eslint rule id (e.g. `local/no-source-grep`); `lint-rule` only.
|
||||
- `check_violation_fixture` (#1346) — path to a KNOWN-BAD subject the wired check is run
|
||||
against to **machine-prove fail-first**; rides BOTH kinds. Capture it to let the item green
|
||||
end-to-end with zero hand-authoring at verify time; for `node-test` the negative test should
|
||||
read its subject from the `GSD_PROHIB_SUBJECT` env var so the prover can inject this fixture.
|
||||
This is a **SOFT capture (CHK-04): a `test`-tier prohibition WITHOUT a descriptor is still
|
||||
allowed** — if the author cannot yet name the wired check, leave the descriptor empty and
|
||||
proceed. It is NOT a hard authoring block; the item simply stays fail-closed/flagged
|
||||
downstream (an absent/partial descriptor → `descriptorFromProjection` null/under-specified
|
||||
→ producer fail-closed locate, never green). Do NOT capture `failFirst` here — it is a
|
||||
downstream (an absent/partial descriptor — or one with no `check_violation_fixture` —
|
||||
→ `descriptorFromProjection` null/under-specified/fixture-less → producer fail-closed
|
||||
locate-or-unprovable, never green). Do NOT capture `failFirst` here — it is a
|
||||
verify-time caller attestation, not a spec-authored field (#1279).
|
||||
- **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.
|
||||
@@ -390,8 +395,8 @@ For each Requirement gathered so far, run the two-stage recall→precision pass:
|
||||
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). On a `test`-tier auto-resolution, capture the `check_kind` /
|
||||
`check_target` / `check_rule` descriptor **only when a wired check is unambiguous**; otherwise
|
||||
leave it empty — `--auto` NEVER fabricates a check path (a wrong locate is re-validated and
|
||||
`check_target` / `check_rule` / `check_violation_fixture` descriptor **only when a wired check is unambiguous**; otherwise
|
||||
leave it empty — `--auto` NEVER fabricates a check path or fixture (a wrong locate is re-validated and
|
||||
fails closed at the producer, but a fabricated path is still noise to avoid). Log:
|
||||
`[auto] prohibitions: R resolved, U unresolved`.
|
||||
|
||||
@@ -402,8 +407,8 @@ 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). A
|
||||
`resolved`/`test` row ALSO carries its captured `check_kind` / `check_target` / `check_rule`
|
||||
descriptor when present (so the projection feeds `verify-phase`'s deterministic locate, #1278);
|
||||
`resolved`/`test` row ALSO carries its captured `check_kind` / `check_target` / `check_rule` /
|
||||
`check_violation_fixture` descriptor when present (so the projection feeds `verify-phase`'s deterministic locate + machine-proof, #1278 + #1346);
|
||||
a `test` row with no captured descriptor is still valid — it stays fail-closed/flagged
|
||||
downstream rather than blocking authoring.
|
||||
|
||||
|
||||
@@ -70,17 +70,17 @@ 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 → ENFORCED via `check prohibition-enforcement` (green on pass, hard-gate on miss/fail).** Accept the `verification: test` value (the SPEC↔must_haves.prohibitions projection contract holds — no forced schema change later). For each test-tier item, the verifier builds `request.check` **DETERMINISTICALLY from the projected descriptor** — it does NOT invent `{ kind, target, rule }`. Read the flat scalar keys `check_kind` / `check_target` / `check_rule` off the `must_haves.prohibitions` item and reconstruct the `CheckDescriptor` via the `descriptorFromProjection` adapter in `prohibition-enforcement` (`descriptorFromProjection(projectedItem)` → `{ kind: check_kind, target: check_target, rule?: check_rule }`). Supply a `violationFixture` (a path to a KNOWN-BAD subject) in the request — it is the field that gates **green**, and it is **NOT sourced from the projection today** (the projected `check_*` scalars carry no fixture; threading a `check_violation_fixture` scalar through is the tracked follow-up **#1346**). So with projection alone the item **hard-gates fail-closed** — green currently requires a hand-supplied `violationFixture`. Do NOT rely on `failFirst`: it is DEMOTED (#1279) and greens nothing on its own. Invoke the producer (CLI surface unchanged):
|
||||
- **test-tier → ENFORCED via `check prohibition-enforcement` (green on pass, hard-gate on miss/fail).** Accept the `verification: test` value (the SPEC↔must_haves.prohibitions projection contract holds — no forced schema change later). For each test-tier item, the verifier builds `request.check` **DETERMINISTICALLY from the projected descriptor** — it does NOT invent `{ kind, target, rule }`. Read the flat scalar keys `check_kind` / `check_target` / `check_rule` / `check_violation_fixture` off the `must_haves.prohibitions` item and reconstruct the `CheckDescriptor` via the `descriptorFromProjection` adapter in `prohibition-enforcement` (`descriptorFromProjection(projectedItem)` → `{ kind: check_kind, target: check_target, rule?: check_rule, violationFixture?: check_violation_fixture }`). The `violationFixture` (a path to a KNOWN-BAD subject) is the field that gates **green** and it is **now projected** (`check_violation_fixture`, #1346) — so a prohibition authored with all four scalars greens through the projection alone, **zero hand-authoring at verify time**. Do NOT rely on `failFirst`: it is DEMOTED (#1279) and greens nothing on its own; an item with no projected fixture hard-gates fail-closed. Invoke the producer (CLI surface unchanged):
|
||||
|
||||
```bash
|
||||
gsd_run check prohibition-enforcement <request.json>
|
||||
```
|
||||
|
||||
where `<request.json>` carries `{ prohibition, check, mode }` — `check` being the wired mechanical-check descriptor `{ kind: 'node-test' | 'lint-rule', target, rule?, violationFixture, failFirst? }`, with `kind`/`target`/`rule` now sourced from the projected `check_*` scalars (not author/verifier invention — #1278). For `node-test`, `target` (from `check_target`) is the negative-test file path; for `lint-rule`, `target` is the PATH to lint and `rule` (from `check_rule`) is the eslint rule id (e.g. `local/no-source-grep`) — both required (a lint-rule without `rule` is not a valid wired check). `violationFixture` is the author-supplied path to a KNOWN-BAD subject the producer runs the check against to **machine-prove fail-first** (for `node-test`, injected via the `GSD_PROHIB_SUBJECT` env convention — #1279); `failFirst` is a DEMOTED, non-authoritative hint kept only for backward route-JSON shape (no path greens on it alone — FF-08). The producer LOCATES the wired check from the projection, **machine-proves it is fail-first** by running it against the violation and confirming it goes RED, RUNS it for a genuine non-vacuous pass, builds `enforcementEvidence`, and emits the `dispositionForProhibition()` verdict (#1259 + #1278 + #1279, ADR-550 D5d). Fail-first is **machine-proven, not caller-attested** — absent a provable violation the producer fails closed, never falling back to attestation. Route the result by its typed fields:
|
||||
where `<request.json>` carries `{ prohibition, check, mode }` — `check` being the wired mechanical-check descriptor `{ kind: 'node-test' | 'lint-rule', target, rule?, violationFixture, failFirst? }`, with `kind`/`target`/`rule`/`violationFixture` now sourced from the projected `check_*` scalars (not author/verifier invention — #1278 + #1346). For `node-test`, `target` (from `check_target`) is the negative-test file path; for `lint-rule`, `target` is the PATH to lint and `rule` (from `check_rule`) is the eslint rule id (e.g. `local/no-source-grep`) — both required (a lint-rule without `rule` is not a valid wired check). `violationFixture` (from `check_violation_fixture`) is the path to a KNOWN-BAD subject the producer runs the check against to **machine-prove fail-first** (for `node-test`, injected via the `GSD_PROHIB_SUBJECT` env convention — #1279); `failFirst` is a DEMOTED, non-authoritative hint kept only for backward route-JSON shape (no path greens on it alone — FF-08). The producer LOCATES the wired check from the projection, **machine-proves it is fail-first** by running it against the violation and confirming it goes RED, RUNS it for a genuine non-vacuous pass, builds `enforcementEvidence`, and emits the `dispositionForProhibition()` verdict (#1259 + #1278 + #1279, ADR-550 D5d). Fail-first is **machine-proven, not caller-attested** — absent a provable violation the producer fails closed, never falling back to attestation. Route the result by its typed fields:
|
||||
- **`status: 'green'`, `flagged: false`** (a genuinely-passing wired negative test / lint rule, `located: true`, non-empty `evidence`) → the item is satisfiable → it can reach **passed**.
|
||||
- **missing, non-attested, or genuinely-non-passing check** (`located: false` OR `status: 'unverified'`, `flagged: true`) → **hard-gate**: disposes flagged-unverified, NEVER green, routing to `gaps_found` in BOTH interactive and autonomous modes (a failing mechanical check blocks even AFK; ADR-550 D4 / D3). The deterministic fail-closed default backing every miss/fail is `dispositionForProhibition()` in probe-core (`status: 'unverified'`, `flagged: true` on empty `enforcementEvidence`).
|
||||
|
||||
> **Descriptor source — deterministic locate (#1278, DELIVERED).** The `check` descriptor's `{ kind, target, rule }` is now sourced **deterministically from the projected `check_kind` / `check_target` / `check_rule` scalars** on the `must_haves.prohibitions` item (authored at `/gsd:spec-phase`, projected by `projectProhibitions`, read back via the `descriptorFromProjection` adapter). So the **locate** half closes with **zero manual descriptor authoring** — the verifier no longer invents `{ kind, target, rule }` (removing the spoofable invent-at-verify-time surface; ADR-857 §147 exogenous grading). **Fail-closed is preserved:** an item with NO projected descriptor — or a PARTIAL one (e.g. a `lint-rule` missing `check_rule`) — makes `descriptorFromProjection` return `null` / an under-specified descriptor, which falls through to the producer's existing fail-closed LOCATE (`located: false`) → flagged-unverified, NEVER green, in BOTH modes. **Important — the projection does NOT yet carry a `violationFixture`** (`descriptorFromProjection` reconstructs only `{ kind, target, rule? }`). Because #1279 now machine-proves fail-first against a `violationFixture`, a prohibition wired **purely through this projected path always hard-gates** (fail-closed, safe) until a `check_violation_fixture` scalar is threaded through — the **tracked follow-up #1346**. Until then a green requires the verifier to hand-supply `violationFixture`; `failFirst` is demoted and greens nothing (#1279, DELIVERED).
|
||||
> **Descriptor source — deterministic locate + machine-proof compose (#1278 + #1346, DELIVERED).** The `check` descriptor's `{ kind, target, rule, violationFixture }` is now sourced **deterministically from the projected `check_kind` / `check_target` / `check_rule` / `check_violation_fixture` scalars** on the `must_haves.prohibitions` item (authored at `/gsd:spec-phase`, projected by `projectProhibitions`, read back via the `descriptorFromProjection` adapter). So both halves close with **zero manual descriptor authoring** — the verifier neither invents the locate (#1278) nor hand-supplies the violation fixture (#1346): a prohibition authored with all four scalars machine-proves fail-first and greens end-to-end through the projection alone (removing the spoofable invent-at-verify-time surface; ADR-857 §147 exogenous grading). **Fail-closed is preserved:** an item with NO projected descriptor, a PARTIAL one (e.g. a `lint-rule` missing `check_rule`), OR a descriptor with **no `check_violation_fixture`** makes `descriptorFromProjection` return `null` / an under-specified or fixture-less descriptor, which falls through to the producer's fail-closed paths (`located: false`, or located-but-unprovable) → flagged-unverified, NEVER green, in BOTH modes. `failFirst` is demoted and greens nothing on its own (#1279, FF-08). Residual (tracked **#1346**): the node-test proof confirms the fixture exists and the check goes RED, but cannot generically prove the red was *caused by* the subject's content vs the env merely being set.
|
||||
|
||||
**Option B: Use Success Criteria from ROADMAP.md**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user