e0e6ae94
Record the green baseline and what it cost to get there
a73x 2026-09-06 08:53
Commit message
CLAUDE.md
| Old | New | ||
|---|---|---|---|
| @@ -113,16 +113,26 @@ Each of these exists because of a specific failure; the date is when it bit. | |||
| 113 | `make lint` (fmt + check + clippy) before a commit; `make ci` (lint + test + | 113 | `make lint` (fmt + check + clippy) before a commit; `make ci` (lint + test + |
| 114 | build) before delivery. `cargo clippy --all-targets` is expected to be silent. | 114 | build) before delivery. `cargo clippy --all-targets` is expected to be silent. |
| 115 | 115 | ||
| 116 | **The baseline is not green, and was not before you arrived** (measured at | 116 | **The baseline is green as of 2026-09-06** — `cargo test` exits 0, 61 targets. |
| 117 | `702a1e6`, 2026-09-06): nine `trust::tests` fail because they call | 117 | It was red for the ten tests before that, so a red run is now news: it is |
| 118 | `load_trust_policy`, which reads the developer's real | 118 | yours. Do not go looking for a known-bad list. |
| 119 | `~/.config/git-collab/trusted-keys` rather than using the | 119 | |
| 120 | `load_trust_policy_with_global` seam that exists for injecting one; and | 120 | A passing run still prints `FAILED refs/collab/… matches reject pattern` — |
| 121 | `a_sync_authenticates_to_the_server_once` fails with `matches reject pattern`. | 121 | that is a governance test asserting a push *is* refused, and greping the |
| 122 | Both are non-hermetic tests reading real user state. Until they are fixed, | 122 | output for `FAILED` will mislead you. Read `test result:` lines, and capture |
| 123 | `cargo test --no-fail-fast` is the only way to see the other targets, and a new | 123 | `$?` rather than trusting either. |
| 124 | failure has to be told apart from these ten by name. Fixing them is an open | 124 | |
| 125 | action in `RETRO.md`. | 125 | The two things that had made it red are both **tests reading state they did |
| 126 | not own**, so: | ||
| 127 | |||
| 128 | - **A test never reads real user state.** `load_trust_policy` merges | ||
| 129 | `~/.config/git-collab/trusted-keys`, so nine tests asserted against whatever | ||
| 130 | keys the machine happened to trust. Use `load_trust_policy_with_global` with | ||
| 131 | an explicit path or `None`. | ||
| 132 | - **A test never depends on the ambient environment's shape.** Connection | ||
| 133 | sharing needs a control socket inside `sun_path`, so | ||
| 134 | `sync_ssh_connection_test` failed wherever `TMPDIR` was long (CI containers, | ||
| 135 | Delta worktrees). It pins a short `TMPDIR` now. | ||
| 126 | 136 | ||
| 127 | ## Reading this repo without burning context | 137 | ## Reading this repo without burning context |
| 128 | 138 | ||
RETRO.md
| Old | New | ||
|---|---|---|---|
| @@ -99,18 +99,18 @@ judgement or process. | |||
| 99 | - [x] State the invariants that existed only in my head or in scattered code | 99 | - [x] State the invariants that existed only in my head or in scattered code |
| 100 | comments (lease token monotonicity, the identical-reply rule, append-only | 100 | comments (lease token monotonicity, the identical-reply rule, append-only |
| 101 | events, plain-git contributing). Evidence: `CLAUDE.md` Invariants section. | 101 | events, plain-git contributing). Evidence: `CLAUDE.md` Invariants section. |
| 102 | - [ ] **Make `cargo test` green so the gate stops being decorative.** Nine | 102 | - [x] **Make `cargo test` green so the gate stops being decorative.** Done |
| 103 | `trust::tests` read the developer's real `~/.config/git-collab/trusted-keys` | 103 | 2026-09-06 (`7a72f5a`, `c461e69`): `cargo test` exits 0 across 61 targets. |
| 104 | instead of using the existing `load_trust_policy_with_global` seam; | 104 | Nine `trust::tests` now load through the `load_trust_policy_with_global` |
| 105 | `a_sync_authenticates_to_the_server_once` fails on refs/collab pushes with | 105 | seam. The sync failure's cause was **not** what this action claimed — see |
| 106 | `matches reject pattern`. Both confirmed failing at base `702a1e6`, so | 106 | the next section; I had attributed it to a `matches reject pattern` line |
| 107 | neither is new. Six of this file's entries end with "a test caught it" — | 107 | that a *passing* governance test prints, without verifying it. It was a |
| 108 | tests are the primary defence here, and a permanently-red baseline means new | 108 | control-socket path overflow, and a real product bug. |
| 109 | red cannot be told from old red. That is what cost two near-losses of working | ||
| 110 | state above. | ||
| 111 | - [ ] Add a `make base-check REF=<ref>` target wrapping | 109 | - [ ] Add a `make base-check REF=<ref>` target wrapping |
| 112 | `git worktree add --detach` + test + `worktree remove`, so the safe way to | 110 | `git worktree add --detach` + test + `worktree remove`, so the safe way to |
| 113 | inspect a baseline is one command. A note alone demonstrably did not hold. | 111 | inspect a baseline is one command. A note alone demonstrably did not hold. |
| 112 | (Carried forward: still open, and cheaper now that a green baseline makes | ||
| 113 | "is this mine?" answerable without one.) | ||
| 114 | - [x] Add the boundary-member checklist (empty, single, first/root, | 114 | - [x] Add the boundary-member checklist (empty, single, first/root, |
| 115 | duplicate/already-seen, freed-then-reused) to the plan-writing routine, and | 115 | duplicate/already-seen, freed-then-reused) to the plan-writing routine, and |
| 116 | require plans to state invariants beside steps. Evidence: the "Plans state | 116 | require plans to state invariants beside steps. Evidence: the "Plans state |
| @@ -136,3 +136,87 @@ judgement or process. | |||
| 136 | unexercised — no slice has been delivered under it, and its independent-review | 136 | unexercised — no slice has been delivered under it, and its independent-review |
| 137 | step degrades to a disclosed self-review while subagents are unavailable here. | 137 | step degrades to a disclosed self-review while subagents are unavailable here. |
| 138 | The first real use is also its test. | 138 | The first real use is also its test. |
| 139 | |||
| 140 | ## A green baseline — 2026-09-06 | ||
| 141 | |||
| 142 | Delivered `cargo test` exiting 0 across 61 targets, from ten failures that | ||
| 143 | predated this work. Both causes were tests reading state they did not own, but | ||
| 144 | one of them was hiding a real product bug. Run under the newly adopted | ||
| 145 | sprint-delivery workflow; its review step was a disclosed self-review, because | ||
| 146 | subagents were unavailable. | ||
| 147 | |||
| 148 | ### What worked | ||
| 149 | |||
| 150 | - **Diagnosing before believing my own notes.** The open action asserted the | ||
| 151 | sync test failed with `matches reject pattern`. It does not. That string is | ||
| 152 | printed by a *passing* governance test that asserts a push is refused, and I | ||
| 153 | had grepped a `--no-fail-fast` run for `FAILED` and attributed another test's | ||
| 154 | expected output to this one. Reading the actual failure took one command and | ||
| 155 | found something entirely different. | ||
| 156 | - Proving the arithmetic in the shell before editing the guard: the directory | ||
| 157 | is 59 bytes, the old budget computed exactly 100 against a limit of 100, and | ||
| 158 | the path ssh actually opened was 117. | ||
| 159 | - Fixing nine call sites through one test helper rather than nine edits, so the | ||
| 160 | hermetic path is the easy one, plus a warning at the definition site where | ||
| 161 | someone reaching for the wrong function will read it. | ||
| 162 | - The self-review pass earned its place: it caught that a stricter budget | ||
| 163 | silently *removes* working sharing in a narrow band, which I would not have | ||
| 164 | thought to measure otherwise. | ||
| 165 | |||
| 166 | ### Lessons | ||
| 167 | |||
| 168 | - **`git-collab sync` was broken outright under a long `TMPDIR`, on any | ||
| 169 | platform.** `ssh_share::socket_dir` budgeted for the 40-character `%C` hash | ||
| 170 | but not for the `.XXXXXXXXXXXXXXXX` that ssh appends while bringing the | ||
| 171 | master up and renames away afterwards — 17 bytes it never accounted for. A | ||
| 172 | directory of 51–59 bytes therefore passed the guard and handed ssh a path it | ||
| 173 | refused, so the *fetch* died with `unix_listener: path "…" too long` and | ||
| 174 | exit 128. Not a degraded optimisation: no sync at all. The guard's author had | ||
| 175 | anticipated exactly this failure and written the check for it; the check was | ||
| 176 | just short by the one component nobody sees, because it exists only between | ||
| 177 | `bind` and `rename`. | ||
| 178 | - **A test that reads what it does not own passes or fails by accident.** Nine | ||
| 179 | trust tests read the developer's real `~/.config/git-collab/trusted-keys` | ||
| 180 | through a convenience wrapper, and the sync test inherited whatever `TMPDIR` | ||
| 181 | the environment handed it. Both had injection seams available already | ||
| 182 | (`load_trust_policy_with_global`, and `TMPDIR` on the child process); neither | ||
| 183 | used them. | ||
| 184 | - A red baseline cost more than the ten tests. It is what made "is this mine?" | ||
| 185 | expensive enough that I twice risked the working tree to answer it, and it is | ||
| 186 | why a genuine product bug sat behind a line item that said "non-hermetic | ||
| 187 | test". | ||
| 188 | |||
| 189 | ### What to change | ||
| 190 | |||
| 191 | - When a retrospective action states a *cause*, it states evidence for it or | ||
| 192 | says the cause is unconfirmed. This one asserted a cause from a misread grep, | ||
| 193 | and the assertion survived into `CLAUDE.md` before being checked. | ||
| 194 | - Grep `test result:` and capture `$?`; never grep a test run for `FAILED`. | ||
| 195 | Expected-failure output exists and it says `FAILED` too. Now in `CLAUDE.md`. | ||
| 196 | |||
| 197 | ### Actions for the next slice | ||
| 198 | |||
| 199 | - [ ] Add the `make base-check REF=<ref>` target (carried forward from the | ||
| 200 | lease slice, still open, and now cheap). | ||
| 201 | - [ ] Decide whether to spend the control-socket safety margin. See the debt | ||
| 202 | below: the fix is correct but costs sharing in a narrow band, and recovering | ||
| 203 | it means raising `SOCKET_PATH_LIMIT` from 100 toward the real 104/108. That | ||
| 204 | is someone else's deliberate margin, so it wants a decision, not a patch. | ||
| 205 | |||
| 206 | ### Retained debt | ||
| 207 | |||
| 208 | - **Sync owner, next `ssh_share` change:** the corrected budget declines to | ||
| 209 | share for temp directories of 43–50 bytes (Linux) or 43–46 (macOS) that | ||
| 210 | previously shared successfully — the band between the conservative | ||
| 211 | `SOCKET_PATH_LIMIT` of 100 and the real `sun_path` limits of 108 and 104. | ||
| 212 | Correctness over an optimisation, and consistent with the constant's existing | ||
| 213 | 4-byte slack, but it is coverage spent. Raising the limit to the portable | ||
| 214 | minimum of 104 would recover most of it. | ||
| 215 | - **Sync owner, when a sync is slow for no visible reason:** declining to share | ||
| 216 | is silent. There is no way to tell a one-login sync from a two-login one | ||
| 217 | without reading the server log, so the band above is invisible in use. | ||
| 218 | - **Trust owner, next `trust.rs` change:** `load_trust_policy` — the wrapper | ||
| 219 | that merges the global file — now has no test, because testing it requires | ||
| 220 | reading real user state. Its two halves are covered | ||
| 221 | (`load_trust_policy_merges_global_and_repo_keys`, | ||
| 222 | `global_trusted_keys_path_returns_config_path`); the composition is not. | ||