fix(gate): rule 3c governs a repository by identity, not by path prefix (BACKLOG #1067) - #653
Queued
wshallwshall wants to merge 1 commit into
Queued
fix(gate): rule 3c governs a repository by identity, not by path prefix (BACKLOG #1067)#653wshallwshall wants to merge 1 commit into
wshallwshall wants to merge 1 commit into
Conversation
…ix (BACKLOG #1067) "Is this repository governed?" was an equality-or-slash-prefix test comparing the target's git COMMON DIR against each allowlisted root's WORKING TREE path, so path containment stood in for repository membership. Any repository living anywhere under a governed root inherited its governance, including an independent clone that shares nothing with it but its path. Measured on the shipped gate at 58e710a: a disarm write in <primary>/vendor/thirdparty DENIED both from its own cwd and by an absolute path token from the primary, and both refusals named the PRIMARY. The refusal's own text is what makes this more than a nuisance. It asserts that every worktree of the repository shares one .git directory, which is untrue of a vendored clone, and rule 3's comment in this same file already records what a misdescribing refusal produces: people route around the gate. The comparison is now against the ROOT'S OWN common dir. Every worktree of the primary, sibling or nested under .claude/worktrees, answers the SAME common dir and keeps denying, so path shape stops being what decides it. EQUALITY-OR-UNDER, NOT EQUALITY ALONE, IS THE LOAD-BEARING HALF. A submodule's git dir is <root>/.git/modules/<name>, so the identity-only predicate the item warned about would have flipped submodules from DENY to ALLOW as a silent side effect of fixing the vendored case -- a control weakened by accident, under cover of a fix. Whether a submodule should be governed is its own decision; a test pins today's answer so that changing it has to be one. Where a root is not a repository's top level there is no identity to compare, so the old path test stays unchanged rather than failing open on a shape it used to catch. An allowlist entry may legitimately name a directory that merely contains checkouts, and without the top-level check a root that is a SUBDIRECTORY of some repo would report that repo's git dir and quietly govern all of it. Cost is two rev-parse calls per root, cached per invocation, on a path reached only once a disarm key is already present. NOT REBUILT FROM w3-l1-gate-3c. That dormant branch carries 1d4056c for this item, from the round whose siblings #1069's row records as having acquired at least five new fail-opens. It is also not cherry-pickable: Resolve-ConfigTarget and ConvertFrom-LocalAdminShare, the helpers it is built on, return zero hits in the current file. This design was derived before that commit was read. ONE RIDER, NAMED SO IT IS NOT SILENT: the surviving path-prefix comparison gains [System.StringComparison]::Ordinal, matching Get-SessionRoot two functions away. The line was being rewritten anyway and the culture-sensitive default was never intended. NO TEST WAS REMOVED OR WEAKENED. Four added, none deleted, none relaxed. Measured, interpreter .venv\Scripts\python.exe in this worktree, -p no:randomly: red-first: the 2 vendored rows FAILED on the unchanged gate, the 2 deny controls passed on both sides, so neither control is co-moving with the fix gate suite -k worktree_gate: 679 passed / 2 skipped at base, 683 passed / 2 skipped after collected node ids: zero lost, exactly the 4 added -- set difference, not a count ruff check . clean; ruff format --check . 1186 files formatted; mypy messagefoundry 267 files clean PowerShell parser on the changed gate: 0 errors; non-cp1252 characters: 0 Mutation, with a harness that hashes the file before and after planting and REFUSES TO SCORE a mutant that did not change it -- a mutant that failed to apply prints the same passing count as a test that cannot fail. All four killed, and the two directions red DISJOINT sets: identity-only (drop equality-or-under) -> reds the SUBMODULE row alone revert to the path prefix -> reds the two VENDORED rows alone, overlap 0 always governed -> 4 reds, incl. two pre-existing ungoverned controls never governed -> 17 reds File restored byte-identical by SHA-256 afterwards. Severity is conditional per CLAUDE.md section 0: a FALSE DENY in developer tooling with no product surface, not reachable until someone vendors a clone under a governed root. Ledger untouched: docs/BACKLOG.md is not in this diff and the banner is not mine to flip. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wshallwshall
added this pull request to the merge queue
Aug 28, 2026
Any commits made after this event will not be merged.
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Aug 28, 2026
wshallwshall
added this pull request to the merge queue
Aug 28, 2026
Any commits made after this event will not be merged.
wshallwshall
added this pull request to the merge queue
Aug 28, 2026
Any commits made after this event will not be merged.
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Aug 28, 2026
wshallwshall
added this pull request to the merge queue
Aug 28, 2026
Any commits made after this event will not be merged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Authored by Builder 1; pushed and opened by the LANDER. Four files, +259/-1.
The defect
Rule 3c decided "is this repository governed" by an equality-or-slash-prefix test comparing the target's git common dir against each allowlisted root's working tree path. So path containment stood in for repository membership.
An independent clone vendored under a governed root inherited that root's governance — and the refusal then asserted a shared
.gitthat the clone does not have. A gate refusing for a reason that is not true of the thing it refused.Now it compares against the root's own common dir, equality-or-under.
Verification, reported by the author with controls
Blob-SHA control across the rebase — every file in the commit captured before and after:
Red-first: the two vendored rows fail on the unchanged gate; the two deny controls pass on both sides. Gate suite
-k worktree_gate: 679 passed / 2 skipped at base → 683 passed / 2 skipped after, with node ids compared by set difference rather than by count — zero lost, exactly the four added.Already-landed re-test, both axes with controls: commits on main citing
BACKLOG #1067= 0 (negative control#999999= 0, positive control#1085= 1);Get-RootCommonDirCmppresent on main = 0 (controlTest-Governed= 16). Subject and content agree, and neither control is silent.ruff checkclean,ruff format --check1186 formatted,mypy267 files clean, PowerShell parser 0 errors, non-cp1252 characters 0. Mutation: 4 mutants, all killed, with the harness refusing to score a mutant that did not change the file's SHA-256.Rebase and ordering
Rebased onto
a490993b4after #1085 landed, since that PR edits the same rule 3c — not a formality.b1-1072is stacked on this and lands after it; the combined-tree gate suite is 698 passed = 694 (#1072 alone) + 4 (#1067's rows), exact.LANDER note on the banner
No
docs/BACKLOG.mdedit, and none is required.backlog-hygiene.yml:124-133demands a banner only when the diff touchesmessagefoundry/,ide/ormessagefoundry_webconsole/. This branch touchesscripts/hooks/,docs/adr/andtests/— no engine or IDE path.I had previously told the author a banner was owed here. That was me quoting BUILDER.md's summary, which drops the condition the gate actually applies.