Repository navigation
Every register write goes through its lane's own checkout <W>-lanes/<lane>/, where lane-start and lane-end read the lane's row; a LANES_REPO naming W refuses; locks name their holder's container; refresh-workspace fast-forwards W (Amendment 23) - #16
Draft
brettheap wants to merge 63 commits into
Draft
brettheap wants to merge 63 commits into
brettheap wants to merge 63 commits into
Conversation
…T007, T009, T010, T018) docs/triad-lane-tooling-analysis.md: for every lane tool, the installer and its receipt, the hooks, the manuals, the tests, CI and the brett-wip register/inventory/handoff data, what the assembly/spec/code split of opensoft/openRepoTools changes, at file:line on c4864ac, with the plan task each change belongs to and whether it is a patch or a proposal. No shipped command changes: no lane tool reads docs/, openspec/ or specs/ at run time (the adoption plan's cross-leg follow-ups for them are comments, usage text, a URL or the object-key grammar), and every fix the triad does need changes today's behaviour, so each is a proposal with a sketch. The suite's test roots (T009/T010) are lane 1's PR #190 (c139e88); this branch's earlier draft of them was withdrawn before commit, and the document records the overlap, one gap and one measured defect in #190 for lane 1. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit c45a452a14a1c2e46d2008652547e9529e6cc154)
…ader, the sweep's leg and paired-tree discovery, lane-end's leg rows Checkpoint for an account swap, on the coordinator's word. NOTHING HERE HAS BEEN RUN UNDER tests/run.sh: `bash -n` passes for lanes-edit.sh and lane-end, and lane-worktrees parses as Python; one manual run of `lanes-edit.sh project-legs` against openRepoShape's assembly-root project.yaml template (pinned submodule) printed the spec and code legs, exit 0, with no workspace configured; 8 for a directory with no manifest; 64 for a relative path. DONE (T018, plan proposal 2 of docs/triad-lane-tooling-analysis.md §4.4): - lanes-edit.sh: `project_leg_paths_file` (lane-start rung 5's awk, the seven deciding lines verbatim, printing every leg) and `assembly_legs`; the read-only subcommand `project-legs <checkout>` (0 rows, 8 none, 1 manifest unreadable, 64 usage), exempt from the workspace guard; listed in the unknown-subcommand refusal and the usage header. - lane-worktrees sweep: where the lane's dir has a project.yaml naming legs, `Leg` rows for each leg and each initialized nested submodule (porcelain kind `leg`, a LEGS section, a summary suffix), counted only where the leg's HEAD's own Lane: trailer is this lane's and it holds work, or unreadable; every leg's worktree registrations enumerated; `<assembly>/worktrees` scanned as the paired root and claimed by the same HEAD-trailer rule as .claude/worktrees; legs' branches under --branches and probed under --fetch. No project.yaml: no helper call and no new code path's output. - lane-end: `leg` porcelain rows counted (retire) or listed (not counted), "leg checkout(s)" in the gate's tally, no `--yes` offered when legs are all that is counted. REMAINS, in order: (1) tests/test_triad_lane_tooling.py — assembly fixture from the pinned template bytes with legs as real repositories and bare local remotes, fake gh exiting 1; single-repository golden comparisons against c45a452's tools (CI checks out depth 1, so the old bytes need a decision: skip where c45a452 is absent, or captured goldens); the shared-awk identity check against lane-start; (2) README-lanes.md and usage text for (a); (3) commit (b) lane-handoff paired-root poll + recording the leg checkout, lane-reconcile's leg registrations; (4) (c) the daily report's legs; (5) (d) add recording the leg; (6) the shell suite once at the end. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 2aa8a59f16891e329b0696f4f6e31e17374348dd)
…ME-lanes, usage text, the dated §4.4 note; 2aa8a59's code now tested Plan task T018 (opensoft/openRepoTools#186), slice (d2), part (a). This commit carries the guidance for the code 2aa8a59 added: a "A triad: its legs, their worktrees and the paired trees (T018)" section and a `leg` row in the porcelain block of docs/README-lanes.md; a "A TRIAD" paragraph in `lane-worktrees --help`; four lines in lane-end's CLOSE-OUT GATE header (`lane-end --help`); and a dated subsection at the end of §4.4 of docs/triad-lane-tooling-analysis.md, including the note "outputs that change regardless of shape" the coordinator asked for (the unknown-subcommand list and usage header of lanes-edit.sh, and the --help texts), for lane 1's ruling. THE INVARIANT: a checkout with no project.yaml declaring a leg other than itself is asked nothing, so a single repository's sweep rows, gate verdict, exit codes, register lines and inventory rows are unchanged. Only usage text moves for every shape, as listed in the §4.4 note. THE PROOF, run under tests/run.sh with this tree (tools as committed here and in 2aa8a59): tests/run.sh -k "t018 or repo_hygiene or test_lane_worktrees" -> 1 failed, 299 passed, 1006 deselected in 1002.77s The 299 include every test of tests/test_lane_worktrees.py and tests/test_lane_worktrees_report.py, the hygiene module, and the T018 tests: the single-repository golden comparison against c45a452's stored outputs and the assembly cases (template bytes, legs as real repositories behind bare local origins, fake gh exiting 1). The one failure was the hygiene check on host-absolute paths, tripped by the golden fixture's `<ROOT>/home/projects/`; that fixture and its test module are the next commit, WIP, with the fix. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 5fcdf6a17961af1d1382d27e39dbf0aef5368fe5)
… the single-repository golden captured from c45a452's tools Checkpoint for an account swap, on the coordinator's word. The module and its stored fixture, after the fix of the one failure the previous run found; the fixed form has NOT been run under tests/run.sh (the re-run, `tests/run.sh -k "t018 or repo_hygiene"`, was stopped while still waiting before the lock, exit 143, so it never held it). DONE: - The stored-fixture decision: the "before" of the single-repository invariant is tests/fixtures/t018/single-repository.golden, generated ONCE from c45a452's own tools (`git archive c45a452 | tar -x -C <dir>`, then `python3 tests/test_triad_lane_tooling.py --capture <dir>`), because CI checks out at depth 1 where c45a452 is not there to run. It covers add (dry run and act), the sweep's porcelain and table with --branches --include-scratch --include-caches, lane-reconcile, lane-end refusing and passing with the row and last log line, lane-handoff's output, top block and inventory, and the estate report, on a repository with a submodule that is no leg and on one whose project.yaml names no leg but itself. - Normalised for what no two runs share and nothing else: the bench's root and $HOME (both spellings), tree ids, operation ids, stamps, hashes, ages, byte sizes, the order of the inventory's sidecars (their file names are a checksum of the bench's path), and the sweep table's note about this account's unreadable /proc processes (a host fact). The golden test runs on Linux only, where it was captured. - The fix since the run: `$HOME` is normalised to `<HOME>` before the root, so the fixture names no `/home/<user>/` path (the hygiene failure). Captured twice from c45a452 afterwards: byte-identical. The current tools' outputs on the same scenario equal it (compared by the module's own functions, not by pytest). REMAINS: run `tests/run.sh -k "t018 or repo_hygiene"` and record the counts; then (b) lane-handoff's paired-root poll and leg-checkout record with lane-reconcile's leg registrations, (c) the report's legs, (d) add's leg record (all three drafted in the session scratchpad, not applied), and the shell suite once at the end. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 8e2227eaa4fdc0afc3a6404927e381821103264a)
…composed mode; three ways a composed job can pin the PR's code Plan task T010 (opensoft/openRepoTools#186). Queued by the coordinator from the review of #190: the §4.14 sketch (code-PR CI with OPENREPOTOOLS_COMPOSED=1 against an assembly checked out at main) would exit 4, because #190 at dbc6837 refuses any code root the assembly does not pin — Roots.absent() (tests/conftest.py:283) runs _leg_at_its_pin (:259), and pytest_configure (:404-412) raises it as pytest.UsageError — while discover_assembly (:159-172) finds only an assembly that mounts the code root. Dated notes in §4.14 and §7 item 7: the composed job must check out an assembly whose code gitlink points at the PR's own commit, or a throwaway assembly built for the run that pins it, or use #190's standalone mode. Documentation only; no tool or test changes. Marked WIP (untested) because the hygiene suite has not been run over this text: the checkpoint came first. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 96da9afc7a22dece1b2a121850955e09a17cd445)
…ut of its own, with a legs section only where an assembly's leg is checked out Plan task T018 (opensoft/openRepoTools#186), proposal 3 of docs/triad-lane-tooling-analysis.md §4.4. A leg is a submodule, its `.git` a FILE, so `Report.build` never counted one as a canonical checkout and the branches all lane work makes after the split never reached the report. Now, for every canonical checkout whose own project.yaml declares legs (read by `lanes-edit.sh project-legs`), each checked-out leg is read as a checkout of its own: its branches ("Unmerged branches…"), its root `main` ("Root main divergence") and its rescue branches ("Awaiting disposition"), each leg row ending with `the <path> leg of <assembly>`; and a section "Assembly legs with uncommitted or unpublished work" lists the legs holding dirty or untracked paths or a HEAD no origin ref holds, or that could not be read. A manifest that cannot be read is a row of "Records the report could not read". Guidance in docs/README-lanes.md's report table and `lane-worktrees --help`. THE INVARIANT: a canonical checkout with no project.yaml is asked nothing; the legs section is added only where a leg was found, so a report of single repositories is byte for byte what it was. This changes the daily report for the estate's existing triads (IRRS, IRSS, MedxEHR, openDox), which is what proposal 3 is for. THE PROOF: * For 8e2227e and 96da9af, which were WIP: `tests/run.sh -k "t018 or repo_hygiene"` -> 162 passed, 1144 deselected in 134.24s (2026-10-09T13:25Z). * For this commit: run.sh's own in-lock command under the same lock, because the wrapper's workstation-wide census had waited 30 minutes on other projects' pytest runs (#184) - on the coordinator's word, the form lane 1 approved on 2026-10-07: `flock -w 7200 /tmp/openrepotools-pytest.lock env TMPDIR=<run root>/tmp python3 -m pytest tests -q -p no:cacheprovider --basetemp=<run root>/basetemp -k "t018 or repo_hygiene or test_lane_worktrees_report"` -> 184 passed, 1124 deselected in 457.65s. That includes the single-repository golden (the report section of c45a452's stored outputs, unchanged), every test of tests/test_lane_worktrees_report.py, the hygiene module, and two new cases: an assembly whose code leg holds an unpushed branch and whose spec leg is dirty, and an estate with no assembly, which has no legs section. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 30f9dc089cd9706c6b6507cfb4058aa66e895fbe)
… says of a tree made from an assembly itself that its legs are empty there Plan task T018 of opensoft/openRepoTools#186, slice (d2), proposal 1 of docs/triad-lane-tooling-analysis.md §4.4. Where the lane's checkout is an ASSEMBLY - its project.yaml declares a leg other than itself, read only through `lanes-edit.sh project-legs`, lane-start's rung-5 reading - `lane-worktrees add` matches the checkout it makes the tree from against each leg by their common git directory (the leg's own toplevel must be the leg's path, so an uninitialized leg, which git answers as the assembly, never matches). A tree made from the leg, or from any worktree of that leg's repository (a paired <assembly>/worktrees/<feature>/code included), is recorded in the #97 inventory with THE LEG as its checkout, which every inventory reader enumerates; the dry-run and success notes name the leg, and the success note says "with <leg> as its checkout" where the checkout given was a worktree of it. A tree made from the assembly itself is made as before, and a note says a worktree does not check the legs out and names the `--checkout <assembly>/<leg>` that makes a leg's tree. A manifest that cannot be read refuses, exit 2, nothing made (Amendment 7(d)). THE INVARIANT: a lane whose checkout has no project.yaml is asked nothing (no helper call), and a manifest that declares no leg other than `.` answers 8 - so on a single repository `add`'s output, exit code, inventory row and take-back text are byte-for-byte what they were. PROOF: test_t018_single_repository_outputs_are_c45a452s_byte_for_byte compares the current tools against tests/fixtures/t018/single-repository.golden, stored from c45a452's tools, whose "add --dry-run", "add" and "add (manifest, no leg)" sections are add's; it passes unchanged. The shell suite's #163 add block (tests/test_lane_helpers.sh) runs once on the merged tree at the end of this slice. Help text that changes regardless of shape (listed in the §4.4 note for lane 1's ruling, item 2): `lane-worktrees --help`'s "A TRIAD" paragraph gains two sentences on add. Guidance: docs/README-lanes.md "In a triad (T018), a tree of a leg is made from the leg". The §4.4 note's first paragraph now names (a) through (d), with (b) a second writer's branch this one merges. Tests (tests/test_triad_lane_tooling.py, fixtures from openRepoShape's assembly-root template bytes, legs behind bare local remotes, gh exits 1): test_t018_add_records_the_leg_it_made_a_tree_from (dry run, an add from <assembly>/code, an add from a paired worktree recorded under the leg, the sweep then reading that tree through its leg) and test_t018_add_from_the_assembly_itself_says_its_legs_are_empty. COUNTS: `tests/run.sh -k "t018 or repo_hygiene or test_lane_worktrees_report"` on this tree: 186 passed, 1124 deselected in 212.76s (0:03:32), exit 0 (30f9dc0's same selection: 184 passed; the two new (d) tests make 186). run.sh's own census and lock (it waited ~48 minutes behind other runs; no flock fallback was used). The selection holds no supervised-restart test. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 13cb52d057a64690296172a7ec5e92a099914da3)
… each tree recorded under its leg Plan task T018 (opensoft/openRepoTools#186), slice (d2), part (b), first half. Two gaps docs/triad-lane-tooling-analysis.md §4.4 and §4.7 measured in the handoff's writer poll, closed SHAPE-GATED through 2aa8a59's shared reader (`lanes-edit.sh project-legs`, called as it is): (1) THE PAIRED ROOT IS POLLED. Where the lane's checkout is an assembly (its own project.yaml declares a leg other than itself), the handoff polls `<assembly>/worktrees/<feature>` and `<feature>/<each declared leg path>` as it polls `.claude/worktrees`: every directory there with a `.git` of its own (anything else under it is the assembly's working tree, where git answers too). A lane with unpushed work in a paired tree used to be absent from its WRITERS block. (2) EACH TREE IS RECORDED UNDER ITS LEG. A polled tree whose repository's common git directory is a checked-out leg's is filed with that leg (project-legs' third column, the path `add --checkout` records) as its checkout. Anything else keeps the lane's dir, as before. The first handoff used to rewrite the leg that `lane-worktrees add --checkout <assembly>/code` recorded to the assembly. A leg that is NOT checked out is passed over: git answers the assembly there, and the drafts' leg map, which lacked that guard, would have filed every tree of the assembly under a deinitialized leg (measured: with the guard deleted, the deinit case fails). AN UNREADABLE project.yaml is never "no legs" (Amendment 7(d)), and the swap still completes (R-A11-11). The paired root is not polled, the top block's WRITERS section carries a `- NOT POLLED:` line, no sidecar is filed for a tree that is not of the lane's own repository (the checkout `add` recorded is kept), and the lane stays SWAPPING. THE GATE: a checkout with no project.yaml is asked nothing. There is no helper call and no git read, and set-lane-tree gets the same argv. One whose project.yaml names no leg but itself costs one `project-legs` call, which answers 8, and nothing else. `--supervise` and `--restart-status` exit before any changed line. No usage or --help text moves. THE PROOF, under tests/run.sh, lanes-edit.sh at base (lane-reconcile is the next commit): tests/run.sh -k "t018 or supervised_restart or repo_hygiene or openrepotools_command" -rs -> 7 failed, 429 passed, 874 deselected in 3149.31s (load 60-70 on 32 cores) The 429 include tests/test_triad_lane_tooling.py, whose single-repository golden from c45a452 passes UNREGENERATED (tests/fixtures/t018/ untouched), and the new tests/test_triad_lane_tooling_handoff.py: three assembly cases built from openRepoShape's template bytes at the pin (legs as real repositories behind bare local origins, fake gh exiting 1), and a second stored golden, tests/fixtures/t018-handoff/single-repository-decoys.golden. That golden was captured twice from c45a452's own tools, byte-identical, on a single repository laid out to look like a triad: a submodule's worktree under worktrees/<feature>/code, and a tree recorded with the submodule as its checkout. Its scenario has no project.yaml, and then one naming no leg but itself. The handoff output, top block, inventory and lane-reconcile before and after all match it. The 7 failures are --restart/--supervise timing cases of tests/test_supervised_restart_regressions.py (TimeoutExpired, "observer never confirmed readiness"). A/B at load ~40, one run.sh run each: this tree: -k "<the 7>" -> 7 passed, 1303 deselected in 143.69s 96da9af (archive copy, no change): -> 7 passed, 1299 deselected in 150.59s so they are load, not this change. Against 96da9af's lane-handoff the three new assembly cases fail (measured); the base handoff turns the `add`-recorded `<assembly>/code` into `<assembly>`. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 4e39854b8e7f3570ab07931e6b8fa911b8b033c1)
…egistrations and the paired root Plan task T018 (opensoft/openRepoTools#186), slice (d2), part (b), second half: the third gap docs/triad-lane-tooling-analysis.md §4.4 measured. `lanes-edit.sh lane-reconcile` read only the checkout's own worktree registrations and the two lane roots, so a tree of a leg's repository that no sidecar names was invisible to it. SHAPE-GATED, through `assembly_legs` (2aa8a59's shared reader, called as it is): - EVERY CHECKED-OUT LEG'S REGISTRATIONS, read beside the checkout's own. Each worktree git registers in a leg that no sidecar names is a TREE row: `unmanaged`, or `stale-registration` where its directory is gone. The row names the leg ("git registers it in <leg>, the code leg of <assembly>/project.yaml (<repository>)"). The leg's main entry, which git spells as its git directory under `.git/modules/`, is skipped by place. A leg that is not checked out (git answers the assembly there) registers nothing of its own and is passed over. - THE PAIRED ROOT, after the lane roots: `<assembly>/worktrees/<feature>` and `<feature>/<each declared leg path>`, each holding a `.git` of its own that nothing above named (a lost registration, or a clone where a worktree belongs), is `unmanaged`. - PATHS NOBODY READ ARE NOT CLEARED: a project.yaml that cannot be read, a leg whose `git worktree list` fails, or a paired root or feature directory that cannot be listed makes the verdict `indeterminate`. The unreadable manifest is said in the words `lane-worktrees`' sweep refuses with ("the legs of the assembly <dir> could not be read"), because the sweep reads this verdict first and refuses on it. The previous run's one failure, tests/test_triad_lane_tooling.py:616, was exactly that: same exit 2, the reconciliation's wording instead of the sweep's. One fact now has one spelling, and that test passes unchanged. Nothing is adopted, deleted or overwritten, as before. THE GATE: a checkout with no project.yaml is asked nothing. One whose project.yaml names no leg but itself gets `assembly_legs`' 8 and nothing else. Every row, count and verdict on a single repository is what it was. THE PROOF, under tests/run.sh, this exact tree (lanes-edit.sh and the test module last written 2026-10-10T02:43:15Z, the run started 02:43:34Z): tests/run.sh -k "t018 or supervised_restart or lanes_index or repo_hygiene or lane_start_claude_current or openrepotools_command" -rs -> 544 passed, 768 deselected in 1318.62s (0:21:58), on a quiet box The 544 include both T018 modules. Two goldens stored from c45a452's tools pass UNREGENERATED: tests/fixtures/t018/single-repository.golden (byte identical) and tests/fixtures/t018-handoff/single-repository-decoys.golden. The latter covers lane-reconcile before and after a handoff on a repository laid out to look like a triad. Also included are the supervised-restart module, the lanes-index module (the real lane-reconcile), lane-start's module (it calls lane-reconcile), hygiene and the installer module. Two new cases are in tests/test_triad_lane_tooling_handoff.py, on openRepoShape's template bytes at the pin: the legs' registrations and the paired root are reported (a spec tree made away from every root, a paired code worktree, a clone in the paired root, a stale registration; neither leg nor its `.git/modules` entry reported), and the indeterminate verdicts (unreadable manifest, unlistable paired root). Against 96da9af's lanes-edit.sh both fail (measured). The earlier run of this half, before the wording fix: 1 failed, 446 passed, 865 deselected in 2718.97s (the :616 case above). Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 9a5bbe24d02f26a942c07b21748f5a2375faca21)
…off and lane-reconcile see a triad's legs Plan task T018 of opensoft/openRepoTools#186, slice (d2), part (b), as two commits on 96da9af: 4e39854 (lane-handoff polls the paired root and records each tree under its leg) and 9a5bbe2 (lane-reconcile reads each checked-out leg's worktree registrations and the paired root). A plain merge into (a), (c) and (d); a trial merge-tree was clean, and the merged tree's counts are in the commit that adds (b)'s guidance to docs/README-lanes.md. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit e15336104723facad36dde55a77eb1e92f64542d)
…ff and lane-reconcile read in a triad, writer 2's draft checked against 9a5bbe2 Writer 2's paragraph (briefs/t018-b-guidance.md, 2026-10-09T21:53Z) after "`lane-end`'s gate" in the T018 section, each sentence checked against the merged lane-handoff (4e39854) and lane_reconcile (9a5bbe2). Two corrections: the unreadable-manifest verdict is spelled in the sweep's words, which the sweep reads first and refuses on (exit 2); and a project.yaml naming no leg but itself is asked one project-legs call (answering 8), not nothing. Untested: committed for an account swap while the merged tree's scoped run was in flight; that run's counts follow in the next commit. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit b71e547d0f16693e9500fb5065cbfe0293a66c94)
… tested, and spelled as the sweep and lane-reconcile spell it test_t018_add_refuses_where_the_manifest_cannot_be_read holds 13cb52d's README claim (mode-000 project.yaml: exit 2, no tree, no inventory row). The refusal reads "the legs of the assembly <dir> could not be read", the sweep's and 9a5bbe2's words, in place of "the legs of the lane's assembly <dir>". Reached only where <lane dir>/project.yaml is there and project-legs fails on it; a checkout with no project.yaml is asked nothing. Untested: committed for an account swap while the merged tree's scoped run was in flight; that run's counts follow in the next commit. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 9991d697849b5aee070ffe1ea3c451ef0314ef05)
…ch on the tree the PR will be judged on origin/main at f87fd5a carries #188's openRepoShape pin (7f84ca4), #189, #192 and #190's explicit test roots. A trial merge was clean; no file is changed on both sides. The merged tree's counts are in the commits that follow. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 8cb13680f9a7dd441bcdca8c49bf4dcd7d616bc0)
… not yet run; left: run it, the commit_push fix, both RECOVERY texts, the reset warning, manual lines, PR done: tests/test_commit_push_published_edit_194.py, the proving test for opensoft/openRepoTools#194, written first and not yet run (the run queued behind another lane's full suite on the shared pytest lock and was stopped for the swap). Six cases: the issue's own sequence end to end (exit 3, the peer commits its file, the printed re-run must publish); a clean checkout with nothing ahead stays a no-op with no network call; a checkout behind origin stays a no-op; a publish past a peer's dirty file keeps the dirty-elsewhere guard; the conflict RECOVERY warns that reset --hard drops a peer's unpushed commit; refuse_dirty_checkout names the act to re-run. left: run the test on unmodified lanes-edit.sh to see it fail; the fix in commit_push (when nothing is staged and origin/<branch>..HEAD is non-empty, go on to the push loop); the two RECOVERY texts (commit_push's dirty-elsewhere exit 3, refuse_dirty_checkout); the reset --hard warning in the conflict RECOVERY; the matching manual lines in docs/README-lanes.md (about :95-97 and :198-211); two test nits (the pull net-call check should not depend on the -C path spelling; the warning/drops line order is provisional until the text is final); then green, push, PR, CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit c9ffdce04615a196ddea7ce8b154efa52c97adf7)
…inds ahead of origin, so the RECOVERY's re-run of `commit` pushes the edit it stranded (#194) `commit_push` returned at "nothing staged ... no commit made" before its push loop. An edit the loop could not get out (a peer's uncommitted file blocking the rebase, six rejected pushes) is already a local commit, so the RECOVERY's re-run of `lanes-edit.sh commit` staged nothing, exited 0 and pushed nothing. Now, with nothing staged, it reads origin/<branch>..HEAD: empty is today's no-op with no network call; non-empty takes the same push loop (the caller's lock, the dirty-elsewhere guard, the retries) without a commit of its own. The RECOVERY texts say what works: the dirty-elsewhere exit 3 says the re-run pushes the local commits; refuse_dirty_checkout separates what the authors commit from the refused act that is re-run; the conflict RECOVERY lists every unpublished commit by content (`cherry -v`) and warns that `reset --hard` drops a peer's unpushed commit along with the reader's own. docs/README-lanes.md says the same in both places. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 9a29bbba1d636bb0e1a5dd1f439e65735e7a58fd)
…em 11, for lane 1's ruling), and §6 says what the branch now ships docs/README-lanes.md, T018 section: a parked or resumed paired tree carries no Lane: trailer (park.sh puts the lane in the WIP subject only; resume soft-resets those commits), so the sweep and lane-end's gate count it for nobody unless the lane's inventory names it. The paragraph says what to read before lane-end in an assembly, and how to record such a tree with set-lane-tree so the gate counts it. docs/triad-lane-tooling-analysis.md: a dated §6 note on what the branch ships beyond its first commit, and §7 item 11, the choice between reading park's WIP subject and giving park's commit a trailer, plus whether resume records the tree it rebuilds. Written by the previous writer on 2026-10-11 and left uncommitted; checked here against HEAD 8cb1368, whose lane-worktrees, lane-end and lane-handoff are byte-identical to 9991d69's. Every line reference holds: lane-worktrees :2424-2431, :4473-4494 and :2124, lane-end :2122-2127, the FOREIGN reason text (lane-worktrees:3037-3039), and workBenches ffd5516 park.sh :226 and :860 and resume.sh :221-231 and :746. Three corrections in item 11: the source is review 5481327496 on #196 (a pull-request review, not an issue comment); its finding 2 was derived from code and not run, so "measured" is now "derived", the document's own word for that; and the inventory names a paired tree through lanes-edit.sh set-lane-tree, not through lane-worktrees add, which makes its trees under the lane's own root only. Docs only. No test run for this commit; the branch's one verification run follows the M1(a) and should-fix commits. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 941330ad73af6cce16f8204873b7a25d1d362104)
…root by the sweep's rule, so one lane's handoff never takes another lane's feature trees; and S1, S5, N1-N5
M1 (a). In an assembly, lane-handoff polled and recorded every .git-bearing
tree under <assembly>/worktrees as the running lane's writer. The paired root
is per feature, not per lane, so after one handoff another lane's paired
work was this lane's inventory: lane-end refused on it, sweep --yes could
rescue and remove it, and the next session was told to relaunch writers into
it. Now a paired tree is polled and recorded only where this lane's inventory
names it (lanes-edit.sh lane-trees, read once), its HEAD's own last Lane:
trailer is this lane's on a commit origin's default branch lacks (a bash
mirror of lane-worktrees' own_trailers/head_lane_trailer; a clone is never
claimed by trailer, as the sweep never adopts one), or the --writers-file
names it. Every other checkout there is one "- PAIRED ROOT, not this lane's:
<path> — branch …" line under its own heading in the top block, outside
WRITERS, not counted and not recorded. Pending lane 1's ruling on #186
between (a) and (b).
S1. A paired root, feature directory or leg directory that cannot be listed
or searched was skipped in silence by the handoff. It is now a "- NOT
POLLED" line, a stderr note and a lane left SWAPPING, as is an inventory
that cannot be read. lane-reconcile (indeterminate) and the sweep's _paired
(exit 2) gain the one case they also passed over: a leg's directory under a
feature that cannot be searched, and in the sweep a root or feature
directory that can be listed but not searched.
N1. lane-reconcile passes the feature directory to awk through ENVIRON, not
-v. N3. The handoff's unreadable-manifest words are the sweep's and
lane-reconcile's ("the legs of the assembly <dir> could not be read").
N4. add's hint names every leg's --checkout, not legs[-1]. N5. The handoff
keeps a recorded checkout of the tree's own repository (a dependency nested
in a leg) instead of re-recording the tree under the assembly.
Tests. N2: the assembly cases wear conftest's NEEDS_UPSTREAM (composed mode
fails rather than skips), and the no-assembly report test needs no marker.
New: the two-lane M1 regression; S1's three unlistable directories (handoff
and reconcile); N5; S5(b) the sweep's absent row and add's toplevel guard on
a deinitialized leg; S5(c) an unreadable leg (counted, lane-end refuses),
the submodule-status refusal (unshadowed by reconcile) and _paired's own
refusals called directly; and the coordinator's quoted-manifest case
(openRepoShape v1.0.0, #395 writes path: "{{SPEC_PATH}}"): project-legs
already strips one pair of " or ' quotes, and the test holds it for both
quote styles. Two existing handoff tests change for M1 (a): the no-commit
paired spec tree is named by a writers file, and the paired code tree of the
deinitialized-leg case carries the lane's trailer.
A checkout with no project.yaml is untouched: every new path runs only where
project-legs answered legs (lane-handoff, lane-reconcile, the sweep) or the
manifest declares them (add).
Untested in this commit: the branch's one verification run follows on the
final tree, and its counts are in the commit that ends this step.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 4d4957db6e093cb17faa5a61298d4e4d1c4ccf92)
…ipt, so `log` reaches refuse_dirty_checkout instead of the Amendment 7(b) session refusal The first locked run showed case 6 failing on both trees for that reason, before the checkout was ever read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 352c1b6f094e1c441cf2fd2ec0798e1f74bf5548)
…hanged; the analysis's false statements about the branch are dated and corrected (S4)
docs/README-lanes.md, T018 section:
- M1 (a): "each lane's handoff records every paired tree" is gone. The
handoff polls and records a paired tree only where the lane's inventory
names it, its HEAD's own Lane: trailer is the lane's on a commit origin's
default branch lacks, or the writers file names it; every other one is a
"PAIRED ROOT, not this lane's" line outside WRITERS, not counted or
recorded. The sweep's bullet says the handoff no longer widens its
paired-root rule, and the parked-tree paragraph says a handoff does not
record a parked tree for the lane.
- S1: the handoff's NOT POLLED, stderr note and SWAPPING for an unlistable
paired root, feature directory or leg directory, or an unreadable
inventory; reconcile's indeterminate and the sweep's refusal now also
cover a leg directory that cannot be searched.
- S2: "no --yes acts on a leg" is now "on a leg checkout": under --branches
a leg's branches are rows like the checkout's own, and --yes deletes a
merged lane branch there as it does in the checkout, bundling first.
- N5: a tree recorded under a checkout of its own repository (a dependency
nested in a leg) keeps that checkout through a handoff.
docs/triad-lane-tooling-analysis.md (S4): dated 2026-10-13 notes at §0 item 1
and §6. "This branch carries no test change of its own" was true of its first
commit only; it now carries two test modules and two goldens and still
changes none of the suite's roots. §6 records M1 (a) and S1, and §7 item 11
says the handoff no longer closes the parked-tree gap by itself.
Verification, one run through tests/run.sh on this commit's tree, 1511f94
(4d4957d plus these docs; main f87fd5a merged at 8cb1368), 2026-10-13
19:10-19:40Z. Every module but the four below was --ignore'd, with
-k "triad_lane_tooling or repo_hygiene or test_the_lane_helper_suite_passes":
181 passed, 1 failed, 1 deselected in 1818.96s.
- tests/test_triad_lane_tooling.py, tests/test_triad_lane_tooling_handoff.py
(both goldens unregenerated) and tests/test_repo_hygiene.py: all passed.
- The shell suite, tests/test_lane_helpers_suite.py::
test_the_lane_helper_suite_passes: 4598 passed, 2 failed. Both are
lane-start row-stamp assertions, "…with clause (c)'s directory and
window in the tail" and "the row's stamp states clause (c)'s two facts
… in its TAIL". lane-start's cut_to_line caps the stamp at 230 characters
(lane-start:3502-3510). Under run.sh the suite's $HOME is
~/.local/state/openRepoTools/tmp/<UTC>-<pid>/tmp/tmp.XXXXXXXXXX/home, and
the stamp then reaches 231 characters before its window value, so the
tail is cut ("…/home/projects/repoA; ..."). With a /tmp-length home it
is 165. This branch changes none of lane-start, tests/test_lane_helpers.sh,
tests/test_lane_helpers_suite.py or tests/run.sh (git diff f87fd5a is
empty for all four), so the two failures do not come from it.
An earlier attempt (run1) ran nothing: zsh passed the --ignore list as one
argument and pytest exited 4.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6658396e45ddeb125f21e48dbf2ce33126388224)
… staged finds ahead, and only lane tooling's own; the conflict RECOVERY lists HEAD and stops on a moved checkout
The review measured the blocker: with another lane's unpushed commit in the
shared checkout deleting `scripts/link-estates`, a no-op `replace-in-row`
published it and origin lost the file. Every write with nothing staged took
the push loop; now only `commit` does (CP_PUBLISH_AHEAD, set in its arm
alone and never read from the environment), and every other no-op write is
today's exit 0 with no network call.
`commit`'s publish reads every queued commit first and refuses with 2,
naming the commit and the path, when one deletes a file or touches a path
outside lanes/ and handoffs/. A failed read refuses the same way. Nothing is
pushed.
The conflict RECOVERY lists `cherry -v origin/<branch> HEAD`, which is what
the reset acts on, and adds a STOP for a checkout that moved after the
abort. It also says a LANES(concurrent@…) or LANES(pre-existing@…) capture
names no lane and is not the reader's. The dirty-elsewhere RECOVERY's claim
is qualified ("if lanes/LANES.md is clean") and names the refusal. The
publish list counts what it leaves out. replace_line's contract comment and
the manual say the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 836e1e1289341fa52678a822817b0faaf08fd1c9)
…iour before its wording, so a regression to publishing fails on the push, not on a note Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit fab2cbc57274522e174d9af079f3ed550bbe27a9)
…ch on the tree the PR will be judged on origin/main at aaa7bc8 carries #191's install from the code an adopted assembly pins (a74c7fc) and #227's Linux-only CI. A trial merge (git merge-tree --write-tree) was clean; no file is changed on both sides: main changed .github/workflows/tests.yml, AGENTS.md, README.md, openRepoTools and four test modules, none of which this branch touches. The merged tree's counts are in the commits that follow. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 7757eaab2b03612eac085755cfbb664fb53f0bf2)
…not read as the sweep does - a TREE row `unreadable` that requires recovery - instead of passing over it in silence
lanes-edit.sh, lane_reconcile, the legs' registrations: a declared leg
whose git toplevel is not the leg itself was skipped whether or not a
.git stood in it, so a leg with a .git of its own that git cannot read
(a gitfile naming no repository, or a .git where git answers the
assembly) said nothing, while the sweep's `leg` row for it is
`unreadable` and counted (exit 3, lane-end refuses). It now takes the
sweep's rule (probe_leg: toplevel not the leg, and a .git stands there)
and the sweep's word: one TREE row `unreadable`, in the sweep's words
("git could not be read through it (it has a .git of its own and git
could not read it: <git's answer>)"), counted under "require recovery",
so lane-start's recovery read counts it too. A leg with no .git (not
checked out) is passed over as before, and a path a sidecar already
names is not reported twice.
The verdict is not changed: the sweep counts that leg and goes on, and
an `indeterminate` here is what the sweep refuses on (exit 2) before it
reads the legs, which would hide its own count and break
test_t018_a_leg_git_cannot_read_is_counted_and_holds_lane_end.
tests/test_triad_lane_tooling_handoff.py: a triad whose spec leg's .git
is a gitfile naming no repository, then an empty .git directory: one
`unreadable` row naming the leg, 1 require recovery, the verdict
ungraceful-stop, and the sweep still exit 3 with the leg `unreadable`
and counted; readable again, and deinitialized, the leg names nothing.
docs/README-lanes.md (T018 section): one sentence for the row.
A checkout with no project.yaml is untouched: the block runs only where
project-legs answered legs (lrc_lgrc 0).
lane-handoff's own leg loop (lane-handoff:2093-2095) skips such a leg
in the same way; it is not changed here.
Untested: the one verification run (run3, on this tree 2f23887) waited in
the shared pytest lock queue from 20:50Z and was stopped at 21:21Z, before
it took the lock, for the account swap; no count was taken.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9f23a6b098212efa2c839624f98ec68100d6e96f)
…readable, as the sweep and lane-reconcile do; verification not run
lane-handoff, the legs' common directories (was lines 2093-2095): a
declared leg whose git toplevel is not the leg itself was skipped whether
or not a .git stood in it. So a leg with a .git of its own that git
cannot read (a gitfile naming no repository, or a .git where git answers
the assembly) said nothing, while the sweep's `leg` row for it is
`unreadable` and counted and lane-reconcile's TREE row (9f23a6b) is
`unreadable` under "require recovery". Worse, a tree `lane-worktrees add
--checkout <leg>` made from that leg was re-recorded under the ASSEMBLY
at the handoff, over the checkout `add` recorded.
It now takes the sweep's rule (probe_leg: toplevel not the leg, and a
.git stands there) and the sweep's words: one `- UNREADABLE LEG:` line in
the top block's WRITERS section ("`<leg>` is unreadable, the <decl> leg
of <dir>/project.yaml (<repo>): git could not be read through it (it has
a .git of its own and git could not read it: <git's answer>)"), a note on
stderr, and a count (`legs: N of the assembly's legs under <dir>
unreadable`). As lane-reconcile keeps its verdict for it, the lifecycle is
not held SWAPPING for the leg itself: the poll looked everywhere it
looks. writer_checkout files no guessed checkout while a leg is
unreadable: a tree of no readable leg's repository, not of the
assembly's own and recorded under no checkout of its own repository is
named and NOT recorded, so the leg `add` recorded stays, and the lane
stays SWAPPING for that tree (the existing "no checkout can be vouched
for" rule). A leg with no .git (not checked out) is passed over as
before. A checkout with no project.yaml, or one naming no leg but
itself, never runs the leg loop: single-repository output is unchanged.
docs/README-lanes.md: the handoff paragraph says so.
tests/test_triad_lane_tooling_handoff.py:
test_t018_the_handoff_reports_a_leg_git_cannot_read_as_the_sweep_counts_it,
beside the reconcile one: the spec leg's .git a gitfile naming no
repository, then an empty .git directory: one UNREADABLE LEG line, the
note and the count, the lane SWAPPED, the reconciliation `unreadable`, 1
require recovery and `resumable`; a tree `add` made from the spec leg
keeps the spec leg as its checkout, named and not recorded, the lane
SWAPPING; readable again, nothing named and SWAPPED. The deinitialized
leg test now also holds that a leg with no .git is never `unreadable`.
VERIFICATION NOT RUN. The one run of the T018 selection (verify.sh, run4.log,
head 9f23a6b, index tree 0b691c8) waited for the shared pytest lock
behind other lanes' runs from 21:59:32Z and ended exit=143 (a SIGTERM
this writer did not send) at 22:26:46Z before any test ran; an account
swap was called at 22:20:05Z. So this commit and 9f23a6b
are both UNTESTED. Before this commit: bash -n lane-handoff is clean,
the test file parses. The next run on this tree verifies both.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit bf2a220ae804aa963c301a5d0a8791f7f742a438)
…hes, refuses merges, type changes and HEAD off the branch, fails closed on every read, and pushes exactly the commit it read The check found three ordinary checkout states where `commit` still published a deletion of scripts/link-estates, and a race: - a merge's own changes (`log --name-status` prints none); - HEAD on another branch (the fence read HEAD, the push sent the branch); - a local branch named origin/main shadowing the tracking ref; - a commit made between the fence and the push. cp_publish_fence now reads refs/remotes/origin/<b>..refs/heads/<b>, spelled in full, and requires HEAD to be refs/heads/<b>. Its reads are plumbing (rev-list --parents, diff-tree --stdin --always --raw) under --no-replace-objects. It refuses merge and root commits, and passes only an added or modified regular file under the register's derived directory (LANES_PREFIX), its log and alias paths, or handoffs/. Every read and every classification is status-checked, and diff-tree must return one header per commit it was given, because it skips an unreadable commit with exit 0. The push sends <tip the fence read>:refs/heads/<b>. After each pull --rebase, CP_AFTER_REBASE re-reads the queue and re-pins the push. Ordinary writes push the branch as before. Lane renames (a log deleted and re-added) stay refused, by design, and the message says what carries them. Both exit-code tables list the new refusal; the manual describes the rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit f34c993e317ccb0c7e5af9a5b3296d8635c8cacb)
Brings in #195 (aa14de3), #196 (983d8c7) and #212 (2732d59) on top of aaa7bc8, so the T018 selection runs on the tree the PR will be judged on. The only file both sides change is lanes-edit.sh: main's #212 edits cleanup's temporaries and rename-lane's handoff stamp, the branch adds project-legs and lane-reconcile's leg and paired-root reads. git merged it with no conflict, the hunks are disjoint, and bash -n passes on the result. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit cf7db4a77c5bf1fe6644c2caae351d59cebecd63)
…leg's, names a tree git cannot read, and says what it withheld (review of bf2a220, S1, S2, N1-N4)
S1. lane-handoff, writer_checkout: while a leg was unreadable, every
tree not of the assembly's own repository was withheld, a worktree of an
unrelated repository in the lane's own root included, so the lane stayed
SWAPPING on every handoff and those records went stale. Now a tree is
withheld only where it could be of that leg's repository: its common git
directory cannot be read, or lies physically inside the assembly or its
git directory (.git/modules/<leg>, a leg's own .git) without being the
assembly's own, or the inventory records it under an unreadable leg. Any
other tree keeps the lane's dir, as when every leg reads.
Test: test_t018_an_unreadable_leg_withholds_no_tree_of_another_repository
(a worktree of a third repository, recorded before and made after the
leg broke; and one a record names under the unreadable leg).
S2. lane-handoff, poll_writer: `git rev-parse --git-dir || return 0`
dropped any tree whose .git git cannot read, so a damaged
.git/modules/<leg> took every tree of that leg out of WRITERS and the
count while the lane went SWAPPED. In an assembly (legs_state set) such
a tree is now listed and counted, UNREADABLE and (NOT recorded), added to
lc_inventory_missed, and named on stderr. A checkout with no project.yaml
naming a leg passes over it as before, so the goldens do not move.
Test: test_t018_a_tree_git_cannot_read_in_an_assembly_is_named_and_holds_the_lane_swapping
(modules/spec deleted after add --checkout spec, with a paired spec tree
the inventory names; and a single repository's tree, passed over).
N1. The UNREADABLE LEG line's clause now depends on whether any tree was
withheld ("the N tree(s) this handoff could not place are named above,
each marked (NOT recorded)", or "every tree this handoff polled could
still be placed, so none is withheld for it"), and a withheld tree's
WRITERS line is marked (NOT recorded). The leg rows are kept as
<leg> TAB <why> and written by top_block after the poll.
N2. lane-handoff's UNREADABLE LEG line and note, and lanes-edit.sh's
lane-reconcile TREE row for an unreadable leg, say "before relaunching
a writer onto a tree of its repository": a leg is the assembly's.
N3. docs/README-lanes.md says the leg count is stderr's `legs: N ...`
line, never WRITERS' (N found), and describes S1, S2 and N1.
N4. Test: test_t018_a_leg_tree_add_made_is_withheld_where_the_legs_git_is_an_empty_directory
(the empty .git directory, where git answers the assembly in the leg,
with a tree add made from it).
The existing handoff and reconcile unreadable-leg tests now also assert
N1 and N2.
bash -n passes on lane-handoff and lanes-edit.sh; the added lines use no
declare -A, ${x,,}, mapfile, readlink -f, <<< or [[.
Authoring smoke check only (the one verification follows on the final
head): the three new tests, the two changed ones and the handoff golden
test, each called once outside pytest with LANES_* and TMUX* unset, all
passed; the three new tests each failed against cf7db4a's tools, on the
withheld third-repository tree, the writer count and the (NOT recorded)
marker respectively.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5d2c1c8dc040664ee8af5aaffac656ecec860ab6)
…sis's §7 (items 3, 11 and a new 12 for M1)
docs/triad-lane-tooling-analysis.md §7: a dated head note naming
comment 6240800023 on #186 (posted 2026-10-17T20:48Z), and each ruling
at its item:
- item 3: proposal 1 (the guidance) goes live now; proposals 2 and 3 go
live with the T018 PR, on the condition of the read-only before/after
measurement over the triads (IRRS, IRSS, MedxEHR, MedxGlass, openDox,
openXdox, openWallet and the codexFactory copies), main's tools against
T018's, and any verdict that flips to a refusal told to its owning
lane before the landing;
- item 11: (ii), park's WIP commit carries a `Lane: <lane>` trailer, and
resume records the tree it rebuilds with `lanes-edit.sh set-lane-tree
<lane> <path> --checkout <assembly>/<leg>`; both workBenches changes,
one workBenches issue linked from the T018 PR ("workBenches issue: to
be linked"); the PR does not wait for it;
- item 12, new: the pre-PR review's M1, (a) as implemented in 4d4957d,
with the two-lane regression test
(test_t018_a_handoff_takes_no_other_lanes_paired_tree).
§6's 2026-10-13 note now says the M1 ruling was made, and where.
docs/README-lanes.md: the two T018 sentences that called these rulings
pending now say they were made, and the parked-tree check before
lane-end stands until the workBenches changes land.
Docs only.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9a8f17549b4982d90951203b8a9d78ac0b147029)
…ing pushed", which test_the_exit_3_documentation_agrees_with_the_die_message_it_describes reserves Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 5b68127fdde730fb49290a28f525f04e47dcd0ac)
Brings in 27b5c89 (lane-start's #138 fences) and 83d2690 (#199, the handoff skill's commit block) on top of 2732d59. Neither touches a file this branch changes: the two sides' file lists do not intersect, and git merged with no conflict. The one cross-check that matters is test_t018_the_legs_reader_is_lane_starts_rung_5_line_for_line, which holds lanes-edit.sh's legs reader equal to lane-start's rung-5 lines: those seven lines are byte-identical at 2732d59 and 83d2690. bash -n passes on lane-start, lanes-edit.sh and lane-handoff. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 107958c7a6def94d994aa37e1c288942e2ccc534)
…(#198) (cherry picked from commit ef16bbe0ae5b00094857aa747ed4bb3d065e54b2)
…nes/<lane>/, made on first use; a LANES_REPO naming W refuses; the locks name their holder's container and steal by claim; refresh-workspace fast-forwards W (Amendment 23 S1, S2, S5) (#198) Act 2's slice 1a, written and not yet fully tested (a checkpoint: lane 2's usage window, the coordinator's word). What changed, in lanes-edit.sh: - S2 routing. After the existing gates, the dispatcher works out the lane a write is attributed to (write_lane_of). Under default resolution the file re-runs itself by exec, same pid, with LANES_REPO/DIR/FILE at the lane's checkout and two internal markers (LANES__CHECKOUT_LANE, LANES__PID). Inherited markers are dropped. An explicit LANES_REPO is honoured in place, and LANES_DIR/LANES_FILE now default from it. Reads, dry runs and publish-handoff are not routed. A write that names no lane refuses (2). - S1. lane_checkout_path/_verify/_ensure clone W's origin with --reference W --single-branch under a creation lock, through a .partial, then mv. Anything at the path that is not a clone of the register is refused (1), and nothing there is deleted or repaired. Also covered: the renamed-lane move, the old name reported, the row's spelling, W's repo-local identity copied, and the clone's own ten-minute budget. - S5. A LANES_REPO whose real path is W refuses every write (2). A per-profile clone beside W is honoured with a warning naming S1's path. LANES_DIR/LANES_FILE outside the checkout refuse (2). - The locks. The pid file is "<pid> <container> <host>". A live holder in this container is never aged out; another container's holder is judged by the lock's age, which git_net refreshes before every network call. The steal is a rename-claim that re-reads what it claimed. A lock is released only while it is still this process's. A lock taken before an exec is adopted only with the marker. The lifecycle, restart and tree sidecars and publish-handoff share one workstation lock, <W>-lanes/.lane-state.lock (ort186 Q3), and take no lock in W. - New verbs: lane-checkout <lane> [--create] and refresh-workspace (fetch and merge --ff-only only; every block is reported and skipped, exit 2). Also the verb list, the exit-code table, the usage lines and the env doc. Tests: - New modules: test_a23_lane_checkout.py, test_a23_write_routing.py, test_a23_lock.py and test_a23_refresh_workspace.py. On this tree: 22, 21, 15 and 10 passed. - Existing fixtures that asserted writes in W now name their old W by the explicit seam, with W a reader copy (decisions D21, D24-D26 in the slice's progress record): lane-start's Sandbox, #214's Box, row-stamp's Sandbox, T018's Bench (a gitfile view, with W's path normalised), the two supervised-restart mutex cases (the sidecar lock), and the shell suite's head (it clears every inherited LANES_* and TMUX*, then sets LANES_REPO=$WIP; ort186 Q9) and its set-lane-tree wait. WHY WIP — what has not run on this commit: - The after-run of the existing modules on this tree stopped at the checkpoint, after the four new modules. In a scratch export with the same files, these passed: commit_push_194 25/25, lane_start_claude_current 34/34, row_stamp 3/3, triad 5 passed 16 skipped and 1 passed 13 skipped, deferred_97 9/9, handoff_skill 24/24, rename_158 20/20, hardening_138 20/20, pathspec 3/3, lane_worktrees 118/118, lane_worktrees_report 20/20, openrepotools_command 97/97, wip_init 63 skipped, and hygiene 189 and guard_launch_mode 13 on this tree. - Not yet run against the new helper: test_supervised_restart_regressions, test_lanes_index, the install modules and test_claude_current. - The one-line mutants are not run yet. - The shell suite cannot be run in part (the repository gives no way), so it has not run. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit d0010a299d1773d9a941be11fe8ef480ff533d79)
No content change: this commit's tree is ea4db0d's. Lane openRepoTools-1, the lander, pushed it on lane openRepoTools-3's word in #186 6250521287, so the landing gate's Linux run tests the merge with current main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 5dd0df07b6bcf8fd79f1dddc4c05b54d06da8e10)
…, #249) into act 2's slice 1a (#198) (cherry picked from commit 465df5343483f945924c27b33559a961a28c2c4f)
(cherry picked from commit 129e201586caa64442dde1c8d803563d83254e3a)
…t, where its writes now go, so a new lane's second start and its end stop reading W's copy that no write updates (Amendment 23 S1, S2; act 2 slice 1a) (#198)
The bug: with slice 1a's routing, every write under the default resolution
goes through the lane's own checkout, <W>-lanes/<lane>/, and W moves only by
`refresh-workspace`. Both commands still read the lane's row from W's
working tree. A new lane's first start added its row through the checkout,
so the second start found a row on origin and none "in this checkout" and
refused at lane-start:1591 ("ALREADY has a row … refusing to add a second
row"). `lane-end` read the same W and said "the register has no row for
lane … nothing to end". Coordinator's ruling: add the minimal read fix to 1a.
The fix, the same block in both (`lane_register_follow`), once the lane's
canonical name is known and before its row is read:
- Under the default resolution (neither LANES_FILE nor LANES_REPO set), ask
`lanes-edit.sh lane-checkout <lane> --create` and read that checkout's
lanes/LANES.md. The checkout is made here if absent, as the first write
would make it.
- `--dry-run` creates nothing, and reads W where there is no checkout yet.
- A helper that predates the verb writes W itself, so W is read.
- A refusal, a failed clone or the creation lock stops the command before
it launches, renames or writes.
- Only the read moves. LANES_REPO, and the handoff lane-start publishes and
commits, stay W's until slices 2a-3.
Also one line in each: a caller's LANES_REPO without LANES_FILE now reads
$LANES_REPO/lanes/LANES.md. That is where the helper writes since this
slice; it no longer reads W's register.
Tests: tests/test_a23_lane_start_read.py drives the real commands with no
seam. Four cases, each killed by its one-line mutant:
- a new lane's second start;
- lane-end after it;
- a dry run makes no checkout;
- LANES_REPO alone is the register read.
Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 51efdb39d6945477f29a0a1648fb4fd9a3f2adb9)
…second checkout by LANES_REPO and LANES_FILE, as the suite's other 24 cases do under Amendment 23's explicit seam (#198) Main's fe58808 merge brought a new helper, r67(), that pointed the commands at a second clone through LANES_WORKSPACE_ROOT alone. Since this slice, the suite exports LANES_REPO and LANES_FILE at $WIP for every case (ort186 Q9's seam). An explicit LANES_REPO is honoured in place and W no longer chooses the checkout a command runs in, so r67's reads and its refused `claim --force` would have run in $WIP, not the #67 clone. r67 now names the clone the same way the other 24 cases were converted (LANES_REPO=<clone> LANES_FILE=<clone>/lanes/LANES.md). Its assertions are unchanged. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 59b39df22e6641cb8b5e5cbfd40f86e6a104b0ed)
(cherry picked from commit eff9107b7469f4483cb366b5477909c1387d149c)
… act 2's slice 1a (#198) (cherry picked from commit fcf7f48578e57d7287e5496065ef21ee6bd696d5)
…tree is #214's final head's (bfbe9d2a), merged above (#198) (cherry picked from commit 0d4ade89409ab4fe460158b95c0c0446263729b3)
… seam (LANES_REPO) and W becomes a view of it, so the indexer cases read the writes they make under Amendment 23's routing (#198) The routed writes went to <W>-lanes/<lane>/, while lanes-index sync reads W's own origin/main. Four cases went red, against the base's two environmental failures. W is now a gitfile view of the writing clone outside the projects root, as in T018's bench. With it: 70 passed and 2 failed, which are the base's own two (the normaliser and the basetemp's uuid). Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit fda7808d64ee31520fa68b88314627916df03d0d)
(cherry picked from commit 6d22d0b53f077d2825b2e56e6f987ea58132b4cd)
(cherry picked from commit eb9eb7495e4e6c523869461580fbaa673b6588c2)
…ranch less this slice's four commits is its tree, aedf23d8 (#198) (cherry picked from commit 9d26089ad28760b09f09717d09ae898ed2a9c39d)
…#271) into act 2's slice 1a (#198) One conflict, in lanes-edit.sh's lane_state_follow, at the check of a snapshot this helper cannot read: this branch releases the workstation's sidecar lock (sidecar_release); main released W's mutex where the follow took it (lsf_own) and gave a new note naming $LSS_WHY. Resolved as the PR says: keep sidecar_release, take main's note. No line of main's that merged on its own uses lsf_own or release_lock inside lane_state_follow, and main's lanes-edit.sh changes since 55aae16 add no lock call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 18f5155163cad2f00302d4529755af4110ae12e9)
…1b, W follows a routed push, a W that is a lane checkout refuses, and a vanished lock is never made again (#198) MUST 2: Amendment 23's routing is behind LANES_ROUTE_WRITES=1. Without it every write goes through LANES_REPO (W by default) and every lock is that checkout's, as on main: the sidecar acts and lane_state_follow take $LOCK, and lane-start/lane-end read W. The two tests this slice had moved onto the sidecar lock are main's again. MUST 1: under the opt-in, once a push from a checkout that is not W has landed, cleanup fetches W's origin/main (git_net, gc and maintenance off) before the index nudge, so the prompt guard's offline read sees it. A failed fetch is a note. MUST 3: a W inside a `*-lanes/` directory, or the same checkout LANES_REPO names under LANES_WORKSPACE_ROOT, refuses (2) and names LANES_WORKSPACE_ROOT: lane-checkout and refresh-workspace always, writes and sidecar acts under the opt-in. Nothing is cloned or locked inside a lane checkout. MUST 4: git_net's heartbeat is `touch -c`, and the creation lock too, which a heartbeat now refreshes every minute through the clone (NIT 5). MUST 5: test_register_scan_hardening_138.py writes through `wip` by LANES_REPO, with W a clone of its own. SHOULD 1: append-line attribution reads the published register first, and a routed arm falls back to the lane it was routed by. SHOULD 2: lane-start/lane-end note a checkout that cannot be made (2, 4) and read W. SHOULD 3: S5 compares the checkout's root and git common directory. SHOULD 4: a routed write refuses while W's register holds an uncommitted edit. SHOULD 5: lane-worktrees reports `<W>-lanes/<lane>` as a register checkout, never FOREIGN. SHOULD 6: lane-start's carry note says no write carries a commit made in W under the opt-in. NITs: 1 request-handoff's `--` ends the options in the router too; 2 the sidecar lock's wording (taken alone, never inside the checkout lock); 3 mutex_release compares container and host as well as the pid; 7 lane-end's own "Nothing was written"; 8 LANES_NO_GIT=1 makes no clone; 9 an explicit LANES_REPO still needs a lane. Sonar: the four copies of the test Box are one module, tests/a23_box.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 874e0204192902554ffece916632a613fcff0627)
…ates, the lane-checkout home check, the creation lock's `touch -c` and heartbeat, lane-end's notes, S5's git-directory clause and the alias name check (#198) The mutation run over 874e020 let 13 mutants of fix round 1 survive its own tests. These 12 cases (15 test items) kill 12 of them; the 13th, N26 (S5's `--show-toplevel` compare), is equivalent, because a path whose toplevel is W also has W's git-common-dir, so the common-dir clause already refuses all it refuses, and no test can tell the two apart. Each case is appended to its module unchanged; no helper, fixture or other test is touched. tests/test_a23_lock.py - test_without_the_opt_in_a_lane_windows_sidecar_act_works_as_on_main kills N03b (sidecar_lock_use is a no-op without the opt-in). - test_a_sidecar_act_in_a_W_that_is_a_lane_checkout_refuses[both, root-only] kills N09 (sidecar_lock_use runs lanes_home_check). tests/test_a23_lane_start_read.py - test_without_the_opt_in_lane_end_reads_W kills N05 (lane-end reads its lane checkout only under the opt-in). - test_a_checkout_that_cannot_be_made_is_said_and_the_end_goes_on kills N21 (lane-end notes a checkout that cannot be made and reads W). - test_a_held_creation_lock_is_said_and_the_start_goes_on kills N22 (lane-start's note arm takes the creation lock's 4 too). - test_a_held_creation_lock_is_said_and_the_end_goes_on kills N23 (lane-end's note arm takes the 4 too). tests/test_a23_refresh_workspace.py - test_refresh_refuses_a_W_that_is_a_lane_checkout[both, root-only] kills N11 (the refresh-workspace arm runs lanes_home_check). tests/test_a23_lane_checkout.py - test_a_creation_lock_that_vanished_is_not_made_again kills N13 (the creation lock's first touch is `touch -c`) and N14 (so is the heartbeat's). - test_the_creation_lock_is_refreshed_while_the_clone_runs kills N15 (a heartbeat touches the creation lock while the clone runs). - test_a_name_the_log_cannot_carry_is_refused_though_an_alias_maps_it [repoCK/1, repoCK 1] kills M31 (a name the log cannot carry is refused before any checkout, and before canon_lane reads with it). It adds `import time` to the module's import block. tests/test_a23_write_routing.py - test_S5_refuses_a_checkout_whose_git_directory_is_W kills N25 (S5's git-common-dir compare). tests/test_register_scan_hardening_138.py - test_item_9_refuses_in_the_scan_under_the_opt_in_too kills N16 (the fixture writes through wip by the seam). Runs (every inherited LANES_* and TMUX* unset, one module at a time), before -> after: test_a23_lock.py 19 -> 22 passed; test_a23_lane_start_read.py 6 -> 10; test_a23_refresh_workspace.py 10 -> 12; test_a23_lane_checkout.py 22 -> 26; test_a23_write_routing.py 34 -> 35; test_register_scan_hardening_138.py 28 -> 29; test_repo_hygiene.py 237 passed; test_test_roots.py 39 passed, 1 skipped. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit ecb96eda1e3d38cdf139659829d45366722e7d52)
…s stale at ten minutes as on main, only `commit` refuses over a hand edit in W, a write for a lane with no row clones nothing, and the daily report knows a lane window's register checkouts (#198) Answers lane openRepoTools-3's re-review of 874e020 (N1-N5, SHOULD 5) and its re-check of the 12 cases at ecb96ed (R3-1, R3-2). N1: mutex_stale. Without LANES_ROUTE_WRITES=1 the lock is W's, shared by every lane, and a live holder in this container whose lock is over ten minutes old is stale, as main's lock_try has it; git_net's `touch -c` heartbeat keeps a working holder fresh. Under the opt-in a live holder is still never aged out (S2). refresh-workspace's note on a stale lock in W no longer calls it "an older helper's leftover". The env doc and the lock comments say so. N2: ws_pending_refuse refuses only `commit`, the verb that publishes a hand edit. Every other routed write says the same as a WARNING and goes on; the edit stays in W, untouched and captured by nothing. hardening_138's routed sweep case pinned the refusal N2 removes: it now warns and captures nothing. N3: that check's `git status` runs with GIT_OPTIONAL_LOCKS=0, so it never rewrites W's index under W's index.lock. N4: lanes_home_check calls a checkout both variables name W itself, not a lane checkout, where it is workspace.yaml's own `path:`. Still exit 2 (S5). N5: lane_row_gate. Where a lane has no checkout yet and the verb's arm needs exactly one row of it (set-row-state, replace-in-row, append-session-id, and rename-lane by its old name), a lane with no row on origin refuses (2) and nothing is cloned. A miss is read again after one log_sync of W; a read that could not be made, or a fetch that did not run, refuses nothing. log, claim, release, commit, append-line, request-handoff and the sweeps take a lane with no row (an archived lane keeps its log), and add-row makes the row. SHOULD 5: lane-worktrees takes the parent of real(W) as the lanes home where that parent is a `*-lanes` directory, as lanes_home_check reads one. R3-1: the two lane-end cases assert "in-flight read from lanes/log/", which lane-end prints only after the follow-up (kills lane 3's n21b). R3-2: Sandbox.start takes a timeout (default 120, unchanged elsewhere); the held-creation-lock start and end get 300 s; the creation-heartbeat case waits up to 30 s for the first beat instead of a fixed 4 s. New tests: test_a23_lock.py test_without_the_opt_in_a_live_holder_is_aged_out_at_ten_minutes (2); test_a23_write_routing.py test_a_hand_edit_in_W_warns_every_other_routed_write_and_stops_none, test_the_hand_edit_check_takes_no_lock_in_W, test_both_variables_naming_W_itself_refuse_and_say_it_is_W, test_a_write_that_needs_a_row_clones_nothing_for_a_lane_with_none, test_a_row_W_has_not_fetched_is_no_reason_to_refuse (2); test_lane_worktrees_report.py test_a_W_that_is_a_lane_checkout_lists_its_siblings_as_register_checkouts. Runs (one module per pytest process, every inherited LANES_* and TMUX* unset), ecb96ed -> this commit: test_a23_write_routing.py 35 -> 41 passed; test_a23_lock.py 22 -> 24; test_register_scan_hardening_138.py 29 -> 29; test_lane_worktrees_report.py 21 -> 22; test_a23_lane_checkout.py 26 -> 26; test_a23_refresh_workspace.py 12 -> 12; test_a23_lane_start_read.py 10 -> 10. Also at this commit: test_lane_start_row_stamp.py 3 passed; test_lane_worktrees.py 118 passed; test_wip_init_command.py 63 skipped; test_repo_hygiene.py 237 passed; test_guard_launch_mode.py 13 passed; test_test_roots.py 39 passed, 1 skipped. The new tests against ecb96ed's scripts: 8 failed (N1 x2, N2, N3, N4, N5, the sweep case, SHOULD 5), and the 6 that pin the head's behaviour passed. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit eead86da50ee351e86918cc10ca647737c244424)
…n, `refresh-workspace` and `lane-checkout` no longer refuse a W that both variables name, because nothing is routed and a write goes through W as on main (#198) Answers lane openRepoTools-3's re-check of eead86d (6301653436), the one item that stopped slice 1a, R4-2. R4-2: lanes_home_check's "W itself" branch (both LANES_WORKSPACE_ROOT and LANES_REPO naming the path: workspace.yaml names) is S5's refusal, and S5 exists only under LANES_ROUTE_WRITES=1. The branch ran unconditionally from `lane-checkout` and `refresh-workspace`, so without the opt-in `refresh-workspace` exited 2, naming an opt-in that is off, for a verb that writes no register; and exit 2 is its own "skipped, nothing changed", so a caller went on with a stale W. The first line of the branch is now `[ "$LANES_ROUTE_ON" = 1 ] || return 0`, lane 3's fix. Under the opt-in the refusal and its words are unchanged, and a W that is a lane checkout is still refused with or without it. The other two callers (the sidecar lock and lane_checkout_enter) run only under the opt-in already. Tests: tests/test_a23_refresh_workspace.py test_without_the_opt_in_both_variables_naming_W_still_refresh_it (exit 0 and W fast-forwarded, no refusal), and tests/test_a23_lane_checkout.py test_without_the_opt_in_both_variables_naming_W_are_no_refusal (`lane-checkout` answers 8, nothing made). Both fail at eead86d, with exit 2 and S5's words, and pass here; the N4 case in test_a23_write_routing.py still pins the refusal under the opt-in. R4-1 is slice 1b's and is not touched. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit 15c8e3f813ed26ee123dfff8b91bd77f51433952)
…'s #198 comment 6341440689 suggests
The line is the `exec "${BASH:-bash}" "$RESOLVED" ...` at the end of the routing path in `lane_checkout_enter` (15412), which shellcheck 0.9.0 reports as SC2093 with the baseline flags of `lint.yml` on #277's branch (`ci/actionlint-shellcheck-job-277` at 935eaef): `--severity=warning --exclude=SC1007,SC2034,SC2069,SC2088,SC2128,SC2141,SC2154,SC2178,SC2209,SC2318`. The `exec` is deliberate, so it now carries `# shellcheck disable=SC2093` and a two-line reason (it hands this one write to the routed checkout's copy and does not return, and the `die` after it runs only if the `exec` fails). Before: 15c8e3f gave exactly SC2093 at 15412 (exit 1) and main's 33ce26d was clean (exit 0). After: this file is clean (exit 0, nothing reported). Nothing else changed.
Refs #198
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 6ca31581c4410e736ce78da373a12388e94da939)
…code PRs 3 to 7) into the code-leg slice 1a (opensoft/openRepoTools#198) Main moved 22 commits past 0cefa1f, the code leg's rewrite of f18b818 on which this slice stands (6 of them are merges). The stack takes the code leg's main, with T017's five lanes-edit.sh fixes: - 4393d79, 9add651, 4dd0fcf, 4e3eebe, 09b5355 and d9e7d56 are six of the old main's eight commits since f18b818, as the code leg holds them (the other two, the v1.1.0 notes and T010's assembly job, touch only docs/ and specs/, which went to the spec leg): tests/test_release_workflow.py runs the release steps (opensoft/openRepoTools#284); the shell suite's wrapper prints its timing table on a pass (opensoft/openRepoTools#265); the register's rewrites refuse a copy or write they cannot prove (opensoft/openRepoTools#263); a lane listing that is not whole no longer removes indexed worktree rows (opensoft/openRepoTools#289); under set -e, macOS's bash 3.2 no longer ends lanes --worktrees, wip init or lane-end where a utility run through command fails (opensoft/openRepoTools#294); CI and the release workflow run on ubuntu-24.04 (opensoft/openRepoTools#296); - 6314fc8, bc7c831, 6c53c9e and 1e6f2d1, merged by 2a057a6 (code PR 1), are the cutover's own: the code leg's front door (AGENTS.md, CLAUDE.md, README.md, the LF rule, the ignore list and the licence), the tests workflow's standalone checkout context, the assembly root's two length caps, and the shell suite's lane-start row-stamp cases; - 609ad61, c23f535, 70632d3, e527b5d and 577110e are code PRs 3 to 7, T017's five lanes-edit.sh fixes: a lane dir with a space is listed, a TAB or backslash in a recorded dir reads back as written, a control root that cannot be searched is UNREAD, a tree record with no absolute path makes its lane UNREAD, and a named worktree read says UNREAD instead of none. Why: this slice was rebuilt on the code leg from old 6ca3158 and stands on 0cefa1f, from before the cutover and before the ubuntu-24.04 pin d9e7d56, so it would not carry the code leg's front door or its five fixes. The 1b, 2a, 2b, 3 and 4 slices take the code leg's main through this one. The merge is clean: no hunk was resolved by hand. git merge-tree of 577110e into 4d6bc14 gave tree de12f63 before the merge was made. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Nov 8, 2026
….1's pin) into the code-leg slice 1a (opensoft/openRepoTools#198) Main moved from 577110e to e393958 when #15 landed at 19:30:50Z: the pin of upstream/openRepoShape moved from 190133d (v1.1.0) to 5017830 (v1.1.1). The merge brings five files, +12 -8: - upstream/openRepoShape: the submodule pointer, 190133d -> 5017830; - contracts/openreposhape-pin.yaml: the pinned commit and its tree_sha256; - .github/workflows/release.yml: the header says the pin moved, and keeps its v1.1.0 provenance (comment lines only); - tests/test_release_workflow.py: the MODEL comment says the same (comment lines only); - tests/test_install_payload_source.py: one docstring names the pointer as openRepoShape:745 at 5017830. Why: this slice lands alone, by merge commit, as #16. Its landed tree must equal a tree CI tested green, so the slice takes the new main in place and is tested there. Slice 1a stood on 577110e, the previous main, and would otherwise be tested on a main that is no longer the one it lands on. The merge is clean: no hunk was resolved by hand. git merge-tree of e393958 into 903d751 gave tree 14d48cf before the merge was made. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
This was referenced Nov 8, 2026
This branch has not been deployed
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.



Lane: openreposhape-2 (openRepoShape-2)
Claim: opensoft/openRepoTools#198 (comment)
Re-homed from opensoft/openRepoTools#282
This is slice 1a of act 2 (Amendment 23), re-homed to the code leg, opensoft/openRepoTools-code, by cherry-pick and not by merge, in the stack order (Brett Heap's Q28 answer; the dates are on opensoft/openRepoTools#198, comment 6722244541: 1a and 1b open as draft PRs here by 2026-11-10). The old PR, opensoft/openRepoTools#282, stays as the archived original until Brett Heap decides (T019); nothing more is pushed to its branch. Below the tests section is the original body, with four edits: its "Stacked on" and "What to review" paragraphs are rewritten for this base; three blocks that the new tests table or the archived original supersede are cut (the first run's before and after table, the broad-selection table, the per-survivor list of proposed cases), each with a line saying so; and bare issue numbers are qualified with the old repository's name, because on the code leg an unqualified reference means this repository's own numbers.
feat/198-a23-slice-1a-routingfeat/198-a23-slice-1a-routing6ca31581c4410e736ce78da373a12388e94da9394d6bc1493b839d374c0d8578e101c2ea4fc5a82ff18b8180cefa1fd1fd52663e692d992f09f60a421fc646df0d363a02c44030360195d4b0c4fdce262086132(the merge of codemaine393958, after903d751's merge of577110e)Method.
f18b818to code0cefa1f: same subject, author and author date; filter-repo only rewrote the short shas in its message.git commit-tree. Its old tree is cut to the code leg's 26 owned top-level paths (theleg: codeentries of the cutover plan,specs/004-migrate-to-triad/adoption-plan-cutover-2026-10-26.yamlon opensoft/openRepoTools-spec ataed1852), and its parents are mapped. Author, author date and committer date are kept; the committer is Brett Heap. The message is kept byte for byte, plus a(cherry picked from commit <old sha>)line placed asgit cherry-pick -xplaces it.docs/, which the cut removes, and two were empty already (a CI re-run and a verify record). Two signed merges lose their signature, because a rebuilt commit cannot be re-signed.The merge of the code leg's
main.903d751mergesmain577110ewith--no-ff.mainis 22 commits past0cefa1f: the code leg's front door (AGENTS.md, CLAUDE.md, README.md, the LF rule, the ignore list and the licence), the tests workflow's standalone checkout context, the assembly root's two length caps, the shell suite's lane-start row-stamp cases, six of the old main's eight commits sincef18b818(the ubuntu-24.04 pin among them), and code PRs 3 to 7, T017's fivelanes-edit.shfixes.f0d363amerges codemaine393958(Pin openRepoShape at v1.1.1, 5017830 on its main: four of the eight mounted files moved; no test expectation moved #15, openRepoShape v1.1.1's pin) with--no-ff, so that the landed tree is one that was tested. The merge was clean, and its tree (14d48cf) equalsgit merge-tree's. It brought five files, +12 -8:upstream/openRepoShape(190133dto5017830),contracts/openreposhape-pin.yaml,.github/workflows/release.yml,tests/test_release_workflow.pyandtests/test_install_payload_source.py. Atf0d363a, withupstream/openRepoShapechecked out at5017830, the 12 modules that read the pin, the submodule orrelease.ymlgave 1479 passed, 0 failed, 31 skipped (standalone-layout, composed-acceptance and built-wheel skips only), release_workflow's 499 among them. CI runs the whole suite on this head.de12f63c7864efad23c9737e0852f280a5c00549, the onegit merge-treegave before the merge was made.main, with T017's fivelanes-edit.shfixes. Slices 1b, 2a, 2b, 3 and 4 take it through this one.mainis this slice alone: 19 files, +3782 -130 (the original body below says 16 files, which was before its fix rounds).Range-diff summary (
git range-diff f18b818..6ca3158 0cefa1f..4d6bc14 --creation-factor=60):docs/hunk, and by nothing else.5fcdf6aagainst new17d8431: its patch id, cut to the 26 paths, equals the new commit's (106 added lines against 14, which is why range-diff does not pair them at that factor).Reading the original body below. Its shas are old shas; the receipt maps each to its new one. The ones it names most:
d0010a2isd7b8c6a,51efdb3is9e1d14e,59b39dfis871b27a,fda7808is4e02fa6, the last merge9d26089is281be58, fix round 1's head874e020isa4e4462,ecb96edis7eb779a, fix round 2'seead86disd22c8a2and fix round 3's15c8e3fiscb05cfc. "Pushed" there means pushed to the old branch. Ten-digit numbers in parentheses are comment ids in the old repository. The branch's old head,6ca3158(here4d6bc14), is one commit past the original body's last section: it marks the routingexecfor shellcheck's SC2093 (lanes-edit.sh, 3 lines added; no behaviour changes).Receipt: the 61 rebuilt commits, old sha to new sha (subjects cut to 56 characters)
c9ffdce04adf63c82fWIP: checkpoint before account swap; done: opensoft/openRepoTools#194 proving ...9a29bbba1c7a3bd142commit_push publishes the local commits a write with ...352c1b6f043cfe910ctest_commit_push_published_edit_194: the refusal case ...836e1e12889d5f6279opensoft/openRepoTools#214 review: only 'commit' publishes the commits a ...fab2cbc572a368d581test_commit_push_published_edit_194: the no-op case ...f34c993e35efc5932dopensoft/openRepoTools#214 adversarial check: 'commit' reads the queue on the ...5b68127fd002b27f29Exit-code tables: say the opensoft/openRepoTools#194 refusal pushes none of ...9f8aea86f76e939e9dopensoft/openRepoTools#214 reviews (openRepoShape-2 6242165339 ...ee33a33099227b6259Say what the publish fence checks: register and handoff ...656b191451ebc1eafeopensoft/openRepoTools#194: a publish the pull empties exits 0 and says so ...41433eb3f75702bcbdMerge openRepoTools-4's opensoft/openRepoTools#214 head 656b1914 as the ...c45a452a1fc0f99941docs: the triad split's lane-tooling side, surface by ...2aa8a59f1138b8b53fWIP (untested): T018 worktree visibility for triad legs ...5fcdf6a1717d8431cfT018 (a): the triad guidance for the sweep and ...8e2227eaa8d28a3f50WIP (untested): T018 (a) tests — ...96da9afc709b8d4a6bWIP (untested): T010 docs — the §4.14 CI sketch exits 4 ...30f9dc089cc4d24809T018 (c): the daily report counts each leg of an ...13cb52d053f3f4f31fT018 (d): lane-worktrees add records the leg it made a ...4e39854b8b00fbc409T018 (b): lane-handoff sees a triad's legs — the paired ...9a5bbe24d834f53651T018 (b): lane-reconcile reads a triad's legs — each ...e153361048ac1e5897Merge feat/triad-lane-tooling-handoff (T018 (b), writer ...b71e547d0c5d9adbe3WIP (untested): T018 (b) guidance — README-lanes says ...9991d697872ee697a7WIP (untested): T018 (d) — add's refusal on an ...8cb13680f85f28c794Merge main f87fd5a into feat/triad-lane-tooling (T018) ...941330ad70eb57d191T018 docs: a parked paired tree is not the lane's to ...4d4957db6b59e36ef3T018 (b), pre-PR review M1 option (a): lane-handoff ...6658396e497469c90fT018 docs: README-lanes and the analysis say what M1 ...7757eaab2a13253085Merge main aaa7bc8 into feat/triad-lane-tooling (T018) ...9f23a6b0986cb75be7WIP (untested): T018: lane-reconcile reports a leg ...bf2a220aed0da540d1WIP (untested): T018: lane-handoff counts a leg git ...cf7db4a77e502a29bdMerge main 2732d59 into feat/triad-lane-tooling (T018)5d2c1c8dc8e3a4f9b4T018: lane-handoff withholds only a tree that could be ...9a8f175499968c5119T018 docs: record lane 1's rulings of comment ...107958c7a84f330be5Merge main 83d2690 into feat/triad-lane-tooling (T018)69f0017d84e017566fMerge main f595e44 into feat/triad-lane-tooling (T018)a4e0b17fa3b82a844cT018: the daily report lists a leg that is also a ...a60cf1cc43bc4f268fT018 docs: the analysis spells bench paths ...88ee9d9c306c5d1331Merge main f5c3bcd into feat/triad-lane-tooling (T018)3d0642c00097f0b23cT018: verify 88ee9d9: 224 passed, 1 failed ...ea4db0d1ca700ef307T018 tests: the unreadable-leg tests ask the git under ...ad0c031a6d598c647eMerge openRepoTools-1's opensoft/openRepoTools#246 head ea4db0d1 as the ...ef16bbe0a53e69898cMerge openRepoTools main 586dea25 (opensoft/openRepoTools#247, opensoft/openRepoTools#237) into act ...d0010a299d7b8c6a64WIP: every register write goes through its lane's own ...465df53430aa7c8d4aMerge openRepoTools main fe588082 (opensoft/openRepoTools#204, opensoft/openRepoTools#206, opensoft/openRepoTools#208 ...51efdb39d9e1d14ec3lane-start and lane-end read a lane's row in its own ...59b39df22871b27a2bThe shell suite's opensoft/openRepoTools#67 cases (from main's opensoft/openRepoTools#221, issue ...129e201582c35ea174Merge branch 'main' into ...eff9107b7d1b65b13dMerge branch 'main' into ...fcf7f4857a280513eeMerge opensoft/openRepoTools#214's final head eff9107b (its refresh over main ...0d4ade894af5929852Merge opensoft/openRepoTools#214's squash 9b94a597 into act 2's slice 1a with ...fda7808d64e02fa631tests/test_lanes_index.py: its Estate names its old W ...6d22d0b53027c345fbMerge openRepoTools main b707bd70 into act 2's slice 1a ...5dd0df07b6467cce66Re-run CI on main fe58808: refs/pull/246/merge was ...eb9eb74953f326e0d2Merge opensoft/openRepoTools#246's final head 5dd0df07 into act 2's slice 1a ...9d26089ad281be5854Merge opensoft/openRepoTools#246's squash 55aae16 into act 2's slice 1a with ...18f5155160beb37dd2Merge openRepoTools main f18b818 (opensoft/openRepoTools#252, opensoft/openRepoTools#264, opensoft/openRepoTools#266 ...874e02041a4e446231Fix round 1 of opensoft/openRepoTools#282 (lane 3's review): routing is ...ecb96eda17eb779a9eFix round 1's surviving mutants are killed: 12 cases ...eead86da5d22c8a29eFix round 2 of opensoft/openRepoTools#282 (lane 3's re-review): without the ...15c8e3f81cb05cfc2aFix round 3 of opensoft/openRepoTools#282 (lane 3's re-check of eead86d) ...6ca31581c4d6bc1493SC2093: mark 1a's routing exec as deliberate, as lane ...Tests on the code leg
Head
903d751. Each module ran in its own pytest process, one at a time about 25 s apart, withEMAILset, every inheritedLANES_*andTMUX*unset,GH_TOKEN=bogus, a stubghfirst onPATH,GIT_SSH_COMMAND=falseand, except for the one module run again below,GIT_CONFIG_GLOBAL=/dev/null. The list is 46 modules: the five a23 modules, the lane tools' own, the workflow, hygiene, package, install and release modules this slice's files touch, and the five modules the code leg'smaingained for T017's fixes (test_dir_with_space_295.py,test_subfield_misreads_301.py,test_unsearchable_control_root_291.py,test_pathless_tree_record_302.py,test_named_read_unreadable_290.py).test_a23_lane_checkout.pytest_a23_refresh_workspace.pytest_a23_write_routing.pytest_a23_lock.pytest_a23_lane_start_read.pytest_dir_with_space_295.pytest_subfield_misreads_301.pytest_unsearchable_control_root_291.pytest_pathless_tree_record_302.pytest_named_read_unreadable_290.pytest_tests_workflow.pytest_repo_hygiene.pytest_guard_launch_mode.pytest_test_roots.pytest_package.pytest_package_stamp.pytest_release_workflow.pytest_register_hardening_120_122.pytest_command_builtin_errexit_292.pytest_register_scan_hardening_138.pytest_wrapper_hardening_138.pytest_pathspec_check.pytest_skill_shell_blocks.pytest_install_by_rename.pytest_wip_init_command.pytest_handoff_skill_98_99_100.pytest_handoff_skill_commit_block.pytest_commit_push_published_edit_194.pytest_handoff_skill_72_80.pytest_handoff_skill_80_85.pytest_install_hook_gate.pytest_install_skill_and_hook.pytest_lane_start_row_stamp.pytest_triad_lane_tooling_handoff.pytest_triad_lane_tooling.pytest_deferred_97_findings_157.pytest_lane_start_hardening_138.pytest_lane_worktrees_report.pytest_precision_90a_96.pytest_lane_start_claude_current.pytest_install_payload_source.pytest_lane_worktrees.pytest_rename_publication_atomic_158.pytest_lanes_index.pytest_openrepotools_command.pytest_lifecycle_fail_open_113_114_115.pyTotal: 1709 passed, 0 failed, 680 skipped, over 46 modules.
test_register_hardening_120_122.pyran first withGIT_CONFIG_GLOBAL=/dev/nullin its environment, and itsnumstatcase writes the global config it needs into its own temporary$HOME(core.attributesFile), so with that variable set the case saw exit 1 where it expects 5. Run again without the variable it passed all 46.README.md,CLAUDE.mdandAGENTS.mdare the assembly root's,docs/README-lanes.mdis the spec root's, the pinned openRepoShape is not checked out, andOPENREPOTOOLS_TEST_WHEELis unset. That is whytest_repo_hygiene.pyreads 220 passed and 17 skipped here, where the original's 237 passed.test_wrapper_hardening_138.pyskips 2 cases because SIGINT is ignored at entry in a background run.test_lanes_index.pypasses all 98. The original's host failed 3 cases there (a session uuid in the scratch path, and a timing case); the scratch path here holds none.test_a23_lock.pypassed 24 in 116 s,test_dead_holder_is_taken_overinside its 45 s bound (load average about 28 on 32 cores).test_lane_helpers_suite.py) andtest_supervised_restart_regressions.pywhole, by the lane's rule. The original ran both (see its tables). The merge leaves the second file as it was, but brings main's changes to the shell suite's two files, so CI'stestsjob is where it runs here.Amendment 23, act 2, slice 1a: the routing, S1, S2, S5 and the locks.
This is a DRAFT, opened for review. It lands by merge commit, on Brett Heap's standing word for act 2's six re-homed slices (opensoft/openRepoTools#198, comment 6723968257): "land each once its CI is green and lane 3's review passes, by merge commit".
Stacked on
The code leg's
main(577110e), merged in as903d751. Nothing else is open: both parents of the original landed, as its body says (old shas, in the old repository):lanes-edit.sh: a write with nothing staged still publishes the local commits origin lacks, so the RECOVERY's re-run of
commitpushes the edit it stranded, and the conflict RECOVERY warns thatreset --harddrops a peer's commits openRepoTools#214 landed as9b94a597. Its final headeff9107bwas merged here asfcf7f48, then its squash with-s oursas0d4ade8. The two trees are equal (bfbe9d2a).Make the lane tools look inside a triad's legs and paired feature trees, so the sweep, lane-end's gate, lane-handoff, lane-reconcile and the daily report can see a lane's work there, and change nothing a checkout with no project.yaml does openRepoTools#246 (T018) landed as
55aae16. It was merged here the same way:b707bd70first, as6d22d0b;5dd0df07, aseb9eb74;55aae16with-s ours, as9d26089.Before that last step I reverse-applied this slice's own four commits in a scratch index. The result is
aedf23d8, which is55aae16's tree exactly.Earlier main merges:
ef16bbe(586dea25) and465df53(fe588082).None of the nine merges needed a hand-resolved hunk.
What to review: the diff against the code leg's
mainis this slice alone, 19 files. Its four first commits ared0010a2(the slice),51efdb3(the lane-start and lane-end read),59b39df(the shell suite's opensoft/openRepoTools#67 cases) andfda7808(the lanes-index fixture), old shas; here they ared7b8c6a,9e1d14e,871b27aand4e02fa6. Fix rounds 1 to 3, below, come after the last merge of the original,9d26089(here281be58).Decisions
Each was made before the code and recorded in the slice's progress file.
Routing comes after the existing gates. It runs after the workstation guard and the "registry not found" gate, and before the verb arms.
write_lane_of, the lane its commit subject carries).exec, keeping the same pid. It exportsLANES_REPO,LANES_DIRandLANES_FILEat<W>-lanes/<lane>/, plus two internal markers,LANES__CHECKOUT_LANEandLANES__PID.LANES__PID == $$marks the process as the re-run itself. Any other value was inherited from a routed parent, so it is dropped together with the three paths.An explicit
LANES_REPOis honoured in place, with no re-exec: it is the test seam and an operator's override.LANES_DIRandLANES_FILEnow default from it. Before, an explicitLANES_REPOalone sent both to W: git ran in one checkout and the edit landed in another, which is Lane writers commit first and sync second, so a pull that refuses or conflicts strands the commit and later writes stack on it: fast-forward before the edit, and refuse before committing (fail closed) openRepoTools#198's defect.LANES_REPOwhose real path is W refuses every write (2). The refusal names the form that writes and S1's path.<W>-*with W's origin, is honoured, with a warning that names S1's path.LANES_DIRandLANES_FILElocate files inside the checkout and never choose it.LANES_DIRorLANES_FILElies outside the checkout it goes through refuses (2).No lane, no write. These refuse (2) and never fall back to W:
commit,log,claimandreleasewith noLANES_LANE;add-rowwhose row has no backticked lane;append-linethat names no lane.A writer missing its own lane argument keeps its arm's usage refusal, word for word.
Some verbs are not routed.
archive-rowsandmigrate-state-cellswithout--yes, andrequest-handoff --dry-run.publish-handoff. Its callers still pass W's file until slices 2b and 3, and lane-start still commits that file in W. It takes only the sidecar lock.S1 creation.
git clone --reference W --single-branch --branch mainof W's origin, made through<W>-lanes/.<lane>.partialand thenmv, under a creation lock<W>-lanes/.<lane>.lock..partialis moved aside, never deleted.mv, so a clone never nests.user.nameanduser.emailare copied.LANES_GIT_TIMEOUTwhere that is longer.S1 verification. Anything at the path that is not a clone of the register is refused (1). That covers:
.gitfile (a linked worktree);Nothing there is deleted, moved or repaired. The refusal names the
mv … .aside-<UTC>a person runs, and, for the alternates cases, thecherry -vto look at first.After a rename:
lanes/aliases.tsv) is moved to the new name once.The locks.
<pid> <container> <host>.git_netbefore every network call.execis adopted only with the marker. This is built for slice 1b's replay.The workstation's sidecar lock is
<W>-lanes/.lane-state.lock(ort186 Q3).set-lane-state,set-lane-tree,set-restart-intentandpublish-handofftake it and no other lock.refresh-workspaceis the only act that changes W:git fetch origin mainandgit merge --ff-only.handoffs/andlanes/by the lane whose row names it, and where in that lane's checkout it belongs (handoff_in_checkout).lane-startandlane-endread the lane's row where its writes go. Without this, slice 1a alone broke them:lane-start:1591, "origin/main ALREADY has a row … and this checkout does not";lane-endfound "no row for lane".So, under the default resolution, both ask
lanes-edit.sh lane-checkout <lane> --createand read that checkout'slanes/LANES.md. A--dry-runcreates nothing and reads W where no checkout exists. A helper that predates the verb writes W itself, so W is read. Only the read moves:LANES_REPO, and the handoff lane-start commits, stay W's until slices 2a-3.One line in each also changed: a caller's
LANES_REPOwithoutLANES_FILEnow reads that checkout's register, which is where the helper writes.Existing fixtures that asserted writes in W now name their old W by the explicit seam (ort186 Q9).
lane-worktrees), W is a gitfile view of the writing clone: its.gitnames that clone's, andlanesandhandoffsare links into it. That applies to the shell suite and to T018's bench.LANES_*andTMUX*, then setsLANES_REPOandLANES_FILE. Main's A register that could not be read no longer reads as a lane with no session ids, so the dead-lane --force check and duplicate-holder fail closed (#67) openRepoTools#221 added a 25th such case,r67(), and it now names its clone the same way.tests/test_lanes_index.py'sEstategets the same view, becauselanes-index syncreads W's ownorigin/<branch>.What changed
lanes-edit.sh:write_lane_of,lane_checkout_enter,lane_checkout_inside,path_real,path_under;lane_checkout_path,lane_ws_objects,lane_checkout_verify,lane_checkout_refuse,lane_checkout_old,lane_checkout_ensure;ws_relative,handoff_in_checkout;refresh_workspace,refresh_workspace_strays;lock_ident,mutex_read,mutex_local,mutex_stale,mutex_steal,mutex_try,mutex_release,lock_adopt;sidecar_lock_dir,sidecar_lock_use,sidecar_try,sidecar_release;cleanup_for_exec.LANES_DIR/LANES_FILEdefaulting fromLANES_REPO;release_lockandlock_try;git_net's heartbeat;lane_state_follow, which now takes the sidecar lock;cleanup.lane-checkout <lane> [--create]: 0 and the path; 8 when absent; 1 when not a clone; 2 when the clone fails; 4 for the creation lock; 64 usage;refresh-workspace: 0 fast-forwarded or current; 2 skipped; 1 when there is no W; 64 usage.LANES_REPO,LANES_WORKSPACE_ROOT, the markers, the locks).lane-startandlane-end:lane_register_follow, the same block in both, and the one-line derivation (decision 12).Tests:
tests/test_a23_lane_checkout.pytests/test_a23_write_routing.pytests/test_a23_lock.pytests/test_a23_refresh_workspace.pytests/test_a23_lane_start_read.pytests/test_lane_helpers.sh:LANES_*/TMUX*sweep, gc/maintenance on origin and W, the gitfile view, and the seam;LANES_WORKSPACE_ROOT=<clone>cases, which becomeLANES_REPO/LANES_FILE;set-lane-tree's wait, which now holds the sidecar lock;<W>-laneschecks;tests/test_lane_helpers_suite.py: the unset list and the prefix sweep;tests/test_lane_start_claude_current.py: theSandbox, also used by the supervised-restart, deferred-97, rename-158 and worktrees-report modules;tests/test_commit_push_published_edit_194.py: theBox;tests/test_lane_start_row_stamp.py: theSandbox;tests/test_triad_lane_tooling.py: theBench, andnormalisemapping W's view back to<HOME>/projects/wip;tests/test_supervised_restart_regressions.py: the two writer-mutex cases now hold the sidecar lock.tests/test_lanes_index.py: theEstate.gc.auto=0andmaintenance.auto=falseon origin and W.Tests (original runs, in the old repository)
Each module ran on its own with
/usr/bin/python3 -m pytest -q -p no:cacheprovider -o tmp_path_retention_policy=failed, withGH_TOKEN=bogus, a stubghfirst onPATH, and every inheritedLANES_*andTMUX*unset.ad0c031/ef16bbe. The last rows were run on an export ofef16bbemerged with mainfe58808.9d26089.The before and after table of this first run (36 rows: the stacked base against the final tree
8e6a5cab, and the tree before the restack) is in the archived original. The five new a23 modules passed 22, 21, 15, 10 and 4 cases there; the fix rounds below give their later counts, and the table under "Tests on the code leg" above gives every module's count at this head. Its one failure note:test_lanes_index.pyhad 2 failed and 80 passed on that tree, and the same two fail at mainb707bd70:test_offline_no_read_the_inventory_acts_are_byte_identical_against_an_unreachable_indexandtest_a_poisoned_worktrees_table_changes_no_inventory_acts_answer, because the module's normaliser rewrites part of the session uuid in that runner's basetemp path unequally between the roots it compares.Mutants. 61 one-line mutants of
lanes-edit.shwere each run against the new tests meant to kill them: 60 killed, 1 survived.check_lane_namebeforecanon_laneinlane_checkout_enter. It is redundant with the check on the canonical name after it, and stays as the guard beforecanon_lanereads with the raw name.set-restart-intent's sidecar lock) is killed by the edited supervised-restart case.lane-start/lane-endwere run by hand, and all four were killed:Not run:
tests/run.shand the full pytest suite. The shell suite did run, throughtests/test_lane_helpers_suite.pyon the final tree.Leaves every writer working
LANES_ROUTE_WRITES=1every write goes throughLANES_REPO, W by default, and every lock is that checkout's, as on main. With it, and the default resolution, every write goes through the lane's checkout and is committed and pushed there (S3-S4, the fetch before the edit and the read-back, are slice 1b's). An explicitLANES_REPOstill writes in place; a per-profile clone is warned about; a lane window whoseLANES_WORKSPACE_ROOTnames its own lane checkout is refused under the opt-in, naming that variable (MUST 3).lane-startandlane-endread the row where it is written (decision 12). A second start and an end of a new lane work under the default resolution, whichtests/test_a23_lane_start_read.pyshows.publish-handoffstill writes the file its caller names, and lane-start still commits the handoff in W, until slices 2a-3. W's handoff paths still resolve.workspace-rootstill answers W.refresh-workspace, and nothing calls it yet. A person runs it.lane-startcalls it from slice 2b. Until then, readers of W's working tree read what W has.Not changed
Main is merged (fix round 1, below):
18f5155merges mainf18b818, past677acc0.Known limits, all for slice 1b:
The index lag is closed by fix round 1's MUST 1: W's
origin/mainis fetched after a routed push, before the nudge.A lane checkout is only as fresh as its own last write, until slice 1b's fetch before the edit.
S3-S4 (the fetch before the edit, the replay and the read-back) and S6's sweeps: later slices. Lock adoption across
execis built for slice 1b's replay and used by nothing yet.lane-handoff,lane,lane-renameandlane-worktrees. The handoff writes move in slices 2a-3.docs/README-lanes.md.handoff_in_checkoutis wired only intorefresh-workspace's stray report. Wiring it intoset-restart-intent --handoffwaits for slice 2b, because lane-start's restart checks compare the bound path with the W path it still publishes.Two things found and left alone:
rename-lane'slane_state_renameruns under the checkout lock only, so it is no longer serialised with the sidecar writers. Taking the sidecar lock there belongs with slice 1b's rename replay.LANES_LOG_DIR,LANES_ALIASES_TSVandLANES_REPOS_TSVpass through unchecked. S2 names onlyLANES_DIR/LANES_FILE.Reads and the sidecar verbs still
log_sync(fetch) in W. A fetch is one of S5's two acts in W.Every decision lane-start takes from the row's cell still comes from the published row (
register-row), whatever the lane checkout's copy says.Fix round 1 (lane openRepoTools-3's review at
9d26089)Head
874e020. Two commits on9d26089, both plain, nothing forced:18f5155merges mainf18b818(past677acc0). There was one conflict, inlane_state_follow, resolved as planned: this branch'ssidecar_release, with main's$LSS_WHYnote. Nolsf_ownis left, and main'slanes-edit.shchanges since55aae16add no lock call.874e020is the fix round itself.How routing is opted into (MUST 2). Export
LANES_ROUTE_WRITES=1. Any other value, or none, keeps main's behaviour:LANES_REPO, which is W by default;lane_state_followtake the checkout's own lock;lane-startandlane-endread W.The opt-in stays off until slice 1b's S3 lands. No install or release should make it the default before then.
Still on by default:
LANES_DIR/LANES_FILEderivation fromLANES_REPO, the defect Lane writers commit first and sync second, so a pull that refuses or conflicts strands the commit and later writes stack on it: fast-forward before the edit, and refuse before committing (fail closed) openRepoTools#198 names;touch -cheartbeat;MUST
cleanupfetches W'sorigin/mainbefore the index nudge. The fetch goes throughgit_net, with gc and maintenance off. So the prompt guard's offline read, the index and everyLANES_NO_FETCH=1read see the write.lane-startneeds nothing of its own, because each of its routed writes is followed.test_a_routed_write_brings_W_origin_main_along: W'sorigin/mainequals origin after the write; an offlineregister-rowread of W shows it; W's HEAD and tree are unchanged.test_a_follow_fetch_that_fails_is_said_and_fails_nothing.LANES_ROUTE_WRITES=1, as described above.set-lane-treewait, andtest_supervised_restart_regressions.py, whose file is now main's byte for byte.test_without_the_opt_in_a_write_goes_through_W_as_before;test_without_the_opt_in_a_sidecar_act_takes_the_checkouts_lock;test_without_the_opt_in_a_new_lane_starts_through_W.*-lanesone refuses (2). So does a W thatLANES_WORKSPACE_ROOTnames whenLANES_REPOnames the same checkout.LANES_WORKSPACE_ROOT, and to keepLANES_REPOto go on writing through that checkout.lane-checkoutandrefresh-workspacealways, and every write and sidecar act under the opt-in.<W>-lanes, so nothing is cloned or locked inside a lane checkout.LANES_WORKSPACE_ROOT"the test seam; nothing else sets it".LANES_WORKSPACE_ROOTin lane windows.test_a_W_that_is_a_lane_checkout_refuses_naming_LANES_WORKSPACE_ROOT, with three cases: both variables set, the root alone, and the same checkout outside-lanes.touch -cingit_netand at the creation lock, so a lock that vanished is never made again as a file.test_a_lock_that_vanished_under_its_holder_is_not_made_again. The lock is removed between the write's pull and its push. No file is left behind, and the next write passes in under 30 s.test_register_scan_hardening_138.py. ItsRegisterwrites throughwipbyLANES_REPO, with W a clone of its own (LANES_WORKSPACE_ROOT). It runs 28 passed: the 27 it had, and the new case.test_a_routed_sweep_refuses_over_a_peer_edit_in_W_and_captures_nothing(SHOULD 4).SHOULD
write_lane_ofreads the published register (rows_named_ci) first and W's working tree second. A routed arm whose checkout predates the row falls back to the lane it was routed by.test_append_line_attribution_reads_the_published_register, in two cases: a fresh checkout, and a stale one.lane-startandlane-endsay so and read W, as the 8 branch does.test_a_checkout_that_cannot_be_made_is_said_and_the_start_goes_on, in which the clone fails and the start exits 0.--show-toplevel) and its git common directory, not the path as typed.test_S5_compares_the_repository_not_the_spelling, withLANES_REPOat W'slanes/.lanes/LANES.mdhas an uncommitted edit. The refusal names the edit and the place in the lane's checkout where it belongs.test_a_hand_edit_in_W_refuses_a_routed_write(commit), and the hardening_138 case above (a sweep).lane-worktreeslists<W>-lanes/<lane>under "Register checkouts (Amendment 23 S1)", with what each holds that origin lacks, and never under FOREIGN.test_a_lanes_register_checkout_is_reported_and_never_foreign.lane-start's carry note says no write will push a commit made in W.Estatestay. Those fixtures now run in the default mode, where nothing is routed.NIT
Fixed:
--endsrequest-handoff's options in the router too, as it does in the arm (test_request_handoff_why_after_the_separator_is_no_dry_run).sidecar_try,lane_state_followandcleanup.mutex_releasecompares the container and the host as well as the pid (test_release_leaves_a_lock_with_its_pid_in_another_container).touch -c) every minute while the clone runs. Its output goes to/dev/null, and it stops with the clone, onexec, or on exit.lane-endnow says "Nothing was written."LANES_NO_GIT=1makes no clone: an absent checkout refuses (2) and nameslane-checkout --create(test_no_git_makes_no_clone).LANES_REPOstill needs a lane under the opt-in (test_an_explicit_LANES_REPO_still_needs_a_lane).Left:
4. The three-way steal race. A
mkdirmutex has no atomic compare-and-delete. Both fixes considered, a steal mutex orflock, either move the race or change what the lock is made of. The window needs a dead holder and three takers inside one rename. It is a known limit for slice 1b.6. The clone happens before the arm validates its arguments. The checkout made is the lane's own, and its next write uses it. Copying every arm's argument checks into the router would make a second copy that can drift.
10. The other creator's lock names (
.create.lock,.creation.lock,.<lane>.create.lockin Eagle'sbrett-wip-lanes/). That launcher's protocol is not in this repository. Aligning the two names belongs to its owner, with slice 4.SonarCloud
The four copies of the test
Boxare now one module,tests/a23_box.py(RegisterBox). Each module keeps only its own rows and helpers. The new cases share the module's helpers and copy no setup. SonarCloud passed at874e020.Runs on
874e020Each module ran in its own pytest process, with
EMAILset andLANES_*/TMUX*unset.test_register_scan_hardening_138.pytest_a23_write_routing.pytest_a23_lock.pytest_a23_lane_start_read.pytest_a23_lane_checkout.pytest_a23_refresh_workspace.pytest_lane_worktrees_report.pytest_repo_hygiene.pytest_test_roots.pytest_guard_launch_mode.pyCI at
874e020:tests,tests-no-submodule,guard-launch-mode,packageand SonarCloud all passed.The broad selection at
874e020ran the same modules as the table under "Tests on the code leg" above, which supersedes its table; the old table is in the archived original. Every module passed there, excepttest_lanes_index.pywith 3 failed and 79 passed on that host: two cases compare paths and fail there because the scratch path holds a session uuid that their normaliser rewrites (<HEX>-<TREE>), and fail the same way at the baseb707bd70; the third,test_a_hung_indexer_delays_no_writer_beyond_the_fork, is a timing case that missed its stub under that host's load. CI'stestspassed all three at874e020.Mutants:
lanes-edit.sh,lane-start,lane-end,lane-worktreesand the hardening_138 fixture, each run against the fix round's new tests: 20 killed, 13 survived. Twelve survivors are killed by 12 proposed cases (listed in the archived original), each run failing with its mutant and passing without it. One is equivalent: S5's--show-toplevelclause alone (the common-dir clause already covers it). Control: the 84 killer cases against the unmutated files passed; the hardening_138 seam underLANES_ROUTE_WRITES=1passed 28.874e020: 60 killed, 1 survived (M31, as before), 0 retired, 0 BAD. Seven were re-pointed: M03, M07, M08, M11, M41 and M43 at the line that now carries the behaviour, and M50 attest_a23_lock.py's newset-restart-intentcase, because the supervised-restart case is main's again. M31 (thecheck_lane_namebeforecanon_lane) is not redundant after all: a hand-madealiases.tsvrow mapping a name no log can carry onto a real lane gets past it. The proposed case kills it.ecb96edadds (see "Since then"); the per-survivor list is in the archived original.Since then:
test_lanes_index.py's three cases on this host, explained above, which CI passes.ecb96edadds the 12 proposed cases, as a plain fast-forward on874e020. CI is green there:tests,tests-no-submodule,guard-launch-mode,packageand SonarCloud. Lane openRepoTools-3's re-check (6291134805) is ADEQUATE AFTER FIXES.eead86d. It answers lane openRepoTools-3's N1, N2, SHOULD 5's*-lanesparent and N5 (6290167859), and the re-check's R3-1 and R3-2. See its section below.Fix round 2 (lane openRepoTools-3's re-review at 874e020 and re-check at ecb96ed)
Head
eead86d, pushed. It is one plain commit onecb96ed, and nothing was forced:git ls-remotegaveecb96edbefore the push andeead86dafter it. Main is not merged in, because no conflict or test needed it. Line numbers below are ateead86d.Every proposed fix is taken. N5 is taken in a narrowed form, and the reason is given under N5.
SHOULD
mutex_stale(lanes-edit.sh:1306-1307). WithoutLANES_ROUTE_WRITES=1, a live holder in this container whose lock is over ten minutes old is stale, as main'slock_tryhas it.git_net'stouch -cheartbeat refreshes the lock before every network call, in both modes.refresh-workspace's note on a stale lock in W no longer calls it "an older helper's leftover" (:15464), because a quiet live holder can now be stale too.:659-664,:1257-1262,:1584-1586).tests/test_a23_lock.py:515test_without_the_opt_in_a_live_holder_is_aged_out_at_ten_minutes, with 2 parameters,pid-container-hostandpid-only.sleep, aged 11 minutes.ecb96edboth parameters fail with exit 4, after the 60 s wait.commit.ws_pending_refuserefuses onlycommit(:15308), the verb that publishes a hand edit.tests/test_a23_write_routing.py:448test_a_hand_edit_in_W_warns_every_other_routed_write_and_stops_none. After a hand edit torepoA23-2's row in W,repoA23-1'sset-row-statelands, and origin does not carry the edit. Atecb96edit exits 2.tests/test_register_scan_hardening_138.pypinned the refusal that N2 removes. It is nowtest_a_routed_sweep_over_a_peer_edit_in_W_warns_and_captures_nothing(:620). The sweep warns, retires the row through the sweeper's own checkout, and leaves W's HEAD and the edit as they were.real(W)is a*-lanesdirectory,lane-worktreestakes that parent as the lanes home (lane-worktrees:5528-5534). It requires a non-empty name before-lanes, aslanes_home_checkdoes.tests/test_lane_worktrees_report.py:725test_a_W_that_is_a_lane_checkout_lists_its_siblings_as_register_checkouts. With W atbrett-wip-lanes/repoA-1, both lane checkouts are listed as register checkouts and neither as FOREIGN. Atecb96edboth are listed FOREIGN.in-flight read from lanes/log/(tests/test_a23_lane_start_read.py:225,:281). Lane-end prints that line only after the follow-up.NIT
git statusin that check runs withGIT_OPTIONAL_LOCKS=0(:15305).git status --porcelain -- <file>rewrote a stat-dirty index with a new inode. With the variable set, it did not.tests/test_a23_write_routing.py:472test_the_hand_edit_check_takes_no_lock_in_W. W's register is made stat-dirty, and a routed write leaves W's.git/indexwith the same inode and mtime. Atecb96edthe inode changed.path:that workspace.yaml gives,lanes_home_checknow calls that checkout W itself, the readers' copy no write goes through (S5). It says to unsetLANES_REPO, and it no longer calls W "a lane checkout" (:14947-14953).tests/test_a23_write_routing.py:489test_both_variables_naming_W_itself_refuse_and_say_it_is_W.lane_row_gate(:15332-15342), called just beforelane_checkout_ensure(:15398). Where a lane has no checkout yet and the verb's arm needs exactly one row of that lane, a lane with no row on origin refuses (2) and nothing is cloned.set-row-state,replace-in-row,append-session-id, andrename-lane, which goes through its old name.log_syncof W. A read that could not be made, or a fetch that did not run (failed,LANES_NO_FETCH=1orLANES_NO_GIT=1), refuses nothing, and the arm decides as before.log,claim,release,commit,append-line,request-handoffand the sweeps take a lane with no row. An archived lane keeps its log (Amendment 16(e)), so a gate there would refuse writes that their arm accepts.add-rowmakes the row.tests/test_a23_write_routing.py:505test_a_write_that_needs_a_row_clones_nothing_for_a_lane_with_none. Atecb96edit cloned the checkout, then refused with "expected exactly 1 row".:520test_a_row_W_has_not_fetched_is_no_reason_to_refuse, with 2 parameters,fetchedandno-fetch. A row a peer published, which W has not fetched, is written in both. These pin the head's behaviour, and they pass atecb96edtoo.Sandbox.starttakes atimeout(tests/test_lane_start_row_stamp.py:206). Its default stays 120, so every other caller is unchanged.tests/test_a23_lane_start_read.py:164,:263,:276).tests/test_a23_lane_checkout.py:419). At the head the case ends at the first beat, and the heartbeat mutant still fails it, after 30 s.Runs
Each module ran in its own pytest process, with every
LANES_*andTMUX*unset,EMAILset and a stubgh.ecb96ed)eead86d)test_a23_write_routing.pytest_a23_lock.pytest_register_scan_hardening_138.pytest_lane_worktrees_report.pytest_a23_lane_checkout.pytest_a23_refresh_workspace.pytest_a23_lane_start_read.pytest_lane_start_row_stamp.pytest_lane_worktrees.pytest_wip_init_command.pytest_repo_hygiene.pytest_guard_launch_mode.pytest_test_roots.pytest_supervised_restart_regressions.py(main's lock holders)The new tests against
ecb96ed's scripts. Only the test files were laid over a clone ofecb96ed. Of the 14 cases, 8 failed and 6 passed.Not run: the shell suite, because no shell-suite file changed and CI's
testsjob runs it; and macOS bash 3.2. A plainVAR='' funccall and$(...)are the only new shell forms.Mutants
Each mutant is one line, built on a clone of
eead86d, never in the worktree, and run against the tests meant to kill it. The control is every killer node id against the unmutated head: 17 passed. After the run all four targets' SHA256 match the head again.The new set: 12 of 12 killed.
return 1test_live_holder_is_not_aged_outtest_without_the_opt_in_a_sidecar_act_takes_the_checkouts_lockcommitno longer refusestest_a_hand_edit_in_W_refuses_a_routed_writeGIT_OPTIONAL_LOCKS=0removed[fetched][no-fetch]log_syncremovedFix round 1's mutants re-run against their changed killers: 6 of 6 killed.
Fix round 3 (lane openRepoTools-3's re-check at eead86d)
Head
15c8e3f813ed26ee123dfff8b91bd77f51433952, pushed. It is one plain commit oneead86d. Nothing was forced:git ls-remotegaveeead86dbefore the push and15c8e3fafter it. Main is not merged in. Line numbers below are at15c8e3f.This answers lane 3's re-check of
eead86d(6301653436), whose one stopping item was R4-2. The proposed fix is taken as written.R4-2 (NIT):
refresh-workspacerefused the real W while the opt-in was offlanes_home_check's "W itself" branch (bothLANES_WORKSPACE_ROOTandLANES_REPOnaming thepath:workspace.yaml gives) is S5's refusal, and S5 holds only underLANES_ROUTE_WRITES=1. The branch ran unconditionally fromlane-checkoutandrefresh-workspace. Without the opt-in,refresh-workspaceexited 2 naming an opt-in that was off, for a verb that writes no register. Its own contract reads exit 2 as "skipped, nothing changed", so a caller carried on with a stale W.lanes-edit.sh:14955, the first line of the branch, before thedie:[ "$LANES_ROUTE_ON" = 1 ] || return 0. A four-line comment above it (:14949-14952) says why. Under the opt-in the refusal and its words are unchanged.The four checks
lanes_home_check(:1399,:15355,:19513,:19537at this head; no other script calls it)::1399(sidecar_lock_use) comes after:1398,[ "$LANES_ROUTE_ON" = 1 ] || return 0, so it runs only under the opt-in. No change.:15355(lane_checkout_enter) has one caller, insideif [ "$LANES_ROUTE_ON" = 1 ] && [ -z "$LANES_ROUTED" ]. Only under the opt-in. No change.:19513(lane-checkout) and:19537(refresh-workspace) are unconditional, and are the two that change. Without the opt-in, both variables naming W now go on:refresh-workspacefast-forwards W, andlane-checkoutprints or makes<W>-lanes/<lane>, each as it does withLANES_REPOunset. That is right, because without the opt-in a write goes through W as on main, and W is W itself, not a lane checkout.lane-startandlane-endreach the two verbs only throughlane_register_follow, which returns first unlessLANES_ROUTE_WRITES=1.*-lanesparent, and both variables naming a lane checkout that is not W, are still refused without the opt-in. The guard sits only inside the W-itself branch.LANES_ROUTE_ONis set first::1137sets it to 0 and:1138to 1 for the opt-in or a re-run process. That is its only assignment, at load, before every caller.tests/test_a23_write_routing.py:489): itsBoxsubclassesRegisterBox, which exportsLANES_ROUTE_WRITES=1by default (tests/a23_box.py:56,:92-93). It already runs under the opt-in, still refuses, and needed no change.lane-checkoutorrefresh-workspace: none. Every other test that setsLANES_WORKSPACE_ROOTnames a lane checkout, two different checkouts, or an emptyLANES_REPO.Tests
tests/test_a23_refresh_workspace.py:189test_without_the_opt_in_both_variables_naming_W_still_refresh_it. A peer pushes, thenrefresh-workspaceruns withLANES_ROUTE_WRITES=""and both variables at W. It expects exit 0, no S5 refusal, "fast-forwarded W", W's HEAD equal to origin's main, and no<W>-lanes.tests/test_a23_lane_checkout.py:358test_without_the_opt_in_both_variables_naming_W_are_no_refusal.lane-checkout <lane>with the same set-up answers 8 (none there yet) with nothing made, where it refused with 2.eead86d's script (the tests alone), both fail for the intended reason: exit 2 with S5's "both name ... which is W itself" refusal (assert 2 == 0andassert 2 == 8). With the guard, both pass.Runs
One module per pytest process, every inherited
LANES_*andTMUX*unset,EMAILset, a stubgh,GH_TOKEN=bogus. Counts ateead86d, then at15c8e3f:eead86d15c8e3ftest_a23_refresh_workspace.pytest_a23_write_routing.pytest_a23_lane_checkout.pytest_a23_lane_start_read.pytest_a23_lock.pytest_lane_worktrees_report.pytest_repo_hygiene.pytest_guard_launch_mode.pytest_test_roots.pytest_supervised_restart_regressions.pyThe one failure is
test_restart_observer_confirms_late_uuid_prepared_by_real_child[zombie]. It is asubprocess.TimeoutExpired:lane-handoff --supervisedid not finish inside the test's 30 s while five lanes shared the host (the module took 2229 s, against fr2's 1805 s). It does not touchlanes-edit.sh's home check. Run alone at15c8e3f, both parameters of that test pass (2 passed in 54 s). The full module was not run a second time.The mutant: NOT RUN
At the usage checkpoint the coordinator asked for no new test run. The mutant was set up in a clone: the head checked out and the guard line deleted. It was then put back and the file compared byte for byte with the head's
lanes-edit.sh(cmp: identical). So no mutant result is claimed. The two new cases are the ones it would run, and both already fail ateead86d's script, which differs from the head only by that line and its four-line comment.Not in this commit
R4-1 (
lane_row_gate, for the other verbs that name a lane) is a SHOULD for slice 1b and goes to slice 1b's writer.lane_row_gateis untouched. Under the opt-in,refresh-workspacewith both variables at W still exits 2 with the S5 message, as lane 3 asked for the opt-in-off case only.Refs opensoft/openRepoTools#198
🤖 Generated with Claude Code