feat(nix): deploy cairn from the PINNED flake package, not a fork in this checkout - #1406
Conversation
…k into this checkout
`~/.local/bin/cairn` was an `mkOutOfStoreSymlink` to `devrc/scripts/cairn`, so
this repo carried a second copy of a reader that now lives in its own extracted
OSS repo. It is now `${cairnPackage}/bin/cairn` from a locked
`github:ZacxDev/cairn` input (rev 9213726), threaded input -> outputs ->
extraSpecialArgs -> nix/home.nix, which takes the argument WITHOUT a default so
a cut thread is an eval error rather than a symlink to `/bin/cairn`.
`cairn-who` deliberately does NOT move. It is devrc-only, absent from the OSS
package, and resolves `scripts/lib/cairn_who.py` through its own `__file__` —
so it keeps `mkOutOfStoreSymlink`. The two deploy modes now differ ON PURPOSE
and every comment saying otherwise was rewritten; `scripts/cairn` and
`scripts/lib/` stay in the tree (the writer and `cairn-who` import them) and
are simply no longer what lands on PATH.
NOT `inputs.cairn.inputs.nixpkgs.follows = "nixpkgs"`, and that is a decision,
not an omission. cairn pins `pkgs.python312` because its Dockerfile is
`python:3.12-slim` and its CI pins 3.12; a bare `pkgs.python3` there once
followed nixpkgs to 3.14 and shipped an interpreter nothing in that repo had
run its suite under. Following would rebuild the client against devrc's
`nixpkgs-unstable`, which cairn's CI has never tested — the thing deployed
would stop being the thing that was tested. The cost is a second nixpkgs node
in the lock.
CAIRN_MIRROR_ROOT is exported, and it is a bug fix rather than configuration.
devrc's fork had the frozen mirror's path hardcoded; the extracted client reads
the env var, and UNSET makes `doctor` report `frozen-mirror NOT-OBSERVABLE` —
a status that contributes nothing, i.e. a check that was PASSING silently
becomes a check that is not RUN. Measured on this host against the real
225-entry cache root:
devrc scripts/cairn frozen-mirror OK
packaged client, var unset frozen-mirror NOT-OBSERVABLE
packaged client, var set frozen-mirror OK (byte-identical doctor
output to the fork)
`NIX_DEPLOY_WHY` in test_cairn_split.py pins that comment paragraph verbatim
and went red on this commit by design; it is updated to the rewritten prose,
not weakened. `test_cairn_is_deployed_out_of_store_not_as_a_store_copy` in
test_cairn_cli.py was the second guard asserting the old truth — it now pins
the package spelling exactly, rather than being loosened to "any deploy mode".
New: scripts/tests/test_cairn_flake_pin.py (8 tests) pins the input, the lock
entry, the absence of a nixpkgs follow (flake.nix AND the lock's own shape),
the thread end to end, the two deploy modes AS ONE RELATIONSHIP, and the
CAIRN_MIRROR_ROOT export.
Red -> green matrix (all 8 red at 65d8bfb, green at HEAD), plus a mutation
kill per guard with each guard's OWN message. Two instrument findings recorded
in the file: the first `follows` guard matched only the dotted spelling and a
block-form mutant walked past it, and `nix develop -c pytest` REWRITES
flake.lock, silently reverting a lock-only mutant before pytest reads it —
scored SURVIVED without ever running.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session-Id: 9662077e-18a9-405d-b4ce-f6697e6fa1bb
…s home.nix alone
Both defects were caught by the authoritative gate, not by review, and neither
was visible from the diff.
🔴 `skills_mapping.py` EVALUATES `nix/home.nix` STANDALONE, and its stub set did
not know about the new argument. Making `cairnPackage` REQUIRED (no default) is
deliberate — a broken thread should be an eval error, not a symlink to
`/bin/cairn` — but nix then refuses to apply the function at all, and the check
reported `nix cannot evaluate /build/src/nix/home.nix`. That message reads as a
verdict on `home.nix` when it is a verdict on the STUB SET, which is the
expensive half: the guard's own text says FIX THE CHECK, do NOT delete it.
`cairnPackage = {}` added, `{}` rather than a string because forcing it in an
interpolation errors instead of silently yielding a plausible path.
⚠ THE COMMENT EXPLAINING THAT DID NOT FIT. `skills_mapping.py` carries a
MAX_MODULE_BYTES = 7_400 ceiling whose own message says an overage is a question
about which ambition crept back, never about raising the number. The file was
7,292 B — 108 of headroom — and the first draft of the comment added 742. It is
now three terse lines (7,361 B, 39 of headroom) and the reasoning lives here
instead. That ceiling is doing its job; do not raise it for prose.
🔴 THE SKILL DESCRIPTION GREW 64 CHARS AND BREACHED A ZERO-HEADROOM RATCHET —
`assert 11256 <= 11192`, plus `MEASURED_ALL_TIER_A_CHARS 11366 != 11430` in
test_skill_tiers.py and a second arm in test_skill_descriptions.py: four
failures, one cause. devrc #1381 already recorded that this ratchet has exactly
zero headroom and kept its edit length-neutral; this one did not.
Fixed length-NEUTRALLY rather than by re-pinning the constants: the fragment
`client `scripts/cairn`` (22 chars) becomes `client `cairn` on PATH` (22 chars),
which is both accurate after the pin and the same length, so every pinned tier
constant stays valid. The deploy detail it wanted to add belongs in the BODY,
where it already is — a description's job is deciding whether the skill loads.
MEASURED after: description 303 chars, unchanged from main; the four guards and
both cairn suites 116 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3KEUsTLLUhS37an52rn28
Claude-Session-Id: 9662077e-18a9-405d-b4ce-f6697e6fa1bb
…th the +8 attributed 12927 carried 149 of SLACK against this tree's 13076. Re-pinned to 13076 - min(50, max(1, 13076/20)) = 13026 — the AUTHORITATIVE gate's own printed count through the documented rule, AFTER rebasing onto origin/main (01956bf), per the ORDER note: a floor is a claim about the tree you measured. THE +8 IS ATTRIBUTED, NOT ASSUMED. The same gate ran on PLAIN origin/main in a separate detached worktree as the control: control (plain main 01956bf) collected=13068 passed=13048 failed=20 branch collected=13076 passed=13056 failed=20 The delta is exactly `test_cairn_flake_pin.py`'s 8 tests, and all 8 pass. 🔴 BOTH SIDES REPORT failed=20, every one in `test_nebula_relay_apply.py` (arrived with #1272). Identical on both sides, so main is red on its own and this branch adds no failure. That control is what makes the collected count trustworthy despite a red gate — without it, "the reds are someone else's" is a theory, and a theory that explains a failure is not evidence for it. ⚠ THE KNOWN-RED BASELINE WENT STALE TWICE IN ONE DAY. The `age` escrow/backup and `opencode` engine 7 that were red this morning are GONE — main fixed them — and 20 new ones arrived from a different commit. The floor comment says to re-derive this baseline rather than quote it, for that reason. Floor sum 20342 -> 20441 (+99); no FATAL from TARGET_FLOORS' two-way pin. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E3KEUsTLLUhS37an52rn28 Claude-Session-Id: 9662077e-18a9-405d-b4ce-f6697e6fa1bb
…at claimed novelty it did not have, and a superseded "unpushed" (#1413) Each of these was a sentence in this doc that had become false, in the exact way the doc itself keeps warning about. 1. THE STORE BULLET IS CLOSED, so the instruction to close it must stop reading as open work. `devrc/cairn`'s `2026-08-29: OPEN:` is now `RESOLVED caec932:` via `cairn put` (revision `da318a4c6a96f9d8`), naming the implementing site rather than asserting closure: `ATTRIBUTION = " [cairn: {actor}/{session}]"` in `scripts/subsystem-store-api/server.py`, appended server-side from the authenticating token. The control that it did what it claimed is the index row moving `17 nuance / 2 OPEN` to `18 nuance / 1 OPEN` — both dimensions touched, not merely bytes landing. The surviving OPEN is a different bullet and was deliberately left unverified rather than closed on no evidence. 2. THE STALE-DOC GOTCHA CLAIMED TO BE A DISCOVERY AND IS NOT ONE. `/resume` step 1 already carries the rule, backed by two older measurements (a 276-line-stale handoff, a 692-commit-stale skill file), and `scripts/resume-state.sh` exists to prevent it — step 2 orders it BEFORE the read for that reason. This session hand-rolled git/gh instead, which step 2 forbids, walked into the trap the tool was built to prevent, then wrote it up as new. Correction appended rather than the block deleted: the measurement is still true, the framing was wrong. When the reconciler was finally run it reported the tree copy stale at 829 vs 930 lines AND a gap block (`gh answered for 5 of 6 referenced PR(s)`) — a distinction a hand-rolled check cannot produce, since it reports a gap as nothing at all. 3. RANK 3 IS NO LONGER UNPUSHED. The doc says the other session's work is a local commit with no PR; it is now open as devrc #1406, found by the pre-create `gh pr list` sweep that step 6 asks for and that the previous PR ran only afterwards. The claim is untouched and still theirs. Claude-Session: https://claude.ai/code/session_01MCjWicwrMAwjGTh3uXsSNT Claude-Session-Id: 054db069-07b1-4abb-962a-f677e633829f Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ol check, and a guard passed while deploying a dangling symlink Round-1 adversarial audit of #1406. One 🔴, one 🟡, six 🟢; the two that ship behaviour are below, each with the measurement that made it a finding rather than a reading. 🔴 **`cairn validate` is no longer the mandated post-write check, silently and at exit 0.** After this PR `~/.local/bin/cairn` is the pinned OSS package, which reimplements `validate` on the READER's resolver instead of shelling `subsystem_touch.py`. Measured on the built package against the live cache: **76 bytes and rc 0** against the writer's **5,765 bytes** carrying `entry shape:`, `marker reachability:` and `dropped lines:` — the last meaning content is ALREADY LOST. Both are "green"; only one looked. So both skills now name the writer's own invocation, and `test_subsystem_touch.py`'s pinned-sentence ledger moves with it — it went RED on the old spelling, which is the mechanism that caught this. Exit codes differ too and both skills say so: the writer exits 3 on a malformed entry, the packaged client 5 (`EXIT_CORRUPT`; its own 3 is `EXIT_UNREACHABLE_NO_CACHE`). 🟡 **`test_the_package_is_passed_to_the_home_module_through_extraSpecialArgs` ran its two checks INDEPENDENTLY over the whole region**, so a decoy satisfied both while the deploy pointed elsewhere. MEASURED SURVIVED at f98be26: cairnPackage = pkgs.hello; cairnUnused = cairn.packages.${system}.cairn; 8 passed, and `home.file.".local/bin/cairn".source` became `${pkgs.hello}/bin/cairn` — home-manager's `insertFileEntry` does an unconditional `ln -s`, so that BUILDS and deploys a DANGLING symlink, the exact outcome the required-argument design claims to prevent. `cairnPackage = pkgs.hello;` ALONE was already killed, so the guard was narrower than its docstring rather than inert. Now the value assertion is bound to that name's own right-hand side, plus a `count == 1` binding check so the asserted binding and the threaded one cannot be different lines. Also `test_home_nix_REQUIRES_the_package_argument_with_no_default` read the header as `split("\n", 1)[0]`. A nix module header may legally span lines, so on a CORRECT tree it failed with "does not accept `cairnPackage`" — a red naming a cause the tree does not have. It now walks to the `:` that closes the argument set. `run-tests.sh`'s baseline comment claimed "main is red on its own" as a durable property. An audit round measured the OPPOSITE at the same commit (30 passed / 0 failed in `test_nebula_relay_apply.py` at 01956bf, hours later). Both readings are real: that suite reads LIVE HOST STATE, and #1272 shipped the sudo apply beside the check, so applying the relay flips it with no commit involved. Its result is keyed to the HOST, not the tree — the comment now says so and says re-derive, never quote. Regression matrix, measured both ways at f98be26 (pre-fix) and HEAD: | mutant | pre-fix | post-fix | |---|---|---| | the decoy above | **SURVIVED** (8 passed) | KILLED, on the rhs assertion | | `cairnPackage = pkgs.hello;` alone | KILLED | KILLED | | a second `cairnPackage =` binding | n/a (no such check) | KILLED, on `count == 1` | | home.nix drops the argument | KILLED | KILLED | | a legal MULTI-LINE header (a CORRECT tree) | **RED — false diagnosis** | green | | the skill reverts to `cairn validate` | n/a | KILLED, on the pinned sentence | 6/6 killed by their intended guard's own message, `PYTHONDONTWRITEBYTECODE=1`, every anchor required to match exactly once, tree diffed byte-identical against the battery's snapshot afterwards. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTRPN6dhkdf8kZJch3mne5 Claude-Session-Id: 50c86165-b6c3-47d8-9fd7-14e70bb637a9
Claude-Session-Id: 50c86165-b6c3-47d8-9fd7-14e70bb637a9
Round 1 — fixes landed, and the base moved under themRound-1 adversarial audit read
The 🔴:
|
| output | rc | contract blocks | |
|---|---|---|---|
packaged cairn validate |
76 B | 0 | 0 |
subsystem_touch.py --validate |
5,765 B | 0 | entry shape: · marker reachability: · dropped lines:, OK — 31 of 31 |
Both are "green"; only one looked. dropped lines: is the one that means content is already
lost. Both skills now name the writer's own invocation, and test_subsystem_touch.py's
pinned-sentence ledger moved in the same commit — it went red on the old spelling, which is
the mechanism working rather than a chore. Exit codes diverge too and both skills say so: the
writer exits 3 on a malformed entry, the packaged client 5 (EXIT_CORRUPT; its own 3
is EXIT_UNREACHABLE_NO_CACHE).
The 🟡: a guard that passed while deploying a dangling symlink
test_the_package_is_passed_to_the_home_module_through_extraSpecialArgs ran its name-check and
its value-check independently over the whole region, so a decoy satisfied both while the
deploy pointed elsewhere. Re-measured this session at f98be263:
cairnPackage = pkgs.hello;
cairnUnused = cairn.packages.${system}.cairn;→ 8 passed, and home.file.".local/bin/cairn".source becomes ${pkgs.hello}/bin/cairn.
home-manager's insertFileEntry does an unconditional ln -s, so that builds and deploys a
dangling symlink — the exact outcome the required-argument design claims to prevent.
cairnPackage = pkgs.hello; alone was already killed, so the guard was narrower than its
own docstring rather than inert; the failure only appears when a decoy carries the string the
second assertion looks for.
Also test_home_nix_REQUIRES_the_package_argument_with_no_default read the header as
split("\n", 1)[0]. A nix module header may legally span lines, so on a correct tree it
failed with "does not accept cairnPackage" — a red naming a cause the tree does not have.
Mutation matrix — measured in both directions, at base and at HEAD
Anchors required to match exactly once (0 or 2 ⇒ INVALID, never a pass), run under
PYTHONDONTWRITEBYTECODE=1, tree diffed byte-identical against the battery's snapshot
afterwards.
| mutant | at f98be263 |
at b79cf63a |
|---|---|---|
| the decoy above | SURVIVED (8 passed) | KILLED — "cairnPackage is bound to something other than the cairn input's package" |
cairnPackage = pkgs.hello; alone |
KILLED | KILLED |
a second cairnPackage = binding |
(no such check) | KILLED — "expected exactly one … found 2" |
nix/home.nix drops the argument |
KILLED | KILLED |
| a legal multi-line header (a CORRECT tree) | RED — false diagnosis | green |
the skill reverts to cairn validate |
(n/a) | KILLED on the pinned sentence |
The multi-line row is the reachability control: it is expected LIVE, and it staying green is
what makes the header change a fix rather than a tightening.
Gate — both tiers, on the merged tree b79cf63a
pytests 28/28 targets PASS collected=21135 passed=21133 skipped=2 failed=0 (floor: 20441)
scripts/tests collected=13090 passed=13090 skipped=0 floor=13026
nodetests suites=5 files=41 tests=1449 pass=1449 fail=0 skipped=0 (floor: 1367)
--check-floors RESULT: PASS GLOBAL floor 20441
⚠ The 20 test_nebula_relay_apply.py failures quoted in this PR's description do not appear
here, and that is not a fix — that suite reads live host state (/etc/nebula/ca.crt,
/etc/nixos/configuration.nix), so its result is keyed to the HOST, not to the tree. An audit
round measured 30 passed / 0 failed in that file at 01956bf0, hours after the run that
measured 20 failures at the same commit. run-tests.sh's baseline comment used to assert
"main is red on its own" as a durable property; f9ff05c4 corrects it to say re-derive with a
control run, never quote these numbers.
Still open — reported, not hidden
- ⚠
tekton/devrc-pytestsis red on this PR at the now-stale headf98be263, naming
TestARefusedWriteIsIndistinguishableFromAnAbsentOne.test_POSITIVE_CONTROL_…, a test this
diff does not touch. It passes locally (5 passed, 739 deselected, 7.09 s) and inside the
hermetic 13090 above. The tier is not broken — feat(bar): AIO pump + case fan RPM pill, and the staged sudo script that keeps its driver #1411 passed it in the same window at
collected=21163 failed=0. The run has been pruned (keep: 20per pipeline, hourly), so
which assertion fired is unrecoverable. Not diagnosed; the fresh verdict onb79cf63ais the
next reading. - Three 🟢 from round 1 are deferred, not fixed, and tracked as rank 18 of the handoff:
(a)nix/sessionVariables.nixhardcodes.claude/analyze-service-index, a second.nix
spelling ofsubsystem_touch.DEFAULT_STORE_ROOTthattest_store_root_ledger.py
structurally cannot see; (b) the packagedlib/host_identity.pyhonoursCAIRN_HOSTand
devrc's copy does not (dormant — nothing sets it); (c)claude/skills/cairn/SKILL.md's
"consolidated in a later slice" names no owner. (a) and (b) both disappear if slice 3 lands.
Round 2 has not been run. By the stop rule it is warranted, because round 1 produced
findings that needed fixing.
legend: <from> = the tip THIS round's audit READ · <to> = the head THIS round's FIXES produced. Different shas — <from> is older.
1. Both `cairn` and `subsystem-index` skills no longer name `cairn validate` as the mandated post-write check; they name `subsystem_touch.py --store ~/.cache/subsystem-store --validate --scope <scope>`.
2. `test_subsystem_touch.py`'s pinned-sentence ledger was moved to the new spelling in the same commit, so the module and its only caller cannot drift apart silently.
3. Both skills now state the exit-code divergence: writer 3 on a malformed entry, packaged client 5 (`EXIT_CORRUPT`).
4. `test_the_package_is_passed_to_the_home_module_through_extraSpecialArgs` now binds its value assertion to `cairnPackage`'s own right-hand side via `_assignment`, instead of searching the whole `extraSpecialArgs` region.
5. That same test now asserts exactly one `cairnPackage =` binding exists (`count == 1`).
6. `test_home_nix_REQUIRES_the_package_argument_with_no_default` now walks to the `:` closing the argument set instead of reading only the first line, so a legal multi-line nix header no longer produces a false red.
7. `scripts/run-tests.sh`'s `scripts/tests` floor comment no longer asserts "main is red on its own" as a durable property, and instructs re-deriving the baseline with a control run.
8. `SECRETS.md`'s subsystem-store row no longer says the token is read by `scripts/cairn`; it names the `cairn` client on PATH and records that since 2026-09-08 that is the pinned flake package.
9. `origin/main` was merged into the branch as `b79cf63a`, and both gate tiers were run on that merged tree rather than on the branch alone.
… and deliberately not marked done, because merged is not live (#1419) `cairn recall`'s digest told readers to run `--ref <name>` / `--limit N` and the client exited 2 with `unrecognized arguments`, so a reader following the output it had just been shown fell back to the raw module. The fix is not the flags. `main()` derived mode/limit/page AND enforced the flag-conflict rules inline, while the client reaches the module as a LIBRARY and never through `main()` — so offering the flags meant open-coding both at a second site. `recall_selection()` and `reject_recall_flags()` are extracted and called by both, so `main()` got shorter instead of the wrapper growing a copy. All four flags wired, not the two the item named: fixing only `--ref` leaves the class open, the same enumeration-vs-derivation shape rank 12 closed. Evidence is recorded split rather than totalled. Two tests are RED at base on their own assertions; a third fails at base with AttributeError and is NOT counted as regression evidence. Four flags measured live against the real store, because a passing suite and a working client are different claims. A defect the change introduced is recorded rather than quietly fixed: exposing `--limit` made `recall()`'s ValueError reachable from the command line for the first time, and `--limit 0` printed a traceback at rc 1. Guarded, watched before and after, reachable by measurement. Also recorded: the two failures the FULL suite found that a four-test subset did not — the mutation battery refusing orphaned anchors after the guards moved, and a TEXT ledger tripping on a comment quoting the callee's name. The second is a false red, so the comment moved and the guard did not; that ledger cannot tell a call from a comment, which is noted and not fixed. 🔴 The rank is NOT marked done. Its closing condition named a merged devrc PR and the fix landed in the OSS client, so `readlink -f ~/.local/bin/cairn` still resolves to devrc/scripts/cairn and `--ref` is still broken on this host. The condition is re-pointed at "#7 merged AND #1406 landed AND the pin bumped past 059ec17", so it is checkable again rather than quietly unsatisfiable. Rank 3 is untouched here: the session that owns it had already recorded #1406 across twelve mentions, and duplicating that would be the drift this doc keeps paying for. Claude-Session: https://claude.ai/code/session_01MCjWicwrMAwjGTh3uXsSNT Claude-Session-Id: 054db069-07b1-4abb-962a-f677e633829f Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round 2 — delta re-audit of
|
…ble, and a comment refuted by a commit in its own merged tree Round-2 delta audit of `f98be263..b79cf63` returned no 🔴 and eight 🟡. Three of them were defects in round 1's own fix round; this closes those three, plus the operator's decision on the writer-invocation fork. The other five are recorded on the PR as open-by-decision. 🔴 **The header walk fell back to the WHOLE FILE and nothing said so.** Round 1 replaced `split("\n", 1)[0]` — which false-RED a legal multi-line header — with a bracket walk that, when the depth never returned to 0, simply ran off the end leaving `header` at its initial value: the entire file. `cairnPackage` occurs twice in the module BODY, so `THREADED_NAME in header` then passed while the argument was dropped from the header entirely. MEASURED at `b79cf63a`: a legal multi-line header that drops `cairnPackage` and carries one unbalanced `(` in a prose comment leaves the suite at **8 passed**. Fixing a false red opened a path to a false green. `_module_header()` now skips `#`/`/* */` comments and `"…"`/`''…''` strings so none of them carry depth, decides "opens with an argument set" on the first CODE token rather than `lstrip()` (which strips whitespace, not comments, and false-RED any file with an explanatory line above its header), and **raises** rather than falling back. 🔴 **The moved ledger pin named FLAGS, so it was walkable back to the defect it guarded.** `--store … --validate --scope <scope>` bound nothing to the writer. MEASURED at `b79cf63a`: routing those same flags at the packaged client leaves the suite **green at 79 passed** — round 1's 🔴, fully re-opened. The pin is now the whole normalised invocation, `cairn sync && cairn-validate --scope <scope>`, with whitespace runs normalised on both sides so a rewrap is not read as a deletion. 🔴 **The `run-tests.sh` comment asserted a diagnosis that `4f12fcef` refutes — and `4f12fcef` is an ancestor of HEAD but NOT of round 1's commit**, so round 1 wrote its theory before the refutation existed. It said `test_nebula_relay_apply.py` is "keyed to the HOST, not to the tree". It was a TIER split with a TREE cause: `apply-nebula-relay.sh` dispatched on a `#!/usr/bin/env bash` shebang, the nix sandbox has no `/usr/bin`, every exec died `bad interpreter`, and the script reported `ABORT … (rc=126)` while the dev-host tier stayed green. Retracted in place, cross-referenced to this file's own tier notes. Comment-only — verified by `git diff -U0 | grep -v '^[+-]\s*#'` being empty. Re-measured: 30 passed / 0 failed on the dev-host tier at `b79cf63a`. **`cairn-validate` ships, on the `cairn-who` seam** (operator decision). The write protocol has to name the writer, and the only spelling that ran was an absolute `python3 /home/…/devrc/scripts/lib/subsystem_touch.py` — a checkout path baked into a protocol whose point is that agents work in other repos; `cairn/SKILL.md` spelled it bare, which is not on PATH and exits 127. The launcher declares no parser: two defaults are PREPENDED to the caller's argv, because argparse's last-occurrence-wins gives the caller the override for free while a scan for an existing `--store` would be a partial re-copy of argparse's rules. `mkOutOfStoreSymlink`, not a store copy — `.resolve()` must land beside `scripts/lib/`, and no package ships the writer. ⚠ **Round 1's own figure was superseded by re-measuring rather than carried.** Both clients, same scope, same moment: the packaged `validate` writes **0 bytes to stdout** (77 B of banner on stderr) and exits 0; the writer writes **5,766 B** with all three contract blocks. "76 bytes of output" understated it — a caller reading stdout gets NOTHING. Every citation of the pair now says so. Regression matrix, red at `b79cf63a` / green at HEAD, on the REAL `nix/home.nix`: header drops the argument + unbalanced `(` in a comment → **8 passed (vacuous green)** → RED with `does not accept 'cairnPackage'`; leading file comment on a correct tree → **RED (false diagnosis)** → 11 passed. Plus 8 new tests red at base with their own messages. Mutation battery, `PYTHONDONTWRITEBYTECODE=1`, every anchor asserted to match exactly once, tree restored between mutants. 11 mutants, 11 killed by their intended guard's own message. ⚠ **A-M2 (reinstate the whole-file fallback) SURVIVED on the first attempt and is reported rather than hidden**: the vacuous-case fixture closes correctly once comments stop carrying depth, so it never reached the fall-off-the-end branch — an unreachable guard. `test_an_UNCLOSED_argument_set_…` was added with a header that has no closing `}` at all, a case no earlier assertion rejects, and the mutant then died. B3 is a control: a cosmetic rewrap of the pinned command stays green, so the normalisation is doing work rather than the pin being brittle. D-M2 swaps `--validate` for `--census` and dies on the contract-block assertion — the case a structural check type-checks straight past. `claude/skills/subsystem-index/SKILL.md` 42,269 → **41,591 B** (`HARD = 40_960`). 678 B cut. ⚠ Under the cap is arithmetically unreachable from round 1's block alone: the file was already 31 B over before round 1 touched it, so deleting that block entirely lands at 40,991. The residue is pre-existing prose — 🟡7, out of scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTRPN6dhkdf8kZJch3mne5 Claude-Session-Id: 50c86165-b6c3-47d8-9fd7-14e70bb637a9
Claude-Session-Id: 50c86165-b6c3-47d8-9fd7-14e70bb637a9
…sured BLOCKED on #1406 because Claude-Session-Id: 054db069-07b1-4abb-962a-f677e633829f
Round 3 — the fix round for round 2's three scaffolding defects, plus the writer-invocation decisionRound 2 read What was wrong, and it was mine🟡1 — round 1 fixed a false RED and opened a path to a false GREEN. The bracket walk fell off 🟡3 — the ledger pin named FLAGS, binding nothing to the writer, so the mandated check could be 🟡4 — the comment asserted a diagnosis Regression matrix — on the REAL
|
at b79cf63a |
at HEAD | |
|---|---|---|
header drops cairnPackage + unbalanced ( in a prose comment |
8 passed — vacuous green | RED: does not accept `cairnPackage` |
| leading file comment, header otherwise correct | RED — false diagnosis | 11 passed |
| same flags routed at the packaged client | green, 79 passed | KILLED on the pinned invocation |
cairn validate restored, writer demoted to an aside |
green, 79 passed | KILLED, same message |
Plus 8 new tests red at base with their own assertion messages.
Mutation battery — 11 mutants, 11 killed, and one that had to be earned
PYTHONDONTWRITEBYTECODE=1, every anchor asserted to match exactly once, tree restored between
mutants.
⚠ A-M2 (reinstate the whole-file fallback) SURVIVED on the first attempt — reported, not
hidden. Once comments stop carrying depth the vacuous-case fixture closes correctly, so it never
reaches the fall-off-the-end branch: an unreachable guard, green for the wrong reason. A third
test with a header that has no closing } at all — a case no earlier assertion rejects — was added,
and the mutant then died.
B3 is a control in the other direction: a cosmetic rewrap of the pinned command across three
lines stays green, so the whitespace normalisation is doing work rather than the pin being
brittle. D-M2 swaps --validate for --census and dies on the contract-block assertion — the
case a structural check type-checks straight past.
cairn-validate — the operator's call on the writer-invocation fork
The write protocol has to name the writer, but the only spelling that ran was an absolute
python3 /home/…/devrc/scripts/lib/subsystem_touch.py — a checkout path baked into a protocol
whose point is that agents work in other repos — and cairn/SKILL.md spelled it bare, which is
not on PATH and exits 127. So it ships on the cairn-who seam: mkOutOfStoreSymlink (not a
store copy — .resolve() must land beside scripts/lib/, and no package ships the writer), and a
launcher that declares no parser, prepending its two defaults to the caller's argv because
argparse's last-occurrence-wins gives the caller the override for free.
🔴 Three of its four guards EXECUTE the binary, deliberately: rank 16's standing point is that
nothing in devrc's gate ever runs the deployed client, which is why the original validate
regression survived 13,076 green tests. Answering a runtime defect with more text pins is what
round 1 did.
⚠ Round 1's own figure was superseded by re-measuring rather than carried forward. Both clients,
same scope, same moment: the packaged validate writes 0 bytes to stdout (77 B of banner on
stderr) and exits 0; the writer writes 5,766 B with all three contract blocks. "76 bytes of
output" understated it — a caller reading stdout gets nothing.
The round-2 open item that is now attributable — with a control
Round 2 recorded tekton/devrc-pytests failing at f98be263 on
TestARefusedWriteIsIndistinguishableFromAnAbsentOne.test_POSITIVE_CONTROL_… as not diagnosed,
because the run had been pruned. It now has a discriminator: PR #1417 failed on the same
assertion, and #1417 changes exactly one file — a markdown handoff doc. A docs-only diff cannot
break a server-API test. Two PRs, disjoint diffs, one failing test ⇒ the failure is in the tier,
not the tree. Still not a root cause, but it is no longer attributable to this change.
Skill budget — the honest arithmetic
claude/skills/subsystem-index/SKILL.md 42,269 → 41,591 B against HARD = 40_960. 678 B cut.
⚠ Under the cap is arithmetically unreachable from round 1's block alone: the file was already
31 B over before round 1 touched it, so deleting that block entirely lands at 40,991. The residue is
pre-existing prose — 🟡7, out of scope.
Noticed and NOT fixed this round — reported so they read as open
claude/skills/resume/SKILL.md:128carries the same baresubsystem_touch.py(exit 127)
defect, in a third file;:150carries the absolute-path spelling. Neither is pinned by any test.subsystem_touch.validate_command()still emits the absolute checkout-path spelling in the
RECOVER —block the skill tells writers to run verbatim.claude/skills/cairn/SKILL.md:89-92carries the same false deployment tense asSECRETS.md.- The prose pin cannot distinguish "mandated" from "merely mentioned" — an adversary who demotes
the command to an aside while keeping the full string intact survives it. Killing that needs
a positional assertion no pin in that ledger makes. cairn/SKILL.md's frontmatterdescriptiondoes not listcairn-validate, so the new binary
has no routing keyword. 🟢.
Not verified: nothing here is measured against a switched host — readlink -f ~/.local/bin/cairn is still /home/zach/workspace/devrc/scripts/cairn and cairn-validate is not
on PATH. Every sentence written this round is conditioned on home-manager switch. The laptop
is a second, independent switch that nothing in this PR forces.
legend: <from> = the tip THIS round's audit READ · <to> = the head THIS round's FIXES produced. Different shas — <from> is older.
1. `_module_header()` raises instead of falling back to the whole file when the argument set is never closed, so the caller can no longer pass vacuously while `cairnPackage` is absent from the header.
2. `#`/`/* */` comments and `"…"`/`''…''` strings no longer contribute bracket depth to that walk.
3. "Opens with an argument set" is decided on the first CODE token instead of `lstrip()`, so a leading file comment no longer false-reds a correct tree.
4. The `test_subsystem_touch.py` ledger pins the whole normalised invocation `cairn sync && cairn-validate --scope <scope>` instead of a flag fragment, with whitespace runs normalised on both sides.
5. `scripts/run-tests.sh`'s floor comment retracts the "keyed to the HOST, not the tree" claim and states the tier/tree mechanism from `4f12fcef`; the hunk is comment-only.
6. `scripts/cairn-validate` is added as a launcher that declares no parser and passes the writer's exit codes through untranslated.
7. `nix/home.nix` deploys `cairn-validate` as a `mkOutOfStoreSymlink`, not a store copy.
8. Three of the four new `cairn-validate` guards execute the real binary rather than reading text.
9. Both `cairn` and `subsystem-index` skills name the same command, `cairn-validate --scope <scope>`; the bare `subsystem_touch.py` and absolute-path spellings are gone from those two files.
10. `claude/skills/subsystem-index/SKILL.md` shrank 42,269 → 41,591 B.
11. Every citation of the two clients' output now states 0 bytes on stdout / 77 B on stderr, replacing round 1's "76 bytes of output".
… job, and a guard blind to the tier it runs in
The merged-tree gate on `4b27c63c` came back `failed=3`, all three from round 3's
own additions and none from `origin/main`. Each is a different lesson.
🔴 **`test_store_root_ledger` went red because `cairn-validate` is a NEW router
through `subsystem_read_store` — that is the seam guard WORKING**, not an
obstacle. Its ledger fails when the set of routers GROWS as well as when it
shrinks, precisely so a new reader cannot quietly start answering "where do I
read?" for itself. A row was added saying what it reads and why: the launcher
routes here for the SAME reason the ledger's three historical regressions
existed — `subsystem_touch`'s own `--store` default is the FROZEN pre-cutover
mirror, so a launcher that inherited it would parse the PRE-write bytes.
🔴 **`test_cairn_validate_defaults_its_store_to_the_SYNCED_CACHE_not_the_mirror`
was structurally incapable of passing in the sandbox tier, and green on the dev
host is what hid it.** It asserted `f"store: {expected}"` on STDOUT. That holds
where the cache exists and the tool takes its success path; the `nix build`
tier's `$HOME` is `/build/home` and carries no `~/.cache/subsystem-store`, so
the tool exits down the not-found path and names the resolved root on STDERR
instead. The claim is WHICH store the launcher chose, never whether one exists —
so it belongs on the combined output. Pinning it to one stream made a guard that
could only ever be green in one of the two tiers this suite runs in.
⚠ Round 3 said of these tests: *"I believe they are sandbox-safe, but that is
reasoning, not a measurement."* It was reasoning, and it was wrong.
MEASURED AT TWO POINTS, because one is not a general claim: green with a real
cache root, and green under a `$HOME` verified to have none. The mutant that
drops the `--store` prepend is KILLED at BOTH, on this guard's own message —
previously the negative half did not exist at all, so a launcher that inherited
the writer's default could have passed whenever both paths were printed. It is
now asserted explicitly: the frozen mirror's path must NOT appear.
🔴 **`test_runtime_shebangs` flagged a spelled `"#!"` in the new launcher test.**
Rather than register a string in its allowlist — which that file reserves for
sites solving the problem a different, VERIFIED way, not for going green — the
assertion now pins the RELATIONSHIP: `cairn-validate`'s interpreter line must
equal `cairn-who`'s. Both are `mkOutOfStoreSymlink` launchers invoked as bare
commands from PATH, so they must agree on how they find an interpreter, and
asserting one in isolation would pass while the two drifted apart. The literal
disappears as a consequence rather than as the goal.
112 passed across the three affected suites; mutation control restored
byte-identical.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTRPN6dhkdf8kZJch3mne5
Claude-Session-Id: 50c86165-b6c3-47d8-9fd7-14e70bb637a9
Claude-Session-Id: 50c86165-b6c3-47d8-9fd7-14e70bb637a9
…nfetched worktree — #1406 is ME Claude-Session-Id: 054db069-07b1-4abb-962a-f677e633829f
…tion is still only HALF met (#1425) * docs(handoff): rank 3 slice 2 MERGED as devrc #1406 squash 9300f23 and verified by content; three a Claude-Session-Id: 50c86165-b6c3-47d8-9fd7-14e70bb637a9 * docs(handoff): the recurring CI intermittent finally carried its evidence — a socket read TimeoutErr Claude-Session-Id: 50c86165-b6c3-47d8-9fd7-14e70bb637a9 * docs(handoff): the CI intermittent is DIAGNOSED as SERVER_BLOCKED_IN_FSYNC by the MECHANISM= line th Claude-Session-Id: 50c86165-b6c3-47d8-9fd7-14e70bb637a9
Rank 3's second slice. devrc consumes
ZacxDev/cairnas a pinned flake input and deployspackages.cairn, instead of themkOutOfStoreSymlinktoscripts/cairn. The operator decided the direction on 2026-09-08 (#1394): consolidate onto the pin.Scope: the pin only. NOT the
entry_shape/writer-vocabulary consolidation (slice 3), NOT deleting devrc'sscripts/lib/copies — the writer andcairn-whostill import them.The asymmetry is the point
cairnmoves into the store;cairn-whodeliberately does not. Both resolve siblings throughPath(__file__).resolve().parent / "lib", so what must hold is that the directory holding the REAL file also holdslib/. Out-of-store satisfies that by resolving back into the checkout; the packagedcairnsatisfies it a second way, by installing script andlib/together underlibexec.cairn-whohas no such package — it is devrc-only and absent from the OSS repo — so a store copy would die on import. Both lines sit together so the difference reads as deliberate, andtest_cairn_flake_pin.pypins the relationship, not each side alone.nixpkgs is deliberately NOT followed
cairn pins
python312because its Dockerfile and CI do; a barepkgs.python3there once followed nixpkgs to 3.14 and shipped an interpreter its suite had never run under. Following would rebuild the client against devrc'snixpkgs-unstable— a nixpkgs cairn's CI has never tested.The guard reads the whole input region (a dotted-spelling draft was walked by the
cairn = { url = …; inputs.nixpkgs.follows = …; }form, which is how home-manager is spelled twenty lines above) and checks the lock structurally: a follow is a list, an unfollowed input a plain node name. Positive-controlled in the same file — home-manager records['nixpkgs'], cairn records'nixpkgs'.⚠ The lock currently maps cairn's nixpkgs to devrc's node. That is deduplication, not a follows: cairn's own lock pins the same rev (
42f17a57f4f6). When the two diverge, cairn keeps its pin.Two defects the gate caught, neither visible in the diff
cairnPackageis a REQUIRED argument (no default, deliberately — a broken thread should be an eval error, not a symlink to/bin/cairn), and that brokeskills_mapping.py, which evaluatesnix/home.nixstandalone with a stub arg set. nix refused to apply the function and the check reported "nix cannot evaluate nix/home.nix" — a message that blameshome.nixfor a defect in the stub set. Its own text says FIX THE CHECK, do NOT delete it. AddedcairnPackage = {}—{}rather than a string, so forcing it errors instead of silently yielding a plausible path.assert 11256 <= 11192;MEASURED_ALL_TIER_A_CHARS 11366 != 11430). Fixed length-neutrally rather than by re-pinning constants:client `scripts/cairn`→client `cairn` on PATH, both exactly 22 chars, so every pinned tier constant stays valid and the description is more accurate after the pin.The comment explaining (1) then breached a third budget:
skills_mapping.pyhas a 7,400-byte ceiling with 108 bytes free and the first draft added 742. That ceiling's message says an overage is a question about which ambition crept back, never about raising the number — so the comment is three terse lines (7,361 B) and the reasoning lives in the commit.Test matrix
test_cairn_flake_pin.pyat baseAll 8 are real regression coverage — none passes vacuously at base. The follows guard is labelled an invariant guard in its own docstring: the
followsline never existed, so it never caught a live bug; its discriminating power was established by mutation.⚠ It also documents a harness trap worth knowing:
nix develop -c pytestrewritesflake.lock, so a lock-only mutant is silently reverted before pytest reads it and scores SURVIVED without ever running. The mtime-cache trap in a different costume.Gate, and the baseline — which went stale twice today
Both sides fail identically, every one in
test_nebula_relay_apply.py(arrived with #1272). main is red on its own; this branch adds no failure. The +8 is exactly the new suite, all passing.The
age/opencode7 that were red this morning are gone — main fixed them. Re-derive this baseline rather than quoting it.Floor re-pinned 12927 → 13026 on the rebased tree per the ORDER note; floor sum 20342 → 20441.
Live verification, through the exact pinned revision
Built
github:ZacxDev/cairn/9213726#cairn:doctor --no-sync, noCAIRN_MIRROR_ROOTfrozen-mirror NOT-OBSERVABLEsessionVariables.nixnow exportsfrozen-mirror **OK**, all three checks OK, exit 10ls-entriesrecall --repo ~/workspace/devrcThat env var is not cosmetic: unset, the check stops looking, which is not the same as passing.
Deploy note
🔴 Merging this requires a
home-manager switch—~/.local/bin/cairnbecomes a store path created by activation. Until the switch it still points atscripts/cairn, which keeps working, so unlike #1381 there is no capability gap; the checkout copy simply stays live a while longer.🤖 Generated with Claude Code