An independent reviewer broke all three new controls before they shipped.
Every finding is reproduced as a fixture or a repro, because the class is
recurring rather than incidental: each hole was a case where the answer was
"allow" because something was ABSENT rather than because it was CHECKED.
1. wrapper-guard read only the spellings it knew. `curl -d@body` (no space),
`--request=POST` (equals form), and a URL path assembled from shell
variables each carried a real provider write straight through. Write
detection now covers every body and method form curl accepts, and the
endpoint match no longer anchors on a literal host path that a variable
can dissolve.
2. wrapper-guard blocked only when the wrapper FILE existed. A host with a
broken or partial install therefore permitted exactly the raw writes the
guard exists to stop. Blocking is now on the endpoint; a missing wrapper
changes the remedy text, not the verdict — a broken install is not
permission to bypass gate 7.
3. mosaic-worktree read a worktree's safety from two questions, and a clean,
fully-pushed tree holding a gitignored `local.secret` answered both with
zero. `git worktree remove` then deleted the one copy in existence. A file
is gitignored precisely so nothing else holds it, so ignored-but-not-
disposable files are now a third evidence question. Build junk
(node_modules, .venv, dist, caches, *.pyc) stays disposable, so the common
case still reads SAFE.
4. check-tools-index counted a documented tool as discoverable at mode 0644.
Every caller tests `[ -x ]`, so a non-executable tool is a missing tool;
it now fails the gate with its own message.
Local gates green: sanitization, resident budget, test enumeration,
tools-index (4/4 self-test, 100% on the enforced git suite), and
wrapper-guard 20/20.
An undocumented tool is, from inside an agent session, indistinguishable from a
tool that was never written. The framework shipped 26 git wrappers and named 6 of
them in its resident index docs — 23% discoverability, with pr-review.sh among the
missing. The observable consequence was an agent obeying Constitution gate 7 as
best it could see it, reaching for raw curl, sending GitHub's APPROVE to a Gitea
host, and getting HTTP 200 with the review silently filed PENDING. Three times.
That is not a discipline failure and no amount of prose fixes it.
Four changes, each converting a rule that decayed into a mechanism that cannot:
- check-tools-index.sh (new, CI-blocking): every tool in an enforced suite must be
named in a resident index doc, and every tool an index names must exist. The git
suite is enforced now; other suites report coverage without failing, so the
ratchet tightens one reviewed PR at a time instead of landing as one sweep. The
enforced list is framework-owned rather than a marker inside operator-owned
TOOLS.md — a doc marker would let an operator silence the gate on exactly the
host where it matters most. Carries --self-test, because a checker that only
ever passes is indistinguishable from one that is not running.
- TOOLS-REFERENCE.md: complete 28-entry git index, plus the APPROVED/APPROVE
dialect note that explains why pr-review.sh is not a formality.
- mosaic-worktree.sh + wrapper-guard.sh (upstreamed): the rule "big work goes on a
work filesystem" already existed in prose, and 255 GB accumulated in $HOME across
842 directories anyway, under five simultaneous placement conventions on one
host. The helper therefore exposes no placement decision — given a branch name,
every path is derived from `git worktree list --porcelain`. Worktrees rather than
clones because enumerability is the only thing that makes reclaim safe, and
reclaim is by evidence (clean tree + no unpushed commits), never by size or age.
The guard blocks three mechanically-detectable mistakes and nothing else:
a checkout into $HOME, a raw provider-API write to an endpoint that has a
wrapper, and the literal APPROVE event. Reads pass untouched.
- STANDARDS.md: model tiering as a standard, named by capability class so it
survives a model generation. Start cheapest, escalate on evidence, benchmark
before demoting a task class, and keep the class->model binding in operator
config with the DB-backed config service as the end state.
Registering the guard in runtime/claude/settings.json is the point of upstreaming
it: ~/.claude/settings.json is a framework-managed copy, so a hand-added hook there
is destroyed by the next upgrade. In the template it survives, and it reaches every
host instead of one.