Skip to content

fix(worktree): a write to the machine-global gate now leaves a record (BACKLOG #1247) - #607

Open
wshallwshall wants to merge 14 commits into
mainfrom
claude/builder-1-1247-gate-receipt
Open

fix(worktree): a write to the machine-global gate now leaves a record (BACKLOG #1247)#607
wshallwshall wants to merge 14 commits into
mainfrom
claude/builder-1-1247-gate-receipt

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

The gate install was a bare Copy-Item. When the installed gate's content changed on this box
while three sessions ran against it, nobody could say who wrote it -- and four separate mechanisms
could each have recorded it. None existed.

All four are now present, each with a named test so removing one reds that test:

  • Backup: the overwritten bytes are copied to .bak first -- the only copy of what was there
    on an unattributed write.
  • Receipt: a JSON sidecar (content_hash, hash_replaced, replaced_ours, written_at_utc,
    source_repo, source_commit, source_blob).
  • Attribution: replaced_ours answers whether the bytes just overwritten were ones this
    installer put there -- false exactly when a previous write was unattributed.
  • Real mtime: Copy-Item carries the source mtime across, so the installed file reported a
    checkout's timestamp as though it were the install's -- worse than no timestamp because it reads
    as one.

Hashes with Get-GateHash, not Get-FileHash -- deliberate, since a byte-exact digest made every
Windows checkout read STALE against a clean git status (LF-vs-CRLF), and the false-STALE remedy
was a re-install that could downgrade the control. -Status and
tests/test_gate_installed_parity.py already share that function; a test pins the receipt to it
too.

-RefuseOnMismatch defaults off: a mismatch isn't proof of tampering (an older installer, a hand
copy, a legitimate out-of-band fix all look identical), so the default records and warns rather
than breaking ordinary installs; on, it refuses and preserves the bytes for inspection.

Tests are structural by necessity: install-gate.ps1 refuses to run when CLAUDECODE is set (a
session that can install its own gate could uninstall it), so the write path cannot execute from an
agent session. Each new test was mutation-checked against the shipped file instead -- removing the
mtime stamp, the backup, or the refuse branch reds exactly one named test each, restored by
byte-copy with the hash verified.

Verified: 104 passed across the gate, wiring, parity and repo-wide inventory test files; ruff
clean; PowerShell parser clean (re-confirmed independently before opening this PR).

Built by Builder1, opened by Lander.

🤖 Generated with Claude Code

wshallwshall and others added 2 commits August 25, 2026 19:10
… (BACKLOG #1247)

The gate install was a bare Copy-Item. When the installed gate's content
changed on this box while three sessions ran against it, nobody could say
who wrote it -- and four separate mechanisms could each have recorded it.
None existed. The change itself moved the gate forward, so this is a
governance defect rather than an incident; an unattributable write to a
shared safety control is the same class of event whether it upgrades or
downgrades.

All four are now present, and each has a named test so removing one reds
that test rather than quietly restoring the gap:

  BACKUP        the bytes being overwritten are copied to .bak first. On an
                unattributed write those are the only copy of what was there.
  RECEIPT       a JSON sidecar carrying content_hash, hash_replaced,
                replaced_ours, written_at_utc, source_repo, source_commit,
                source_blob.
  ATTRIBUTION   replaced_ours answers the question nobody could answer when
                this was filed: were the bytes I just overwrote ones THIS
                installer put there? It is false exactly when a previous
                write was unattributed.
  REAL MTIME    Copy-Item carries the SOURCE mtime across, so the installed
                file reported a checkout's timestamp as though it were the
                install's. That is worse than no timestamp because it reads
                as one, and this row records an inherited mtime carrying a
                true finding into retraction.

THE RECEIPT HASHES WITH Get-GateHash, NOT Get-FileHash, AND THAT IS THE
DETAIL MOST LIKELY TO BE UNDONE. This file already learned it once: a
byte-exact digest made every Windows checkout read as STALE while git status
called the file clean, because git stores LF and checks out CRLF. The remedy
that false STALE printed was a re-install -- which DOWNGRADES a machine-global
control when the checkout is older than the installed gate. -Status and
tests/test_gate_installed_parity.py already share that function; a third
basis in the receipt would reintroduce the same defect one instrument over.
A test pins it.

-RefuseOnMismatch DEFAULTS OFF, deliberately. A mismatch against the receipt
is not proof of tampering -- an older installer, a hand copy or a legitimate
out-of-band fix all look identical -- and refusing by default would break
first installs and ordinary upgrades. Off, it warns and records the replaced
hash; on, it refuses and preserves the bytes for inspection rather than
destroying the evidence.

THE TESTS ARE STRUCTURAL AND THAT IS FORCED, NOT CHOSEN. install-gate.ps1
refuses to run when CLAUDECODE is set -- "a session that can install its own
gate can uninstall it" -- so the write path cannot be executed from a test
session at all, sandboxed HOME or otherwise. Unsetting that variable to reach
the code would be defeating the control the file exists to be. The existing
-Status tests work only because -Status sits above that refusal. Each new
test was mutation-checked against the shipped file: removing the mtime stamp,
the backup, or the refuse branch reds exactly one test each, restored by
byte-copy with the hash verified.

ONE OF THOSE TESTS WAS WRONG FIRST, IN THE WAY IT WAS TESTING FOR. It searched
the raw installer text for "Get-FileHash" and matched its own comment saying
"Get-GateHash, NOT Get-FileHash" -- failing on a file that was correct. Both
occurrences in this installer are in comments. It now reads code lines only:
a string search cannot see whether a name is being CALLED or RULED OUT, and
the mention most likely to exist is the one ruling it out.

tests/test_gate_installed_parity.py is untouched: the receipt does not change
what installed-versus-source parity means, and it already shares the hash
function.

Verified: 104 passed across the five gate, wiring, parity and repo-wide
inventory test files. ruff clean. install-gate.ps1 parses clean under the
PowerShell parser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@wshallwshall
wshallwshall enabled auto-merge (squash) August 26, 2026 00:12
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.

1 participant