Skip to content

Amendment 23 slice 1b: every writer fetches and fast-forwards under its lock or refuses with nothing written, pushes with no pull and reads origin back, a refused push replays the one edit, and commit with nothing staged is the publish act - #17

Draft
brettheap wants to merge 67 commits into
feat/198-a23-slice-1a-routingfrom
feat/198-a23-slice-1b-guard-publish

Conversation

@brettheap

@brettheap brettheap commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

Lane: openreposhape-2 (openRepoShape-2)
Claim: opensoft/openRepoTools#198 (comment)

Its spec half (docs/README-lanes.md) is opensoft/openRepoTools-spec#15, at df909ba. The two land together, the code half first or at the same moment, never the spec half ahead of it. That order holds for the code leg's own CI only: the assembly's composed job reads the pinned spec, so the assembly's code and spec pins move in one commit (lane openRepoTools-3, at 34799b7: with the spec leg's main two hygiene cases fail; with #16's 903d751 and this spec half the exit-3 doc test fails).

Re-homed from opensoft/openRepoTools#307

This is act 2's slice 1b, opensoft/openRepoTools#307, re-homed on the code leg by Brett Heap's answer to Q28 on opensoft/openRepoTools#165: act 2's six drafts move to the triad's legs in their stack order, by cherry-pick and not by merge.

  • Heads. Old: 4b79d67 (opensoft/openRepoTools, feat/198-a23-slice-1b-guard-publish). Rebuilt here: 07bf43c, the same 62 commits. New: 458cb380bf7693dd5d5b9cf3111a13fe079da60e, which adds five commits: the merge of slice 1a's merge of main (195fca9), the re-pins (34799b7), the fix round from lane openRepoTools-3's review of 34799b7 (8e42208, cc8f6ab) and the merge of slice 1a's second merge of main (458cb38).
  • Method. Each of the old branch's 62 commits above slice 1a was rebuilt with git commit-tree, one for one, in order and with the same parents mapped, on the code leg's counterpart of old main f18b818, 0cefa1f. Each tree is the old tree cut to the 26 top-level paths the code leg owns (the leg: code entries of the cutover plan, specs/004-migrate-to-triad/adoption-plan-cutover-2026-10-26.yaml on opensoft/openRepoTools-spec at aed1852). Author, author date, committer date and message are kept, the committer is the estate's identity, and the message ends with git cherry-pick -x's line naming the old commit. All 62 were checked against these rules (tree, parents, author, committer, message, other headers): no mismatch. This slice's docs/README-lanes.md half goes to an opensoft/openRepoTools-spec PR. Amendment 23 slice 1b: every writer fetches and fast-forwards under its lock or refuses with nothing written, pushes with no pull and reads origin back, a refused push replays the one edit, and commit with nothing staged is the publish act (#198) openRepoTools#307 stays as the archived original until Brett decides (T019); nothing more is pushed to it. Every commit below is named by its new sha, with the old one in brackets.
  • Range-diff (git range-diff 6ca3158..4b79d67 4d6bc14..07bf43c, in a clone that has fetched both repositories): 57 pairs differ only by the cherry-pick line and the dropped docs/README-lanes.md hunks. Two old commits touched only the manual, 5588307 and 333af4e (decisions 34 and 45): their rebuilt commits 02f2d08 and 5be93b3 are empty here, so range-diff pairs nothing with them. The three merges are not shown by range-diff; tree equality covers them. Nothing else differs.
Receipt: the 62 commits, old and new
# old new
1 a86ba10 fdf34f6
2 ef1377f 8652321
3 e3b6810 87e94da
4 139144e c14bd3b merge
5 7da3d61 7e07e62
6 3905009 2ed9e62
7 2e2eee4 2e18850 merge
8 a41762b 0562d07
9 6ecfc52 9714694
10 c454247 db98ee6
11 10169f3 caeca9c
12 92fbe46 d878c40
13 3c6b240 6197298
14 6471fd1 af4d0e1
15 d9ff4e6 c4393e8
16 c71621f 59c238d
17 e6ea8f8 e594547
18 948e1e0 4b80898
19 9683fd1 bf77bf2
20 9cdf8ab 8089633
21 4b94f87 37b0f51
22 638b13a 0f26c5e
23 514b0c4 c14738a
24 0c822f3 b324e00
25 f9b1de9 0edcfb4
26 3a4bc4a e3e8124
27 251e900 99ea7d3
28 24efc8a 40fa53c
29 5588307 02f2d08 manual only: empty here
30 c71068b 82977c1
31 dba848a 5c5e9d3
32 db4d2a4 0308f0c
33 2165551 4bcc6a8
34 85575ee 38fff00
35 c2fa058 b2638a7
36 a465cb5 1d59ce5
37 333af4e 5be93b3 manual only: empty here
38 93475ae bd9d6ab
39 afad3e5 d6064de
40 7faa577 21ad618
41 fc6f34d a01ea3a
42 15e71d9 b98d3a7
43 53108b4 5fb952d
44 aa0f864 2e6f8f1
45 921dd58 941163c
46 29103a5 ca94468 merge
47 a44b73d 0fb5416
48 0e56301 60d81d6
49 aa45be1 479d413
50 50e8916 89e98f2
51 af29be6 27fede5
52 173f3c8 4c6da04
53 40c57b9 c248df2
54 126bccd ee16b26
55 59551dd d8859c8
56 9137569 70edb34
57 9785ad9 88a3692
58 0ddde5e 8a88f50
59 458cdde 7fe295d
60 f97723c 97fb75c
61 80ae89e 9fcbf30
62 4b79d67 07bf43c

Merging slice 1a' (decisions B70-B73). 195fca9 merges 903d751, slice 1a re-homed here with the code leg's main 577110e merged in. Over this branch's base, main brings opensoft/openRepoTools#284, opensoft/openRepoTools#265, opensoft/openRepoTools#263, opensoft/openRepoTools#289, opensoft/openRepoTools#292 with opensoft/openRepoTools#294, opensoft/openRepoTools#296, #1 and T017's #3 to #7. git stopped on three hunks, all in lanes-edit.sh and all with opensoft/openRepoTools#263 (4dd0fcf [d9065df]); the 2026-10-24 rehearsal on the old repository met two more in docs/README-lanes.md, which this leg does not hold. The merge commit holds only these resolutions (git show --remerge-diff 195fca9):

The re-pins (decisions B74-B76), 34799b7, pushed with the merge so that the merge, whose tree fails opensoft/openRepoTools#263's module, is never a head:

Tests at cc8f6ab, one pytest process per module, 25 s apart, with every LANES_* and TMUX* unset, GH_TOKEN=bogus and a stub gh first on PATH. The roots are standalone (no assembly or spec root here), so the cases that read those skip.

Module Result
tests/test_register_hardening_120_122.py 47 passed
tests/test_repo_hygiene.py 235 passed, 18 skipped
tests/test_a23_guard_refusals.py 150 passed
tests/test_a23_write_routing.py 57 passed
tests/test_commit_push_published_edit_194.py 25 passed
tests/test_a23_lock.py 29 passed
tests/test_guard_launch_mode.py 13 passed
tests/test_deferred_97_findings_157.py 9 passed
tests/test_register_scan_hardening_138.py 29 passed
tests/test_command_builtin_errexit_292.py 14 passed
tests/test_a23_publish_replay.py 82 passed
tests/test_lanes_index.py 98 passed

At the merge 195fca9 alone, tests/test_register_hardening_120_122.py gives 22 failed, 24 passed: the cases B74 and B75 re-pin.

The fix round from lane openRepoTools-3's review of 34799b7 (opensoft/openRepoTools#198, comment 6726042267: no MUST, ready after fixes) takes S4, N1, N2, N3 and N5. S1 (the identities), S2 (this title) and S3 (the spec half's Claim: line) are not changed here: the titles and the spec half are main's, and the identities are Brett Heap's to rule, so these commits keep the identity of every other commit on this branch. Each ends with the Lane: trailer.

  • S4, 8e42208. B73's refusal in retire_rows (the log published under another case than the row's) had no case: S3 refuses a rename whose commit is not pushed before the scan, so only origin moving after S3's fast-forward reaches it. test_120_item_2_a_log_respelled_on_origin_after_the_fast_forward_refuses_in_the_scan has a peer respell repoX-3's log and a reader fetch it into the shared checkout right after the sweep's behind-check: exit 2 with the scan's words, no log made for the lane ahead of it, HEAD, register and log unchanged. The die's recovery is now ensure_log's sentence, word for word, and the case checks that lanes-edit.sh holds it twice. Red first: on the old sentence the case fails at the recovery assertion, after the shim, exit 2, S3's silence and the scan's cause pass. Two mutants are killed: dropping rr_from, and asking only where the checkout has no log (the sweep then dies in the write loop and leaves ?? lanes/log/repoX-2.md).
  • N2 and N3, cc8f6ab. The identical-row case asserts that The register's rewrites refuse a copy or write they cannot prove and put a cut-short register back, retire-rows refuses an unpushed log rename before it writes and calls a lane closed only from its published log, and add-row refuses an archive row it cannot read and takes back a row refused after its rebase (#120, #122) openRepoTools#263's rebase note ("origin/main already carries this exact row") is not printed. Against the code leg's main 577110e scripts, in main's own harness (the checkout as LANES_WORKSPACE_ROOT, no LANES_REPO), its two parameters passed without it (2 passed) and fail on it (2 failed). Four cases named for the rebase and the take-back are renamed for what they assert. The capture case asserts cs_refuse_register's "holds an uncommitted edit, which may be a peer's", not only exit 2: a mutant of those words passes the old case and fails the new one.
  • N1 and N5 are in this description: decisions 2 and 10 say where the Questions went, and the first paragraph says the pins move in one commit.

