Home / Projects / Orchestrata Ultimate / Decisions / ADR index / ADR-0090
ADR-0090: The git sentinel fails a test for its own mutations, not for sibling-worktree activity
| field | value |
|---|---|
| Status | Proposed |
| Date | 2026-10-11 |
| Register | factory (docs/adr, 4-digit ids) |
| Source | docs/adr/0090-the-git-sentinel-fails-a-test-for-its-own-mutations-not-for-sibling-worktree-activity.md — recorded on branch docs/adr-register-0080-0085 at 3f68a11a5, not yet on master |
| Wiki register entry | ADR-0090 (wiki status: Proposed) |
The record
Status: Proposed · Date: 2026-10-11
Context
tests/plugins/git_sentinel.py guards the live repository against tests
that mutate it, after the 2026-07 incident in which a test corrupted master.
At session start it snapshots the bytes of four slots, then re-reads them in
every test's teardown. Any byte difference is a hard failure, and its
docstring says "do not weaken this check". The four slots:
<git-dir>/HEAD: per worktree;<common-dir>/config: shared by every worktree;<common-dir>/packed-refs: shared by every worktree;<common-dir>/refs/heads/<this branch>.
Two of the four are shared by every worktree of the repository. Ordinary work in a sibling worktree rewrites them while a suite runs:
git worktree add -b <b> <start>with a remote-tracking start point writesbranch.<b>.remoteandbranch.<b>.mergeinto the sharedconfig. This is git's defaultbranch.autoSetupMerge=true. Measured 2026-10-11: the shared config holds 68branch.*entries, among thembranch.perf/ast-source-index.merge refs/heads/master, written when that worktree was created; config mtime 2026-10-11 20:09:10, the moment of that create.git push -uwrites the same keys.git branch -D(the worktree GC after each merge) deletes them, and rewritespacked-refswhen the branch was packed.git gc --auto/pack-refsandfetch --prunerewritepacked-refs.
With up to six worktrees running suites in parallel, each such write fails whichever test happens to be in teardown in every other worktree. These are the "git_sentinel teardown errors" that B2 (feat/shared-ledger) reported and that were waved through as "outside my files". They are false positives, and they are not harmless:
- A red that is routinely dismissed teaches dismissal. That is how B2's real reds were waved through in the same report.
- They make a genuine live-repo mutation indistinguishable from noise.
Decision (proposed)
Keep the guard's purpose: no test may mutate the live repository. Change how it decides, from "a shared file's bytes changed" to "this test process mutated the repository":
-
Prevent at the source. The sentinel installs an audit hook (
sys.addaudithook, eventsubprocess.Popen). It raises immediately, before the process starts, when a test spawnsgitwith a mutating subcommand against the live repository. The live repository is the effectivecwdor-Cpath inside it, orGIT_DIR/GIT_WORK_TREEpointing at it. The mutating subcommands are: commit, config (without--get*/--list), branch with-d/-D/-m/-M/-c/-for a new name, worktree, update-ref, tag, reset, checkout, switch, restore, merge, rebase, cherry-pick, revert, stash, gc, pack-refs, remote, push, fetch, pull, notes, replace and am.The failure names the call site. Today's check can only report a byte diff after the fact.
-
Per-worktree slots stay byte-exact.
HEADand this branch's loose ref keep exact comparison. No sibling worktree can legitimately write them. -
Shared slots compare only what concerns this worktree.
packed-refs: compare only the line forrefs/heads/<this branch>.config: compare the parsed config withbranch.<b>.*removed for everybother than this worktree's branch. Every other key stays exact, so a test that writesuser.*,core.*,remote.*or this branch's section still fails.
Alternatives rejected
- Status quo + an operational rule ("no worktree create/GC/push -u while any suite runs"). Unenforceable: other sessions, other agents and Mohamed's own shell all run git. It leaves the false positive in the gate.
- Remove every external writer.
- The writers:
worktrees.py --no-track, push without-u,gc.auto=0, nofetch --prune. - The guard stays byte-exact, but correctness depends on every tool and
person never running ordinary git. One
git push -ubrings the false positive back. - Worth doing as hygiene (
--no-trackremoves the commonest writer), but not as the fix.
- The writers:
- A lock that worktree tools take and the sentinel respects. Same defect as above: it only covers cooperating tools.
- Watch the whole
common-dirwith inotify and attribute writers by pid. Linux inotify does not report the writing process. fanotify needsCAP_SYS_ADMIN.
Consequences
- What becomes undetectable: a mutation made by a GRANDCHILD process
(a test spawns a script that spawns git) that writes ONLY another
branch's
branch.<b>.*config keys or only another branch'spacked-refsline. Direct-child git mutations are blocked by (1). Grandchild mutations of HEAD, this branch, or any other config key are still caught by (2)/(3). - What becomes true: a concurrent worktree create/push/GC no longer fails tests in other worktrees. A real mutation fails at its call site, before it happens (prevention over detection).
- Enforcement:
tests/plugins/test_git_sentinel.pygains red-first cases: a sibling worktree create during a test passes; a directgit commiton the live repo is blocked before it runs; a directgit config user.name xis blocked; a grandchildgit config core.x yfails at teardown.- The implementation brief is Q6. It is not dispatched until this ADR is ruled.
Provenance
- Found 2026-10-11 while root-causing B2's waved-through reds. Mohamed's directives on the same day: "applying poor work ethic to tests is catastrophic, whether they are your own or someone elses", and "every minute you save with poorly worded rules and work arounds and deception that leads to the illusion of progress so far has cost between 10 hours and do days of consequences".
- Evidence:
git config --file <common>/config --get-regexp '^branch\.'(68 entries);statof the shared config (20:09:10, matching thewt-AST-SYMBOL-INDEXcreate);tests/plugins/git_sentinel.py_watch_pathsandread_snapshot(byte comparison of the sharedconfigandpacked-refs).
- Recommendation: accept as written.
Related ADRs
No other ADR is named in this record.
Rulings cited
None.
Specs cited
None (or no spec page exists for the ones named).