Conversation
…ase (cubs-halogen-probe-1) A writeShellApplication for the campaign's kit entries: block on worker:8731/health until busy:false (10 s poll, 20 min deadline, then exit 69 and a receipt that says outage), one fresh `pi -p --mode json` process on the task's own git worktree from ~/agency/<repo>'s current HEAD, the campaign's diff guard (built-in fallback with the same rules), the task's validation command under a 10 min timeout, exactly one repair process fed bundle + diff + transcript, a commit on pass (never a push), receipt.json in the campaign's schema plus attempts.json, one usage line at $TALLY_USAGE_SOURCE_PATH, a SIGTERM trap that commits WIP and writes a partial receipt within the kernel's 30 s grace, and a 3-consecutive-fail fuse (exit 2). --dry prints the plan; --help lists the exit codes. Runs under the kernel's empty environment: HOME from the passwd entry, every tool a store path, the two profile bins appended last for a validation_cmd that reaches for nix. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…FRONT-12 bootstrap)
120 generated entries beside the untouched LOCAL-SMOKE trio: build:CUBS-<n>
runs the cubs-iteration store executable with cwd ~/mecattaf/cubs-campaign,
an empty env_allowlist, usage_source halogen-usage/1 under the rewrite
state's uplink/usage/cubs-*.jsonl, and a static stdin POINTER
{worklist, id} into worklists/current.jsonl that the script resolves; the
scope()/eval() cells are the existing noopEntry. A kit entry's stdin is
static bytes in a store file (lake kit.mjs, uplink.mjs:623), so the day's
task JSON cannot live there without a switch per day.
MEASURED: the rendered kit carries 123 entries; nix build
.#checks.x86_64-linux.tally-uplink-topology -> rc 0.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the executable and the CUBS kit Stub Pi (event stream + scripted edit) and stub Halogen (http.server) in a scratch dir; throwaway git repos as the CUBS tree. Clauses C1..C11: usage and exit codes, env -i --dry on the kit's own stdin, pass with commit + usage line + ledger + idempotent rerun, fail + one repair, the fuse and its reset, the spec.md guard, outage without touching the fuse, SIGTERM -> WIP commit + cancelled receipt inside 25 s and a clean retry, the events summariser, readKit over all 120 CUBS refs, the topology check. MEASURED: PROBE cubs-iteration: PASS (all clauses). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… receipt, the exit codes, the two operator acts, DF-CUBS-1..5 docs/local-ai/cubs-campaign.md records what cubs-iteration does step by step, the receipt field sources, the exit-code table, why the kit's stdin is a pointer (kit.mjs / uplink.mjs:623 / exec.rs:747), the MEASURED kit and check outputs, the hermetic oracle, one MEASURED real run against Halogen (pass first try, 17 s, 4 tool calls, usage summed), and the two acts left to Tom: the coordinator switch and the non-dry arm. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…per-attempt timeout -k 30, settings.json, newline gate) From the smoke-pi-halogen lane (GO-WITH-FIXES): `pi -p` reads a non-TTY stdin to EOF as prompt text, so every Pi process now gets </dev/null after the task JSON has been read in full; each Pi process runs under `timeout --foreground -k 30 <budget>` (1200 s first attempt, 900 s repair) because a dropped Halogen connection leaves Pi's own auto-retry hanging; the health gate uses `curl --max-time 5`; preflight requires pi/settings.json beside models.json (compaction reserveTokens / keepRecentTokens live there); a trailing-newline gate over the allowed touched files follows the diff guard (git diff --check misses it); bash tool calls are already counted per attempt in tool_call_names / attempts.json and reasoning tokens in usage — the doc now says so. The probe gains clauses for Pi's stdin, settings.json reachability, the bash count and the newline gate. MEASURED: PROBE cubs-iteration: PASS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…g_tokens, attempts_path; timeout exit 124; stray_files emits exactly one JSON array under pipefail
stray_files() captured the guard pipeline and appended `|| echo '[]'`; the guard
exits 1 on any violation (including "empty diff" when nothing is untracked), so
under pipefail it printed two JSON values, `jq --argjson stray` failed, and the
receipt stayed `pending`. It now captures into a local and prints `${out:-[]}`.
probe-cubs-iteration.sh C8b: a tracked-file-only edit and a stray untracked file
both leave a terminal receipt that validates against the campaign's
tools/receipt.schema.json (stdlib validator). C6b/C6c reset the fuse so the
guard clauses no longer blow it for the clauses after them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… so campaign days 2-7 need no second coordinator switch The kit now renders 603 entries: 200 labels x 3 cells, plus the unchanged LOCAL-SMOKE trio. probe C10 resolves all 200 through the pinned lake's readKit and refuses build:CUBS-201. docs/local-ai/cubs-campaign.md follows. The fuse rule is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d, the exit-code table is reconciled One global counter meant three consecutive failures ANYWHERE retired all 28 day-1 items: the plan put day one finishing at ~17%. The counter is now per work package. - <state>/fuse.d/<package> holds one package's consecutive failures. A fail increments only its own package, a pass resets only its own package, and a blown package retires only its own remaining items: three forced fails in WP1 leave WP2, WP3 and WP7 running. - <state>/fuse stays the MASTER fuse and is now READ-ONLY to the executable, so `echo 3 > ~/.local/state/cubs-campaign/fuse` still stops every package (RETURN-CHECKLIST (g) is unchanged). Either fuse resets by removing its file. - Receipts gain `censored` (required): true exactly on an UNRUN item a blown fuse retired before it started (no Pi process, terminal_status "fuse", exit 2). The task that BLEW the fuse ran and is censored false, so `jq -s '[.[]|select(.censored)]|length'` counts the unrun items exactly. - A censored receipt never reached the preflight, so campaign_sha, skill_digest and bundle_digest are null rather than the empty strings the campaign schema's patterns reject; the schema now spells them ["string","null"] and the probe validates a censored receipt against it. - --dry reports both counters and their paths. The exit-code table in STDIN-CONTRACT §4 is reconciled with the executable: earlier drafts folded fail and cancelled into exit 0 and named a 70 the script never returns. Probe: 69 clauses, PROBE cubs-iteration: PASS (was 58). New C5b proves the DoD directly — three forced fails in package WPA censor a fourth WPA item while a WPB item passes in the same state dir, the master fuse then stops WPB too, and jq over that state dir's receipts counts censored == unrun. The campaign side (STDIN-CONTRACT.md §2/§3.1/§3.9/§3.10/§4/§5/§6 and tools/receipt.schema.json) is edited in ~/mecattaf/cubs-campaign and left uncommitted there for its own owner. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Ruling 4 of the 2026-09-16 evening session: "Coordinator switching must NOT
be required every time a Tally flow is added. A new kit entry / flow must
load without a home-manager or NixOS switch. ... The kit path must become a
runtime-read, git-tracked location, not a store path baked at switch time."
FT-3 (dotfiles#361, TL-18 / D-B18, dotfiles#304) shipped the box's argv table
as a `pkgs.writeText` store file, so every new flow cost a switch. This moves
it:
- home/dot_config/tally/kit.json — the committed 603-entry table, installed at
~/.config/tally/kit.json by `mkOutOfStoreSymlink` into the dotfiles checkout
(home/home.nix's own motion for raw dotfiles). The uplink's `readKit` is a
`readFileSync` per wake, so an edit is live at the next wake: a commit,
never a switch.
- services.tally-uplink.kit = "${config.home.homeDirectory}/.config/tally/kit.json"
(the pinned lake's option is nullOr str). The rendered ExecStart carries
`--kit /home/tom/.config/tally/kit.json` and still no `--plan`.
- tools/render-tally-kit.py renders the LOCAL-SMOKE trio plus the CUBS 3N
entries (N=200, as #402; `--dry` NOT appended) deterministically; `--check`
diffs the committed bytes. Supersedes the nix-rendered `cubsEntries`.
- Every argv[0] is a STABLE per-user profile path —
/etc/profiles/per-user/tom/bin/{tally-local-smoke,cubs-iteration} — because a
hashed store path in a git-tracked file goes stale on the next rebuild and
refreshing it would be the switch the ruling removed. pkgs/tally-local-smoke
promotes FT-3's `localSmoke` writeShellScript to a writeShellApplication
(bytes unchanged); both packages join home.packages and are exposed as flake
packages so the probe can realise the argv before the switch.
`grep -c /nix/store home/dot_config/tally/kit.json` == 0 is the fence.
- flake.nix `tally-uplink-topology` asserts flipped: the kit is the runtime
path and NOT a store path, the committed JSON exists, parses through
`builtins.fromJSON` and carries build:LOCAL-SMOKE + build:CUBS-1 while still
not carrying claude:headless, and ExecStart carries
`--kit /home/tom/.config/tally/`.
- tests/tally-uplink/probe-FT-3-kit.sh: K1/K3/K5 inverted, new K0 (rendered
bytes, no store path), K2 reads the CHECKOUT file with the pinned lake's own
readKit (resolves the LOCAL-SMOKE / CUBS-1 / CUBS-200 trios, refuses
claude:headless and CUBS-201) and runs with no nix at all under
TALLY_KIT_LAKE / TALLY_KIT_NODE, K4 kept.
- tests/tally-uplink/probe-cubs-iteration.sh follows the kit to the checkout
and to the stable argv[0]; rc 0, C1-C11.
- docs/local-ai/tally-uplink-input.md rewritten for the runtime-read kit;
DECISIONS.md records ruling 4 plus four "default, unruled" lines (Rule 9
yields for this file); DEFERRED.md DF-KIT-1 [OPERATOR] the one switch,
DF-KIT-2 [OTHER-REPO] the kit.d/ second step and per-item evaluator stdin,
DF-KIT-3 the inherited repo-wide flake-check reds with their tails verbatim.
Contains #402 (campaign/cubs-iteration, local head 25fe0db) by merge.
Issues: dotfiles#361 (FT-3), dotfiles#304 (TL-18 / D-B18), dotfiles#293
(Rule 9, which yields here), dotfiles#362 (the Claude-seat ruling, untouched).
MEASURED: render --check rc 0 (603 entries); no store path in the JSON;
tally-uplink-topology rc 0; probe-FT-3-kit.sh rc 0 (K0-K6 ok, K7 [ENV]-fenced);
probe-cubs-iteration.sh rc 0; probe-u-d14-uplink.sh rc 0. `nix flake check
--offline --no-build` is rc 1 on this branch AND byte-identically on main
202d9c3 (checks.x86_64-linux.nas-topology, the 8731 firewall assertion) — no
NEW red.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
What
Ruling 4 (2026-09-16 evening): "Coordinator switching must NOT be required every time a Tally flow is added. A new kit entry / flow must load without a home-manager or NixOS switch. … The kit path must become a runtime-read, git-tracked location, not a store path baked at switch time."
FT-3 (#361, TL-18 / D-B18, #304) shipped the box's argv table as a
pkgs.writeTextstore file, so every new flow cost a coordinator switch. This PR moves it out of the store.home/dot_config/tally/kit.json— the committed 603-entry table, installed at~/.config/tally/kit.jsonbyconfig.lib.file.mkOutOfStoreSymlinkinto the dotfiles checkout (the motionhome/home.nixalready performs for every raw dotfile). The pinned lake'sreadKitis areadFileSyncper pass, so an edit is live at the next timer wake.services.tally-uplink.kit = "${config.home.homeDirectory}/.config/tally/kit.json"(the option isnullOr str). Rendered ExecStart carries--kit /home/tom/.config/tally/kit.json, and still no--plan.tools/render-tally-kit.pyrenders the LOCAL-SMOKE trio plus the 3N CUBS entries (N = 200 as cubs-halogen-probe-1: cubs-iteration executable and kit entries (FRONT-12 bootstrap) #402,--dryNOT appended) deterministically;--checkdiffs the committed bytes. Supersedes the nix-renderedcubsEntries.argv[0]is a stable per-user profile path —/etc/profiles/per-user/tom/bin/{tally-local-smoke,cubs-iteration}. A hashed store path inside a git-tracked file goes stale on the next rebuild, and refreshing it would be the very switch the ruling removed.pkgs/tally-local-smokepromotes FT-3'slocalSmokewriteShellScriptto awriteShellApplication(bytes unchanged,runtimeInputsdeliberately empty so it runs underenv -i); both packages joinhome.packagesand are exposed as flake packages so the probe can realise the argv before the switch.flake.nixtally-uplink-topologyasserts flipped:hasPrefix "/home/tom/.config/tally/" cfg.kit,!(hasPrefix "/nix/store/" cfg.kit),builtins.pathExists ./home/dot_config/tally/kit.json,builtins.fromJSON (builtins.readFile …)parses and carriesbuild:LOCAL-SMOKE+build:CUBS-1and still notclaude:headless, and ExecStart carries--kit /home/tom/.config/tally/.tests/tally-uplink/probe-FT-3-kit.sh: K1/K3/K5 inverted, new K0 (the committed bytes are the rendered bytes and hold no store path), K2 reads the checkout file through the pinned lake's ownreadKit, K4 kept.tests/tally-uplink/probe-cubs-iteration.shfollows the kit to the checkout and to the stableargv[0].docs/local-ai/tally-uplink-input.mdrewritten for the runtime-read kit (a new flow is a JSON edit + a commit);DECISIONS.mdcites ruling 4 and records that Rule 9 (claude-transcript-mirror is two hand-written unit files and an untracked script — declare it (Rule 9) #293) yields for this file — the review moves from the store path to the git object, and the path on the box is a symlink into the checkout, so editing it is editing the repository — plus four "default, unruled" lines;DEFERRED.mdgains DF-KIT-1[OPERATOR], DF-KIT-2[OTHER-REPO], DF-KIT-3 with the inherited red tails.Why
~/.local/state/tally-rewrite/ledger.jsonlhas 0lease_grantrows ever. Adding a Tally flow or kit entry must never require a coordinator switch (ruling 4), and until this lands it did.Base and ordering
Contains #402 (
campaign/cubs-iteration, local head25fe0dbb) by merge. Merge #402 first, then this shows only its own diff. It supersedes the nix-renderedcubsEntriesthat #402 added tohome/tally-uplink.nix— the same 600 CUBS entries, same stdin pointer contract, samecwd, rendered by the checked-in tool instead.pkgs/cubs-iterationand the campaign's STDIN contract are untouched.Oracle — rc and tails (MEASURED 2026-09-17, on this branch)
Runtime-read proved, no nix invoked. Adding
build:HAND-EDITED-DEMOto the committed JSON and re-resolving through the pinned lake'sreadKitwith the store paths already in hand printedREFS=604and the new argv immediately;render-tally-kit.py --checkthen went rc 1 on the drift (the intended friction) and rc 0 again after revert.The reds, verbatim, not laundered
Two oracles are rc 1 on this branch and byte-identically rc 1 on
main202d9c3 in a clean worktree — inherited, not introduced:An earlier pass died first at the other one, which is an evaluation-order difference, not a second cause:
tests/tally-uplink/test-tally-uplink-input.sh— rc 1, clause A only (that flake check). Every other clause[P]. Same onmain.tests/tally-uplink/probe-FIX-E12.sh— rc 1, clause S5 only (the same assertion). Same onmain. Not fenced: it is rc 1 here exactly as it is rc 1 onmain.probe-FT-3-kit.shclause K7 carries one narrow[ENV]fence, recorded inDECISIONS.mdas "default, unruled" and inDEFERRED.mdDF-KIT-3 with both tails: it fires only when the suite's failing set is exactly{A}. A second failing clause is a hard FAIL, so a real regression cannot hide behind it. The alternative — a kit probe that can never be green while an unrelated repo-wide check is red — would retire it as an oracle for every later change.What is left out (all written down)
[OPERATOR]— the one coordinator switch that installs the symlink and the two executables. No overnight lane mayhome-manager switch/nixos-rebuild. Until it lands,services.tally-uplink.kitnames a path that does not yet exist and the uplink fails legibly ("cannot read the kit …") rather than running with no argv table. After it, adding a flow is a JSON edit plus a commit and never a switch again.[OTHER-REPO]— ruling 4's second step: akit.d/directory (needs the lake's--kit <dir>, tally-ts-sdk#135, then a pin bump here), and per-item stdin foreval(...)entries (the same seam; the evaluator cells stay declared no-ops until G7 rules a mechanical verdict).modules/tally-b.nix,home/seat-feeder.nix,home/dot_local/bin/tally-seat-feeder,tests/tally-b/**,docs/local-ai/tally-b-input.md(D-B owns them);pkgs/cubs-iterationand the campaign's STDIN contract.Nothing was switched, started, armed or deployed; no live state under
~/.local/state/was written; no credential was read.🤖 Generated with Claude Code