Merging slice 1a's new head (458cb38). While this round was made, the code leg's main moved to e393958 (#15, the openRepoShape v1.1.1 pin), and slice 1a re-merged it as f0d363a, #16's new head. 458cb38 merges f0d363a into this branch with --no-ff, Brett Heap's identity and the Lane: trailer. git merge-tree --write-tree gave tree c76b695 first, with no conflict, and the merge's tree is that one. It brings main's five files and nothing else, +12 -8: upstream/openRepoShape, contracts/openreposhape-pin.yaml, .github/workflows/release.yml, tests/test_release_workflow.py and tests/test_install_payload_source.py. At the merge: tests/test_register_hardening_120_122.py 47 passed; tests/test_repo_hygiene.py 235 passed, 18 skipped; tests/test_release_workflow.py 5 passed and 494 skipped with upstream/openRepoShape not checked out, and 499 passed with it checked out (a --no-hardlinks clone of /workspace/projects/openRepoShape, detached at the pinned 5017830). The other ten modules ran at cc8f6ab, the merge's first parent, and the merge touches none of their files.

Stacked on

Slice 1a, re-homed here from opensoft/openRepoTools#282 as feat/198-a23-slice-1a-routing (its PR on this repository is opened beside this one). This branch started at 1a's a4e4462 [874e020]. 1a's later heads came in by three --no-ff merges: fix round 2's d22c8a2 [eead86d] in c14bd3b [139144e], fix round 3's cb05cfc [15c8e3f] (R4-2) in 2e18850 [2e2eee4], and its SC2093 disable 4d6bc14 [6ca3158] in ca94468 [29103a5]. Please review the commits after a4e4462 [874e020] that are this slice's own: git log --first-parent --no-merges a4e4462..07bf43c, 59 commits: 82977c1 [c71068b] to a01ea3a [fc6f34d] are the fix round from lane openRepoTools-3's review of 02f2d08 [5588307] (decisions 35-47), b98d3a7 [15e71d9] to d8859c8 [59551dd] the second round (decisions 48-58), and 70edb34 [9137569] to 07bf43c [4b79d67] the third (decisions 59-65). The slice's whole diff over 1a' is git diff 903d751 HEAD, because the merges carry 1a in full; the two commits on top of 07bf43c are the re-home's (above).

Hold (lane openRepoTools-3's MUST 282)

No release, no install, and no by-path run of main's scripts with this slice in them, until slices 2b and 3 and the routing flip ship together, as the plan's single install after 1a-3 (plan-20261017T2135Z.md:1049). With routing off, S3's unpushed row makes one lane's local commit in the shared checkout refuse every other lane's write through W. Until 2b and 3, lane-start's stamp (lane-start:4470-4511), lane-handoff's PAUSED commit (lane-handoff:2674-2677) and the skill's commit block (skills/handoff/SKILL.md:447-450) still commit to W without the lock and then pull and push. While such a commit is local, every write through W exits 2 ("HEAD carries 1 unpublished commit(s)"): lane-start dies on add-row's 2, and its set-row-state and log failures are only notes, so those records go unwritten (lane 3's P5; at the base cb05cfc [15c8e3f] both writes exit 0 and carry the stamp). That breaks ratified choice 5's "confined to one lane's checkout". Lanes run main's scripts by path (lane 3 runs the shared workspace checkout's lanes-edit.sh), so landing this slice without 2b and 3, and then moving such a checkout, is an install too. Nothing in this PR changes for it. Slice 2b also rewrites hs_carry_note's middle branch (lane-start:4317-4318), which still says the next lanes-edit.sh write "pushes the branch and carries this commit with it", so that it names the publish act.

Decisions

Decisions 5, 9, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 48, 49, 52, 53, 55 and 57 are cut here to their first two sentences, marked (Cut.), to keep this description under GitHub's limit; each is in full on the archived original, opensoft/openRepoTools#307.

  1. S3 and S4 apply to every writer, routed or not (the brief's decision 1). With LANES_ROUTE_WRITES off, a write goes through W as on main, and S3–S4 are 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 fix for exactly that checkout, so they apply there too.

  2. LANES_ROUTE_WRITES stays opt-in (the brief's decision 2). The flip waits on the conditions of Q1 on Amendment 23 slice 1b: every writer fetches and fast-forwards under its lock or refuses with nothing written, pushes with no pull and reads origin back, a refused push replays the one edit, and commit with nothing staged is the publish act (#198) openRepoTools#307, whose Questions section is not repeated here: Brett Heap has heard the disk cost (about 0.4 GB per writing lane), and every lane checkout is clean.

  3. checkout_sync <what> <lane> <write|carry|publish> [<paths>…] runs under the checkout's lock before the verb reads its file, in the amendment's order: fetch (a failed one, or LANES_NO_FETCH=1, refuses: choice 2); an operation in progress or unmerged entries; not on main; staged entries that are not the write's own; unpushed commits by content (git cherry; the publish act alone is exempt); diverged; a pending change to a path the write commits (the register refuses every writer but commit: choice 9); the fast-forward (git's refusal quoted, naming the file; an unstaged change origin did not touch passes: choice 8). Every refusal is exit 2, names the checkout, says nothing was written and prints its recovery. Every ref is spelled in full.

  4. commit_publish replaces commit_push with the same call shape. Commit by pathspec as before; push with no pull; read back (fetch, merge-base --is-ancestor). Confirmed is 0; a push that went or timed out and cannot be confirmed is 3, pushed, not confirmed: <sha>, never success, commit kept on HEAD (or, where HEAD no longer holds it as this write's last commit, named by cp_not_head's or cp_rewritten's words with the number still 3: decision 30).

  5. A refused push replays the one edit: read back first (it may have landed), then the drop takes this write's commit off the branch (a compare-and-swap of the branch, then the index and the working tree: decision 25) and the same argv runs again under the same pid (exec), the lock adopted across it, so the verb's own checks re-run on origin's new tip: claim's rescan first (CLAIM-LOST 7, or 9 for a takeover whose dead lane is alive again), the one-match guard, add-row's retirement and duplicate checks. (Cut.)

  6. Six attempts in all; at the bound the commit is kept on HEAD and named (6), and every later write refuses on S3's unpushed row (choices 4 and 5).

  7. A push refused with origin unmoved is 6, not a replay (made while coding): a hook, a permission or a protected branch would meet the same refusal six times.

  8. commit's carry form drops to <sha>^ in the index alone (made while coding; its form since decision 25): its edit is the hand edit, which no argv makes again. So the branch moves to the commit's parent, S3's base, and only the index follows (read-tree -m -i, what reset --mixed does), which leaves the change in the working tree for the re-run to carry. A drop to origin's tip would have dropped the edit, and the re-run would have been an empty publish act.

  9. A carried row edit is anchored by the lines it replaces (made while testing): git apply refused the carry whenever origin had changed the row beside the edited one, the diff's context. Where every hunk removes a line, a failed apply is retried with -C0; an edit to a row origin also changed is still refused and kept in a file, and an insertion still needs its full context. (Cut.)

  10. The publish act (commit with nothing staged): S3 without the unpushed row and without the fast-forward; the queue listed by content before the push; lanes-edit.sh: a write with nothing staged still publishes the local commits origin lacks, so the RECOVERY's re-run of commit pushes the edit it stranded, and the conflict RECOVERY warns that reset --hard drops a peer's commits openRepoTools#214's fence kept; a fast-forward push only — a stack origin has moved past is refused (2), naming S6.3's archive then reset --keep (Q2 on Amendment 23 slice 1b: every writer fetches and fast-forwards under its lock or refuses with nothing written, pushes with no pull and reads origin back, a refused push replays the one edit, and commit with nothing staged is the publish act (#198) openRepoTools#307, answered in decision 44); read back; no replay.

  11. --sweep is refused (2) for every verb but the two hooks (choice 9); --no-sweep is accepted and changes nothing.

  12. The --whole form of commit_publish is slice 2a's: its one caller is write-handoff.

  13. R4-1, from lane openRepoTools-3's re-check of 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) (#198) openRepoTools#282 at d22c8a2 [eead86d], is taken (7e07e62 [7da3d61]). Under the opt-in, lane_row_gate now also gates log, commit, request-handoff and append-line. (Cut.)

  14. 1a's 7eb779a [ecb96ed] and d22c8a2 [eead86d] came in by a --no-ff merge (c14bd3b [139144e]) with no textual conflict. The one semantic conflict was N5's [no-fetch] case meeting S3 step 1, and it is re-pinned. (Cut.)

  15. A confirmed push still prints main's line, pushed origin/<branch> (attempt N) (B23, 2ed9e62 [3905009]). It now prints only after the read-back finds the commit on origin, so "pushed" means confirmed, and lane-start, the triad goldens (T018) and every caller that reads the line stay right. A push it cannot confirm is never that line: it is exit 3.

  16. Writers' fixtures fetch (B18, B31). A test fixture that exported LANES_NO_FETCH=1 and then ran a writer stops exporting it for the writer, since S3 step 1 refuses a writer whose fetch did not run (choice 2). (Cut.)

  17. lane-worktrees sweep --yes writes its NOTED line with a fetch (MUST 3 of the pre-review, 9714694 [6ecfc52]). Helper.call gave every call after the first LANES_NO_FETCH=1, so S3 refused the sweep's register line and the sweep stopped after its first removal. (Cut.)

  18. A replay never drops a commit it did not make (MUST 4, db98ee6 [c454247]). Until slices 2b and 3, lane-start's stamp, lane-handoff's commit and the skill's commit are made in the same checkout with no lock. (Cut.)

  19. claim and add-row run again from the top when S3's fetch finds origin moved under their checks (MUSTs 1-2, caeca9c [10169f3]). Their checks run before the lock, on what log_sync fetched. (Cut.)

  20. One tracked and one untracked log is named as a rename that never landed (NIT 2 of the second report). S3 fast-forwards before ensure_log, so the stray canonical copy a failed case-only rename leaves now meets the published old-cased log in the working tree. 15(d)'s hand merge would publish the copy. (Cut.)

  21. The manual no longer describes the capture or the dirty-checkout guard as current (SHOULD 1). The carry rule now reads the manual a paragraph at a time and knows the capture's words, and the no-pull rule no longer stops at a # (NIT 1). (Cut.)

  22. The tip claim and add-row compare is the one their checks read (MUST-N of the re-check at caeca9c [10169f3]). Decision 19 first asked only whether S3's own fetch moved the ref. (Cut.)

  23. A commit another writer rewrote is named as rewritten (SHOULD A). Until slices 2b and 3, lane-start's stamp and lane-handoff rebase and push in the same checkout with no lock. (Cut.)

  24. A commit that lands in the replay's window is never dropped (NIT 2 of the re-check at caeca9c [10169f3]): its first fix undid the reset where ORIG_HEAD was not this write's commit, which an up-to-date pull --rebase defeats (SHOULD C). Superseded by decision 25; ORIG_HEAD is read nowhere. (Cut.)

  25. The replay's drop is a compare-and-swap (SHOULD C of the re-check at c4393e8 [d9ff4e6], 59c238d [c71621f]). git update-ref -m … refs/heads/<b> <to> <this write's sha> moves the branch only from this write's own commit, by the sha read before it, so no fetch can move it under the drop; then read-tree -m -u <sha> <to> moves the index and the working tree, the two-tree merge reset --keep makes (-i, the index alone, for the carry form). (Cut.)

  26. migrate-state-cells' skip line for a log published under one case and held here under another tells the two states apart (NIT 4 of the re-check at c4393e8 [d9ff4e6], bf77bf2 [9683fd1]). It said "Pull first, or merge them (15(d))". (Cut.)

  27. A file a write edits in place, deleted on origin while its push was in flight, keeps the commit (slice 2a's A37, built here, 8089633 [9cdf8ab]). CP_KEEP_IF_GONE, empty at load and set by the one writer that edits a file in place (2a's write-handoff --stamp/--prepend), names that file. (Cut.)

  28. The refusal with origin unmoved reads HEAD before it says "kept on HEAD" (w2a-c6's note, the shape 2a's A41 took out of its S4.4, 0f26c5e [638b13a]). While the push was in flight, an unlocked writer's commit on top, or a rewrite that took this write's commit off the branch, was misnamed: the words said HEAD was this write's commit. (Cut.)

  29. The scan's two-logs line gives the stray copy B36's recovery (NIT 4's neighbour, c14738a [514b0c4]). Where migrate-state-cells finds exactly two logs whose names differ only by case, one tracked under the published log's name and one untracked, it no longer says "15(d)'s hand merge", which would publish the copy as a second file: compare (diff -- <published> <copy>), re-run any line only the copy holds, rm -- <copy>, re-run. (Cut.)

  30. The last two "kept on HEAD" claims read HEAD too (w1b-c6's find with decision 28, listed by rv1b-c3 as left; 0edcfb4 [f9b1de9]). cp_not_confirmed (exit 3) and the publish act's refusal with origin unmoved (6) said the commit(s) were "kept on HEAD" of a HEAD nobody had read: a commit made on top, or a rewrite that took the commit off the branch, while the push was in flight. (Cut.)

  31. A drop's compare-and-swap refused for another reason is not "a commit on top" (rv1b-c3's NIT E, e3e8124 [3a4bc4a]). update-ref <ref> <new> <old> is also refused where another git process holds refs/heads/<b>.lock (a peer's commit or a pack-refs in flight), and HEAD is then still this write's commit. (Cut.)

  32. "Committed here" is asked of HEAD, not of the index (rv1b-c3's NIT D, 99ea7d3 [251e900]). The skip line for a case-only rename called a STAGED, never committed one (a write's git commit failed after its add) "committed here and not on origin" and gave the archive recovery, whose push is a branch identical to origin's. (Cut.)

  33. The ^ of A37's check has a case (rv1b-c3's NIT F, its mutant M4; 40fa53c [24efc8a]). The creates-it case of the A37 test (origin has no log for the lane when the write starts, the write creates it, a peer's commit lands at the push) must replay and land (0). (Cut.)

  34. The manual names what add-row's refusal names (02f2d08 [5588307]). docs/README-lanes.md said a row a peer pushed is refused "naming it and the git pull --rebase that settles it". It now says the row verbs (set-row-state, replace-in-row) that act on it, each of which fast-forwards first (Amendment 23 S3), as ar_where does.

The fix round from lane openRepoTools-3's review of 02f2d08 [5588307] (6334179243), whose verdict was to land after this round. Decisions 35 to 47, in lane 3's order: before this slice lands (281, 291, 292, 293), before the flip (283, 285 with Q4), then 284 and the NITs. 282 needs no code here; its hold is above. Each fix has a case that is red on the code before it and green after, and an undoing mutant that is killed (the table's C rows).

  1. S3 fast-forwards to the sha its own fetch read (MUST 281, 82977c1 [c71068b]). checkout_sync compared origin's tip with the tip the checks read (decisions 19 and 22), then fast-forwarded to refs/remotes/origin/<b> by name, about ten git calls later. (Cut.)
  2. A write knows its own commit by what it is, and the replay's drop takes it alone (291, 5c5e9d3 [dba848a]). The sha came from rev-parse HEAD after git commit, so an unlocked writer's stamp landing between the two (lane 3's E8) was taken for this write's commit, and the replay dropped it with exit 0. (Cut.)
  3. B22's -C0 stays only behind a uniqueness guard (292, and lane 3's answer to Q3, 0308f0c [db4d2a4]). -C0 drops the context and edits the first copy of a replaced line it finds: the second of two identical lines was pushed onto the first copy, and a replaced blank line landed elsewhere (both reproduced here, red at 5c5e9d3 [dba848a]). (Cut.)
  4. The read-back that stops a doubled log line has a case (293, 4bcc6a8 [2165551]). Lane 3's mutant PB-2 survived every case of the replay and lanes-edit.sh: the RECOVERY "re-run lanes-edit.sh commit" cannot publish an edit that is already committed — on a clean tree commit returns before the push openRepoTools#194 modules. (Cut.)
  5. The managed seam is asked again right after S3 (283, 38fff00 [85575ee]). set-row-state, replace-in-row, append-session-id, rename-lane and write_event's lane verbs asked it only before the lock, on the ref as last fetched, and S3's fast-forward could bring in the ledger's marker: set-row-state wrote over it (lane 3's P2). (Cut.)
  6. Under the opt-in, the gate takes claim, release and the sweeps' runner lane; and under LANES_NO_FETCH=1 every routed writer refuses before any clone (285 with lane 3's answer to Q4, b2638a7 [c2fa058]). LANES_LANE=<typo> claim … cloned a 0.4 GB checkout and published a CLAIMED for a lane nobody started (P4); lane_row_gate's row-or-log list now holds claim, release, retire-rows, and archive-rows and migrate-state-cells (routed with --yes only). (Cut.)
  7. A claim's re-run makes no network read with the lock held (284, 1d59ce5 [a465cb5]). The replay and cs_recheck's re-run hold the lock from the top, and the top held gh_reads (gh issue view, two gh pr list, an ssh ls-remote) and, on a takeover, gh_stale_claim_url, none bounded by git_net (lane 3's P3: all four ran under the lock). (Cut.)
  8. S3 step 6 tells a hand merge to archive first (NIT 287, bd9d6ab [93475ae]). git cherry skips merge commits, so a merge carrying a line of its own read as nothing unpublished, and the printed reset --keep would drop that line. (Cut.)
  9. The NOT TRACKED recoveries work (NIT 288, pre-existing, d6064de [afad3e5]). The migration's skip line, and retire-rows' and archive-rows' refusals (the same words, found with it), gave git commit -m … -- <path> of an untracked file, which git refuses ("did not match any file(s) known to git"), and such a commit would then be an unpublished one S3 refuses every writer over. (Cut.)
  10. The publish act's refusal over a moved origin fills the lane in, and stops first (NIT 294, and lane 3's answer to Q2, 21ad618 [7faa577]). It printed a literal archive/<lane>-stranded-…; it now fills in ${LANES_LANE:-<lane>}, as S3's own refusal does, gives the list and the stop line before the archive, and spells refs/remotes/origin/<b> in its reset --keep (a local branch named origin/main wins the short name). (Cut.)
  11. The manual names the decision 19/22 re-run, and exit row 3 the rewrite (NITs 286 and 296, 5be93b3 [333af4e] and a01ea3a [fc6f34d]). "How to write" step 2 and the claim section say that where S3's fetch finds origin moved under claim's or add-row's checks, nothing is written and the verb runs again from the top with the lock held (within the six attempts; 2 at the sixth), reading nothing from GitHub again. Exit row 3, in both tables and in step 5, says the commit stays on HEAD unless another writer rewrote it while its push was in flight, which the words then say. (Cut.)
  12. The body's The register's rewrites refuse a copy or write they cannot prove and put a cut-short register back, retire-rows refuses an unpushed log rename before it writes and calls a lane closed only from its published log, and add-row refuses an archive row it cannot read and takes back a row refused after its rebase (#120, #122) openRepoTools#263 re-pinning list is corrected (NIT 295): see Merging main.
  13. 282 is a hold, with no code here (MUST 282): see Hold, above.

The second round: lane openRepoTools-3's re-checks of a01ea3a [fc6f34d] and b98d3a7 [15e71d9], its 393 on opensoft/openRepoTools#307, 351's companion and 360 from its review of opensoft/openRepoTools#308, and one finding of slice 2a's writer. Decisions 48 to 58, in commit order. Each fix has a case red before it and green after, and an undoing mutant that is killed (C24 to C53).

  1. A replaced line must also occur once in the version the edit was made on (SHOULD 341, b98d3a7 [15e71d9]; 382's case 0fb5416 [a44b73d]). Decision 37 counted it in origin's version only, so a line unique there but doubled where the edit was made (a copy a peer changed since, or the register's own shape) was still applied with -C0, onto the wrong copy. (Cut.)
  2. Under the opt-in, the NOT TRACKED recovery makes no commit in W (331, 5fb952d [53108b4]). Where LANES_REPO is W (by git common directory, as S5), untracked_recovery says to move the file into the lane's own checkout (after lane-checkout <lane> --create), add and commit it there, then the publish act, or to rm it. (Cut.)
  3. S3's and the publish act's printed commands spell refs/remotes/origin/<b> (333 with 342, 941163c [921dd58]): step 5's list and its archive reset, step 6's reset, and the publish act's list. A local branch named origin/main wins the short name; the cases run each printed command word for word past one.
  4. A claim's re-run carries attempt 1's reads capped at 32,768 bytes (334, 479d413 [aa45be1]). An object with about 1,800 claim comments made the exec fail with 126, since Linux caps one environment string at 128 KiB. The copy is cut back to a whole line, with a line saying so; attempt 1 printed them in full.
  5. Once the read-back finds a carried edit on origin, each copy this run saved is removed by name (393, which is 351's companion; 89e98f2 [50e8916]). Step 7 records each copy it writes (CP_CARRY_COPIES); the record crosses both execs as LANES__CARRY_COPIES, read and unset at load and honoured only for this pid. (Cut.)
  6. cp_under names both causes and gives the archive (343, 27fede5 [af29be6]): the commits under this write's are ones origin lacks and this write did not make, another writer's here or history origin no longer has (or never had); the list and the STOP line, then S6.3's archive, reset and redo (cp_archive_steps, which cp_publish_moved shares), never the publish act, which refuses there. The four cp_not_head callers that read HEAD after a push say "before or while its push was in flight"; the three-argument form is unchanged. (Cut.)
  7. The refusal with origin unmoved lists the commits under this write's, with the STOP line, before any reset (344, 4c6da04 [173f3c8]), where <sha>^ is not on origin's history; its reset names the full ref. Exit row 6 and step 5 say that commit's carry form drops to its own parent by design: a commit under it stays, the hand edit goes back to the working tree with its copy kept, and the re-run's S3 refuses on it (2).
  8. A writer whose checkout is removed while it waits for the lock refuses at once with 1 (360 of the review of Amendment 23 S6.2: lane-end --sweep-checkouts removes the register checkouts CLOSED lanes left under <W>-lanes/ only where nothing holds them, leaves first, each decided under its lock after a fetch, and records them in a NOTED line (#198, slice 4) openRepoTools#308, c248df2 [40c57b9]). mutex_try took a lock whose directory is gone for a held one: the waiter exited 4 after about a minute, "another lanes-edit run is active". (Cut.)
  9. SC2093 is disabled on this slice's two routing exec lines (ee16b26 [126bccd]), with the reason in 1a's form; 1a's own came with 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) (#198) openRepoTools#282's 4d6bc14 [6ca3158], merged in ca94468 [29103a5] with no conflict. shellcheck 0.9.0 with lint.yml's baseline flags exits 0 on lanes-edit.sh, also at d8859c8 [59551dd].
  10. A LANES_REPO below its checkout's top level refuses (2) before any edit (found by slice 2a's writer, d8859c8 [59551dd]). The helper spells every pathspec from the top level, and git reads one relative to where it runs: with LANES_REPO=<c>/handoffs and LANES_FILE=<c>/lanes/LANES.md, every writer made its edit and then died 6 at git add, and step 7 never saw commit's hand edit (a4e4462 [874e020]'s git add was the same). (Cut.)
  11. This description (NIT 381): decision 37 and C05 name CS_CARRY_BASE, and the counts and the mutant table are this head's.

The third round: lane openRepoTools-3's re-check of 59551dd (6368621425), NITs 701-707:

  1. lock_gone_die names the lock's own folder, with a restore that works (701, 70edb34 [9137569]). For the checkout's own lock the restore is git -C <c> checkout HEAD -- <dir>, as before. For any other lock, such as the workstation's sidecar lock in <W>-lanes/, it names that folder, says git cannot restore it, and says to move it back. Exit 1 is unchanged.
  2. S6.3's archive names the lane the write was given, or none (702, 88a3692 [9785ad9]). cp_archive_steps prints archive/<lane>-stranded-<UTC> with LANES_LANE or S3's lane (CS_LANE), or archive/stranded-<UTC> where no lane is named. It never prints a literal <lane>. S3's own refusal is not changed.
  3. A claim's re-run posts the whole reads (706, 8a88f50 [0ddde5e]). Over 334's cap, attempt 1 also writes the reads whole to <git dir>/lanes-edit/claim-reads-<UTC>-<pid>.txt. Its name crosses the exec (LANES__CLAIM_READS_FILE), the comment's "three reads" come from it, and the EXIT trap removes it. The environment string keeps the cap. A file that cannot be written falls back to the capped copy, marked.
  4. Every printed cherry -v and reset --keep names refs/remotes/origin/<b> (703, 7fe295d [458cdde], the class of 333 and 342). This covers six lines, cp_rewritten's log --oneline -5 and README row 6. A case runs the LOOK line as printed past a shadow origin/main branch.
  5. Where origin moved, the sibling notes give S6.3's recovery (705, 97fb75c [f97723c]). The new cp_recovery_steps gives the publish act's list, its STOP line and the archive. It is used by the bound, the keep-if-gone note, and cp_not_head's two notes when they have no fourth argument. With a fourth (origin unmoved) the publish act is still offered. cp_rewritten's offer stands, because the rewrite sits on origin's tip.
  6. A refused re-run carry names every copy it keeps (707, 9fcbf30 [80ae89e]). cs_earlier_copies reads 393's record and names every copy but the last.
  7. The two-copies test no longer depends on the clock (704, 07bf43c [4b79d67]). It asserts one removal note per recorded copy, and a [same-second] case gives both runs one second with a date shim.

What changed

  • lanes-edit.sh:
    • S3: checkout_sync and its helpers (cs_refuse, cs_refuse_register, cs_own_pending; cs_recheck with CS_RECHECK, and log_sync's cs_seen_mark with CS_SEEN, for decisions 19 and 22); wired into write_event, set-row-state, replace-in-row, append-session-id, append-line, add-row, claim, commit, migrate-state-cells, retire-rows, archive-rows and rename-lane (the sweeps and rename-lane also ask cs_own_pending right before their first byte).
    • S4: commit_publish, cp_confirm, cp_pushed, cp_not_confirmed, cp_replay (its drop a compare-and-swap, decision 25), cp_not_head, cp_rewritten (both with the optional fourth and fifth arguments: decisions 28 and 30), cp_lost_hold, CP_KEEP_IF_GONE (decision 27), cp_publish_act, cp_publish_moved, the replay markers (LANES__REPLAY_PID, LANES__ATTEMPT, read and unset at load), and claim_lost_write.
    • migrate-state-cells' scan: the skip lines of decisions 26, 29 and 32.
    • Deleted with the pull: commit_push's loop, dirty_elsewhere and the 5(d) straight push, git_timeout_die, cp_publish_refence, add_row_retirement_rescan, CP_AFTER_REBASE, tracked_dirty, refuse_dirty_checkout, handle_preexisting, capture_register_edit and PRE_DIRTY_LANES.
    • Text: the exit table (rows 0, 2, 3, 6), the --sweep header and usage lines, every "Pull first — git pull --rebase" refusal, and every comment that named commit_push or its rebase.
    • The fix round (decisions 35-45): checkout_sync's cs_now and cs_to (281), step 6's merge branch (287) and cs_removed_unique (292); cp_own_commit, cp_untold and cp_under (291); the managed seam after S3 in write_event, set-row-state, replace-in-row, append-session-id and rename-lane (283); lane_row_gate's verb list and lane_checkout_enter's LANES_NO_FETCH refusal, with the gate's own branch gone (285, Q4); LANES__CLAIM_READS and LANES__CLAIM_URL with claim's re-run (284); untracked_recovery (288); cp_publish_moved's recovery (294); exit rows 3 and 6.
    • The second round (decisions 48-57): CS_CARRY_BASE in cs_removed_unique (341); untracked_recovery's branch for W (331); the full ref in S3's and the publish act's printed commands (333); reads_carry (334); CP_CARRY_COPIES, LANES__CARRY_COPIES and cp_drop_carry_copies (393); cp_under's words and cp_archive_steps (343); the origin-unmoved list (344); mutex_try's 2 and lock_gone_die, for the checkout's lock and the creation lock (360); the two SC2093 disables; S3's top-level refusal (decision 57); exit rows 1, 2 and 6.
  • docs/README-lanes.md: the exit-code rows 0, 2, 3 and 6; add-row's refusal sentence (decision 34); the Layout note on the register's checkout; "How to write" (its five steps and the recovery, which no longer resets with --hard); the claim section's pull and capture paragraphs; and, in the fix round, step 2's carry and re-run (292, 286), step 5's drop and rewrite (291, 296), the claim section's re-run (286), and rows 3 and 6 (296, 291); and, in the second round, step 2's carry (341), "no GitHub read" (332), step 5 and row 6 for the carry form (344), and row 2 (decision 57).
  • Tests: see below.

Tests

Module Before (at a4e4462 [874e020]) After (at d8859c8 [59551dd])
tests/test_a23_guard_refusals.py (new) – 150 passed (136 at a01ea3a [fc6f34d]; the second round adds 331's two, 333's two and decision 57's ten)
tests/test_a23_publish_replay.py (new) – 76 passed (66 at a01ea3a [fc6f34d]; the second round adds 341's two, 333's, 382's, 334's, 393's two, 343's and 344's two)
tests/test_commit_push_published_edit_194.py (rewritten where it pinned the pull) 25 passed 25 passed
tests/test_repo_hygiene.py (exit-3 rule re-pinned; two new rules) 237 passed 253 passed
tests/test_guard_launch_mode.py 13 passed 13 passed
tests/test_a23_lock.py (two cases moved off the pull; 360's four) 19 passed 28 passed
tests/test_deferred_97_findings_157.py (4d's fixture) 9 passed 9 passed
tests/test_a23_write_routing.py (R4-1's cases; composite asserts split; 285's and Q4's cases) 34 passed 57 passed
tests/test_register_scan_hardening_138.py 28 passed 29 passed
tests/test_lane_helpers_suite.py (the shell suite runs as one case; CI only) not run here passed in CI at d8859c8 [59551dd] (run 39891826693)

Each local run was one pytest process per file, with every LANES_* and TMUX* unset, GH_TOKEN=bogus and a stub gh first on PATH. After: every module above at d8859c8 [59551dd], one pytest process each (runs/c15-final-set.log). Also run at their own commits' trees: lane_checkout 27, lane_start_read 10 and refresh_workspace 13 (285's), rename_publication_atomic_158 20 (283's), skill_shell_blocks 6 and test_roots 39 with 1 skipped (each manual change, the last at d8859c8 [59551dd]). CI at 34799b7, as lane openRepoTools-3 read it: tests 3475 passed, 94 skipped; tests-no-submodule 2591 passed, 978 skipped. CI's results at this head are on the PR's checks.

Mutants

125 one-line mutants of lanes-edit.sh (mutants.py, a harness outside the repository), each run against the case(s) the second table names, one pytest process each, with the helper put back at once. Each one's text occurs exactly once at d8859c8 [59551dd] (checked). All 125 are KILLED. The first round's 95 were run in one run at a01ea3a [fc6f34d] (runs/c8-mut-all95.log; S10 and R01 re-anchored there, decisions 35 and 40; R03 retired: its line went with Q4, and C13 undoes the rule that replaced it). C24 to C53 were run once each at its own fix (the decision names the commit), killed at their own assertions. Five whose lines later commits reworded were re-anchored and run again at d8859c8 [59551dd]: B04 (343's words), C16 and C17 (334's capped copy), C21 (333's full ref) and C38 (the SC2093 comment). C06 is lane 3's PB-2; C02 is killed by the words naming the stamp as this write's commit, since C03's guard refuses its drop. Controls, not rows: B15 against the keep form's case alone, and B30 against the three A37 cases older than creates-it, both SURVIVED, as they should.

Mutant What it undoes
S01 LANES_NO_FETCH=1 is a failed fetch for a writer
S02 a fetch that fails refuses
S03 an operation in progress refuses
S04 HEAD off the branch refuses
S05 a foreign staged entry refuses
S06 an unpushed commit refuses
S07 the publish act is exempt from the unpushed row
S08 a diverged checkout refuses, by name
S09 a pending register edit refuses a write that does not commit it
S10 the checkout is fast-forwarded before the edit
S11 the publish act makes no fast-forward
S12 commit applies the carried hand edit again
S13 an insertion is never applied without its context
S14 a row edit carries past a neighbour's changed row
S15 write_event runs S3 under the lock
S16 a row verb runs S3 under the lock
S17 --sweep is refused
P01 the read-back decides confirmed
P02 a timed-out push goes to the read-back, not the replay
P03 an unconfirmed push exits 3
P04 a refused push replays
P05 six attempts in all
P06 the replay drops this write's commit (B40: the tree step after the branch's CAS)
P07 a refusal with origin unmoved is 6, not a replay
P08 commit's carry form replays its hand edit
P09 the replay's markers are read at load
P10 a replay adopts its lock at load
P11 a replay's claim rescans first
P12 CLAIM-LOST exits with its code
P13 the publish act lists the queue before the push
P14 the publish act refuses a stack origin moved past, before any push
R01 R4-1: log, commit, request-handoff and append-line are gated
R02 R4-1: a published log passes a lane with no row
R04 R4-1: a log cat-file does not find is no log, not no answer
B01 B35: add-row runs again from the top when S3's fetch moved origin
B02 B35: claim runs again from the top when S3's fetch moved origin
B03 B35/B37: origin's tip after S3's fetch is compared at all
B07 B37: the comparison's base is the tip the checks read, not the one before S3's fetch
B08 B37: a tip that differs from the one the checks read re-runs the verb
B09 B37: cs_seen_mark records the tip at all
B10 B37: log_sync records the tip its fetch left
MB1 SHOULD B: cs_recheck stops at the sixth attempt (rv1b-c's MB1)
B04 B34: the replay asks HEAD first, and keeps a commit made on top
B11 B38: a commit not in HEAD is named as rewritten, not as kept on HEAD
B12 B40: the drop moves the branch only from this write's own commit (the CAS's old value)
B13 B40: a refused drop names the state, and moves nothing
B14 B40: a refused tree step's put-back is a CAS too (NIT 3)
B15 B40: the carry form's drop is the same CAS (NIT 3; a mixed-only CAS loss)
B17 item 1 (w2a-c6's note): the origin-unmoved refusal reads HEAD first, and names a commit on top or a rewrite in cp_not_head's words
B21 item 7: a refused carry-form tree step says the hand edit could not be given back
B22 item 7: a refused tree step puts the branch back on this write's commit
A01 A37: a file the write edits in place, gone from origin, keeps the commit (6)
A02 A37: a file neither the write's base nor its commit has is never gone (the existence test as a whole; the ^ is B30's)
B16 NIT 4: the migration's skip line tells a committed rename from the stray copy
B18 item 2: the stray copy beside the published log gets B36's recovery
B19 item 2: a tracked log is told from an untracked one
B20 item 2: the stray-copy words only where the tracked one is the published log
B05 B36: one tracked and one untracked log is the stray copy's refusal
B06 B36: a tracked log is told from an untracked one
B23a item 1: the exit-3 die reads HEAD first (a commit on top)
B23b item 1: the exit-3 die reads HEAD first (a rewrite)
B24a item 1: the publish act's refusal with origin unmoved reads HEAD first (a commit on top)
B24b item 1: the publish act's refusal with origin unmoved reads HEAD first (a rewrite)
B25 item 1: a commit on top of an unconfirmed push keeps exit 3
B26 item 1: a rewrite of an unconfirmed push keeps exit 3
B27 item 1: cp_not_head hands the exit status on to cp_rewritten
B28 item 2: git's own words are shown where the drop's compare-and-swap is refused
B29 item 2: where HEAD is still this write's commit, no commit on top is named
B31 item 3: "committed" is asked of HEAD, not of the index
B32 item 3: a rename that is only staged has a line of its own
B33 item 3: a staged rename is unstaged by restore --staged of both of its paths
B30 item 4 (rv1b-c3's M4): the file is asked of this write's base, not of its commit
C01 281: the fast-forward goes to the sha S3's fetch read, never the ref by name
C02 291: this write's sha is not HEAD read after the fact (E8)
C03 291: the keep form's drop is made only where this write's commit sits on origin's history
C04 291: two commits with this write's message are no answer
C05 292: -C0 is tried only where every line the edit replaces occurs once in origin's version, and once in HEAD's blob before the fast-forward (CS_CARRY_BASE)
C06 293 (lane 3's PB-2): a refused push that landed is read back and pushed, never replayed
C07 283: write_event's lane verbs ask the seam again after S3
C08 283: set-row-state asks the seam again after S3
C09 283: replace-in-row asks the seam again after S3
C10 283: append-session-id asks the seam again after S3
C11 283: rename-lane asks the seam again after S3
C12 285: R4-1's gate takes claim, release and the sweeps' runner lane
C13 Q4: under LANES_NO_FETCH=1 every routed writer refuses before any clone
C14 284: a re-run makes no Rule 1 read with the lock held
C15 284: a takeover's re-run reads no stale-claim URL with the lock held
C16 284: the stale claim's URL crosses the exec
C17 284: Rule 1's reads cross the exec
C18 NIT 287: a diverged HEAD holding a merge origin lacks is told to archive first
C19 NIT 288: the migration's NOT TRACKED line gives a recovery that works
C20 NIT 288's sibling: retire-rows' NOT TRACKED refusal gives the same recovery
C21 NIT 294: the stop line comes before the archive
C22 NIT 294: the archive names the lane
C23 NIT 294 (Q2's option): the reset names the tracking ref in full
C24 341: each line the edit replaces is also counted in the version the edit was made on
C25 341: the version the edit was made on is HEAD's, not origin's after the fetch
C26 331: under the opt-in, W's NOT TRACKED recovery gives the move into the maker's lane checkout, never a commit in W
C27 331: the publish act in the NOT TRACKED recovery names the lane whose checkout this is
C28 331: W is known by its git common directory, so the dry run in W takes the W branch
C29 332: the re-run's note says it makes no GitHub read (its git fetches and push still run)
C30 333/342: S3 step 5's list line names refs/remotes/origin/
C31 333/342: S3 step 5's archive recovery resets to refs/remotes/origin/
C32 333/342: S3 step 6's no-merge recovery resets to refs/remotes/origin/
C33 333: the publish act's list line names refs/remotes/origin/
C34 382: the carry's base read from HEAD's blob (a staged hand edit is the same edit), not from the index
C35 334: the claim's carried copy of Rule 1's reads is capped at 32768 bytes before it crosses the exec
C36 393: cp_pushed removes the run's copies
C37 393: every copy is recorded, not only the last
C38 393: the record crosses the replay's exec
C39 393: the re-run reads the record at load
C40 393: never a glob on the pid
C41 393: never a glob on every copy
C42 393: removed only after cp_confirm's proof, never at the commit
C43 343: cp_under names commits origin lacks, with both causes, not another writer here alone
C44 343: cp_under gives S6.3's archive after the list and the STOP line
C45 343: cp_under never offers the publish act, which refuses there
C46 343's smaller half: the replay's HEAD check says the commit on top came before or while the push was in flight
C47 344: where this write's commit does not sit on origin's history, the origin-unmoved refusal lists what is under it with the STOP line
C48 360: mutex_try no longer tells a lock's missing directory from a held lock
C49 360: acquire_lock no longer names a gone checkout (mutex_try's 2 read as a held lock)
C50 360: a checkout still there is called removed where only its lock's directory went
C51 360: the creation lock's gone -lanes/ is said as another writer creating the checkout
C52 360: lock_try turns mutex_try's 2 into 1
C53 the pathspec item: a LANES_REPO below its checkout's top level is no longer refused before the edit
Case (module) Mutants it kills
test_every_S3_row_refuses_every_writer_with_nothing_written (guard) [set-row-state--no-fetch] S01; [set-row-state--fetch-fails] S02; [set-row-state--cherry-pick] S03; [set-row-state--branch] S04; [set-row-state--staged] S05; [set-row-state--unpushed] S06, S16; [set-row-state--diverged] S08; [log--pending-register] S09; [log--unpushed] S15
test_publish_act_is_exempt_from_unpushed_only (replay) [unpushed-only] S07
test_the_1312Z_shape_is_pushed (guard) S10
test_a_checkout_behind_origin_with_nothing_of_its_own_is_still_a_no_op (opensoft/openRepoTools#194) S11
test_commit_carries_a_hand_edit_across_the_ff (replay) S12, S14, C36
test_commit_refuses_an_insertion_whose_context_moved (replay) S13
test_sweep_flag_is_refused (replay) [set-row-state] S17
test_timed_out_push_is_read_back (replay) [lost] P01, P02
test_unconfirmed_push_exits_3 (replay) P03
test_refused_push_replays_on_a_fresh_fast_forward (replay) P04, P06, P09
test_bound_keeps_the_commit_on_head_exit_6_and_next_write_refuses (replay) P05
test_push_refused_with_origin_unmoved_is_kept_exit_6 (replay) P07
test_commit_replay_keeps_the_hand_edit (replay) P08
test_add_row_replay_refuses_on_peer_retirement (replay) [retires-it] P10
test_replay_reruns_claim_to_claim_lost_exit_7 (replay) P11, P12
test_publish_act_lists_cherry_before_push (replay) P13
test_publish_act_on_moved_origin_refuses (replay) P14
test_a_write_for_a_lane_with_no_row_and_no_log_clones_nothing (routing) [log] R01, R04; [commit] R01; [request-handoff] R01; [append-line] R01; [claim] C12
test_a_lane_with_a_published_log_and_no_row_is_no_reason_to_refuse (routing) R02
test_an_add_row_whose_name_lands_before_S3s_fetch_refuses (replay) [retires-it] B01, B03; [adds-it] B01
test_a_rival_claim_landing_before_S3s_fetch_loses_exit_7 (replay) B02, B03
test_a_rival_claim_a_reader_fetched_into_the_shared_checkout_loses_exit_7 (replay) B03, B07, B08, B09, B10
test_an_add_row_whose_name_a_reader_fetched_into_the_shared_checkout_refuses (replay) B07, B08, B09, B10
test_a_claim_that_meets_a_move_at_every_S3_fetch_stops_at_six_exit_2 (replay) MB1
test_a_commit_made_on_top_before_the_replay_is_kept_exit_6 (replay) B04
test_a_commit_made_on_top_at_the_sixth_push_is_named_before_the_bound_exit_6 (replay) B04
test_a_commit_another_writer_rewrote_is_named_as_rewritten_exit_6 (replay) B11
test_a_commit_landing_before_the_replays_drop_is_never_dropped_exit_6 (replay) B12, B13
test_a_drop_whose_tree_step_and_put_back_are_both_refused_is_named_off_HEAD_exit_6 (replay) B14
test_a_commit_landing_before_the_carry_forms_drop_is_never_dropped_exit_6 (replay) B15
test_a_push_refused_with_origin_unmoved_names_a_commit_on_top_exit_6 (replay) B17
test_a_push_refused_with_origin_unmoved_names_a_rewritten_commit_exit_6 (replay) B17
test_a_carry_forms_drop_whose_tree_step_is_refused_keeps_the_commit_exit_6 (replay) B21, B22, C42
test_a_file_the_write_edits_in_place_deleted_on_origin_keeps_the_commit_exit_6 (replay) [deletes-it] A01; [never-had-it] A02; [creates-it] B30
test_a_migration_dry_run_names_a_rename_that_never_landed_and_never_says_pull (guard) [committed] B16
test_a_migration_dry_run_names_a_stray_copy_beside_the_published_log_as_one (guard) [stray-copy] B18, B19; [committed-rename] B20
test_a_stray_copy_from_a_rename_that_never_landed_is_named_as_one (guard) B05, B06
test_an_unconfirmed_push_reads_head_before_it_says_kept_on_head_exit_3 (replay) [on-top] B23a, B25; [rewritten] B23b, B26, B27
test_a_publish_act_push_refused_with_origin_unmoved_reads_head_exit_6 (replay) [on-top] B24a; [rewritten] B24b
test_a_drop_refused_for_another_reason_names_git_and_no_commit_on_top_exit_6 (replay) B28, B29
test_a_migration_dry_run_never_calls_a_staged_rename_committed (guard) B31, B32, B33
test_a_rival_claim_a_reader_fetched_in_after_S3s_compare_loses_exit_7 (replay) C01
test_an_add_row_whose_retirement_a_reader_fetched_in_after_S3s_compare_refuses (replay) C01
test_a_commit_landing_just_after_this_writes_commit_is_never_taken_for_it_exit_6 (replay) C02, C46
test_a_commit_landing_just_before_this_writes_commit_is_never_dropped_with_it_exit_6 (replay) C03, C44, C45
test_a_commit_with_this_writes_own_message_made_with_it_is_no_answer_exit_6 (replay) C04
test_commit_refuses_a_hand_edit_whose_line_occurs_twice_on_origin (replay) C05, C24, C25; [the-registers-own-shape-staged] C34
test_a_push_git_called_failed_that_landed_under_a_peers_commit_is_written_once (replay) C06
test_a_marker_S3_brings_in_is_asked_again_and_refuses_the_legacy_act (guard) [log-PAUSED] C07; [set-row-state] C08; [replace-in-row] C09; [append-session-id] C10; [rename-lane] C11
test_no_fetch_refuses_every_routed_writer_before_any_clone (routing) [set-row-state] C13
test_a_claims_re_run_makes_no_network_read_while_it_holds_the_lock (replay) C14, C17, C29
test_a_takeovers_re_run_cites_the_claim_url_attempt_1_read_and_reads_nothing_with_the_lock (replay) C15, C16
test_a_diverged_checkout_holding_a_hand_merge_is_told_to_archive_first (guard) C18
test_a_migration_dry_run_gives_an_untracked_log_a_recovery_that_works (guard) C19
test_retire_rows_gives_an_untracked_log_the_same_recovery_and_it_works (guard) C20
test_the_publish_acts_moved_origin_recovery_names_the_lane_and_stops_first (replay) C21, C22, C23
test_under_the_opt_in_a_migration_dry_run_in_W_gives_no_commit_in_W (guard) C26, C28
test_a_routed_sweep_names_its_runners_lane_in_the_publish_act (guard) C27
test_s3s_recovery_commands_read_origin_past_a_local_branch_named_origin_main (guard) [unpushed] C30, C31; [diverged] C32
test_the_publish_acts_list_line_reads_origin_past_a_local_branch_named_origin_main (replay) C33
test_a_claims_re_run_carries_reads_too_long_for_one_environment_string (replay) C35
test_a_lost_races_two_copies_of_the_hand_edit_are_both_removed_once_origin_has_it (replay) C37, C38, C39
test_a_carry_removes_only_the_copies_it_wrote_never_another_runs_under_the_same_pid (replay) C40, C41
test_history_origin_no_longer_has_under_this_writes_commit_is_never_called_a_writer_here_exit_6 (replay) C43, C44, C45
test_a_push_refused_with_origin_unmoved_over_a_stamp_under_it_lists_them_and_stops_first_exit_6 (replay) C47
test_a_writer_waiting_on_a_checkout_that_is_removed_refuses_at_once (lock) [removed] C48, C49, C52; [moved-aside] C48, C49, C52; [its-lanes-directory-removed] C48, C49, C50, C52
test_a_writer_waiting_to_create_a_checkout_whose_directory_is_removed_refuses_at_once (lock) C48, C51
test_a_lanes_repo_inside_the_checkout_refuses_before_any_edit (guard) C53
test_the_top_level_the_refusal_names_carries_the_hand_edit (guard) C53

Merging main

This branch merges slice 1a' (903d751), which carries the code leg's main 577110e, in 195fca9, and re-pins opensoft/openRepoTools#263's module in 34799b7: decisions B70-B76, above. The three conflict regions were the same in a simulation against main with and without T017's #3 to #7. opensoft/openRepoTools#263's comments and refusal that auto-merged now describe 1b; opensoft/openRepoTools#284's, opensoft/openRepoTools#265's, opensoft/openRepoTools#289's, opensoft/openRepoTools#292/opensoft/openRepoTools#294's and opensoft/openRepoTools#296's commits and the code leg's own merge cleanly.

Slices 2a and 4. Re-homed here as 1320847 [7bc572c] and e5063e0 [7d19459], each merges this head with no conflict (git merge-tree --write-tree, git 2.43, at 34799b7; slice 4's later head here, 2d7f80d, merges it clean as well). Resolving row 6 to main's side instead would have made 2a conflict there.

Not changed

  • lane-start and lane-handoff: they still pull for the handoff stamp, and lane-start's carry note (hs_carry_note) still promises that the next lanes-edit.sh write carries a stranded stamp. Under this PR that write refuses instead (S3's unpushed row), which is MUST 282's hold. Both are slice 2b's, which rewrites hs_carry_note's middle branch to name the publish act; test_no_lane_writer_pulls_with_rebase asserts lanes-edit.sh only until then.
  • The skill text (skills/handoff/SKILL.md): slice 3's.
  • first_landed_of and archive_text read the short ref origin/$LANES_BRANCH, which a local branch named origin/main shadows (lanes-edit.sh: a write with nothing staged still publishes the local commits origin lacks, so the RECOVERY's re-run of commit pushes the edit it stranded, and the conflict RECOVERY warns that reset --hard drops a peer's commits openRepoTools#214's bypass 3 shape). Found, not this slice's.
  • The empty-origin push -u path has no test, as before.
  • The routing module's other ten composite asserts are slice 1a's lines, and are left to it (SHOULD 3 split the 16 in this slice's own lines).
  • NIT 5 (the re-check at c4393e8 [d9ff4e6]): the optional narrowing is not made, and lane 3 agrees (284: "still right"). A claim or add-row that waited on the lock behind any push in a shared checkout re-runs, with no network read since decision 41, and the six-attempt bound and decision 35's fast-forward to the fetched sha stop it, since a reader can move the ref too (281). A narrowing would be one more place a missed move could hide (MUST-N's class).
  • The publish act's own call of cp_not_confirmed (the fence's tip as the sha, cp_publish_act) has no case of its own for a commit on top or a rewrite: it shares the one code path the [on-top] and [rewritten] cases of decision 30 cover through commit_publish.
  • The push still sends the branch, not this write's commit alone. A commit an unlocked writer lands on top of, or under, this write's commit before its push, goes out with it when the push succeeds, as it did before this PR; decision 36 makes sure the replay never drops one, and S3 refuses every later write over one that stays local. Sending <sha>:refs/heads/<b> instead is not in this round.
  • claim_is_stale and checkout_sync share the variable names cs_lane, cs_obj, cs_utc, cs_file and cs_line (bash has no locals here). They never run at once (the stale test runs before S3), so it is harmless today. Found, left.
  • Two kept copies after a lost race whose re-run carry is refused (found with 393): attempt 1's copy and attempt 2's, the same edit, both stay, and the refusal (2) names only the last. Both are kept edits, so lane-end's sweep holds on either; naming both is a small follow-up.
  • cp_rewritten's words, and exit row 3's, still say the rewrite came "while its push was in flight" (found with 344). An unlocked writer's rebase between git commit and the push is possible too, as 343 found for a commit on top (decision 53). Lane 3 named only the HEAD check, so they are left.
  • 360's two other timings. Decision 55 covers a writer waiting on the lock. A writer still checking when its checkout goes exits 1 in lane_checkout_verify, naming a mv of the gone path; and an exported LANES_REPO naming the removed path is told to re-run wip init. Lane 3's fix named mutex_try only.
  • The cutover (Triad migration (feature 004-migrate-to-triad): execution tracking under Brett Heap's word of 2026-10-07 openRepoTools#186): docs/README-lanes.md goes to openRepoTools-spec. If this slice is still open at the cutover, its manual edits become an openRepoTools-spec change, and the rest becomes an openRepoTools-code change. Every test here that reads a docs/ file goes through doc(...), which skips in the code leg.

Refs opensoft/openRepoTools#198

🤖 Generated with Claude Code

Brett Heap and others added 30 commits October 20, 2026 11:03
…heckout under the lock before it edits, or refuses with nothing written (#198, slice 1b stage 1)

The writers committed first and synced second: an edit made on whatever the
checkout last saw, then a `pull --rebase` in front of the push. A checkout
one adjacent row behind conflicted, aborted and kept the write as a local
commit origin never heard of, which is the stranding #198 measured and
Amendment 23 exists to end.

`checkout_sync` is S3, written once and run by every writer under its
checkout's lock, before it reads the file it writes, routed or not: the
fetch (a failed one, or LANES_NO_FETCH=1, refuses), an operation in
progress or unmerged entries, not on the branch, staged entries that are
not the write's own, unpushed commits by content, a diverged branch, a
pending change to a path the write commits (the register refuses every
verb but `commit`, which carries the hand edit across the fast-forward),
and the fast-forward itself, whose refusal quotes git and names the file.
Each refusal is exit 2, names the checkout, says nothing was written,
prints its row's recovery and releases the lock. Every ref is spelled in
full, so a local branch named origin/main cannot shadow the tracking ref.

It replaces `refuse_dirty_checkout` and the two captures in `write_event`
and in set-row-state, replace-in-row, append-session-id, append-line,
add-row and `commit`. `claim` loses its whole-checkout guard (ratified
choice 8), `--sweep` is withdrawn with exit 2 (choice 9), and `commit` is
the hand-edit commit or, with a clean register, the publish act.
`commit_push` is unchanged in this stage; S4 replaces it next.

New: tests/test_a23_guard_refusals.py, every S3 row against every writing
verb, the fast-forward's refusal, choice 8's pass, the 13:12Z shape, and
the guard through a routed lane checkout and through W with routing off.
tests/a23_box.py gains the seam checkout, a logging LANES_GIT shim and the
snapshot a refusal is compared against.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit a86ba1077633b8d0564dd802006334e7119d38bd)
… and reads origin back, a refused push replays the one edit on origin's new tip six times at most, and `commit` with nothing staged is the publish act (#198, slice 1b stage 2)

`commit_publish` replaces `commit_push`. The commit by pathspec stays as it
was (the case-only rename's index commit, the bytecode refusal, `add`,
`commit`); nothing staged is still a no-op with no push. What changed is
everything after the commit:

- The push sends `refs/heads/main` with no pull. A push that went, or that
  timed out, is read back: a fresh fetch, then `merge-base --is-ancestor`.
  Confirmed is 0 and `pushed … confirmed by a read-back`; not confirmed is 3,
  `pushed, not confirmed: <sha>`, with the commit kept on HEAD and the
  look-by-content steps printed. Exit 3 now has that one cause.
- A refused push is read back first (it may have landed). Where origin moved,
  `reset --keep` drops this write's commit, the only unpublished one S3
  allows, and the same argv runs again under the same pid with the lock
  adopted across the `exec` (`LANES__REPLAY_PID`, `LANES__ATTEMPT`, read and
  unset at load). The verb's own checks re-run on the new content: `claim`'s
  rescan first (CLAIM-LOST, 7, through the new `claim_lost_write`), the
  one-match guard, `add-row`'s retirement and duplicate checks.
- At the sixth attempt the commit is kept on HEAD and named (6); every later
  write from that checkout refuses on S3's unpushed row (ratified choices 4
  and 5). A `reset --keep` git refuses, and a push refused with origin
  unmoved (a hook, a permission: no lost race), are 6 the same way.
- `commit`'s carry form replays by `reset --mixed <sha>^`, because its edit
  is the hand edit and no argv makes it again: `reset --keep` would have
  dropped it and the re-run would have been an empty publish act.
- A carried row edit whose own lines are unchanged on origin applies with no
  context when its full context no longer matches, so a neighbour's row
  changed on origin does not refuse it; an insertion has no line of its own
  and still needs its context.
- `commit` with nothing staged is the publish act: S3 without the unpushed
  row and without the fast-forward; the queue listed by content before the
  push; #214's fence kept; a fast-forward push only, refusing (2) a stack
  origin has moved past and naming S6.3's archive then `reset --keep`
  (Open question 2); then the read-back, and no replay.

Removed with the pull: `dirty_elsewhere` and the 5(d) straight push,
`git_timeout_die`, `cp_publish_refence`, `add_row_retirement_rescan`,
`CP_AFTER_REBASE` and its `ADD_ROW_*` state, and the CLAIM-LOST block after
the pull. The unpushed refusal keeps NIT 5's words ("if any '+' line is not
your own write, STOP and take it to the lane its subject names").

The exit table (rows 0, 2, 3, 6) and the manual's copy say this.
`tests/test_repo_hygiene.py`'s exit-3 rule is re-pinned to its one cause,
`cp_not_confirmed`, and also refuses a die or row that still names a
withdrawn cause. New `tests/test_a23_publish_replay.py` (28 cases);
`tests/test_commit_push_published_edit_194.py` rewritten where it pinned the
pull, the straight push or the conflict recovery, its fence cases kept;
`tests/test_a23_guard_refusals.py`'s two expectations corrected.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ef1377f6312b3ef101da518b0034f5f190933f82)
… its lock and asks every path it commits again before its first byte, the dirty-checkout guard and the pre-existing capture are deleted, and no message tells a person to `pull --rebase` (#198, slice 1b stage 3)

`migrate-state-cells`, `retire-rows`, `archive-rows` and `rename-lane` run
`checkout_sync` under their lock, before their behind test (which can then
fire only for a dry run, or a race), and `cs_own_pending` on every path they
commit right before the first byte: the register, the logs, the archive, the
alias table and the handoff. A pending change there refuses (2) with nothing
written, where the capture used to commit it first (ratified choice 9).

Deleted, with what they guarded: `tracked_dirty` and `refuse_dirty_checkout`
(the whole-checkout guard a `pull --rebase` needed; choice 8 narrows it to
the fast-forward and the write's own paths), `handle_preexisting` and
`capture_register_edit` (the `LANES(pre-existing@…)` capture), and
`PRE_DIRTY_LANES`. `identify_changed_lanes` and `warn_dirty` stay: `commit`
uses them.

Every "Pull first — `git -C … pull --rebase`" refusal is reworded: a write
already fetched and fast-forwarded under its lock, so it says origin moved
since and to re-run, or names the restore where a file is missing here.
`add-row`'s published-row refusal names the verbs that fast-forward. The
`--sweep` header and the usage lines say the flag is withdrawn.

The manual's Layout note, "How to write" (its five steps and a recovery that
never resets with `--hard`) and the `claim` section say what S3 and S4 do.

`tests/test_repo_hygiene.py` gains `test_no_lane_writer_pulls_with_rebase`
(`lanes-edit.sh` in this slice; `lane-start` and `lane-handoff` join in 2b)
and `test_no_text_promises_the_carry` (the script's messages and the manual;
red at `874e020` on three lines of
the script and one row of the manual), each with its own red and green cases.

Existing tests brought to S3, one fixture or expectation each:
- `test_a23_lock.py`: the vanished-lock case removed the lock on a pull no
  writer runs, and the heartbeat case had gone vacuous the same way; both
  now act at the push and the fetch.
- `test_deferred_97_findings_157.py` 4d: an unpushed fixture commit is
  refused by S3 first; the case asserts that, then publishes it and asserts
  the row-shape refusal it was written for.
- `test_register_scan_hardening_138.py`: the capture case is now the
  refusal; item 8 publishes its stamp first; item 1 pins the published
  read's failure without the local edit S3 now refuses.
- `test_rename_publication_atomic_158.py`: its fixture no longer exports
  `LANES_NO_FETCH=1` for writers (ratified choice 2).

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e3b6810c25639c9e569e233cc08aa8fe0188507e)
…nflict, and N5's `[no-fetch]` case now meets S3 step 1's refusal (#198)

Brings `ecb96ed` (fix round 1's mutant cases, tests only) and `eead86d`
(lane openRepoTools-3's re-review of #282: N1-N5, SHOULD 5, R3-1, R3-2)
onto slice 1b's stage 3 (`e3b6810`). The merge base is `874e020`.

Each meeting point was weighed on purpose:

- N5's `lane_row_gate` and S3 step 1 (ratified choice 2). Under
  `LANES_NO_FETCH=1` the gate still refuses nothing, as 1a wrote it, and
  slice 1b then refuses the write (2) in the lane's checkout, because for a
  writer a fetch that did not run is a fetch that failed. So
  `test_a_row_W_has_not_fetched_is_no_reason_to_refuse[no-fetch]` now
  asserts S3's refusal, no "has no row" refusal, and nothing published.
  `[fetched]` is unchanged. This case and the docstring below are the
  only changes the merge makes beyond git's own result.
- N2's warning in `ws_pending_refuse` and S3 step 4 (ratified choice 9)
  guard different trees, so both are kept. Under the opt-in a hand edit in
  W is no path the routed write commits: it warns, and only `commit`
  refuses. Without the opt-in the write commits through W, so S3 refuses
  every writer but `commit`, which carries the edit. The routed sweep
  case's docstring no longer calls the case above it "the capture".
- `capture_register_edit` and `PRE_DIRTY_LANES` (deleted by 1b): nothing
  in 1a calls them.
- N1's ten-minute rule and the replay: `lock_adopt` keeps the replaying
  writer's own lock, and `git_net` refreshes it before every network call.
- `ws_follow` (1a's MUST 1) still keys on the nudge, which 1b's
  `cp_pushed` sets only after the read-back confirms the push.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 139144e04aeb4526c7be3a22126f6e5a22544030)
…d-line` for a lane with no row and no log clone nothing, and under `LANES_NO_FETCH=1` the gate gives S3 step 1's refusal before any clone (#198, slice 1b)

Lane openRepoTools-3's re-check of #282 at `eead86d` (R4-1, a SHOULD for
slice 1b): under `LANES_ROUTE_WRITES=1`, `log NOTED lane:repoX-99 …`, a
typo for repoX-9, cloned `<W>-lanes/repoX-99/` for a lane that does not
exist and left it. That is 0.35-0.5 GB of working tree on Eagle. The same
happened on `commit`, `request-handoff` and `append-line`, and on
`set-row-state` under `LANES_NO_FETCH=1`.

- `lane_row_gate` now also gates those four verbs. A row OR the lane's
  object log on origin passes a lane, because an archived lane has no row
  and keeps its log (Amendment 16(e)). The four row verbs still need a
  row. A lane with neither, after the one fetch of W and the re-read,
  refuses (2), and nothing is cloned. `add-row`, a new lane's first act,
  is not gated, and neither are `claim`, `release` or the sweeps.
- The fail-open is kept for a fetch that failed and for a read that could
  not be made, except under `LANES_NO_FETCH=1`. Every gated verb is a
  writer, and slice 1b's S3 step 1 refuses a writer whose fetch did not
  run (ratified choice 2), so failing open there only cloned a checkout
  the write could never use. On a miss the gate now gives that refusal
  itself.
- `lrg_known` reads `lane_log_exists`' 2 (two logs whose names differ
  only by case) as no answer, and every other non-zero status as no log.
  `git cat-file -e` gives 128 for a path that is not in the tree.

Tests, in `tests/test_a23_write_routing.py`:
- `test_a_write_for_a_lane_with_no_row_and_no_log_clones_nothing`, for
  each of the four verbs;
- `test_a_lane_with_a_published_log_and_no_row_is_no_reason_to_refuse`;
- the `[no-fetch]` case of `test_a_row_W_has_not_fetched_is_no_reason_to_refuse`
  now also asserts that nothing was cloned.

Before the change: 5 failed and 2 passed. All four typo cases cloned,
`[no-fetch]` cloned, and the archived case and `[fetched]` passed, as
designed. After: 8 passed, with N5's own case.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7da3d6178ee7af1891689c715dc15341fb6096c8)
…, byte for byte as on main, and now prints only after the read-back finds the commit on origin, so lane-start, the triad goldens and every caller that reads that line stay right (#198, slice 1b)

Stage 2 printed `pushed origin/main: <sha>, confirmed by a read-back
(attempt N)`. That moved T018's goldens, which hold c45a452's line
`lanes-edit: pushed origin/main (attempt 1)`, in
`test_triad_lane_tooling.py` and `test_triad_lane_tooling_handoff.py`.
The words are main's again. What changed is when the line prints: since
S4 it prints only after `cp_confirm` finds the commit on origin, so
"pushed" now means confirmed. A push that cannot be confirmed is never
this line: it exits 3 as `pushed, not confirmed: <sha>`.

The replay module and #214's module now assert the line itself, and that
attempt 1's line never appears once the push replays.

Before (at e3b6810): triad 1 failed, 4 passed, 22 skipped; triad handoff
1 failed, 21 skipped. After: replay 28 passed; #214's module 25 passed;
triad 5 passed, 22 skipped; triad handoff 1 passed, 21 skipped;
hygiene 247 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 3905009ef839edaf51f5acadf9135ed37b362c33)
… no conflict, and without the opt-in a W-itself `LANES_REPO` now meets S3 like any unrouted write (#198)

Brings `15c8e3f`, lane openRepoTools-3's R4-2 from its re-check of #282
at `eead86d`. The W-itself branch of `lanes_home_check` now returns 0
unless `LANES_ROUTE_WRITES=1`, so `refresh-workspace` and `lane-checkout`
no longer refuse the real W while the opt-in is off. It adds one case
each to `test_a23_refresh_workspace.py` and `test_a23_lane_checkout.py`.

The meeting point with slice 1b was weighed on purpose, and nothing
changes. Without the opt-in, a write whose `LANES_REPO` and
`LANES_WORKSPACE_ROOT` both name W now goes through W, as on main. Slice
1b's S3 and S4 apply to every writer, routed or not (decision 1), so that
write fetches and fast-forwards W under its lock, or refuses with nothing
written, and it publishes with no pull. Under the opt-in the refusal is
unchanged (S5). No slice 1b test pins a refusal there.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 2e2eee4501d709a698dc4de19e5de6b8f59daa34)
…`, because slice 1b's S3 refuses every writer whose fetch did not run, and the module's sandbox origin is a local bare repository (#198, slice 1b)

Every case in `tests/test_supervised_restart_regressions.py` builds its
lane through real `add-row`, `log` and `claim` writes. With the fixture's
`LANES_NO_FETCH=1`, Amendment 23 S3 step 1 refused each of them with exit
2 (ratified choice 2), and 153 of the 173 cases failed. 152 of those
failures quote that refusal.

The line is replaced by a comment. The sandbox's origin is a bare
repository under `tmp_path`, so the fetch makes no network call.

Runs:
- Locally at 3905009 plus this file: 167 passed and 0 failed before a
  checkpoint signal stopped the run, with 6 cases not reached.
- CI run 39585851301, at a9190aa, which is 3905009 plus this file and the
  unfinished stage 4 shell suite: the module passed, with 0 failures.
- This commit sits on the 1a merge 2e2eee4, which brings R4-2's
  `lanes_home_check` guard. The next CI run covers that merge with it.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit a41762b33ab1465e60c28c412609cf2a8104d3b5)
…OTED line with a fetch, because slice 1b's S3 refuses a writer whose fetch did not run, and the sweep stopped after its first removal (#198, slice 1b)

`Helper.call` gave every call but the first fetching read
`LANES_NO_FETCH=1`, and `note_line` called `log NOTED` with
`fetch=False`. Under Amendment 23 S3 step 1 (ratified choice 2), a writer
whose fetch did not run is refused with exit 2. So the sweep made its first
removal, its register line was refused, and it stopped with "NOT WRITTEN".

`call` takes `write=True`, which is never given `LANES_NO_FETCH=1`, and
`note_line` passes it. No other call in `lane-worktrees` writes: rv1b
checked every shipped caller, and the other `LANES_NO_FETCH=1` calls are
reads or sidecar verbs that make no commit.

Test: `tests/test_lane_worktrees.py::test_the_real_helper_inventory_and_register_line`.
- Before: failed in CI run 39585851301 in both jobs ("was NOT written
  (exit 2)"), and locally on 3905009's copy (1 failed).
- After: passed. The whole of `test_lane_worktrees.py` passed (118), and so
  did `test_lane_worktrees_report.py` (22).

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6ecfc52cd82fd413a9fecc81c7ef8c34776bd379)
…HEAD is not replayed, so a commit another writer made on top of it in the same checkout is never dropped by the replay's reset; both are kept on HEAD and named, exit 6 (#198, slice 1b)

Until slices 2b and 3, lane-start's stamp, lane-handoff's commit and the
skill's commit are made in the same checkout with no lock. While routing
is off, that checkout is W, every lane's checkout. One of those commits
could land on top of this write's commit while its push was in flight.
The replay's `reset --keep refs/remotes/origin/main` then dropped that
commit and its file, and the write exited 0 with nothing said. The carry
form's `reset --mixed <sha>^` would have done the same.

`cp_replay` now first asks for HEAD. If HEAD is not this write's commit,
it names both commits and `git -C … log --oneline <sha>^..HEAD`, and
exits 6 before either reset. Every later write from that checkout then
refuses under S3's unpushed row until the commits are published (the
publish act) or archived. Exit row 6 names the new cause, in the code's
table and in the manual.

Test: `tests/test_a23_publish_replay.py::test_a_commit_made_on_top_before_the_replay_is_kept_exit_6`,
which follows rv1b's probe.
- Before: it failed on 3905009's helper, exiting 0 where it expects 6.
- After: it passes, and the replay module has 29 passed in this commit's
  own tree.
- On the worktree together with MUSTs 1-2 (the next commit), these all
  passed: replay 32, guard 122, #214's module 25, a23_write_routing 46,
  deferred_97 9, hygiene 247 and a23_lock 24.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c45424795c22640d62e8bfa8fb7b3d8d66c0af3d)
…om the top on origin's new tip when S3's fetch finds it moved under their own checks, so a rival's claim, a peer's retirement or a peer's row that lands before S3's fetch is never written over (#198, slice 1b)

`claim`'s holder checks and `add-row`'s retirement and duplicate checks
run before the lock, on what `log_sync` or an earlier fetch last saw. A
rival's CLAIMED, a peer's retirement of the name, or a peer's own row for
it, could land between that read and S3's fetch under the lock. S3 then
fast-forwarded onto it, and the write was pushed on top at attempt 1. The
results were two lanes holding one object, a retired name reissued, or a
second row. S4's replay never ran, because the push was not refused. The
base's rescan after every `pull --rebase` went with the pull.

- `CS_RECHECK`, set at load and never read from the environment, is set by
  `claim` (beside its replay env, before `write_event`) and by `add-row`
  (before its lock).
- `checkout_sync` reads `refs/remotes/origin/<branch>` before and after its
  fetch. Where that ref moved, nothing is written yet. `cs_recheck` then
  runs the verb again from the top on the new tip, with the lock held, as
  S4's replay does with `exec` and `LANES__ATTEMPT`: `claim`'s rescan comes
  first (CLAIM-LOST 7, or 9), and `add-row`'s checks refuse (2). This stays
  inside S4's six attempts, and at the sixth it refuses (2) with nothing
  written.
- No other verb opts in. The row verbs, `rename-lane` and the sweeps read
  what they check after S3. An opt-in for every verb would add a fetch and
  an `exec` to every write made from a checkout behind origin.

Tests, in `tests/test_a23_publish_replay.py`, after rv1b's probes:
- `test_a_rival_claim_landing_before_S3s_fetch_loses_exit_7`;
- `test_an_add_row_whose_name_lands_before_S3s_fetch_refuses[retires-it|adds-it]`.

Before: all three failed on 3905009's helper, each exiting 0. After: all
pass. On this tree these passed: replay 32, guard 122, #214's module 25,
a23_write_routing 46, deferred_97 9, hygiene 247 and a23_lock 24.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 10169f3124b6c5280540d1e3e095caf33534a021)
…s the withdrawn capture and dirty-checkout guard as current, the carry rule reads the manual a paragraph at a time and knows the capture's words, and the no-pull rule is no longer stopped by a `#` (#198, slice 1b)

Three passages of `docs/README-lanes.md` still told a reader that a peer's
uncommitted register edit is taken into the next write as its own
`LANES(pre-existing@…)` commit, and that `refuse_dirty_checkout` refuses the
next run. Slice 1b deleted both: Amendment 23 S3 refuses a write over a
pending change to a path it commits (ratified choice 9), and only `commit`
carries a hand edit. The three passages (the sweeps' "Nothing is
half-PUBLISHED", the hand-edit paragraph of "How to write", and the
install's symlink note) now say so.

`test_no_text_promises_the_carry` missed them because it read one line at
a time and looked only for "carry". It now reads a document a paragraph at
a time, its lines joined, and gains four patterns for the capture and the
deleted guard ("captured by the next", "as its own LANES(pre-existing@",
"by the dirty-checkout guard", `refuse_dirty_checkout`). A script is still
read a line at a time, comments aside.

`_PULL_REBASE` stopped at the first `#`, so `die "pull (#198) --rebase"`
escaped the no-pull rule; the `#` is gone from its class (a comment line
is still skipped whole).

Tests (`tests/test_repo_hygiene.py`):
- New fixture cases: `the-carry-rule…[a-wrapped-capture]`,
  `[a-wrapped-own-commit]`, `[the-guard]`, `[the-function]` (red),
  `[the-withdrawal]` (green); `the-no-pull-rule…[a-hash-inside-it]` (red).
- Before: the five red cases FAIL under the old reader and regex (the new
  fixtures with HEAD's three definitions, 5 failed, 12 passed), and the
  widened rule finds 4 hits in HEAD's manual (:3160 twice, :4468, :4554)
  where the old one found none.
- After: the whole module passed (253).

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 92fbe46da4a877d1174156979443b9d8c99f93cc)
…he stray copy a case-only rename leaves, not two logs for 15(d)'s hand merge; the 1312Z case asserts the stamp's first push; and slice 1b's composite asserts are split (#198, slice 1b)

NIT 2. Amendment 23 S3 fast-forwards before `ensure_log` runs, so the
state a failed case-only rename leaves (the canonical file here,
untracked, and the old-cased one still published) reaches the two-logs
check with both files in the working tree. Its recovery was 15(d)'s hand
merge, and a commit of the stray copy would publish a second file for one
lane. Where exactly one of the two is tracked, the refusal now says which
is which: it names the tracked log and the untracked copy, the `diff`
that compares them, the `rm -- <copy>` and the re-run, after which the
write renames the tracked log once, inside its own commit. Every other
two-logs state keeps 15(d)'s words. `ensure_log`'s published-path guard
is unchanged; it still answers where the published log is not in the
working tree.

SHOULD 2. `test_the_1312Z_shape_is_pushed` asserts the stamp pushed on
its first attempt and met no refused push, as two asserts. Without S3's
fast-forward the stamp edits the stale tree, its push is refused, and the
replay catches up, so every other assert held and m1b's S10 survived.

SHOULD 3. The 16 composite `assert A and B` in slice 1b's own lines are
split, one condition per assert (Sonar S9073): the replay module 10,
#214's module 3, the guard module 2, the routing module 1. The routing
module's other 10 are slice 1a's lines and are left to it.

Tests:
- New: `tests/test_a23_guard_refusals.py::test_a_stray_copy_from_a_rename_that_never_landed_is_named_as_one`.
  Before (HEAD's helper): 1 failed, with the "Merge them by hand" refusal
  rv1b quoted. After: passed.
- S10 against the strengthened 1312Z case: KILLED, at the first-attempt
  assert (the stamp's stderr shows `push refused (attempt 1 of 6)`).
- The set on this tree, one pytest process per module: the guard module
  123 passed, the replay module 32, the routing module 46, #214's
  module 25, hygiene 253, guard_launch_mode 13.

The shell suite's repoDrop cases are re-pinned to the new refusal in the
stage 4 commit, with the rest of the suite's rewrite.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 3c6b240dca88ab87f8cbd183b7888123453c670d)
…row` compare origin's tip with the tip their checks read, so a move another process made in a shared checkout while they waited on the lock re-runs them too, and the six-attempt bound has a test (#198, slice 1b)

MUST-N. B35 asked only whether S3's own fetch moved
`refs/remotes/origin/<branch>`. In a checkout several writers share (W,
every lane's while routing is off), another process moves that same ref
while `claim` or `add-row` waits on the lock after its checks: a reader's
`log_sync`, a peer's push, its read-back. S3's fetch then found nothing
new, so no re-run happened: two lanes' claims of one object both exited
0, and a peer's retirement fetched in by another write was answered by
reissuing the name. The base settled both, through its rescan after
`pull --rebase`, so this was a regression of 10169f3.

Now `log_sync` records the tip it leaves (`cs_seen_mark`, `CS_SEEN`),
once per process and never from the environment, and `checkout_sync`
re-runs a `CS_RECHECK` verb when origin's tip after S3's fetch differs
from that tip, or from the one just before the fetch (the old test,
kept). The first tip is kept, not the latest: a later `log_sync` could
only make a move seen twice, never missed. The re-run is B35's own: the
replay's `exec` with the lock held, so `claim`'s rescan comes first
(CLAIM-LOST 7, or 9) and `add-row`'s checks refuse (2).

SHOULD B. `cs_recheck`'s six-attempt bound had no test, and rv1b-c's MB1
(`if false` for it) survived the replay module.

Tests (`tests/test_a23_publish_replay.py`):
- `test_a_rival_claim_a_reader_fetched_into_the_shared_checkout_loses_exit_7`
  and `test_an_add_row_whose_name_a_reader_fetched_into_the_shared_checkout_refuses`,
  modelled on rv1b-c's probe and made exact: a third write holds the
  checkout's lock at its push; the write under test is seen waiting in
  `mutex_try` (a `sleep` it started, read from /proc), so its checks have
  run; the rival claim lands from another clone, or the peer pushes the
  retirement; a real reader (`who`) in the shared checkout moves its
  `origin/main`; then the lock is let go. On 92fbe46's helper both fail
  (`assert 0 == 7`, `assert 0 == 2`: each pushed at attempt 1); here
  both pass.
- `test_a_claim_that_meets_a_move_at_every_S3_fetch_stops_at_six_exit_2`:
  exit 2, "on each of six attempts", exactly six moves, the five attempt
  notes, nothing published. It passes on 92fbe46's helper, as the bound
  worked there, and kills MB1 (1 failed: the counter wraps to "attempt 1
  of 6" and the claim goes on).
- The set on this tree, one pytest process per module: the replay
  module 35 passed, the guard module 123, the routing module 46, #214's
  module 25, deferred_97 9, hygiene 253, a23_lock 24.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6471fd178f5386f20e2d40da1d39d2acfaf0a764)
…r writer rewrote is named as rewritten, not as kept on HEAD; a commit that lands during the replay's reset is put back; and exit rows 2 and 6 name the re-run's bound (#198, slice 1b)

SHOULD A. Until slices 2b and 3, lane-start's stamp and lane-handoff
rebase and push in the same checkout with no lock. One that runs while
this write's push is in flight rebases this write's commit, and may push
its copy. `cp_replay` then said "it and the commit on top of it are kept
on HEAD", but the sha is on no branch there, and the change may already
be on origin. Where HEAD is readable and does not contain the commit,
`cp_rewritten` now says another writer rewrote it, and points to
`git cherry -v origin/main HEAD`. That read tells whether its copy
reached origin. The exit is still 6, and nothing is replayed.

NIT 2. The replay's HEAD check and its reset are two git calls, and an
unlocked commit landing between them was dropped with this write's
commit, with exit 0. After either reset, `cp_reset_check` reads
ORIG_HEAD, which `reset` writes with or without a reflog. Where that is
not this write's commit, the reset is undone, but only while HEAD is
still where the reset put it, so nothing that landed after it is
touched. Both commits are kept on HEAD and named (6). Where HEAD has
moved on, nothing is touched, and the reflog is named.

NIT 1. Exit row 2, in the code's table and the manual, gains `claim` or
`add-row` meeting origin moved under its own checks on each of six
attempts, before any edit. Row 6 says the bound is a sixth refused push,
and that S3 finding the sixth move before any edit is 2. It also names
SHOULD A's rewrite and NIT 2's put-back.

Tests (`tests/test_a23_publish_replay.py`):
- `test_a_commit_another_writer_rewrote_is_named_as_rewritten_exit_6`
  (rv1b-c's probe): exit 6, "rewrote this write's commit", no "kept on
  HEAD", the named sha not on HEAD, its copy on origin, nothing
  unpublished. On 92fbe46's helper it fails at the words.
- `test_a_commit_landing_during_the_replays_reset_is_put_back_exit_6`: a
  `git` first on PATH commits on top just before the replay's
  `reset --keep`, which makes the window exact. Exit 6, the reset
  undone, the other writer's commit on HEAD over this write's, both
  unpublished. On 92fbe46's helper it fails with `assert 0 == 6`: the
  other commit was dropped, and the write pushed at attempt 2.
- The set on this tree, one pytest process per module: the replay
  module 37 passed, #214's module 25, hygiene 253, the guard module 123,
  a23_lock 24, the routing module 46, guard_launch_mode 13.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d9ff4e6e6ff5520d8de09162ec67183888eab705)
…are-and-swap of the branch from this write's own commit, so a commit that lands in its window is never dropped, and an up-to-date pull that rewrites ORIG_HEAD no longer loses the write while saying it is kept (#198, slice 1b)

SHOULD C. After the replay's reset, `cp_reset_check` read ORIG_HEAD to
learn what the reset moved from. ORIG_HEAD is one slot that every git
process in the checkout writes, and an up-to-date `pull --rebase`,
`merge --ff-only` or `pull --ff-only` sets it to HEAD without moving
HEAD. Those are the unlocked writers NIT 2 is about (lane-start's stamp,
lane-handoff, until slices 2b and 3). One that ran just after the reset
turned the "undo" into a reset to where HEAD already was. The helper
then said both commits were kept on HEAD and exited 6, with the write
on neither HEAD nor origin. At 6471fd1 the same race replayed and the
write landed (0), so this was a regression. A second gap: the keep-form
reset named `refs/remotes/origin/<b>`, which a reader's fetch can move
after the check resolved it.

The drop is now a compare-and-swap, and ORIG_HEAD is read nowhere:
- `git update-ref -m ... refs/heads/<b> <to> <this write's sha>`, which
  git refuses unless the branch still holds this write's commit. Where
  it is refused, nothing has moved, and `cp_not_head` (the HEAD
  analysis the pre-check uses) names the state: on top of it, both
  kept on HEAD (6); not in HEAD, rewritten (`cp_rewritten`); HEAD
  unreadable, named as such.
- Then the index and the working tree follow: `read-tree -m -u <sha>
  <to>`, the same two-tree merge `reset --keep` makes, or `read-tree -m
  -i <sha> <to>` for `commit`'s carry form, as `--mixed`. Both name the
  resolved sha.
- A tree step git refuses (a change in the way) puts the branch back the
  same way, from where the drop put it, and the commit is kept on HEAD
  (6). Where that is refused too (a commit landed on the branch in
  between), `cp_lost_hold` names the commit as off HEAD with
  `reflog -5`, never as kept.
- "Kept on HEAD" is said only where `merge-base --is-ancestor` finds the
  commit on HEAD.

`cp_reset_check` is deleted. The reviewer's minimum fix (undo only to an
ORIG_HEAD that holds this write's commit, otherwise put the commit back)
was weighed. It cannot tell case A from a real commit on top, so it
would put the commit back and exit 6 where the write can land. Measured
on git 2.43.0, the CAS form leaves the same branch, index and working
tree as `reset --keep` and `reset --mixed`: clean, an unrelated
unstaged change, a change in the way (refused alike, nothing moved),
untracked files, and the carry form. The one difference is a change
another process STAGED on an unrelated path in the window: it stays
staged instead of being unstaged, and the re-run's S3 names it.

Exit row 6, in the code's table and the manual, says the drop never
takes another commit and names the off-HEAD case. "How to write" step 5
says the drop is made only while the branch still holds the commit.

Tests (`tests/test_a23_publish_replay.py`; a `git` first on PATH makes
the window exact, and its patterns match the drop at d9ff4e6 and after):
- `test_an_up_to_date_pull_after_the_replays_drop_is_no_commit_on_top`
  (the reviewer's case A): exit 0, the replay at attempt 2, the write on
  origin once, nothing said to be kept, nothing unpublished. On
  d9ff4e6's helper it fails with `assert 6 == 0`.
- `test_a_stamp_committed_and_pulled_around_the_replays_drop_is_never_lost`
  (case B, lane-start's order): exit 6, nothing replayed, the write and
  the stamp on HEAD, and every commit of this write's that the words
  call kept is on HEAD. On d9ff4e6's helper it fails: the write is on
  neither HEAD nor origin.
- `test_a_commit_landing_before_the_replays_drop_is_never_dropped_exit_6`
  (NIT 2's case, re-pinned): the CAS is refused, both commits are kept
  on HEAD, and the branch's reflog has no drop. On d9ff4e6's helper it
  fails at the words.
- Before, on an export of d9ff4e6 with this module (`runs/c3-shouldc-before.log`):
  the three cases, 3 failed, each for the reason above.
- After, on this tree, one pytest process per module, paced
  (`runs/c3-shouldc-set.log`): the replay module 39 passed, the guard
  module 123, hygiene 253. The shell suite is left to CI.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c71621fc1d786f8e04da05188dcbf7443e63e1fb)
…l, the capture, the carry or a race at `pull --rebase` now pins what a write does instead, and the thirteen cases CI found red at a9190aa are rewritten (#198, slice 1b stage 4)

Slice 1b's writers fetch and fast-forward under the lock (S3) and push
with no pull (S4), so `tests/test_lane_helpers.sh` is brought to them:

- The claim-guard and R11 capture cases pin ratified choices 8 and 9: a
  pending register edit refuses every writer but `commit` (2, nothing
  written), and an unstaged change origin did not touch does not block.
- The CLAIM-LOST races (repoH#4, #60, #70, repoT#1, repoTx#1) and
  `repoRace-1` run on a `race-at-push` `LANES_GIT` wrapper, which lands
  the peer's prepared commit at this claim's push, so the replay's rescan
  is what finds the rival. They no longer export `LANES_NO_FETCH=1`.
- The hung push says what the read-back found, and then the publish act;
  the #32 section takes the replay's words, and a same-row rewrite on
  origin's tip is exit 0 now.
- The deliberate strands are published by the fixture after each case,
  and nine fixture commits that were never pushed are pushed by
  `wip_publish` (the old carry pushed them with the next write).
- add-row's, `ensure_log`'s and the alias table's refusals take their
  reworded recoveries; the own-log case is S3 step 4; the
  alias-unreadable rename runs without `LANES_NO_FETCH=1`; B23's line.

The thirteen cases red in CI run 39585851301, by rv1b's four groups:

- R33 (two cases): the durable half no longer hand-edits the row on a
  current HEAD, which S3 now refuses every writer over. The checkout is
  left genuinely behind (HEAD before the peer's stamp, asserted an
  ancestor of origin/main, the register clean), and S3 fast-forwards it.
- repoDrop (three): re-pinned to the stray-copy refusal of `3c6b240`
  (NIT 2), which S3's fast-forward now reaches first.
- add-row losing to a peer retirement (four): the race lands at the first
  fetch carrying S3's refspec, inside add-row's lock and after its
  checks, so the case meets MUSTs 1-2's re-run and add-row's own
  RETIRED refusal. A new case asserts that the race fired. NIT 3: the
  "no resurrected active row" check reads the bare origin. It used to
  read a peer's tracking ref that nothing moved, so it passed whatever
  happened.
- migrate-state-cells over an unpushed marker (four): the seam's
  1/UNKNOWN answer is pinned on the dry run, which is a read with no
  S3, and `--yes` is pinned to S3's unpushed row (2, nothing changed).

`bash -n` passes. The suite runs whole only (each section builds on the
state the ones before it leave), so it was not run here; CI's `tests`
job is its run of record.

CI record: run 39638244009, at 1d8cdee (c71621f plus this file, byte for
byte): `tests` 1 failed, 3058 passed, 75 skipped; `tests-no-submodule`
1 failed, 2261 passed, 872 skipped. The one failure in both is
tests/test_claude_current.py::test_a_command_that_ignores_term_is_killed_after_the_grace[host]
("KILL never came: 30.0s", the Ubuntu 26.04 image's uutils `timeout`),
which this file does not touch, so the shell suite passes there.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit e6ea8f8abbef939643f59d5bf207257ecf538ff9)
…y form's compare-and-swap and a put-back that cannot be made each have a case, so neither refusal of the replay's drop can be lost without a test failing (#198, slice 1b)

NIT 3 found two branches of the replay's drop that no test reached.
SHOULD C (c71621f) replaced both with the compare-and-swap, so the two
cases are aimed at what it made:
- `test_a_commit_landing_before_the_carry_forms_drop_is_never_dropped_exit_6`:
  `commit`'s carry form, with an unlocked commit landing on top just
  before its drop. git refuses the compare-and-swap and nothing moves:
  the hand-edit commit and the one on top are kept on HEAD and named
  (6), the working tree is clean, nothing is replayed, and the branch's
  reflog has no drop.
- `test_a_drop_whose_tree_step_and_put_back_are_both_refused_is_named_off_HEAD_exit_6`:
  the drop moved the branch, the tree step after it is refused, and an
  unlocked writer commits on the branch in that moment, so the
  put-back's compare-and-swap is refused too (`cp_lost_hold`). The other
  writer's commit stays HEAD, and this write's commit is named as off
  HEAD with `reflog -5`, never as kept (6).
A `git` first on PATH makes each moment exact (`_git_shim`).

Red before: on an export of d9ff4e6 with this module
(`runs/c5-nit3-red.log`), 2 failed. The carry-form case fails at the
words: d9ff4e6's ORIG_HEAD undo kept both commits too, but said "while
the replay dropped it". The put-back case cannot fire there: d9ff4e6's
drop is one `reset` with no tree step, so its `fired` assert fails.

Mutants, one pytest run each (`runs/c5-mut.log`):
- B14, the put-back without its old value: KILLED by the put-back
  case (the branch is put back over the other writer's commit, and the
  words say "kept on HEAD").
- B15, new, the carry form's drop without the compare-and-swap (the
  keep form keeps it): KILLED by the carry-form case (exit 2 after a
  replay that dropped the stamp's commit). Its control B15c, the same
  mutant against the keep form's case alone, SURVIVED, which is why the
  carry form needs its own case.

After, on this tree, one pytest process per module, paced
(`runs/c5-nit3-set.log`): the replay module 41
passed (39 and the two new cases), the guard module 123, routing 46,
#214's module 25, hygiene 253, a23_lock 24. The shell suite is left
to CI.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 948e1e0f034b232062998cf2a0be2c49e755b820)
… for a log published under one case and held here under another no longer says "Pull first, or merge them": it tells the rename's stray copy from a rename committed here, and gives each its recovery (#198, slice 1b)

The migration's scan skipped such a lane with "a case-only rename whose
commit never landed. Pull first, or merge them (15(d))". No writer
pulls now, and a hand pull in a shared checkout is the 1312Z hazard; a
hand merge of a stray copy would publish a second file for one lane,
which is why `ensure_log` stopped advising it (B36, 3c6b240). The dry
run reaches this line only with the checkout at origin's tip (its
behind test refuses first), so the other name is this checkout's own,
in one of two states, and the line now says which:
- NOT TRACKED: the copy a rename's two `mv`s left when its commit was
  never made. B36's recovery: compare it with the published log, re-run
  as its own write any line only the copy holds, remove the copy, put
  the published log back (`checkout HEAD -- <it>`) and re-run; the
  lane's next write renames it once, inside its own commit.
- committed here and not on origin: the publish act refuses it (#214's
  fence takes no rename, which deletes and re-adds the log) and no later
  write carries it, so it is archived and dropped (S6.3: the archive
  push, then `reset --keep origin/main`) and the dry run re-run.
Tracked is asked with `core.ignorecase=false` (`CP_EXACT`), as the other
pathspecs here are.

Test (`tests/test_a23_guard_refusals.py`):
`test_a_migration_dry_run_names_a_rename_that_never_landed_and_never_says_pull`,
for both states: the line names the state and its recovery, the output
has no "Pull first", the line no "merge them", the dry run writes
nothing, and the recovery it gives, followed, leaves no row skipped.
Red before, on 948e1e0's helper with this module
(`runs/c5-nit4-red2.log`): 2 failed, each at "Pull first". Mutant B16
(the tracked test made `false`) is KILLED by the committed case
(`runs/c5-mut.log`).

After, on this tree, one pytest process per module, paced
(`runs/c5-nit4-set.log`): the guard module 125
passed (123 and the two new cases), the replay module 41, routing 46,
#214's module 25, hygiene 253, a23_lock 24. The shell suite is left
to CI.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9683fd18a59e4d6b961e665b78fea8f9606a575f)
…write edits in place, so a refused push whose origin no longer has that file keeps the commit on HEAD and names it, instead of replaying into nothing and dropping the write (#198, slice 1b)

Slice 2a's A37: a `write-handoff --stamp` or `--prepend` that loses its
race to a peer's delete of a TRACKED handoff is dropped by the replay
with its commit, and the re-run finds no file and says "Nothing was
written", so the write is only in the commit the replay dropped. The
check belongs to 1b's `commit_publish`, as A11's and A26's did, so 1b
makes it, and slice 2a's `write-handoff` sets the global for those two
modes (2a uses it; no verb of 1b sets it).

- `CP_KEEP_IF_GONE=""`, set at load beside the other CP_* and never
  from the environment: the path a write edits in place.
- `cp_replay`, after its HEAD check and before the bound and the drop:
  where the global names a path this write's base (`<sha>^`) had and
  origin, just fetched by the read-back, does not, the commit is kept
  on HEAD and named (6), with the look and the S3 unpushed-row notes
  the bound gives. Asked of the base, so a file the write itself
  created is never "gone". Asked after the HEAD check, so "kept on HEAD"
  is said only of a commit HEAD is (`cp_not_head` answers otherwise).
  A file its base had and origin lacks means origin moved, so the
  origin-unmoved refusal before it can never be this case; the check
  sits in `cp_replay` rather than beside 2a's S4.4 block, which keeps
  the two textually apart for 2a's merge of this head.

Exit row 6 is not reworded here: its "kept on HEAD and named" already
covers the case, and the verb that reaches it is 2a's, whose row-6
clause (A27, A32) can name it.

Test (`tests/test_a23_publish_replay.py`), slice 1b's own:
`test_a_file_the_write_edits_in_place_deleted_on_origin_keeps_the_commit_exit_6`,
on a copy of the helper whose one load-time line names the lane's log
(`_helper_naming`; asserted to occur once; the shipped `repos.tsv`
beside it), with `log` as the write:
- `deletes-it`: a peer's `git rm` of the log lands at the push: 6, the
  words, `kept on HEAD: <sha>`, no replay, the write in this checkout's
  log, origin without the file, one commit unpublished;
- `keeps-it`: a peer's empty commit lands: replayed and pushed at
  attempt 2 (0);
- `never-had-it`: the global names a path neither the base nor origin
  has: replayed and pushed (0).
Red before: the check made `if false &&` (mutant A01) fails `deletes-it`
with exit 0, the log re-made by the re-run and pushed at attempt 2
(`runs/c5-mut.log`, `runs/c5-a37-red.out`); at
948e1e0 the global does not exist, so the copy cannot be made. Mutant
A02 (the base test made `true`) is KILLED by `never-had-it`
(`runs/c5-mut.log`).

After, on this tree, one pytest process per module, paced
(`runs/c5-a37-set.log`): the replay module 44
passed (41 and the three new cases), the guard module 125, routing 46,
#214's module 25, hygiene 253, a23_lock 24, guard_launch_mode 13,
deferred_97 9. The shell suite is left to CI.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9cdf8abff0453cc6739d17e9e80c354288596d0b)
…ly it can answer, and the on-top case asserts that the HEAD check answered it, so removing the check no longer survives behind the drop's compare-and-swap (#198, slice 1b)

m1b-c2's re-run of slice 1b's mutants found B04 alive: with `cp_replay`'s
HEAD check removed (`[ "$cr_head" = "$1" ] || cp_not_head …` made `:`),
every case still passed, because the drop's compare-and-swap (SHOULD C,
c71621f) refuses a commit on top as well, and exits 6 with the same
outcome in other words. The two cases (m1b-c2's, from
`S/act2/m1b/probe-b04.patch`, the docstring reworded):
- `test_a_commit_made_on_top_before_the_replay_is_kept_exit_6` gains
  one assert: the words are the HEAD check's, "in this checkout while
  its push was in flight", not the drop's.
- `test_a_commit_made_on_top_at_the_sixth_push_is_named_before_the_bound_exit_6`,
  new: five refused pushes, and at the sixth an unlocked writer's stamp
  lands on top. The HEAD check comes before the six-attempt bound, so
  the stamp is named as not this write's (6), never folded into the
  bound's "kept on HEAD", and both commits stay on HEAD.

Run on this tree (`runs/c5-b04-new.log`): the two cases, 2 passed;
hygiene 253 passed (`runs/c5-b04-hyg.log`). B04 against each case
alone, one pytest run each (`runs/c5-mut.log`, B04a and B04b): KILLED
by both.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 4b94f8774031089c55e1fc37e44ccbf77a55770b)
…AD": a commit an unlocked writer made on top while the push was in flight, or a rewrite that took this write's commit off the branch, is named in `cp_not_head`'s words, as the replay names it (#198, slice 1b)

w2a-c6 found it, the shape slice 2a's A41 took out of its S4.4:
`commit_publish`'s exit 6 for a push refused with origin/<b> unmoved
(a hook, a permission, a protected branch) said "the commit is local
and kept on HEAD: <sha>" without reading HEAD. Until slices 2b and 3,
lane-start's stamp and lane-handoff commit in the same checkout with
no lock, so while the push is in flight:
- a commit made on top sat at HEAD, and the words named this write's
  commit as HEAD;
- a rewrite (an amend, a rebase) left HEAD without this sha at all, and
  the words still said it was kept there.

Now HEAD is read first. Where it is not this write's commit,
`cp_not_head` answers, as `cp_replay` does: a commit on top keeps
both and names both, a rewrite goes to `cp_rewritten`, and an
unreadable HEAD says so. All exit 6, nothing replayed. Where HEAD is
this write's commit, the words are as before, byte for byte.

`cp_not_head` keeps its three arguments, so slice 2a's call
(`cp_not_head "$cp_sha" "$msg" "while its push was in flight"`, its
A41) is unchanged. A fourth, optional argument, given only by this
refusal, replaces "the push was refused because origin/<b> moved" with
this refusal's own clause, "the push of <sha> was refused with
origin/<b> unmoved (no lost race: a replay would meet the same
refusal)". It also drops the note's "dropping this write's commit to
replay it would drop that one too", since no drop would be made.
`cp_rewritten` takes the same clause as an optional fourth. With three
arguments, every word of both is as it was.

Tests (`tests/test_a23_publish_replay.py`), on a push hook that runs
the other writer, then refuses the push with origin unmoved:
- `test_a_push_refused_with_origin_unmoved_names_a_commit_on_top_exit_6`:
  a stamp is committed on top. Both commits are kept on HEAD and named,
  every sha of this write's named is on HEAD, there is one push and
  no replay.
- `test_a_push_refused_with_origin_unmoved_names_a_rewritten_commit_exit_6`:
  the commit is amended. The words say "rewrote this write's commit"
  and never "kept on HEAD", and the sha they name is not on HEAD.

Red before the fix: both failed, with the old words "kept on HEAD:
<this write's sha>" of a HEAD that was not it (`runs/c6-i1-red.log`).
Mutant B17 (the HEAD check made `true ||`, the code before the fix)
was run against each case alone and KILLED by both
(`runs/c6-mut.log`, B17a and B17b).

After, on this tree: the two cases with the four neighbours they share
words with, 6 passed (`runs/c6-i1-green.log`). The replay module had
47 passed (45 and the two new cases) and hygiene 253
(`runs/c6-i1-set.log`). The exit table's row 6 already names both
causes ("a commit another writer made on top", "REWROTE"), so it is
not reworded.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 638b13a88c278343214b63802ae6cb6acf153fb8)
…e no longer says "15(d)'s hand merge" of the stray copy: the published log tracked with an untracked copy beside it gets `ensure_log`'s B36 recovery, and every other pair keeps 15(d)'s words (#198, slice 1b)

w1b-c5 found it beside NIT 4 and left it for the reviewer (~:12312 at
4b94f87). The migration's scan reads the lane's log in the working tree
whatever its case. Where it found two, it said "this checkout holds 2
object logs for it whose names differ only by case — 15(d)'s hand
merge". One such pair is `ensure_log`'s B36 state: the published log,
tracked, and beside it the untracked copy that a case-only rename leaves
when its commit never landed, once the published log is back. A hand
merge commits that copy as a second published file for one lane, the
thing Amendment 15 refuses.

The state reaches the line. The published log is fast-forwarded into the
checkout (the dry run's behind test passes), the copy is untracked, and
`log_path_ci` finds one log on origin, so the two-logs branch is where
the scan lands.

Now, where the scan finds exactly two logs, one tracked under the
published log's own name (an exact `ls-files --error-unmatch`, as NIT 4's
line uses) and one untracked, the line gives B36's recovery: compare the
two (`diff -- <published> <copy>`), re-run as its own write any line only
the copy holds, remove the copy (`rm -- <copy>`), and re-run. Any other
pair keeps 15(d)'s words. That includes a rename committed here with the
published spelling back beside it untracked, where removing the
untracked file would remove the published spelling.

Test: `test_a_migration_dry_run_names_a_stray_copy_beside_the_published_log_as_one`
(`tests/test_a23_guard_refusals.py`), parametrised:
- [stray-copy]: the dry run names the copy NOT TRACKED with the diff and
  the `rm`, never "hand merge", and writes nothing. Following the
  recovery, the next dry run skips no row.
- [committed-rename]: the stray-copy recovery is never given.

Red before the fix, on 638b13a's helper (`runs/c6-i2-red.log`):
[stray-copy] failed on "15(d)'s hand merge", and [committed-rename]
passed, since it guards the new condition. Mutants, run against
the case each one targets (`runs/c6-mut.log`), all KILLED:
- B18: the condition made `false`;
- B19: the tracked test made `false`;
- B20: the published-name check removed, which tells the user to remove
  the published spelling in [committed-rename].

After, on this tree: the migration cases, 4 passed
(`runs/c6-i2-green.log`); the guard module 127 passed and hygiene 253
(`runs/c6-i2-set.log`).

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 514b0c48fc53ecd5d1b3a5e84a340699ab7c19e9)
… on this write's commit and the commit is kept on HEAD and named (6), so neither the carry form's own refusal nor its put-back can be lost without a test failing (#198, slice 1b)

`cp_replay`'s carry form (`commit`'s, which gives its commit back as the
hand edit it was) drops the branch to the commit's parent by a
compare-and-swap, then moves the index alone (`read-tree -m -i`). Where
git refuses that step, the branch is put back by a compare-and-swap from
where the drop put it, and exit 6 says "this commit could not be turned
back into the hand edit it was to replay it, so it is kept on HEAD". The
keep form's refusal has a case
(`test_replay_refused_by_a_change_in_the_way_keeps_the_commit_exit_6`),
and so does the put-back that is refused too (NIT 3). The carry form's
own refusal had none.

`test_a_carry_forms_drop_whose_tree_step_is_refused_keeps_the_commit_exit_6`
(`tests/test_a23_publish_replay.py`): a `git` first on PATH refuses the
carry form's `read-tree -m -i`. The exit is 6, with git's words quoted.
The sha the refusal names is HEAD, and the reflog shows the put-back.
The hand edit is in HEAD's register, the working tree is clean, nothing
is replayed, nothing reached origin, and the one commit is kept for the
publish act.

Mutants, each run against this case alone (`runs/c6-mut.log`), both
KILLED:
- B21: the carry form's refusal branch made `if false`, so the keep
  form's words were printed;
- B22: the put-back made `true ||`, which left the branch on the parent
  while the words said HEAD.

After, on this tree: the replay module 48 passed (47 and this case)
(`runs/c6-i7-set.log`), hygiene 253. The case alone: 1 passed (`runs/c6-i7-new.log`).

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0c822f33c34dcb5de337df2f60fae70ad416f545)
…d it first: an unconfirmed push (3) and the publish act's refusal with origin unmoved (6) name a commit on top, or a rewrite, in `cp_not_head`'s and `cp_rewritten`'s words (#198, slice 1b)

w1b-c6 found them with 638b13a, which took the same flaw out of
`commit_publish`'s refusal with origin unmoved, and rv1b-c3 listed them
as left. Both still said the commit(s) were "kept on HEAD" of a HEAD
nobody had read. Until slices 2b and 3, lane-start's stamp and
lane-handoff commit and rebase in the same checkout with no lock, so
while a push is in flight a commit can land on top of this write's, or
a rebase can take it off the branch altogether.

- `cp_not_confirmed` (exit 3): HEAD is read first. Where it is not this
  write's commit, `cp_not_head` answers (a commit on top, or an
  unreadable HEAD) or hands on to `cp_rewritten` (a rewrite), each with
  the clause "pushed, not confirmed: <sha> - <why> (never reported as
  success)" in place of "the push was refused because origin moved".
  The number stays 3: the one cause the table gives it is that nothing
  can settle where the push went, and a commit on top does not change
  that. So `cp_not_head` and `cp_rewritten` take an optional fifth
  argument, the exit status (6 unless given). Where HEAD is this
  write's commit, the die is byte for byte what it was.
- The publish act's refusal with origin unmoved (6): HEAD is read
  against the commit the fence read (`CP_FENCE_TIP`); where it differs,
  `cp_not_head` answers with "the push of the N commit(s) up to <sha>
  failed with origin/<b> unmoved". Where it is the same, the die is as
  it was. A commit on top is the one the fence leaves local by design;
  it is now named instead of left unsaid.

`cp_not_head`'s three-argument form is unchanged, so slice 2a's call
(`cp_not_head "$cp_sha" "$msg" "while its push was in flight"`, its
A41) is the same call. Its fourth argument (638b13a) is unchanged; the
fifth is new and optional.

Tests (`tests/test_a23_publish_replay.py`), hooks that land the other
writer's act while the push is in flight:
- `test_an_unconfirmed_push_reads_head_before_it_says_kept_on_head_exit_3`
  [on-top, rewritten]: the push lands (the hook runs it itself, then
  the other writer), the read-back's fetch fails: exit 3, the die names
  HEAD and this write's commit, or says the commit was rewritten and
  names no "kept on HEAD"; origin holds this write's commit.
- `test_a_publish_act_push_refused_with_origin_unmoved_reads_head_exit_6`
  [head-is-the-tip, on-top, rewritten]: the first pins the die's words
  as they were; the others are the new ones. One push, no replay.

Red before the fix: the four new cases failed on the old words
("the commit is kept on HEAD, and this is never reported as success",
"they are kept on HEAD, and nothing of them reached origin") and the
head-is-the-tip case passed (`runs/c7-i1-red.log`). Green after: the
five and their seven neighbours, 12 passed (`runs/c7-i1-green2.log`).
Mutants B23a/B23b (the exit-3 HEAD check, each case alone), B24a/B24b
(the publish act's), B25 and B26 (the die's number, on top and
rewritten) and B27 (the number handed on to `cp_rewritten`): all
KILLED (`runs/c7-mut-final-i1.log`). The replay module 53 passed and
hygiene 253 passed (`runs/c7-i1-set.log`, `runs/c7-i1-hyg2.log`); the
exit-3 rule reads the die as before, and the call that carries its
number is spelled with a variable, since the rule reads a line ending
in a quoted `... 3` only as a `die`.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit f9b1de9fb7df320897381bc6ae2baf7f1be4f29d)
…anch (a held `refs/heads/<b>.lock`) shows git's own words and says nothing was moved, where it named a commit on top that nobody made (#198, slice 1b)

rv1b-c3's NIT E (its probe P2). The replay's drop is `update-ref <ref>
<new> <old>`, a compare-and-swap that git refuses when the branch is
not at <old>. It refuses for another reason too: another git process
holds `refs/heads/<b>.lock` (a peer's commit in flight, or an auto
`pack-refs`). HEAD is then still this write's commit, and nothing is
lost, but the refusal went to `cp_not_head`, which said "HEAD is X, not
this write's commit X: another commit was made on top of it" and "it
and the commit on top of it are kept", and git's own words, read into
`cr_err`, were never shown.

- `cp_replay`: where the compare-and-swap is refused, `cr_err`'s first
  lines are printed as "git said: ...", as at the tree step below it,
  before `cp_not_head` answers.
- `cp_not_head`: after the unreadable-HEAD case, where HEAD is still
  `$1`, it says that no commit was made on top, that git could not move
  `refs/heads/<b>` off it, and that nothing was moved, and dies "git
  could not move the branch off this write's commit to drop it, so
  nothing was moved and it is kept on HEAD: <sha> <subject>" (6). A
  commit on top, a rewrite and an unreadable HEAD keep their words.
  The three- and four-argument forms are unchanged.

Test (`tests/test_a23_publish_replay.py`):
`test_a_drop_refused_for_another_reason_names_git_and_no_commit_on_top_exit_6`,
rv1b-c3's P2 in the module's own fixtures: a `git` first on PATH
creates `refs/heads/main.lock` just before the drop. Exit 6, git's
"cannot lock ref ... main.lock" shown, "HEAD is still this write's
commit <sha>" in place of "another commit was made on top", one commit
kept for the publish act, nothing replayed.

Red before the fix: 1 failed, "git's own words are shown"
(`runs/c7-i2-red.log`). Green after: that case and the three neighbours
that share the drop's words, 4 passed (`runs/c7-i2-green.log`).
Mutants B28 (git's words not printed) and B29 (the HEAD-is-this-commit
branch off): both KILLED (`runs/c7-mut.log`). The replay module 54
passed and hygiene 253 passed (`runs/c7-i2-set.log`).

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 3a4bc4ac6d55bf9cb71f1f76fb0cf63479952cf4)
…where HEAD holds the file, so a rename that is only staged is unstaged and never archived, as it was when the index answered for HEAD (#198, slice 1b)

rv1b-c3's NIT D (its probe P1). The dry run's line for a lane whose
log is published under one case while this checkout has it under
another asked `ls-files --error-unmatch` whether the rename was
committed, and that reads the INDEX. A rename that was STAGED and never
committed, which is what a write's `git commit` failure leaves after its
`add` (exit 6), was told it was "committed here and not on origin" and
given the archive recovery. Following it pushed an archive branch
identical to origin's main, and the `reset --keep` after it only
unstaged the rename, leaving the copy for the NEXT dry run to name as
NOT TRACKED. Nothing was lost, but the recovery was two acts too many
and its first words were wrong.

"Committed" is now asked of HEAD (`git cat-file -e HEAD:<path>`). The
line has three states:
- in HEAD: "committed here and not on origin", as before, with the
  archive recovery;
- only in the index: "STAGED and not committed", with no archive step:
  `git restore --staged -- <published log> <this name>` unstages it,
  which leaves the copy NOT TRACKED, and then the stray-copy recovery
  (compare, remove the copy, put the published log back, re-run) that
  line already gave;
- in neither: "NOT TRACKED", as before.

Test (`tests/test_a23_guard_refusals.py`):
`test_a_migration_dry_run_never_calls_a_staged_rename_committed`, rv1b-c3's
P1 in the module's own fixtures: the rename staged with HEAD = origin.
The line must not say "committed here", must say "STAGED and not
committed", give the `restore --staged` command, and give no archive or
`reset --keep`; the dry run writes nothing. The test then follows the
line: the command is run, the next dry run names the copy NOT TRACKED,
that recovery is run, and the third dry run has nothing SKIPPED.

Red before the fix: 1 failed, the old "committed here" line
(`runs/c7-i3-red.log`). Green after: that case and the four neighbours
(`runs/c7-i3-green.log`, 5 passed). Mutants B31 (the index asked again,
the code before the fix), B32 (no line of its own for a staged rename),
B33 (the unstage paths in the wrong order): all KILLED
(`runs/c7-mut.log`). The guard module 128 passed and hygiene 253 passed
(`runs/c7-i3-set.log`).

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 251e90056dd317c692965bbeb387347127f27a0b)
… `$1^:$CP_KEEP_IF_GONE`, asked of the commit's base and not the commit, is pinned, where the mutant that took it out survived (#198, slice 1b)

rv1b-c3's NIT F, found by its mutant M4. `cp_replay` asks, before it
drops anything, whether the file a write edits in place was in the
write's BASE (`$1^:<file>`) and is gone from origin's tip. The `^`
matters: a file the write itself creates (a lane's first log line)
is in the commit and in neither the base nor origin, and asked of the
commit (`$1:<file>`) it would read as deleted on origin, so an ordinary
lost race on that write's first push would be refused (6) and kept
instead of replayed. The three cases that existed (deleted, kept, a file
nobody has) all passed with the `^` taken out: "a file nobody has" is
absent from the commit too, so it does not tell the base from the commit.
M4, `$1^:` turned into `$1:`, survived 3 passed (`runs/c7-mut.log`,
B30x).

The case is `creates-it` in
`test_a_file_the_write_edits_in_place_deleted_on_origin_keeps_the_commit_exit_6`
(`tests/test_a23_publish_replay.py`): origin has no log for the lane
when the write starts (a peer removed it before this checkout was
cloned), the write is `log NOTED lane:<it>` naming that log as the file
it edits in place, and a peer's commit that touches nothing lands at
the push. The write creates the log at the replay: exit 0, one refused
push and one that landed, the line in origin's log, nothing left
unpublished.

No change to `lanes-edit.sh`. Green: the four cases, 4 passed
(`runs/c7-i4-green2.log`). Mutant B30 (M4) is KILLED by `[creates-it]`
(`runs/c7-mut.log`), and B30x shows it SURVIVES the three old cases,
which is the finding. The PR body's A02 row said a file the base never
had is never "gone"; its case pins the existence test as a whole, not
the `^`, and the body now says so and lists B30 for the `^`.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 24efc8accd864b43f2037ab6bed25230149dec66)
… it names the row verbs, which fast-forward the checkout first, and no `git pull --rebase` (#198, slice 1b)

`docs/README-lanes.md` said `add-row`'s refusal of a row on origin and
not yet in this checkout names "the `git pull --rebase` that settles
it". Since this slice no writer pulls: `ar_where` in the refusal says
the row is on origin and not yet here, and to use `set-row-state` /
`replace-in-row` on it, "each fast-forwards this checkout to it first
(Amendment 23 S3)". The manual is the code's words now, in its own
wrapping.

Found by w1b-c6 with item 1, listed by rv1b-c3 as left. Text only: no
code, no test names the sentence. Hygiene 253, `test_skill_shell_blocks`
6 and `test_test_roots` 39 passed, 1 skipped, the three tests that read
the manual (`runs/c7-i5-set.log`).

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 558830708aa3329a06d5212171a918283c1c3d9d)
…origin/<b>` by name, so a reader's fetch in the shared checkout between S3's compare and its fast-forward is no longer brought in unchecked (#198, slice 1b)

Lane openRepoTools-3's review of #307 at 5588307, MUST 281 (its P1 and
P7). `checkout_sync` compared origin's tip after its fetch with the tip
the checks read (decision 22), and then, about ten git calls later,
fast-forwarded to the tracking ref by name. In a checkout several
processes share (W while routing is off; a lane's checkout under the
opt-in), anything that fetches without the lock moves that ref in
between: every reader's `log_sync` (`who`, `history`, `lane-dir` ...)
and every other writer's pre-lock one. What it moved in was
fast-forwarded in unchecked, and the push, a plain fast-forward, landed
at attempt 1: a rival's CLAIMED under this claim (two holders of one
object), a peer's retirement under this `add-row` (a retired name
issued again).

- `checkout_sync`: `cs_now` is read once, right after S3's fetch, for
  every writer (it was read only inside the re-check), and step 8's
  ancestry test and `merge --ff-only` use `cs_to="${cs_now:-$cs_trk}"`.
  The write is made on that sha, so where origin moved since, its push
  is refused and S4's replay decides on the new tip.
- Steps 5 and 6 keep the ref by name, on purpose: a newer ref there
  only finds more of HEAD published, while the sha would call a commit
  an unlocked writer's `pull` brought in from origin "unpublished".

Tests (`tests/test_a23_publish_replay.py`), with a `git` first on PATH
that runs between S3's compare and its fast-forward (armed at S3's own
fetch, fired at step 3's `symbolic-ref -q HEAD`):
- `test_a_rival_claim_a_reader_fetched_in_after_S3s_compare_loses_exit_7`
  (P1): a rival's claim lands and a reader's `who` fetches it in. Now
  push refused at attempt 1, CLAIM-LOST (7), one holder.
- `test_an_add_row_whose_retirement_a_reader_fetched_in_after_S3s_compare_refuses`
  (P7): a peer's retirement lands and is fetched in. Now push refused,
  the re-run refuses "is RETIRED" (2), the name not issued.

Red before the fix: 2 failed, "two lanes hold one object" and "a
retired name was issued again" (`runs/c8-281-red.log`). Green after,
with the three neighbours of the window before S3's fetch: 5 passed
(`runs/c8-281-green.log`). Mutant C01 (`cs_to="$cs_trk"`, the ref by
name again): KILLED (`runs/c8-mut.log`).

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c71068bc9c25e52c529b2431e31dbd1f59c6c4ff)
Brett Heap and others added 18 commits October 22, 2026 15:28
…ver the index's: a test now stages the register's own shape, so a base read from the index can no longer pass (#198, slice 1b)

382 of lane openRepoTools-3's re-check of #307 at `15e71d9`. 341's guard
counts each line the hand edit replaces in the version the edit was made
on, `CS_CARRY_BASE`, the blob of `HEAD:<register>` (S3 step 7), the same
version the kept diff is taken against (`diff HEAD`). No test staged the
edit, so a mutant reading the base from the index (`":$LANES_PATH"`)
passed the replay module: with the edit staged, the base is the edited
version, the LANDED line counts once there, and the register's own shape
is pushed onto the wrong copy again (341).

`test_commit_refuses_a_hand_edit_whose_line_occurs_twice_on_origin` gains
`[the-registers-own-shape-staged]`, lane 3's tested parameter: the same
edit as `[the-registers-own-shape]`, staged with `git add` before the
peer's push. No helper change.

- Red on `fc6f34d`'s helper: 3 failed (the two 341 cases and the new one,
  each `assert 0 == 2`), 2 passed.
- Green: 5 passed; the replay module 70 passed.
- Mutant C34 (the base read from the index) against the new case alone:
  KILLED (`assert 0 == 2`).

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit a44b73d8d9f74286ac71ff8ff3572e13139e1dcb)
…ve scratch files, and names them, where it said four (#198, slice 1b)

383 of lane openRepoTools-3's re-check of #307 at `15e71d9`. Beside a kept
hand edit, `checkout_sync` writes `apply.err`, `fetch.err`, `ff.err` and
`removed` there, and the replay writes `reset.err`: five, the five #308's
sweep lists at `e78e9fd`. A comment change only, with no test.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0e56301fb0c63179635dfe2d11c09cb3c8a9fce0)
… an object with thousands of comments no longer makes the exec fail with exit 126 (#198, slice 1b)

334 of lane openRepoTools-3's re-check of #307 at `fc6f34d`. 284 carries
attempt 1's whole `gh_reads` output across the re-run's `exec` as one
environment string, `LANES__CLAIM_READS`. Linux caps one environment
string at 128 KiB (MAX_ARG_STRLEN): past that the `exec` failed with
"Argument list too long" and the write exited 126, outside the exit table,
with no "Nothing was written".

`claim` now puts a capped copy in `CP_REPLAY_ENV`: where the reads exceed
32,768 bytes, the copy is cut at 32,768 bytes, back to the last whole
line, and ends with a line saying it was truncated and that attempt 1
printed all of Rule 1's reads. Attempt 1 still prints and cites them in
full; a re-run prints and cites the copy it was given. The count strips
BSD `wc`'s padding, as hygiene's portability rule asks. The takeover URL
is one line and is not capped.

New: `test_a_claims_re_run_carries_reads_too_long_for_one_environment_string`
(replay module). `_network_reads_logged` gains `bulk`: its `gh` stub then
answers `gh issue view --comments` with that many `CLAIMED —` lines; the
test asks for 1,800 (about 190 KB of reads), and origin moves once at
S3's fetch so the claim runs again from the top.

- Red at `0e56301`: 1 failed, exit 126, "line 2296: /usr/bin/bash:
  Argument list too long".
- Green: 3 passed with the two 284 cases; hygiene 253 passed; the replay
  module 71 passed (run before the `tr -d ' '`, which hygiene asked for
  and which changes nothing under GNU `wc`).
- Mutant C35 (the cap's size test made `false`) against the new case:
  KILLED, exit 126.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit aa45be1c750329ba99cb78bde4f477c6f9747503)
…it this run saved is removed by name, so lane-end's sweep no longer holds the checkout for good (#198, slice 1b)

393 of lane openRepoTools-3 on #307 (comment 6345448649), the companion of
#308's 351. S3 step 7 saves `commit`'s hand edit as
`.git/lanes-edit/hand-edit-<UTC>-<pid>.diff` before the fast-forward, and
nothing removed it after a carry that landed. #308's fix holds a checkout
on anything in `.git/lanes-edit/` but its five scratch files, since a KEPT
edit there may be a person's only copy, so every checkout that ever
carried would be held for good.

- `checkout_sync` records each copy it writes, by name, in
  `CP_CARRY_COPIES`.
- The record crosses the replay's `exec` (and `cs_recheck`'s) as
  `LANES__CARRY_COPIES`, read and unset at load and honoured only where
  `LANES__REPLAY_PID` is this process, as the other markers are. A lost
  push race replays the carry under the same pid and saves a second copy,
  while `CS_CARRY_DIFF` names only the last.
- `cp_pushed`, reached only after `cp_confirm`'s fresh fetch and
  `merge-base --is-ancestor` have found this write's commit on origin,
  removes each recorded copy (`cp_drop_carry_copies`). It removes a name
  only if it has no `/` and ends in this run's own pid, and it never uses a
  glob: pids recur, and an older run's kept copy can carry this pid. A copy
  that cannot be removed is named, and the write is still 0.
- A refused, kept edit (2), an unconfirmed push (3) and every 6 keep their
  copies. The carry note says the copy stays "until the read-back finds the
  edit on origin".

Tests (replay module):
- `test_commit_carries_a_hand_edit_across_the_ff` (`:594-596`) now asserts
  the copy is gone after a success, not that it exists.
- New: `test_a_lost_races_two_copies_of_the_hand_edit_are_both_removed_once_origin_has_it`.
- New: `test_a_carry_removes_only_the_copies_it_wrote_never_another_runs_under_the_same_pid`.
  A kept copy under another pid stays. So does one planted under this
  run's own pid by a `git` first on PATH at the carry's `diff --binary
  HEAD`, whose parent is this run.
- `test_a_carry_forms_drop_whose_tree_step_is_refused_keeps_the_commit_exit_6`
  also asserts that the copy stays (6: origin does not have the edit).

Results:
- Red on `aa45be1`'s helper: 3 failed and 1 passed (the exit-6 guard).
  Each failure leaves the copies behind, and the lost race left two
  distinct copies.
- Green: 6 passed with the two carry-replay neighbours. The replay module
  73 passed; hygiene 253 passed.
- Mutants C36-C42, each KILLED at its own assertion:
  - C36: no removal.
  - C37: only the last copy recorded.
  - C38: the record not exported at the replay's `exec`.
  - C39: the record not read at load.
  - C40: a glob on the pid.
  - C41: a glob on every copy.
  - C42: removal at the commit, before the proof.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 50e8916e01f528cd8267c410694eee0cf0b8457e)
…and this write did not make, with both causes, and gives S6.3's archive after the list and the STOP line, never the publish act, which refuses there (#198, slice 1b)

343 of lane openRepoTools-3's re-check of #307 at `fc6f34d`. `cp_under`
said "another writer in this checkout committed while this write was
being made" of every commit under this write's that origin lacks, a cause
it never checks: origin's history rewritten at the push, and a new
branch's first write raced by another, gave the same words with no such
writer. Its first remedy was the publish act, which always refuses there,
since origin has moved past HEAD ("moved past", 2).

- The note now says "commits that origin/<b> lacks and this write did not
  make: another writer's here ..., or history origin no longer has
  (rewritten there) or never had (a new branch's first write, raced by
  another)", and the die says "commit(s) that origin lacks and this write
  did not make".
- The recovery is the publish act's refusal's own order: the list
  (`log --oneline <origin's tip>..<sha>`) with the STOP line, then the
  archive on origin, the `reset --keep refs/remotes/origin/<b>` and the
  redo. The three archive lines move into `cp_archive_steps`, which
  `cp_publish_moved` and `cp_under` both call, so their words cannot drift.
- The smaller half: the HEAD checks said a commit on top came "while its
  push was in flight", but 291's case (a), a stamp just after
  `git commit`, comes before the push. The four callers of `cp_not_head`
  that read HEAD after a push (the replay, the origin-unmoved refusal, the
  unconfirmed push and the publish act) now say "before or while its push
  was in flight". `cp_not_head` keeps its three-argument form.

New: `test_history_origin_no_longer_has_under_this_writes_commit_is_never_called_a_writer_here_exit_6`
(a force-push at the push takes the peer commit S3 fast-forwarded onto).
`test_a_commit_landing_just_before_this_writes_commit_is_never_dropped_with_it_exit_6`
asserts the new words and the recovery's order; the case (a) test asserts
"before or while"; three tests that pinned "in this checkout while its
push was in flight" now pin "before or while".

- Red at `50e8916`: 6 failed, 1 passed (the rewritten parameter of the
  exit-3 case, which `cp_rewritten` words).
- Green: 8 passed, with the publish act's recovery case; hygiene 253
  passed; the replay module 74 passed.
- Mutants C43-C46 (the old cause, the archive dropped, the publish act
  offered again, "while" alone at the replay): all KILLED, each at its
  own assertion.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit af29be6cb923a8c58ba25cb244d0637e92097551)
…with the STOP line before any reset, and step 5 and exit row 6 say that `commit`'s carry form drops to its own parent, so a commit under it stays and the re-run refuses on it with 2 (#198, slice 1b)

344 of lane openRepoTools-3's re-check of #307 at `fc6f34d`.

- Case (e2): an unlocked writer's stamp lands after S3 and just before
  this write's `git commit`, so it sits under this write's commit, and the
  push is refused with origin unmoved. The refusal named this write's
  commit alone and offered `reset --keep origin/main` with no STOP line,
  over a stamp it never mentioned. Now, where `<sha>^` is not on origin's
  history, it says how many commits origin lacks under it and lists
  `refs/remotes/origin/<b>..HEAD` with the STOP line, before the reset,
  which now spells `refs/remotes/origin/<b>` in full.
- Exit row 6 (the header and the manual) and the manual's step 5 stated
  the keep form's drop rule as every write's. `commit`'s carry form drops
  to its own commit's parent by design (B21): a commit under it stays, the
  hand edit goes back to the working tree with its copy kept, and the
  re-run's S3 refuses on that commit (2, not 6). Both now say so.

New (replay module):
`test_a_push_refused_with_origin_unmoved_over_a_stamp_under_it_lists_them_and_stops_first_exit_6`
and `test_a_stamp_under_the_carry_forms_commit_stays_and_the_re_run_refuses_on_it_exit_2`,
which pins what step 5 and row 6 now say (it passes before and after:
no behaviour changes there).

- Red at `af29be6`: 1 failed (the list), 1 passed (the carry form's pin).
- Green: 4 passed, with the two origin-unmoved cases; hygiene 253 passed;
  the replay module 76 passed.
- Mutant C47 (the list's condition made `false`): KILLED at its assertion.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 173f3c82677aea33f4c6855cbb420b9bc60b863c)
…ed while it waits for that lock refuses at once with 1, naming the checkout as removed, instead of waiting out its minute and saying another run holds the lock (#198, slice 1b)

360 of lane openRepoTools-3's review of #308, for #307.

- lane-end's sweep removes a CLOSED lane's checkout, and a person may
  move one aside, while a writer for that lane waits on the checkout's
  lock. The waiter's `mkdir` then fails on a missing parent;
  `mutex_stale` read the missing lock as one naming no holder and over
  ten minutes old, and `mutex_steal` could not take it, so the writer
  waited out its 60 s and exited 4: "another lanes-edit run is active
  (holder pid unrecorded)".
- `mutex_try` now answers 2 at once where the directory the lock lives
  in is gone (it still never exits); `lock_try` passes the 2 on, and
  `acquire_lock` calls `lock_gone_die`: "the checkout <c>, which this
  write goes through, was removed while the write waited for its lock",
  the sweep and a move aside as its causes, "Nothing was written", and
  the re-run (a routed write makes the checkout again, S1). Where the
  checkout is still there and only its `lanes/` went, it names that
  directory and its restore. Exit 1, as for a register that is not there:
  no other run holds anything.
- The creation lock (`<W>-lanes/.<lane>.lock`) has the same shape: where
  `<W>-lanes/` itself goes while a writer waits to clone, it says so (1)
  instead of "another writer is creating" the checkout after the clone's
  eleven-minute budget.
- Exit row 1 in the header names the new cause. `sidecar_try` makes its
  directory just before it waits and keeps reading any failure as 1.

New (a23_lock module):
`test_a_writer_waiting_on_a_checkout_that_is_removed_refuses_at_once[removed|moved-aside|its-lanes-directory-removed]`
and `test_a_writer_waiting_to_create_a_checkout_whose_directory_is_removed_refuses_at_once`.

- Red at `173f3c8`: 4 failed. The three waiters exited 4 after 57-58 s
  with "holder pid unrecorded"; the creation waiter was still waiting
  20 s after `<W>-lanes/` went.
- Green: 4 passed; the a23_lock module 28 passed; hygiene 253 passed.
- Mutants C48 (the directory test made `false`), C49 (`acquire_lock`
  ignores the 2), C50 (a checkout still there called removed), C51 (the
  creation lock ignores the 2) and C52 (`lock_try` turns the 2 into 1):
  all KILLED at their own assertions.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 40c57b9bbf34c70a2bb1154abbd5ba5516bcbdbc)
…d `cp_replay`'s replay, carry `# shellcheck disable=SC2093` with their reason in 1a's form, so lint.yml's baseline passes on lanes-edit.sh (#198, slice 1b)

Lane openRepoTools-3's #198 comment 6341440689: #277's lint job and act 2
collide on SC2093. Slice 1a's line (`lane_checkout_enter`) took the
disable in `6ca3158`; these are 1b's own (`10169f3`, `ef1377f`).

- Before: shellcheck 0.9.0 with lint.yml's baseline flags from `935eaef`
  (`--severity=warning --exclude=SC1007,SC2034,SC2069,SC2088,SC2128,SC2141,SC2154,SC2178,SC2209,SC2318`)
  exits 1 with SC2093 at lines 2333 and 3354.
- After: exit 0, no output. Hygiene 253 passed. Comments only.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 126bccddf76c40080664710ee491c1d7c6bc8c5e)
… level refuses with 2 before any edit, naming the top level and the register, instead of making its edit and dying 6 at `git add` (#198, slice 1b)

Found by slice 2a's writer w2a-c10 at `commit_publish`'s `git add`.

- Every pathspec the helper gives git is spelled from the top level
  (`LANES_PATH`, the lane's log, `LANES_PREFIX`), and git reads a
  pathspec relative to the directory it runs in. With
  `LANES_REPO=<c>/handoffs` and `LANES_FILE=<c>/lanes/LANES.md`, every
  writer made its edit ("line 5 rewritten", "1 line appended") and then
  died 6, "pathspec 'lanes/LANES.md' did not match any files" / "git add
  failed", leaving the edit uncommitted. `commit`'s pending hand edit went
  unseen by S3's step 7 (its `git diff HEAD -- lanes/LANES.md` asked about
  `handoffs/lanes/LANES.md`), and every printed recovery named the wrong
  path. `874e020`'s `commit_push` makes the same `git add`: not new
  with this slice.
- `checkout_sync` now asks `git rev-parse --show-prefix` right after its
  not-a-checkout test, under the lock and before the verb reads its file:
  a prefix refuses (2) "LANES_REPO names <prefix> inside the checkout
  <top>, not its top level", with the recovery "name the checkout itself:
  LANES_REPO=<top> LANES_FILE=<the register>", then the re-run.
- Chosen over top-level pathspecs (`git -C <top>` or `:/`): dozens of
  `git -C "$LANES_REPO" ... -- <path>` calls and printed commands would
  each need it, and one missed in step 7 is ratified choice 9's capture
  again. Not a silent rewrite of `LANES_REPO` to its top level either:
  without `LANES_FILE`, the register's default comes from `LANES_REPO`,
  so the recovery names both. `LANES_NO_GIT=1` runs no git and is
  unchanged.
- Exit row 2, in the header and in the manual, names the cause.

New (guard module): `test_a_lanes_repo_inside_the_checkout_refuses_before_any_edit`
for all nine writing verbs, and `test_the_top_level_the_refusal_names_carries_the_hand_edit`,
which follows the printed recovery word for word: the same `commit`
carries the hand edit and publishes it.

- Red at `126bccd`: 10 failed, every verb exit 6 at "git add failed"
  after its edit.
- Green: 10 passed; the guard module 150 passed; hygiene 253,
  skill_shell_blocks 6, test_roots 39 passed and 1 skipped.
- Mutant C53 (the prefix test made `false`): KILLED.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 59551dd97f0d7efed966fa0fd357b65c186ed530)
… for the workstation's lock names that folder and its restore, a move back, instead of W's `lanes/` and a `git checkout` that restores nothing (#198, slice 1b)

701 of lane openRepoTools-3's re-check of #307's round 2.

- `lock_gone_die` (360) told only "the checkout is gone" from "it is
  still there": wherever `$LANES_REPO` was still a directory it said
  `$LANES_DIR` had gone and gave `git -C <c> checkout HEAD -- $LANES_DIR`.
  The workstation's sidecar lock lives in `<W>-lanes/`, which is no part
  of any checkout, so where that folder went while a sidecar act waited,
  W's `lanes/` was still there and the restore did nothing.
- Both branches now say what went in `mutex_try`'s words (`MX_WHY`).
  The git restore is given only where the lock is the checkout's own
  (`$LANES_DIR/.lanes-edit.lock`); any other lock's folder is said to be
  no part of the checkout, with its restore, a move back, and that a
  re-run with it still gone makes it again, empty. Exit 1, unchanged.

New (a23_lock module):
`test_a_sidecar_act_whose_lock_directory_is_moved_aside_names_it_and_a_restore_that_works`
(follows the restore it names: moved back, the act runs, 0). The
`[its-lanes-directory-removed]` case of the 360 test now also asserts
the `git checkout` restore it prints.

- Red at `59551dd`: 1 failed (W's `lanes/` named).
- Green: 5 passed with the 360 cases; the a23_lock module 29 passed;
  hygiene 253 passed.
- Mutants C54 (the sidecar's branch made `false`), C55 (the lock test
  dropped) and C50 re-anchored to the new condition: all KILLED at their
  own assertions.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9137569decd6ec8837fc5ada34953ec6fbef42f1)
… lane the write was given, or none where no lane is named, never a literal `<lane>`, so the archive and the drop run as printed (#198, slice 1b)

702 of lane openRepoTools-3's re-check of #307's round 2.

- `cp_archive_steps` printed
  `archive/${LANES_LANE:-<lane>}-stranded-<UTC>`. Where the lane is named
  by the write's argument alone (LANES_LANE unset: `cp_under`'s case, a
  `set-row-state <lane>` that lost its push race over a commit origin
  lacks), the branch read `archive/<lane>-stranded-…`: git made a branch
  of that name, and a shell reads `<lane>` as a redirection.
- `checkout_sync` keeps the lane it was given in `CS_LANE` (set at load
  to empty, never from the environment). The archive names LANES_LANE,
  or that lane; where neither names one (`commit` with no LANES_LANE,
  without the opt-in: under it a write names its lane, S2) it is
  `archive/stranded-<UTC>`, with no lane segment.

New (replay module): `test_s63s_archive_and_drop_run_as_printed[by-argument|by-nobody]`
runs the printed archive and drop with `bash -c`, word for word: the
archive branch on origin holds the stack, and HEAD reaches origin's tip.

- Red at `9137569`: both cases print `archive/<lane>-` ([by-nobody]
  run on that helper once it went off the opt-in).
- Green: 6 passed with the publish act's and `cp_under`'s cases; the
  replay module 78 passed; hygiene 253 passed.
- Mutants C56 (`CS_LANE` not kept), C57 (the no-lane form prints
  `<lane>` again) and C22 re-anchored to the new lane line: all KILLED
  at their own assertions.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9785ad975e9541e296397ee5d9255176e75ae24e)
…eps in the checkout's git directory, not the 32,768-byte copy that crosses the `exec`; the environment string keeps its cap (#198, slice 1b)

706 of lane openRepoTools-3's re-check of #307's round 2.

- 334 capped the copy of Rule 1's reads that crosses a re-run's `exec`
  at 32,768 bytes, since Linux caps one environment string at 128 KiB.
  The re-run's claim comment took its "three reads" block from that
  copy, so with 1,800 comments the posted comment held about 260 lines
  and the truncation marker.
- Where the reads are over the cap, attempt 1 now also writes them whole
  to `<git dir>/lanes-edit/claim-reads-<UTC>-<pid>.txt`, beside the
  carried hand-edit copies, and its name crosses the `exec` with the
  other claim markers (`LANES__CLAIM_READS_FILE`, read and unset at
  load). The re-run prints the capped copy as before and posts the file.
  The file goes in `cleanup`, when the claim's last run ends, however it
  ends (`exec` runs no EXIT trap). Where it cannot be written, the
  re-run posts the capped copy, marked, as before.

New (replay module):
`test_a_claims_re_run_posts_the_whole_reads_not_the_capped_copy`; the
`gh` stub of `_network_reads_logged` keeps a posted comment's body when
asked (`posted=`).

- Red at `9785ad9`: 1 failed (the body held the truncation marker and
  not `bulk-01800`).
- Green: 5 passed with 284's and 334's cases; the replay module 79
  passed; hygiene 253 passed.
- Mutants C58 (the body cites the capped copy), C59 (the file never
  read), C60 (its name does not cross the `exec`) and C61 (the file
  outlives the claim): all KILLED at their own assertions. C16 and C17
  (284's markers) re-anchored to the env line, which no longer ends it:
  KILLED.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0ddde5e92c115a617fe3a8d4dad5850e29e5a93b)
…in full, `refs/remotes/origin/<b>`, so a local branch named `origin/<b>` no longer stands in for origin in a `cherry` or a `reset --keep` run as printed (#198, slice 1b)

703 of lane openRepoTools-3's re-check of #307's round 2: the class of
333 and 342 was open in six more printed commands.

- `git cherry -v origin/<b> HEAD` in `cp_not_confirmed`'s LOOK line,
  `cp_rewritten`'s read by content and the publish act's origin-unmoved
  refusal; `reset --keep origin/<b>` in `cp_replay`'s keep-if-gone and
  six-attempts notes and in the migration's case-only rename. A local
  branch named `origin/<b>` wins the short name over the tracking ref,
  so the LOOK line listed a landed commit as missing, and the reset
  would have dropped to that branch.
- `cp_rewritten`'s `log --oneline -5 origin/<b>`, in the same note
  block, is spelled the same way. README row 6 says
  `git cherry -v refs/remotes/origin/main HEAD`.
- Left alone on purpose: slice 1a's two (`lane_checkout_verify` and W's
  ahead note), and README's lines 105, 108 and 242 (not row 6).

New (replay module):
`test_an_unconfirmed_pushs_look_line_reads_origin_past_a_local_branch_named_origin_main`
runs the LOOK line with `bash -c`, word for word, past a shadow
`origin/main` branch behind origin. The bound, unconfirmed,
publish-act-unmoved [head-is-the-tip], rewritten and A37 [deletes-it]
cases, and the guard's migration case, assert the new words.

- Red at `0ddde5e`: 7 failed at their 703 assertions (the LOOK case
  listed the landed commit as `+`), 1 passed (the migration's
  [untracked], which prints no reset).
- Green: 8 passed; the replay module 80 passed, the guard module
  150 passed, hygiene 253 passed.
- Mutants C62-C68, one per changed line, each back to the short name:
  all KILLED at their own assertions.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 458cdde04c3d4e34bd79ba84ac4f5a58883f5a52)
… notes give S6.3's recovery, the list and the STOP line and then the archive, instead of offering first the publish act, which refuses there (#198, slice 1b)

705 of lane openRepoTools-3's re-check of #307's round 2: the flaw 343
fixed in `cp_under` was left in four sibling notes. `cp_replay`'s
six-attempts and keep-if-gone notes, and `cp_not_head`'s HEAD-still and
on-top notes, offered `LANES_LANE=<lane> lanes-edit.sh commit "<what it
is>"`, the publish act, first. Origin had moved past HEAD in each, so
the publish act refused with 2 ("moved past"), a detour.

- New `cp_recovery_steps [<what is kept>]`: the lead ("every later
  write ... refuses until it is archived and dropped here: origin has
  moved past HEAD, so the publish act refuses (2)"), then
  `cp_publish_moved`'s own RECOVERY line, its list line with the STOP
  line, and `cp_archive_steps`. `cp_publish_moved` calls it with no
  lead, so its words are unchanged.
- The bound and the keep-if-gone note call it. `cp_not_head` calls it
  where it is given no fourth argument (origin moved). With a fourth
  (origin unmoved: `commit_publish`'s refusal, the publish act's, a
  push not confirmed) the publish act is offered as before.
- `cp_rewritten`'s offer is unchanged: a rewritten commit sits on
  origin's tip, so the publish act is a fast-forward there.

Tests (replay module): `_recovery_is_s63s_archive` asserts the lead,
RECOVERY, the list, the STOP line, the archive and the drop in that
order, and no publish-act offer. It runs in the bound, the A37
[deletes-it] case, the drop refused over a held ref lock (HEAD still
this write's commit), and three on-top cases. The origin-unmoved
on-top case asserts the publish act is still offered and no archive.

- Red at `458cdde`: 6 failed at the order assertion; 2 passed (the
  origin-unmoved case and the publish act's own refusal, the controls).
- Green: 8 passed; the replay module 80 passed, the guard module
  150 passed, hygiene 253 passed.
- Mutants C69-C75 (each call site, both branches of the fourth-argument
  test, the lead, and the publish act's call): all KILLED. C21 and C33,
  whose list line moved into the helper, re-run: KILLED. 703's C65 and
  C66 are retired: their lines are gone, and C69 and C70 cover the
  notes that replaced them.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f97723cc086e63942e5e40b7d2a215774720de69)
…mes every copy of the hand edit it keeps, attempt 1's as well as the re-run's, instead of the last one alone (#198, slice 1b)

707 of lane openRepoTools-3's re-check of #307's round 2, the small
follow-up the body disclosed with 393.

- A `commit` that loses a push race replays its carry, which saves the
  hand edit a second time. Where the peer that won changed the same
  row, the re-run's carry no longer applies, and the refusal (2) kept
  both copies but named only `CS_CARRY_DIFF`, the last. Lane-end's
  sweep holds the checkout on either one.
- New `cs_earlier_copies` reads 393's record (`CP_CARRY_COPIES`, which
  crosses the replay's `exec`) and prints every copy but the last. The
  refusal says "KEPT in <the last> (and, as this write saved it before
  its push lost the race, in <the earlier>)". A replay in the same
  second reuses the name (704), so that copy is the last one and is
  named once.

New (replay module):
`test_a_lost_races_refused_re_run_carry_names_every_copy_it_keeps`.
The refusal line names each copy once, by the names the carry notes
printed and the files kept, so it holds for one name or two.

- Red at `f97723c`: 1 failed (attempt 1's copy not named), and the
  single-copy refusal case passed.
- Green: 7 passed with the refusal cases; the replay module 81
  passed, the guard module 150 passed, hygiene 253 passed.
- Mutants C76 (the earlier copies are not asked for) and C77 (the last
  copy named twice): KILLED at the naming assertion.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 80ae89e731e8a70d73f8ba90a64f7a1821b8d45d)
…d, not two distinct names, and a `[same-second]` case gives attempt 1 and its replay one second, so the test no longer depends on the clock (#198, slice 1b)

704 of lane openRepoTools-3's re-check of #307's round 2.

- A copy's name carries the time to the second. A replay in the same
  second gives both copies one name: one file, written twice and
  removed once origin has the edit, which is right. The test asserted
  two distinct names, so it would have failed there. The replay's
  three-second pause hides it unforced.
- `test_a_lost_races_two_copies_of_the_hand_edit_are_both_removed_once_origin_has_it`
  is now `[unforced|same-second]`. `[same-second]` puts a `date` first
  on PATH that answers the copies' own format with one fixed second
  and hands every other call to the real `date`. Both cases assert two
  recorded copies, one removal note per recorded copy, and no copy
  left; `[same-second]` also asserts the two names are one.

Test-only: `lanes-edit.sh` is not changed.

- Red at `80ae89e`, with the shim and the old assertion: `[same-second]`
  failed at the distinct-names assertion (1 == 2), `[unforced]`
  passed.
- Green: 3 passed (both cases and 707's refusal case); the replay
  module 82 passed, hygiene 253 passed.
- Mutants C37, C38 and C39 (393's record, its export across the
  replay's `exec`, and its read at load), run on the whole test and
  on `[same-second]` alone: all KILLED at the removal-note assertion.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 4b79d6718f19973e3266cac4e3988de23e51a72e)
…ode main 577110e) into slice 1b: three hunks in lanes-edit.sh with opensoft/openRepoTools#263, each resolved to keep main's change and 1b's (opensoft/openRepoTools#198)

Brings `903d751`, slice 1a re-homed on opensoft/openRepoTools-code
(`feat/198-a23-slice-1a-routing`) with the code leg's main `577110e`
merged in. Over this branch's base `0cefa1f` (old main `f18b818`), main
brings opensoft/openRepoTools#284, opensoft/openRepoTools#265,
opensoft/openRepoTools#263, opensoft/openRepoTools#289,
opensoft/openRepoTools#292 with opensoft/openRepoTools#294 and
opensoft/openRepoTools#296 of the old repository, the cutover's code
follow-ups (#1) and T017's
#3 to #7. The
merge base is `4d6bc14`, 1a's re-homed head (old `6ca3158`), which 1b
already holds.

git stopped on three hunks, all in `lanes-edit.sh` and all with
opensoft/openRepoTools#263 (`4dd0fcf`, old `d9065df`). The rehearsal of
2026-10-24 on the old repository met five: the other two were in
`docs/README-lanes.md`, which this leg does not hold (it went to
opensoft/openRepoTools-spec at the cutover). Each was resolved on
purpose, by rule (`git show --remerge-diff` shows exactly these):

- B70. The exit table's rows 5 and 6 in the header of `lanes-edit.sh`:
  opensoft/openRepoTools#263's row 5 (a register rewrite whose copy,
  write or result could not be proved, opensoft/openRepoTools#120 item
  1) and 1b's row 6 (the commit kept on HEAD and named, Amendment 23
  S4). Main's row 6 ("git add / commit / push failed") is the line 1b
  rewrote; keeping it would conflict again where slice 2a adds its own
  clause to row 6.
- B71. The manual's rows 5 and 6 and its "The sweep" paragraph are the
  spec leg's, and are not in this merge: the openRepoTools-spec change
  that carries 1b's `docs/README-lanes.md` half takes the same two rules
  (rows as B70; 1b's sweep paragraph with opensoft/openRepoTools#263's
  three additions, without opensoft/openRepoTools#263's sentences on the
  capture).
- B72. `add_row_retirement_rescan`: 1b's deletion stands (decision 9: no
  `pull --rebase`; a refused push replays, and add-row's own checks
  refuse on origin's new tip with nothing left at HEAD).
  opensoft/openRepoTools#263's new `retired_identity_hits` body, which
  add-row's checks call, is kept.
- B73. `retire_rows`' published-log refusal:
  opensoft/openRepoTools#263's test (`rr_from`, `rr_here`, the case-only
  rename whose commit never landed) and words, with 1b's recovery for a
  writer that never pulls, in place of "Pull first —
  `git pull --rebase`".

Nothing else is changed in this commit: it is git's result and these
three hunks. opensoft/openRepoTools#263's
`tests/test_register_hardening_120_122.py` still pins the capture, the
after-rebase take-back that 1b deletes and three scans that S3 now
reaches second, so it fails here; the next commit re-pins those cases
and makes opensoft/openRepoTools#263's auto-merged comments say what
holds under 1b. The two are pushed together.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'s push or meet S3's refusal of an unpushed commit, and its auto-merged comments and one refusal no longer describe the capture or the after-rebase take-back that slice 1b deletes (opensoft/openRepoTools#198, slice 1b)

The merge before this one brings opensoft/openRepoTools#263 (`4dd0fcf`,
old `d9065df`), whose module `tests/test_register_hardening_120_122.py`
pins what slice 1b deletes: the capture of a peer's register edit
(`handle_preexisting`; S3 step 4, ratified choice 9) and add-row's
`pull --rebase` with its after-rebase rescan and take-back (decision 9).
This commit makes them say what holds under 1b. No code path changes.

- B74. NIT 295's eight cases (lane openRepoTools-3's review of
  opensoft/openRepoTools#307), re-pinned as the 2026-10-24 rehearsal
  wrote them. The race moves to the writer's `push`:
  `peer_pushes_during_pull` is `peer_pushes_before_its_push`, its shim
  fires on the checkout's first `push`, and its seven callers and their
  "never raced" messages follow.
  `test_122_a_refusal_after_the_rebase_takes_its_row_back` is
  `test_122_a_refusal_on_the_replay_leaves_no_row_to_publish`; it and
  the other raced cases assert the replay's refusal (1), no compensating
  commit, nothing in `origin/main..HEAD`, and the next write publishing
  no extra row. The context case publishes its nearby edits first, since
  S3 refuses a write over unpushed commits, and asserts that no
  take-back diff is asked for. The capture's case becomes
  `test_120_item_1_a_peer_edit_is_refused_before_the_copy_and_never_captured`:
  S3's refusal (2) before the copy, nothing captured, HEAD and the edit
  as they were.
- B75. The three cases NIT 295 did not list, found by the rehearsal's
  run (43 passed, 3 failed):
  `test_120_item_2_a_rename_whose_commit_never_landed_refuses_in_the_scan`
  and
  `test_120_item_3_a_closing_line_only_this_checkout_has_is_not_called_closed`
  (ENDED and RETIRED). Each commits and does not push, so S3 refuses the
  unpushed commit (2) before the scan. Each now asserts S3's words
  ("lacks by content", the commit's subject, "(Amendment 23 S3). Nothing
  was written."), the scan's words absent, HEAD unchanged and nothing
  written; item 3 also that the lane is not called CLOSED and its row is
  not retired. Two constants of the module, `S3_LACKS` and `S3_WORDS`,
  hold S3's words. The scan's words are reached only where origin moves
  after a write's fast-forward under its lock; the uncommitted closing
  line of
  `test_120_item_1_a_sweep_refused_part_way_names_what_it_wrote_and_is_not_called_closed`
  still reaches them ("in THIS CHECKOUT ends on a RETIRED line").
- B76. opensoft/openRepoTools#263's auto-merged text in `lanes-edit.sh`,
  five places: the comments on the capture (`handle_preexisting`), on
  the take-back's `REG_COMMITTED_HERE` and `DL_SAYS_*`, on the refusal
  sentences `delete_lines` takes from its caller, and on the rescan
  after the rebase, and one refusal's sentence: "the next one would
  commit it as a pre-existing edit" is now "the next `commit` there
  would publish it as its hand edit". `REG_COMMITTED_HERE` and
  `DL_SAYS_*` stay as opensoft/openRepoTools#263 wrote them, set by no
  caller now. The manual's take-back paragraph is the spec leg's.

Tests at this commit's tree (at the merge's tree alone,
register_hardening_120_122 gives 22 failed, 24 passed), one pytest
process per module, 25 s apart, with every `LANES_*` and `TMUX*` unset,
`GH_TOKEN=bogus` and a stub `gh` first on PATH:
register_hardening_120_122 46 passed, hygiene 235 passed and 18 skipped
(no assembly or spec root here), guard 150 passed, routing 57 passed,
commit_push_published_edit_194 25 passed, a23_lock 29 passed,
guard_launch_mode 13 passed, deferred_97_findings_157 9 passed,
register_scan_hardening_138 29 passed, command_builtin_errexit_292 14
passed, replay 82 passed, lanes_index 98 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap brettheap changed the title Amendment 23 slice 1b: every writer fetches and fast-forwards under its lock or refuses with nothing written, pushes with no pull and reads origin back, a refused push replays the one edit, and commit with nothing staged is the publish act (opensoft/openRepoTools#198) Amendment 23 slice 1b: every writer fetches and fast-forwards under its lock or refuses with nothing written, pushes with no pull and reads origin back, a refused push replays the one edit, and commit with nothing staged is the publish act Nov 8, 2026
Brett Heap and others added 3 commits November 8, 2026 19:42
… S3 now leaves reachable only where origin moves after its fast-forward, says ensure_log's recovery sentence and has a case (lane openRepoTools-3's S4 on #17)

B73's hand-resolved die (opensoft/openRepoTools#263's test, 1b's recovery)
had no case: S3 refuses a case-only rename whose commit is not pushed
before the scan, so the state the die was written for never reaches it,
and nothing else did either. Its recovery sentence ("Origin moved after
this write's fast-forward under its lock ... re-run it, and it starts from
origin's new tip") was also not ensure_log's, which the comment above the
scan promises ("in its words"). It is now ensure_log's, word for word:
"Re-run the write: it fast-forwards this checkout to origin's tip under
its lock first (Amendment 23 S3), and the rename then happens once, inside
its own commit." The comment says when the scan meets it.

The new case, test_120_item_2_a_log_respelled_on_origin_after_the_fast_forward_refuses_in_the_scan,
builds the state S3 does not refuse: a clean checkout in step with origin,
holding repoX-3's log in the row's spelling. A git shim hands back the
sweep's behind-check (0 behind), then a peer pushes a respelling of the
log and a reader fetches it into the shared checkout. The sweep, over
repoX-2 and repoX-3, exits 2 with the scan's words and ensure_log's
recovery, no log made for repoX-2, HEAD, the register and the log
unchanged. The case also checks that lanes-edit.sh holds the sentence
twice, so the two dies cannot drift apart.

Red first: the case fails on the old sentence at the recovery assertion,
after every earlier assertion (the shim fired, exit 2, S3 silent, the
scan's cause and the row's spelling) passed. register_hardening_120_122:
47 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ture case asserts S3's refusal in its words, and four cases named for #263's rebase and take-back say what they assert (lane openRepoTools-3's N2 and N3 on #17)

N2. The identical-row case (two parameters) also passed against the code
leg main's 577110e scripts, so it pinned the invariant (the peer's row is
left alone) but not that the replay ran. It now asserts that
"origin/main already carries this exact row", #263's rebase note, is not
printed. Its message names the reason.

The four cases whose names said the opposite of what they assert are
renamed:
- test_122_a_refusal_after_the_rebase_leaves_a_peers_identical_row_alone
  is test_122_a_refusal_on_the_replay_leaves_a_peers_identical_row_alone;
- ..._after_the_rebase_takes_back_its_own_row_beside_a_peers_copy is
  test_122_a_refusal_on_the_replay_drops_its_own_row_beside_a_peers_copy_in_another_place;
- test_122_a_take_back_that_cannot_rewrite_says_add_row_has_committed is
  test_122_a_refusal_on_the_replay_says_nothing_of_a_committed_row_whatever_is_cut;
- test_122_a_removal_that_cannot_be_committed_says_so is
  test_122_the_replay_makes_no_removal_for_a_refused_add_or_commit_to_strand.

N3. The capture case asserted exit 2 and absences only, so any other
exit-2 refusal passed it. It now asserts cs_refuse_register's words,
"holds an uncommitted edit, which may be a peer's".

register_hardening_120_122: 47 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ode PR 15, the openRepoShape v1.1.1 pin) into slice 1b: no conflict, five files (opensoft/openRepoTools#198, slice 1b)

f0d363a is slice 1a's --no-ff merge of the code leg's main e393958 into
903d751. `git merge-tree --write-tree` gave tree
c76b695 before this merge was made, with
no conflict, and the merge's tree is that one. It brings the five files
of main's code PR 15, +12 -8: upstream/openRepoShape,
contracts/openreposhape-pin.yaml, .github/workflows/release.yml,
tests/test_release_workflow.py and tests/test_install_payload_source.py.
No hunk was resolved by hand.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Nov 8, 2026

Copy link
Copy Markdown

brettheap pushed a commit that referenced this pull request Nov 8, 2026
…ings its fix round after 34799b7 (S4, N2 and N3) and slice 1a's merge of the code leg's main e393958 (code PR 15, the openRepoShape v1.1.1 pin) (opensoft/openRepoTools#198)

Slice 1b's head on the code leg, feat/198-a23-slice-1b-guard-publish at
458cb38, named by main at 2026-11-08T20:2xZ. It holds 7 commits past
34799b7, the last 1b head merged into this slice (as 2b1a232), 3 of them
merges:
- 8e42208 (S4 of lane openRepoTools-3's review of
  #17): retire-rows' published-log refusal says
  ensure_log's recovery sentence, with a case;
- cc8f6ab (N2 and N3 of that review): two register-hardening cases assert
  more, and four are renamed for what they assert;
- 458cb38, which merges slice 1a's f0d363a, slice 1a's merge of
  openRepoTools-code main e393958 (code PR 15: upstream/openRepoShape,
  contracts/openreposhape-pin.yaml, .github/workflows/release.yml,
  tests/test_release_workflow.py, tests/test_install_payload_source.py).

Why: slices 2b and 3 take 1b's head through this merge, and this slice's
branch has to carry the code leg's main that the PRs below it carry.

The merge is clean: no hunk was resolved by hand. 8e42208's one change
to lanes-edit.sh (retire-rows' die) is in a place this slice's rounds A62
to A64 did not touch. git merge-tree --write-tree of 458cb38 into 2ff2c34
gave tree 6977724 before the merge was
made, and the merge's tree is checked against it.

Refs opensoft/openRepoTools#198

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant