Skip to content

fix(harness): re-wire the de-wired gates + route the control plane (#93) - #94

Merged
dollspace-gay merged 1 commit into
mainfrom
fix/rewire-control-plane-gates-93
Jul 29, 2026
Merged

fix(harness): re-wire the de-wired gates + route the control plane (#93)#94
dollspace-gay merged 1 commit into
mainfrom
fix/rewire-control-plane-gates-93

Conversation

@dollspace-gay

Copy link
Copy Markdown
Member

Closes #93.

What was broken

5581b65f ("Stage-3 REQ-1: the @bv clause tag, parse-gated") removed three hook entries from .claude/settings.json — the PostToolUse/Read recorder and the PreToolUse/Write|Edit pair invoking both tooling/spec-discipline.py and tooling/anti-pattern-gate.py. The commit message is entirely about the @bv clause tag and never mentions the settings change. The crosslink-generic entries survived; only the project-specific ones vanished — the signature of crosslink init regenerating a file that is tracked but machine-authored.

Both agent-facing gates were dormant for the entire Stage-3 arc, while README.md:172 ("they enforce automatically — no setup"), goal.md's R-XLATE-1/2/3, and all four .claude/agents/acto-*.md kept asserting they fire.

It stayed hidden because no route covered the control plane. doc-drift.py pins freshness for files reachable from spec-routes.toml, and .claude/settings.json was not one of them. The design layer governed everything except the file that decides whether the governance runs.

What this does

.claude/settings.json Restores the three entries — byte-identical to the pre-clobber c7dc32be revision.
tooling/control-plane-check.py (new) Asserts every hook the docs claim is live is wired and its script present on disk.
tooling/tests/test_control_plane.py (new) Nine hand-authored oracle fixtures (R-CHAR-3).
tooling/spec-routes.toml Routes .claude/settings.json, .claude/agents/*.md, and the new gate.
.design/tooling/control-plane.md (new) Governing doc, REQ-1..REQ-7 + AC-1..AC-7, content-pinned.
CI + Makefile control-plane gate (hook wiring) step; make control-plane / control-plane-test.
README.md The enforcement claim now names the check that keeps it true.

Design choices worth a reviewer's attention:

  • Assert, don't regenerate. A merge step that silently re-injects the entries after crosslink init is silent repair of a control plane — you stop noticing something keeps breaking it. This fails loudly and prints the exact JSON to paste back.
  • Matcher coverage, not equality. Write|Edit|Bash satisfies a Write|Edit requirement; Write alone does not. Equality false-fails on a harmless reorder, substring matching passes a matcher that silently dropped Edit.
  • An unparseable settings.json is a FINDING (exit 1), not INCONCLUSIVE — Claude Code loads no hooks at all from one, so it is maximally gate-dead. Exit 3 is reserved for the gate's own environment failing (doc-drift.py REQ-9 / R-HONEST-3: a gate that fails open is a silent pass).
  • Wired-but-absent is its own class. Every command is if [ -f "$HOOK" ]-guarded, so a wiring naming a missing script degrades to a silent no-op. MISSING-SCRIPT is separate from MISSING-WIRING because the fix differs.
  • The routes are DECLARATIVE for spec-discipline, ENFORCED for doc-drift. is_gated_path requires .rs + a thermite-/forge crate dir + a src/ component, so the hook structurally cannot gate a .json/.md file — the same honest limitation doc-drift-tripwire.md REQ-11 records for tooling/*.py. Same split as the existing doc-drift.py dogfood route.
  • Not in make audit. Hook wiring is a development-discipline invariant, not a link in the proof-trust chain (doc-drift decision 5). scripts/audit.sh is byte-identical.

Verification

control-plane exit=0    doc-drift exit=0 (control-plane.md CURRENT)
req-status    exit=0    req-registry exit=0 (453 reqs, 119 views)
46 tooling tests OK (37 existing + 9 new)
scripts/audit.sh byte-identical (AC-7)

Dogfooded. Removing the anti-pattern entry again fires both gates: doc-drift reports DRIFT on control-plane.md, and control-plane-check reports two MISSING-WIRING findings with paste-ready restore snippets. O-2's test fixture is the verbatim post-5581b65f settings.json, so the suite goes red if the gate ever stops catching this exact regression.

Deliberately left open (recorded as OQ-1..OQ-3 in the design doc)

These are the other findings from #93; each is a judgment call I did not want to make silently inside a wiring fix:

  1. OQ-1acto-critic.md / acto-doc-author.md declare model: fable in frontmatter while their bodies read "Opus — always. … Never substitute." Routing them means any future edit re-pins the doc, but which side is correct is a harness-behavior decision for the maintainer.
  2. OQ-2 — no clippy.toml; ci.yml:95 is stock cargo clippy -D warnings with no disallowed_*/unwrap_used. The anti-pattern rules are still hook-only, so a clone that never runs Claude Code gets none of them.
  3. OQ-3acto-critic withholds Edit but retains Write + unrestricted Bash, so "cannot modify production code" is prose-patched. The critic genuinely needs Write for test files, so the fix is a read-only mount or path-scoped allowlist, not dropping Write.

🤖 Generated with Claude Code

Commit 5581b65 ("Stage-3 REQ-1: the @bv clause tag, parse-gated") removed
three hook entries from .claude/settings.json: the PostToolUse/Read recorder
and the PreToolUse/Write|Edit pair invoking tooling/spec-discipline.py and
tooling/anti-pattern-gate.py. Its message is entirely about the @bv clause tag
and never mentions the settings change; the crosslink-generic entries survived
while the project-specific ones vanished — the signature of `crosslink init`
regenerating a tracked-but-machine-authored file.

Both agent-facing gates were therefore dormant for the entire Stage-3 arc,
while README.md:172 ("they enforce automatically — no setup"), goal.md's
R-XLATE-1/2/3, and all four .claude/agents/acto-*.md kept asserting they fire.
The "asserted enforcement that isn't" failure mode, in the harness built to
prevent it.

It stayed hidden because no route covered the control plane: doc-drift.py pins
freshness for files reachable from spec-routes.toml, and .claude/settings.json
was not one of them. The design layer governed everything except the file that
decides whether the governance runs.

- .claude/settings.json: restore the three entries, byte-identical to the
  pre-clobber c7dc32b revision.
- tooling/control-plane-check.py: NEW. Asserts every hook the docs claim is
  live is wired and its script present. Matcher COVERAGE not equality
  (Write|Edit|Bash satisfies Write|Edit; Write alone does not). Exit 0/1/3
  mirroring doc-drift.py — an unparseable settings.json is a FINDING (Claude
  Code loads no hooks from it) and exit 3 is reserved for the gate's own
  environment failing, so it never fails open. A finding prints the exact JSON
  to paste back.
- tooling/tests/test_control_plane.py: NEW. Nine hand-authored oracle fixtures
  (R-CHAR-3). O-2's fixture is the verbatim post-5581b65f settings.json, so the
  suite goes red if the gate ever stops catching this exact regression.
- tooling/spec-routes.toml: route .claude/settings.json, .claude/agents/*.md
  and the new gate to .design/tooling/control-plane.md. DECLARATIVE for
  spec-discipline (is_gated_path needs .rs + a thermite-/forge crate + src/),
  ENFORCED for doc-drift — the same split as the doc-drift.py dogfood route.
- .design/tooling/control-plane.md: NEW governing doc, REQ-1..REQ-7 + AC-1..7,
  content-pinned. OQ-1..OQ-3 record the three gaps found alongside this one and
  deliberately left open (agent-def model:fable vs "Opus — always"; no
  clippy.toml backstopping the anti-pattern rules; critic read-only-ness
  conventional rather than capability-enforced).
- CI + Makefile: `control-plane gate (hook wiring)` step, `make control-plane`
  / `control-plane-test`. Not in `make audit` — wiring is a
  development-discipline invariant, not a link in the proof-trust chain
  (doc-drift decision 5); scripts/audit.sh is byte-identical.
- README.md: the enforcement claim now names the check that keeps it true.

Verified: control-plane 0, doc-drift 0 (control-plane.md CURRENT), req-status
0, req-registry 0 (453 reqs), 46 tooling tests OK. Dogfooded — removing the
anti-pattern entry again fires BOTH gates (doc-drift DRIFT + two
MISSING-WIRING findings).

Closes #93

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dollspace-gay dollspace-gay self-assigned this Jul 29, 2026
@dollspace-gay
dollspace-gay merged commit 21563e4 into main Jul 29, 2026
10 checks passed
@maxinelevesque

Copy link
Copy Markdown
Collaborator

For the record: I disabled these intentionally when I was doing my work because I found the default process flow on the repo quite annoying haha.

I think it is worth a design note for how to collaborate between folks who use different agent-dev / gating strategies -- what is actually in the repo vs. kept locally for individual devs' preferences.

@dollspace-gay

Copy link
Copy Markdown
Member Author

Yeah that was a problem with crosslink and still is, individual preferences overriding the defaults :p

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec-discipline & anti-pattern PreToolUse gates de-wired from settings.json since 5581b65f — docs still claim they enforce automatically

2 participants