Skip to content

Amendment 23 act 2, slice 2a: write-handoff writes a lane's handoff in the checkout its writes go through, handoff-path says where, publish-handoff keeps its contract on one shared act, and set-restart-intent takes the row's spellings - #18

Draft
brettheap wants to merge 42 commits into
feat/198-a23-slice-1b-guard-publishfrom
feat/198-a23-slice-2a-handoff-verbs

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 exit rows 2, 6 and 7) is opensoft/openRepoTools-spec#16, at b3084ab, stacked on 1b's opensoft/openRepoTools-spec#15. It carries NIT 5's clause (decision 35). The two land together, the code half first or at the same moment, never the spec half ahead of it.

Re-homed from opensoft/openRepoTools#312

This is slice 2a of Amendment 23 act 2, opensoft/openRepoTools#312, moved to the code leg. Since the triad cutover (2026-10-26 to 10-28), lanes-edit.sh and its tests live here, and docs/ lives in opensoft/openRepoTools-spec. Brett Heap's answer to Q28 re-homes act 2's six drafts here in their stack order (confirmed on opensoft/openRepoTools#198, comment 6722244541), and they move by cherry-pick, not by merge (opensoft/openRepoTools#186, comment 6548291952, S18). The old branch on opensoft/openRepoTools stays as it was, as the archived original.

  • Heads. Old: feat/198-a23-slice-2a-handoff-verbs at 7bc572c on opensoft/openRepoTools (A60, the slice's last head there). New: this branch at ef469e1. 1320847 is 7bc572c re-homed, 2b1a232 merges slice 1b's code-leg head into it (decision 31), 56a9b87 is A61, the fix for the two failures CI found at 2b1a232 (decision 32), and 0547408, 053b382 and 2ff2c34 are A62 to A64, the fixes for lane openRepoTools-3's review of this PR at 2b1a232 (decisions 33 to 35), and ef469e1 merges slice 1b's new head 458cb38 (decision 36).
  • The method. Each of the slice's 36 commits (30 commits and its 6 merges of slice 1b) was written again with git commit-tree, in topological order, so nothing could conflict:
    • its tree is the old commit's tree cut to the 26 top-level paths the code leg owns (the cutover plan's leg: code entries), so docs/, specs/, openspec/ and the root-only files are left out;
    • its message, author and both dates are the old commit's, with a (cherry picked from commit <old id>) line added, and its committer is Brett Heap;
    • its parents are mapped: an old main commit to its counterpart in the code leg's carve (old main's f18b818 is 0cefa1f here), and an act-2 commit to its rewritten one. A merge stays a merge.
  • The checks. Every new commit's tree equals its old commit's cut tree, with its parents, author, committer and message as above: 0 mismatches over act 2's 227 commits. git range-diff of this slice (old 1b..old 2a against new 1b..new 2a) pairs all 30 commits, each marked !, and leaves none unmatched. Each pair differs by its cherry-pick line, and 6 of them (357ec07, 76a019b, e2037b9, 68014b4, 36a25da, c962db7) also by a docs/README-lanes.md section the code leg does not own. Nothing else differs. range-diff does not show the 6 merges; the tree check covers them.
  • Not here: docs/README-lanes.md. This slice's clauses in the manual's exit rows 2, 6 and 7 (decisions 18, 22, 25 and 26) are the spec leg's now: a net change of 3 lines from slice 1b's 59551dd, which goes to opensoft/openRepoTools-spec on its own. No test here reads a docs/ file.
  • Ids below. The commit ids in the sections below are opensoft/openRepoTools's, as the review rounds named them, and the receipt maps each of this slice's to its id here. "Stacked on", decisions 31 to 36 and the 11 modules' rows under "Tests" name this branch's commits.
The receipt: the slice's 36 commits, by old id (opensoft/openRepoTools) and new id (here)
old new subject
9f4f4f7 35c04ff publish-handoff's act becomes handoff_publish_atomic, one implementation of the restart fences, the prior digest and the one-rename replace that write-handoff will share, and publish-handoff keeps its contract for every old caller (opensoft/openRepoTools#198, slice 2a stage 1)
357ec07 32f2f6a write-handoff writes a lane's handoff in the checkout its writes go through, as a Rule 3 stamp, a new top block or a whole prepared file, under S3, the translation, the restart fences and S4, and handoff-path says where that is (opensoft/openRepoTools#198, slice 2a stage 2)
548095e bbb8f48 write-handoff asks the managed seam again on what S3 fetched, so a managed-owner marker that lands between its first ask and S3's fetch is read, and the write is refused with nothing written (opensoft/openRepoTools#198, slice 2a)
43baa97 54ff3b2 Merge slice 1b at 6ecfc52 into slice 2a: git found no conflict, and write-handoff joins lane_row_gate in the next commit (opensoft/openRepoTools#198)
6ee2c11 8129efa write-handoff joins lane_row_gate's row group, so under the opt-in a write for a lane with no row is refused before its checkout is cloned, after one fetch of W or at once under LANES_NO_FETCH=1 (opensoft/openRepoTools#198, slice 2a)
d5cabdc c9fd35d set-restart-intent --handoff takes the row's own spellings, a repository-relative handoffs/… cell and the estate's ~/…, and binds them as the absolute path in the checkout the lane's writes go through, while an absolute path is still kept exactly as given (opensoft/openRepoTools#198, slice 2a stage 3)
b02618c bc01690 Merge slice 1b at 10169f3 into slice 2a: exit 6 names both sides' new causes, and both sides' new globals are kept (opensoft/openRepoTools#198)
6c2a9cc 2e56bb4 MUST 1 (rv2a's pre-review of slice 2a): write-handoff counts a handoff git does not track yet as a pending change, so a lost push race no longer drops it with the replayed commit, and a peer's untracked file is never added under this write's subject (opensoft/openRepoTools#198, slice 2a)
011f241 9fb46cd SHOULD 1 (rv2a's pre-review of slice 2a): write-handoff writes a file under handoffs/ and nothing else, so a --target naming the register, or a row cell that climbs out to an object log, is refused before a byte instead of replaced and pushed (opensoft/openRepoTools#198, slice 2a)
7561d5a 2ae7336 SHOULD 3 (rv2a's pre-review of slice 2a): write-handoff asks the managed seam again on what S3 fetched whatever S3 answered, so a managed-owner marker that lands at S3's fetch refuses a --prepend or --whole that S3's later refusal would still have written (opensoft/openRepoTools#198, slice 2a)
346e403 b643ae0 SHOULD 2 (rv2a's pre-review of slice 2a): S3's refusal of the register checkout no longer stops the write of another repository's handoff, which no tool here commits, so an offline lane's Amendment 8(d) stamp is written and the verb exits 0 with the 8(d) note (opensoft/openRepoTools#198, slice 2a)
3cd2162 2978d69 SHOULD 4 (rv2a's pre-review of slice 2a): write-handoff's tests now pin four behaviours that a one-line mutant used to leave alive, the exit 3 and LANES_NO_GIT paths, and carry no composite assert (opensoft/openRepoTools#198, slice 2a)
96bf464 0d7e64d NITs 1 and 3 (rv2a's pre-review of slice 2a): write-handoff and handoff-path read the lane's row through a checked read, so a register that could not be read is 1 and never "no handoff", and a --target write for a lane with no row is 8 instead of a pushed commit carrying Lane: <that lane> (opensoft/openRepoTools#198, slice 2a)
ea1d293 e7a4960 commit_publish's S4.4 refusal reads HEAD, so where another writer's commit landed on top of a whole-file write-handoff while its push was in flight, the words name HEAD and say both commits are kept, instead of saying this write's commit is HEAD (opensoft/openRepoTools#198, slice 2a)
287f6e5 f559a29 Merge slice 1b at e6ea8f8 into slice 2a: exit rows 2 and 6 list both sides' new causes (opensoft/openRepoTools#198)
1624f17 8fd9bcb S4.4's refusal sends a commit another writer rewrote, and a HEAD that cannot be read, through slice 1b's cp_not_head, so a whole-file write-handoff never says its commit is kept when HEAD does not hold it (opensoft/openRepoTools#198, slice 2a)
e1a166a c1149b3 Merge slice 1b at 514b0c4 into slice 2a: no conflict, and 2a's S4.4 call to cp_not_head keeps its three-argument form (opensoft/openRepoTools#198)
76a019b 844a88d A37: write-handoff names the handoff --stamp and --prepend edit in place in slice 1b's CP_KEEP_IF_GONE, so a peer that deletes it while the push is in flight leaves the write kept on HEAD and named, instead of replayed into nothing (opensoft/openRepoTools#198, slice 2a)
0d7d853 c6fd24a Merge slice 1b at 0c822f3 into slice 2a: no conflict, one test of 1b's carry form added (opensoft/openRepoTools#198)
a928dbb 8b81216 A44: write-handoff holds its handoffs/ rule in another clone of the register too, so a --target or a row cell naming a sibling lane's register or object log is refused (2) instead of replaced as another repository's handoff (opensoft/openRepoTools#198, slice 2a)
2fc394d 27e76d7 A45: write-handoff asks whether its handoff is tracked whatever S3 answered, so the recovery note names the git add an untracked handoff needs even where S3 refused for another reason (opensoft/openRepoTools#198, slice 2a)
e2037b9 f00def1 A46: A37 keeps a --stamp or --prepend on HEAD only where a replay could not follow the handoff, so a handoff a peer moves with its row's cell mid-push is replayed into the moved file instead of kept (opensoft/openRepoTools#198, slice 2a)
5ad9b4a 9e9281f A47: write-handoff refuses a target inside a git directory, so a --target or a row cell naming another clone's .git/config, or a bare clone's, is refused (2) instead of written as another repository's handoff and every git command there broken (opensoft/openRepoTools#198, slice 2a)
68014b4 ad1a44d A48: A46 follows a moved handoff only to a file the replay can write, so a peer that empties the row's cell, or points it at a name origin has no file for or outside handoffs/, keeps the --stamp or --prepend on HEAD (6) instead of dropping it with nothing written (opensoft/openRepoTools#198, slice 2a)
2a41e83 d7522f6 A49: tests for the fifteen checks of write-handoff and handoff-path that m2a's mutant battery found no case for, each shown failing on its mutant: six usage values, an empty block or prepared file, a flag where the lane goes, four of handoff-path's reads, a link out of the checkout, and the handoffs/ rule's own cases (opensoft/openRepoTools#198, slice 2a)
37b000d bec6a4e A51: tests for the seven checks that the second half of m2a's mutant battery found no case for: an empty handoff, a row naming a file that is not there, --expected-digest none, a cell spelled otherwise whose file a peer deletes mid-push, a register a peer deletes mid-push, and a routed bind that must clone nothing (opensoft/openRepoTools#198, slice 2a)
1bb4f75 392e722 A52: tests for the three checks that m2a's last re-check of slice 2a found no case for: the line a refused --stamp says, a cell this checkout cannot translate, and a cell padded otherwise (opensoft/openRepoTools#198, slice 2a)
f43deb3 d8b257d Merge slice 1b at 59551dd into slice 2a: four blocks resolved as unions, and cp_own_commit given the whole message with the Lane: trailer (opensoft/openRepoTools#198, review of opensoft/openRepoTools#312, 606)
ed63c17 cee0346 A53: a test for 606, the write whose commit an unlocked writer's lands just after: write-handoff's own commit is told by its whole message, the Lane: trailer with it, and named under the other, both kept on HEAD (6) (opensoft/openRepoTools#198, slice 2a, review of opensoft/openRepoTools#312)
6d861d4 54fa27b A54: 601 in the translation the three verbs share: write-handoff, handoff-path and set-restart-intent --handoff refuse a handoff in W or in another checkout beside it (2, nothing written, no path printed), and a clone git cannot read is never "no repository" (1) (opensoft/openRepoTools#198, slice 2a, review of opensoft/openRepoTools#312)
31c6198 6eb236e A55: 602, a repository-relative cell or --target whose . or .. components climb out of the checkout is refused by write-handoff and handoff-path (2, nothing written, no path printed), its spelling asked first (opensoft/openRepoTools#198, slice 2a, review of opensoft/openRepoTools#312)
36a25da 351bc22 A56: 603, write-handoff answers the shared act's two restart-fence 2s (the bound file, the pre-image) with 7, so its 2 is never a restart fence's; publish-handoff keeps its documented 2 (opensoft/openRepoTools#198, slice 2a, review of opensoft/openRepoTools#312)
c962db7 60f943e A57: 604, as lane openRepoTools-3 ruled it, write-handoff commits and pushes an Amendment 8(d) handoff in its own repository, as lane-start and lane-handoff did at e2037b9, and never in another clone of the register (opensoft/openRepoTools#198, slice 2a, review of opensoft/openRepoTools#312)
7ffbcb0 a79d0d9 A58: 732, carried in 2a's file, refresh_workspace_strays' git status in W takes no optional lock, so the stray scan never rewrites W's index (opensoft/openRepoTools#198, slice 2a, review of opensoft/openRepoTools#316)
60ddad3 2bb3e2a A59: the NITs of lane openRepoTools-3's review of opensoft/openRepoTools#312, a fenced --prepend takes --expected-digest none only (605, 64), no glob character in a handoff's path (607, 2), S4.4 says "before or while" and names the commits under it (609), the cases 610 asks for, and the guard's comment names write-handoff's exemption (611) (opensoft/openRepoTools#198, slice 2a, review of opensoft/openRepoTools#312)
7bc572c 1320847 A60: 991, under 723, handoff-path and set-restart-intent --handoff refuse a handoff whose path in the checkout holds a glob character, as write-handoff does (607), by one shared question (opensoft/openRepoTools#198, slice 2a, review of opensoft/openRepoTools#311)

Stacked on

Slice 1b's branch feat/198-a23-slice-1b-guard-publish on the code leg at 458cb38, the last 1b head merged here (as ef469e1, decision 36; before it 34799b7, merged as 2b1a232, decision 31). Please review the commits after it: this PR's own diff is git diff 458cb38 HEAD. Slice 1b was merged in six times on opensoft/openRepoTools (6ecfc52, 10169f3, e6ea8f8, 514b0c4, 0c822f3, 59551dd, in its ids) and twice here, each with --no-ff and with every conflict recorded under "Decisions".

Decisions

Decisions 8, 19 and 20 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#312.

  1. One publication act, shared. publish-handoff's body is moved whole into handoff_publish_atomic, which both verbs use. It follows the link to the physical file, checks the restart fences and the prior digest, then replaces the bytes with one rename beside the file (Publish rename-lane's Rule 3 handoff stamp through a same-directory temporary file and one atomic rename, so a writer killed mid-stamp leaves the handoff whole openRepoTools#212's way, which was this act's own). publish-handoff keeps every argument, word, code and its one stdout line. The only difference is invisible: a refusal's lock is released by the exit trap. rename-lane keeps its own inlined copy (a question below).

  2. write-handoff <lane> (--stamp "<line>" | --prepend <block> | --whole <prepared> --expect-digest <sha>) --subject "<s>" [--target <path>] [--restart <op> <gen> <attempt> <state> <transcript> [--expected-digest <sha>]], the plan's interface. It is routed: write_lane_of names its lane, so it runs in the lane's checkout under the opt-in, in an explicit LANES_REPO in place, and in W with the opt-in off. Usage errors are 64. The subject, the stamp and the fences are each one line with no control character.

  3. Order, under the checkout's lock:

    • the managed seam, before the lock;
    • S3, then the managed seam asked again on what S3 fetched, whatever S3 answered;
    • the lane's row, read whatever --target says;
    • the target, translated, then S5 and the handoffs/ scope;
    • S3 step 4 for the handoff, an untracked handoff included;
    • the read, the splice and its proof;
    • the sidecar lock nested inside, for the fences and the replace only;
    • then S4.

    The lock order is always checkout, then sidecar, and no act takes them the other way round.

  4. What S3's refusal leaves, mode by mode. --stamp writes nothing and prints the line it would have written, because a Rule 3 stamp the checkout cannot take is written by nobody (lane-start's note). --prepend and --whole write the file, commit nothing and print its path (R-A11-11: a swap is never left unwritten). Each exits with S3's code. S5, a fence, a digest or a failed proof writes nothing. Another repository's handoff is the exception (decision 16).

  5. Translated, never followed (S2). handoff_in_repo <checkout> <cell>:

    • a repository-relative cell is joined to the checkout;
    • a ~/… or absolute path whose physical parent is under W becomes the same path in the checkout;
    • a path already in the checkout stays itself;
    • anything else is another repository's (Amendment 8(d)): written there, then committed and pushed in that repository (decision 26).

    A target that is physically W's file, from a checkout that is not W, is refused (2, S5).

  6. The splices are the callers' own, moved. The stamp is lane-start's, under the header block, proven by taking it out again. The block is lane-handoff's, with --- and a blank line before the old file whole, proven by bytes. The whole file is the prepared file, and its prior digest is --expect-digest (7 on a mismatch).

  7. The restart (Amendment 17). The five fences are publish-handoff's.

    • An intent bound to W's spelling of the file still fences the lane's copy: the bound path is compared directly, then through the translation into this checkout (HPA_CHECKOUT).
    • --expected-digest is the restart's bound digest, which the pre-image must be (in lane-start's words; 7 since 603, decision 25).
    • --stamp under --restart records the stamped file's digest as the intent's prepared digest BEFORE the bytes, in-process (lane-start's reservation). A child set-restart-intent would wait on a lock this act already holds.
    • Only the stamp reserves.
  8. S4 for a handoff. The commit is by pathspec, with a Lane: <lane> trailer (CP_TRAILER, a second -m). A trailer that is not one Key: value line is refused (2) before anything is staged; only write-handoff sets it (w1b's conditions). (Cut.)

  9. handoff-path <lane> [--create] is a read, not routed. It prints the row's cell translated into the checkout write-handoff writes through, whether or not that checkout exists yet. --create makes the lane's checkout first. A lane with no row, or a row that names no handoff, is 8, and nothing is cloned for it. A register that could not be read is 1, never 8 (R22).

  10. set-restart-intent --handoff: an absolute path is kept exactly as given; the row's own spellings are translated.

    • Every older caller binds an absolute path, and each binds the file it then publishes and compares: W's, until slice 2b. Translating it would bind a file the older lane-handoff never publishes, and that the older lane-start compares, with -ef, against the file it finds in W. So every /ctx under the opt-in would stop. The read side translates instead (decision 7).
    • NEW: a repository-relative handoffs/… cell and a ~/… path, both refused today (64), are translated into the checkout the lane's writes go through, stored as that absolute path, with that file's digest at pending. Any other relative path, and any . or .. component, is still 64.
  11. write-handoff joins lane_row_gate's row group after 1b's R4-1. Under the opt-in, a lane with no row is refused before its checkout is cloned.

  12. No caller changes. lane-start, lane-handoff and the skill keep their own writes until slice 2b, which makes these calls.

  13. An untracked handoff is a pending change (MUST 1 of the pre-review). A handoff git has never had would be added by this write's commit under its subject, and a lost race's drop would take the file with that commit; the re-run then said 8, "Nothing was written". Now it is refused as S3 step 4 refuses any pending change: --stamp writes nothing, --prepend and --whole write it and commit nothing, and the recovery names the git add first. It does so whatever made S3 refuse (A45, the re-review's NIT 1). Where a foreign staged entry made S3 refuse, the add was left out, and the printed commit failed on a pathspec git did not know. For slice 2b: a new lane's first handoff is written, not committed, by write-handoff; its caller adds and commits it once.

  14. Only a file under handoffs/ (SHOULD 1). In this checkout write-handoff replaces a handoff and nothing else: never the register, an object log or anything under lanes/, read from the physical path so that .. and a link out of handoffs/ are caught. 2, nothing written. The same rule holds in another clone of the register (A44, the re-review's SHOULD 1). A path outside this checkout, in a work tree whose top level carries the register too (a sibling lane's <W>-lanes/<lane>/), is tested relative to that top level. A repository that carries no register is still 8(d)'s (decision 16), and a path in no repository is as it was. Never inside a git directory (A47, the re-check's SHOULD 1). A path in another clone's .git/, or in a bare clone, is in no work tree, so A44's test cannot see it, and it was written as another repository's handoff (--stamp put its line into that clone's config). git rev-parse --is-inside-git-dir is asked first, and such a path is refused (2, nothing written): no 8(d) handoff lives in a git directory. This checkout's own .git/ is the handoffs/ rule's. The wider rule is 601's (decision 23).

  15. The managed seam, asked on what S3 fetched, whatever S3 answered (A28 and SHOULD 3). S3 can refuse after its fetch, and --prepend and --whole still write then, so a managed-owner marker that fetch brought is read before them.

  16. Another repository's handoff is not this checkout's write (SHOULD 2). It is never committed through this checkout (8(d); it is committed in its own repository, decision 26), so S3's refusal of the register checkout, an offline lane's fetch included, does not stop it: written, its line printed, exit 0 with the 8(d) note. A clone of the register is not another repository for decision 14's rule (A44): there, only a file under handoffs/ is written this way. A git directory is never another repository's handoff (A47).

  17. The row is read whatever --target says, through a checked read (NITs 1 and 3). handoff_cell_of is handoff_of_lane's checked twin: 8 where the register was read and has no row, 1 where it could not be read. A --target write for a lane with no row is now 8 through the seam and in default mode too, so no commit carries Lane: <a lane with no row>.

  18. The manual's exit row 6 gains two clauses, the S4.4 cause and A37's (w1b's condition for the S4.4 global). A37's clause also says that a handoff moved with its row's cell, to a file under handoffs/ that origin has, is replayed into the moved file (A46, A48). Every other manual line for the two verbs is slice 5's.

  19. The merges of slice 1b: 6ecfc52 as 43baa97: no conflict. 10169f3 as b02618c: three blocks. (Cut.)

  20. Every survivor of m2a's mutant battery has a test (A49, A51, A52). m2a, an independent runner, ran 60 one-line mutants of this slice's code at e2037b9: 38 were killed and 22 survived. Each survivor now has a case in tests/test_a23_write_handoff.py that passes on the code and fails on the mutant. (Cut.)

  21. No routing exec of this slice's own (A50, shellcheck SC2093). shellcheck 0.9.0 with lint.yml's flags finds three SC2093 in lanes-edit.sh here, and nothing else. All three are slice 1b's (10169f3, ef1377f) or 1a's (d0010a2), and they come clean here with 1b's next merge.

  22. The merge of slice 1b at 59551dd (A53), as f43deb3, with --no-ff. merge-tree predicted four blocks, and each is the union of both sides:

    • the header's exit row 1: 1b's clause (a checkout, or its lock's directory, removed while the write waited), then 2a's R22 clause for handoff-path and write-handoff;
    • the header's exit row 6: 1b's rewritten list of kept causes whole, then 2a's S4.4, A37 and A48 causes;
    • lane_row_gate: 2a's write-handoff in the row group, and 1b's claim, release, retire-rows, archive-rows and migrate-state-cells in the log group;
    • README exit row 6: the same union.

    606 is in the merge commit, as your review asked. commit_publish gives cp_own_commit the whole message as git stored it (cp_msg: the subject, then the Lane: trailer as the second paragraph where CP_TRAILER is set). The words still print the subject, and for every other writer cp_msg is the subject, as before. Its test is the next commit (ed63c17), red on the merge without the change.

  23. 601 is in the translation the three verbs share (A54; your correction, Amendment 23 act 2, slice 2a: write-handoff writes a lane's handoff in the checkout its writes go through, handoff-path says where, publish-handoff keeps its contract on one shared act, and set-restart-intent takes the row's spellings (#198) openRepoTools#312 6368669583, and Amendment 23 act 2, slice 2b: lane-start and lane-handoff write the Rule 3 stamp and the PAUSED block with one write-handoff through the lane's checkout, W moves only by fast-forward, and a launch refuses only at a restart's fences (#198) openRepoTools#316's 723). With the refusal in write-handoff alone, handoff-path still printed a sibling checkout's handoff, and lane-start delivered that file's top block as this lane's first prompt. So the check is one helper, handoff_beside_w <checkout> <path>, which all three verbs call.

    • It sits beside handoff_in_repo, not inside it. handoff_in_repo has three more callers, and their || return arms would read a new refusal code as "another repository's" or "not bound".
    • It asks of the physical path, by path, and asks git nothing: a path in W where the checkout is not W (S5), or under <W>-*/ and not under this checkout. Every spelling lands where it lands (absolute, .., ~/), so a clone git cannot read beside W is refused the same way.
    • write-handoff refuses with 2, nothing written. It asks after the git-directory and other-clone checks, so their words keep precedence.
    • handoff-path refuses with 2, nothing on stdout. Under --create, the lane's own checkout is made first, because the check needs its path.
    • set-restart-intent --handoff checks a translated cell. An absolute path is still kept as given (decision 10), and is checked only where it lands under <W>-*/.
    • The fail-closed part (601's second half, R22). Where git gives no top level for a target outside this checkout, but a .git is on the way up from it, that repository is one git could not read here, and the write is refused (1). A read that failed is never "no repository".
  24. 602: the climb rule, its spelling asked first (A55). handoff_rel_climbs_out <checkout> <cell> answers for a relative spelling (not ~, ~/ or /) with a . or .. component. It climbs out by its text (a .. above the top level, whatever exists on the way), or, where it resolves, by the file.

    • Physical containment alone is not the rule, so D08's link out, which has no such component, stays 8(d)'s.
    • write-handoff and handoff-path ask it first, before S5 and 601. set-restart-intent already refused these spellings (64) before 601's check, and handoff-path must ask the text before --create clones. Exit 2, nothing written, no path printed.
    • A .. that climbs out through a directory that does not exist yet (handoffs/notyet/../../../outside/x.md) said 8 before, because no check by path can resolve it. It is 2 now, by its text, and no directory is made.
  25. 603: the restart fence's 2s are 7 in write-handoff (A56). handoff_publish_atomic's two fence refusals that were 2 now exit ${HPA_FENCE_RC:-2}: the publication target is not the operation-bound handoff, and the restart handoff changed before its owned write. write-handoff sets it to 7. publish-handoff leaves it unset, so its documented 2 is as it was.

    • Not mapped: "restart ownership is malformed" stays 2 in both verbs. It is not a fence that did not match, and 7's one meaning is "you lost the race".
    • The header's rows 2 and 7, write-handoff's exit list and README rows 2 and 7 say so.
  26. 604, as you ruled it: Amendment 23 S2 governs (A57; Amendment 23 act 2, slice 2a: write-handoff writes a lane's handoff in the checkout its writes go through, handoff-path says where, publish-handoff keeps its contract on one shared act, and set-restart-intent takes the row's spellings (#198) openRepoTools#312 6368669583). S2 says an Amendment 8(d) handoff "is not the register's and is unchanged", and unchanged is what lane-start and lane-handoff did with it at e2037b9.

    • write-handoff's 8(d) branch commits in the handoff's own top level, by --literal-pathspecs pathspec, with this write's subject and the Lane: trailer. It pushes the branch that repository is on (main wherever e2037b9's was).
    • No rebasing pull. No line of lanes-edit.sh may run or print one (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; hygiene's test_no_lane_writer_pulls_with_rebase). So e2037b9's pull --rebase before the push is S3's way here: the branch is fetched and fast-forwarded before the commit, and a fast-forward that cannot be made is left to the push. These are not S3 and S4 themselves. That repository is not the register, so nothing there is asked about, replayed or read back.
    • The handoff is always written (R-A11-11). Where its commit is not made or not pushed, the command that finishes it is said: in no git work tree, 0; under LANES_NO_GIT, 0; HEAD on no branch, a failed add or commit, a failed fetch, or a refused push (the commit kept on HEAD there), 6. The header's exit row 6 and README exit row 6 gain that cause.
    • Never in another clone of the register. A top level that carries the register too (A44's test) is another lane's clone, not 8(d)'s. A commit and push there would publish to the register's origin outside S3 and S4, from a clone this lane does not hold. Beside W, 601 refuses it, and elsewhere A63 does too (decision 34). Only a top level that comes to carry the register after both checks would still be written there and not committed (0, said): the branch is kept as a backstop.
    • This round's first draft refused that file instead (2). It was put to you as question 4, and your answer was to refuse it: A63, decision 34.
  27. The NITs (A59):

    • 605: a fenced --prepend takes --expected-digest none only. A sha there is usage (64), refused before the lock. lane-handoff binds its prepend at preparing with none and takes the digest again at pending, so no caller passes one.
    • 607: a glob character (*, ? or [) in the path of a handoff in this checkout is refused (2, nothing written). Slice 1b's commit_publish pathspecs are left as they are, and no lane's handoff is named so. At 7ffbcb0 its commit carried an untracked x1.md under its subject and pushed it. handoff-path and set-restart-intent --handoff refuse it too (decision 30).
    • 609: S4.4 says "before or while its push was in flight", as 1b's 343 does. Where commits that origin lacks sit under this write's, it counts them and says to stop and take them to the lane their subjects name, as 1b's 344 does.
    • 610: cases for 605, 607 and 609, for the checkout lock held while an S3-refused --prepend writes, and for the exemption from the workstation guard. 601, 602 and 603 have theirs (decisions 23 to 25).
    • 611: the guard's comment says that write-handoff is not in its list, on purpose (R-A11-11).
  28. 608 is a decision, not code. --target can name another lane's handoff, which is then committed under this lane's subject and trailer. That is deliberate today: --target is how a caller names a file its row does not. As your review suggests, a refusal is weighed once slice 2b moves explicit handoffs onto --target.

  29. 732 is carried here (A58; your review of Amendment 23 act 2, slice 2b: lane-start and lane-handoff write the Rule 3 stamp and the PAUSED block with one write-handoff through the lane's checkout, W moves only by fast-forward, and a launch refuses only at a restart's fences (#198) openRepoTools#316, 1a's code in this slice's file). refresh_workspace_strays ran a plain git status in W, which rewrites a stat-dirty index under W's index.lock. That is a write in W, which S5 forbids, and 2b's launch runs the scan twice. It now runs with GIT_OPTIONAL_LOCKS=0, as ws_pending_refuse's status does.

  30. 991: the three verbs share 607's question too (A60; your first review of Amendment 23 act 2, slice 3: the handoff skill locates its file with handoff-path and writes it with ONE write-handoff --whole call from the lane's own checkout, so it spells, commits in, pulls and pushes no checkout of its own (#198) openRepoTools#311, 991, 2a's to close under 723). At 60ddad3, handoff-path printed <checkout>/handoffs/<e>/x[1].md, a path write-handoff refuses (607), for a restart to bind and a launch to read, and set-restart-intent --handoff bound a restart to it.

    • 607's question is now one helper beside 601's, handoff_rel_globbed <checkout> <path>: the physical path, relative to the checkout, holds *, ? or [. A path outside the checkout is not its rule's (8(d)'s is committed by its own pathspec, decision 26).
    • write-handoff asks it where it asked 607's, with the same words and exit (2).
    • handoff-path asks it after 601's check: 2, nothing on stdout. Routed, a read without --create makes no checkout, and --create makes the lane's own first, as for 601.
    • set-restart-intent --handoff asks it where it asks 601's: the row's cell, translated into the checkout, and an absolute path in the lane's own checkout beside W, which is the path handoff-path prints under the opt-in. 2, no intent written.
    • Not asked of W's own absolute spelling. set-restart-intent keeps it as given (decision 10), as every older caller binds it, and write-handoff refuses it when it writes (2).
    • The cases were red at 60ddad3 (5 failed, each 0 == 2), and a mutant of each of the four places is killed.
  31. The merge of slice 1b at 34799b7, its head on the code leg, as 2b1a232, with --no-ff. 1b's head holds 32 commits past d8859c8 (old 59551dd), 8 of them merges: 1b's own round after 59551dd; slice 1a's merge of the code leg's main 577110e, where 1b kept main's change and its own in 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 three hunks of lanes-edit.sh, its rewritten exit row 6 whole; and the re-pin of 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 register-hardening cases to what holds under slice 1b.

    • git found no conflict, and the tree is the one merge-tree predicted (0802f5a). 1b kept its exit row 6, so this slice's clauses there (S4.4, A37, A48 and 604's) merged beside it. Every line this slice adds to lanes-edit.sh and to tests/test_a23_write_handoff.py over d8859c8 is in the merge.
    • With it, this slice carries the code leg's main, and slices 2b and 3 take it through this merge.
  32. CI's git 2.56.0 and the other-owner case (A61, 56a9b87; CI on this PR at 2b1a232). tests and tests-no-submodule each failed 2 and passed the rest (tests: 2 failed, 3652 passed, 94 skipped; tests-no-submodule: 2 failed, 2768 passed, 978 skipped). Both failures are in tests/test_a23_write_handoff.py: test_a_clone_git_cannot_read_is_never_taken_for_no_repository[beside-W-other-owner] and [elsewhere-other-owner], at _unreadable's own probe, git -C <clone> rev-parse --show-toplevel, which exited 0 where the case asserts it fails ("git can still read the clone"). The bad-config variants passed on CI. This workstation has git 2.43.0, where git's test hook GIT_TEST_ASSUME_DIFFERENT_OWNER=1 refuses the clone (128, "detected dubious ownership"), so the failure could not be seen here. Two causes fit, and git 2.56.0 is not on this workstation: (a) that git ignores the hook, or (b) the runner's system config (/etc/gitconfig) names safe.directory = *, which the box's own .gitconfig does not override.

    • The case is hermetic against (b). other-owner now sets GIT_CONFIG_NOSYSTEM=1 beside the hook, in the environment _unreadable returns, so the probe, the check of the lane's own checkout and the write under test all run in it. The box scrubs every GIT_* variable and sets HOME to its own, so its .gitconfig is still the global config git reads, and still names the box's repositories safe.
    • It is honest against (a). Where the probe still reads the clone, the case is skipped with pytest.skip, never passed. The reason has git --version, every safe.directory git reports in that environment with the file it comes from (git config --show-origin --get-all safe.directory), and the cause it fits: a * in the global config, or a git that ignores the hook. CI's output then says which it was.
    • bad-config is as it was.
    • Shown here, on git 2.43.0: both other-owner cases still run and pass, not skipped (-rs lists no skip). A scratch system config naming safe.directory = * (GIT_CONFIG_SYSTEM) lets the probe read the clone without GIT_CONFIG_NOSYSTEM (exit 0, CI's failure) and refuses it with it (128). A * put into the box's own .gitconfig, and the hook given as 0 to stand for a git that ignores it, each skip both cases with their own reason, by a local edit that was reverted.
    • Not shown: git 2.56.0 itself, which is not on this workstation. If CI skips these two cases, the reason in its -rs output names the cause.
    • Slice 2b (Amendment 23 act 2, slice 2b: lane-start and lane-handoff write the Rule 3 stamp and the PAUSED block with one write-handoff through the lane's checkout, W moves only by fast-forward, and a launch refuses only at a restart's fences #20) carries the same test file and merges this head.
  33. S4.4's two notes no longer offer the publish act (A62, 0547408; your review at 2b1a232, SHOULD 2). commit_publish's S4.4 refusal of a whole-file write-handoff runs only after a push was refused because origin moved. Its two notes (this write's commit is HEAD; another commit is on top of it) still said "until it is published ... the publish act", and the first also offered a short reset --keep origin/main. The publish act refuses there: after the refusal it exits 2, "refusing to publish: origin/main has moved past the local commits ... Nothing was pushed." Slice 1b's round after d8859c8 (705, NIT 294) moved every other origin-moved note to cp_recovery_steps, and cp_not_head's comment names this S4.4 as such a caller; the merge of 1b (decision 31) brought the rule in without applying it to these two lines.

    • The two notes are cp_recovery_steps "it is" and cp_recovery_steps "they are" now, as your sketch has them: what is kept must be archived and dropped, then S6.3 in order (the list, the STOP line, the archive on origin, the drop with refs/remotes/origin/main in full).
    • The case test_whole_kept_by_S4_4_offers_the_archive_not_the_publish_act_that_refuses runs both notes (how: this write's commit is HEAD, or a stamp is on top of it), checks S6.3's order with test_a23_publish_replay's _recovery_is_s63s_archive, that no short reset --keep origin/main is printed, then runs the publish act exactly as the old note offered it: it exits 2, "moved past the local commits", "Nothing was pushed.", and origin and HEAD are as they were. The stamp-on-top setup is one helper now, shared with the older test. Four older assertions that said the commits wait "for the publish act" say "for the archive".
    • Red first, on the parent's lanes-edit.sh (56a9b87): both cases failed, at _recovery_is_s63s_archive's assert 0 <= lead (no "until ... archived and dropped here" line); with the two lines they pass.
  34. Question 4: a handoffs/ file in another clone of the register, not beside W, is refused (A63, 053b382; your answer to question 4: REFUSE with 2). Clones of the register outside W's siblings exist (per-lane LANES_REPO clones), and an uncommitted write there wedges that lane's next write at S3 step 4, 601's harm.

    • write-handoff asks A44's test of the top level once (whf_oreg). A file there that is not under handoffs/ was refused already (A44). A file under handoffs/ is refused now, after 601's check, so a clone beside W keeps 601's words and A44's keep theirs: 2, before a byte, the stamp line said, nothing on stdout, that clone, this checkout and origin as they were. By --target or by an absolute cell, in --stamp, --prepend and --whole.
    • 604's step 7 branch that wrote it there and did not commit it stays as a backstop for a top level that came to carry the register after the checks above; its comment says so.
    • The case test_a_handoff_in_another_clone_of_the_register_elsewhere_is_refused (four forms) replaces the one that pinned the old behaviour (..._is_written_never_committed). Red first, on the parent (0547408): all four failed, assert 0 == 2 (the old code wrote the file and exited 0); with the refusal they pass, with the 601, A44, A47 and 8(d) cases (27 passed in that selection).
    • The manual's exit row 2 (the spec leg's) names none of write-handoff's S2 refusals, so it needs no change for this one.
    • Not changed, and asked: handoff-path and set-restart-intent --handoff still accept a cell that names a file in such a clone (601 made all three verbs refuse one beside W). Probed at this head: handoff-path prints the path (0), with its Amendment 8(d) note, which says write-handoff writes it there and commits it there "never in another clone of the register", and set-restart-intent --handoff binds it (0), while write-handoff now refuses it (2). Your answer was about the write, so they are as they were. Say if you want them to refuse it too, by handoff_beside_w's way, and a later round does it.
  35. The NITs (A64, 2ff2c34; your review at 2b1a232, NITs 5 to 7):

    • NIT 5. The header's exit row 6 listed 604's Amendment 8(d) cause among the kept-on-HEAD causes, and the row ends "Every later write from that checkout refuses under S3 until the lane publishes or archives it", which is false for 8(d): the commit is in the handoff's own repository. The clause now ends "(that commit is in that repository; no write here refuses over it)", the words The lane manual's exit rows 2, 6 and 7 carry act 2 slice 2a: write-handoff answers the restart fences 7, keeps on HEAD a handoff origin changed or deleted meanwhile, and names an Amendment 8(d) handoff whose commit or push failed openRepoTools-spec#16's exit row 6 takes, on one line so the two can be found together. No test compares the code's header with the manual's row 6: the hygiene test that compares them reads row 3 (the exit-3 row), and no test that opens the manual quotes row 6 or 8(d).
    • NIT 6. The comment on the fenced --prepend said "605; A58". The change is A59 (decision 27); A58 is 732 (decision 29).
    • NIT 7. SonarCloud's S9073 at the test file's A46 and A52 cases: assert _origin_has(box, MOVED_REL) and not _origin_has(box, HANDOFF_REL) and assert HANDOFF_REL not in names and odd in names are two asserts each now, with messages. An AST scan of both test files for an assert whose test is an and or or found those two in tests/test_a23_write_handoff.py before, and none after.
    • tests/test_handoff_skill_commit_block.py:911 (984) is slice 3's. It is not in this slice's file (483 lines here), so this slice does not touch it. At slice 3's f2340ad it is line 901, assert block_file and block_file != "<none>", first.stdout, in test_block_1_block_2_and_step_4_each_in_its_own_shell_carry_what_step_2_did; slice 3 splits it in its own round, and this slice's change leaves nothing to conflict there. This file also has two composite asserts older than the slice (found and found.group(1) in before, commits and adds), which slice 3 already splits; they are left as they are.
    • NIT 4 (trailers): the three commits of this round carry Lane: openreposhape-2 (openRepoShape-2). The nine you list lack it, and so does A61, which came before your review; none can be mended without a force-push.
  36. Slice 1b's new head 458cb38 merged (ef469e1, with --no-ff; merge base 34799b7, the head merged as 2b1a232). Named by main after 1b's fix round. 1b's head holds 7 commits past 34799b7, 3 of them merges:

    • 8e42208, 1b's S4 (your review of 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): retire-rows' published-log refusal says ensure_log's recovery sentence, with a case;
    • cc8f6ab, 1b's N2 and N3 (the same review): two register-hardening cases assert more, and four are renamed for what they assert;
    • 458cb38, which merges slice 1a's f0d363a, 1a's merge of the code leg's main e393958 (code PR 15, the openRepoShape v1.1.1 pin): upstream/openRepoShape, contracts/openreposhape-pin.yaml, .github/workflows/release.yml, tests/test_release_workflow.py and tests/test_install_payload_source.py.
    • git found no conflict, and the tree is the one git merge-tree --write-tree predicted before the merge was made (6977724). 1b's one change to lanes-edit.sh (the retire-rows die and its comment) is in a place this round's A62 to A64 did not touch; the test file this slice owns is not touched by 1b.
    • With it, this slice carries the code leg's main e393958 (through 1a), where 2b1a232 carried 577110e, and slices 2b and 3 take it through this merge.
    • The tests of the merge are under "Tests". upstream/openRepoShape was filled for tests/test_release_workflow.py as above.

For slice 2b

What 2b can rely on at this head:

What changed

  • lanes-edit.sh:
    • new verbs: write-handoff and handoff-path;
    • new functions: handoff_publish_atomic, handoff_bound_is, handoff_restart_reserve, path_physical, handoff_in_repo, handoff_checkout_of, handoff_cell_of, handoff_beside_w, handoff_rel_climbs_out, handoff_rel_globbed, whf_stamp_said, whf_in_scope, whf_keep_if_gone;
    • publish-handoff now runs on handoff_publish_atomic;
    • ws_relative now rests on path_physical, with the same answers;
    • handoff_in_checkout now calls handoff_in_repo;
    • write_lane_of routes write-handoff, and lane_row_gate gates it;
    • commit_publish gains CP_TRAILER (checked) and the S4.4 whole-file check, which reads HEAD and hands a rewritten commit or an unreadable HEAD to slice 1b's cp_not_head;
    • write-handoff sets slice 1b's CP_KEEP_IF_GONE to its handoff for --stamp and --prepend, and records the cell it followed;
    • slice 1b's cp_replay asks whf_keep_if_gone before it keeps such a write (one clause, decision 8);
    • write-handoff refuses a target inside a git directory (A47), and whf_keep_if_gone follows another cell only to a file under handoffs/ that origin has (A48);
    • cleanup prints HANDOFF_OUT, and cleanup_for_exec clears it;
    • set-restart-intent --handoff translates the row's spellings, and refuses a handoff beside W (601) and a glob character in its path in the checkout (991);
    • write-handoff and handoff-path refuse a handoff in W or beside it (601), a relative spelling that climbs out (602) and a glob character in the handoff's path in the checkout (607, 991), and write-handoff refuses a fenced --prepend with a sha (605);
    • handoff_publish_atomic's two restart-fence 2s exit HPA_FENCE_RC, which write-handoff sets to 7 (603);
    • write-handoff's 8(d) branch commits and pushes in the handoff's own repository, never in another clone of the register (604), and a handoffs/ file in a clone of the register that is not beside W is refused (2) before a byte, as one beside W is (A63);
    • commit_publish gives cp_own_commit the message with its trailer (606, in the merge), and S4.4's notes say "before or while", name the commits under it (609) and, after a push that origin's move refused, give S6.3's archive and not the publish act (A62);
    • refresh_workspace_strays' git status takes no optional lock (732);
    • text: the sidecar lock's comment (which act nests it, and in which order), the env doc, the usage header, the unknown-subcommand list, the workstation guard's comment (611), and the exit table's rows 1, 2, 6, 7 and 8.
  • docs/README-lanes.md, not in this PR (the spec leg's since the cutover, "Re-homed" above): exit row 6's clauses (the S4.4 cause, A37's, 1b's merged list, and 604's), and rows 2 and 7 (603).
  • tests/test_a23_write_handoff.py (new), on slice 1b's box, imported from tests/test_a23_publish_replay.py and not copied, with a case for each survivor of m2a's mutant battery (decision 20), and, since A61, an other-owner case that sets GIT_CONFIG_NOSYSTEM=1 and skips, saying why, where git still reads the clone (decision 32); the cases of decisions 33 and 34 are in it too.

Tests

One pytest process per file, with every LANES_* and TMUX* variable unset, a refusing gh stub, and nothing reaching a real remote.

The set was run at two heads, each file in its own pytest process. At 2ff2c34, A64, before the merge, the 11 modules of the table below gave, in its order (passed, then skipped where there were any): 184; 235 and 18 skipped; 39 and 1 skipped; 82; 150; 57; 29; 13; 27; 10; 25. At the merge, ef469e1, the table below: the same 11 modules and the two the merge brings changes to (code PR 15, the v1.1.1 pin), tests/test_release_workflow.py and tests/test_install_payload_source.py. Only one count moved, tests/test_test_roots.py, from 39 passed and 1 skipped to 40 passed: upstream/openRepoShape was filled with a --no-hardlinks clone of the shared checkout, detached at 5017830 (v1.1.1), so the skipped case (the pinned openRepoShape was not checked out) ran, and tests/test_release_workflow.py reads the pin it checks. The 9 skips of tests/test_install_payload_source.py are its composed-acceptance cases, which need this checkout mounted as an assembly's code leg beside the assembly's root openRepoTools.

CI at 2b1a232 failed the two other-owner cases (decision 32); A61 fixed them (56a9b87). Before this round the set had 179 for tests/test_a23_write_handoff.py and the counts below for the rest, at 2b1a232 and 56a9b87; this round's commits (A62 to A64) change lanes-edit.sh and tests/test_a23_write_handoff.py only, so the other counts are the same.

file result at
tests/test_a23_write_handoff.py 184 passed ef469e1 (this head)
tests/test_repo_hygiene.py 235 passed, 18 skipped ef469e1 (this head)
tests/test_test_roots.py 40 passed ef469e1 (this head)
tests/test_a23_publish_replay.py 82 passed ef469e1 (this head)
tests/test_a23_guard_refusals.py 150 passed ef469e1 (this head)
tests/test_a23_write_routing.py 57 passed ef469e1 (this head)
tests/test_a23_lock.py 29 passed ef469e1 (this head)
tests/test_a23_refresh_workspace.py 13 passed ef469e1 (this head)
tests/test_a23_lane_checkout.py 27 passed ef469e1 (this head)
tests/test_a23_lane_start_read.py 10 passed ef469e1 (this head)
tests/test_commit_push_published_edit_194.py 25 passed ef469e1 (this head)
tests/test_release_workflow.py 499 passed ef469e1 (this head)
tests/test_install_payload_source.py 100 passed, 9 skipped ef469e1 (this head)
tests/test_supervised_restart_regressions.py -k handoff 11 passed, 162 deselected d5cabdc (stage 3)
tests/test_guard_launch_mode.py 13 passed 357ec07 (stage 2)
tests/test_skill_shell_blocks.py 6 passed 357ec07
tests/test_handoff_skill_80_85.py 41 passed 357ec07
tests/test_handoff_skill_commit_block.py 24 passed 357ec07
tests/test_lane_start_row_stamp.py 3 passed 357ec07
tests/test_lane_start_claude_current.py 34 passed 357ec07
tests/test_triad_lane_tooling.py 1 failed, 4 passed, 22 skipped 357ec07
tests/test_triad_lane_tooling_handoff.py 1 failed, 21 skipped 357ec07

The two triad failures are the t018 goldens, red at this branch's base with the same assertion text. The shell suite and the whole of tests/test_supervised_restart_regressions.py were not run here; CI's tests job is the suite of record. The 18 skips in tests/test_repo_hygiene.py are the code leg's own for a standalone checkout: 14 cases read the assembly root's AGENTS.md, CLAUDE.md and README.md, and 4 the spec leg's docs/README-lanes.md, and the composed run from the assembly root (OPENREPOTOOLS_COMPOSED=1) reads them.

Mutants

mutants where result
18 one-line mutants (the pre-review's, at 6ee2c11) each against its own -k selection 13 killed: M1, M3-M9, M11, M13, M15, M20, M28. M2 equivalent. M23, M27, M29 and M30 survived then.
M23, M27, M29, M30 again, at 346e403 with the new pins one targeted run each, in a separate clone all 4 killed, each by its new test
the code before each change of the pre-review round (MUST 1, SHOULD 1, 2, 3, NITs 1 and 3, the S4.4 wording, the S4.4 rewrite case), before A37, before each change of the re-review round (A44, A45, A46), and before each change of the re-check round (A47, A48) its new tests, on the code before the change each new test failed there, and passes after
9 one-line mutants of the re-review round: A44's register test dropped and its scope check skipped; A45's question asked only under a passing S3, and always answered "untracked"; A46's --target keep dropped, its no-row keep dropped, --target's cell recorded, its comparison inverted, and origin's row read through the cached register_text_checked each against its own -k selection, at e2037b9, in a separate clone all 9 killed, by this round's tests and, for the register test, by the 8(d) test of a top-level handoff.md in another repository
6 one-line mutants of the re-check round: A47's check undone; A48 undone; A48's origin check dropped, and inverted; its scope check dropped; A46's comparison inverted each against its own -k selection, at 68014b4, in a separate clone all 6 killed
m2a's battery, first half (m2a-c1): 33 of 60 one-line mutants of this slice's code, written by an independent runner each against the tests that reach its line, at e2037b9, in m2a's own clone 18 killed; 15 survived: A02, A04, A06, A07, A09, A10, A11, A13, B02, B04, C02, C03, D08, E01, E02
those 15 again, with A49's cases at 2a41e83: in this writer's clone, each against its new cases' -k selection, and again by m2a-c3 in m2a's clone, each against A49's cases alone all 15 killed, both times. E01 run against A47's and A44's cases alone survived, so the case of this checkout's own .git/config stays
m2a's battery, second half (m2a-c2): the other 27 at e2037b9, in m2a's clone 20 killed; 7 survived: G05, G06, H03, J01, J02, J03, K03
those 7 again, with A51's cases; J02 twice, against the estate-spelled cases and the link cases each against A51's own cases only, at 37b000d (A51's head), in this writer's clone G05, G06, H03, J01, J03, K03 and J02 (the link cases) killed. J02 against the estate-spelled cases survived: equivalent there since A48 (decision 20)
m2a's re-check (m2a-c3, m2a-c4): 35 one-line mutants, among them the 15 of m2a-c1 with A49's cases alone, the 7 above, and 13 more (E05, E07, E08, J04b, N01 to N09) each against the tests that reach its line, at 2a41e83, in m2a's clone 25 killed; 10 survived: G05, G06, H03, J01, J02, J03, K03 (A51's cases kill them, above), and J04b, N03, N05
those 3 again, with A52's cases each against its own case's -k selection, at 1bb4f75, in this writer's clone; m2a-c4's probes of the same three, outside the tree, killed them too J04b, N03 and N05 killed. J04b is practically equivalent for this tool's own registers (decision 20); its case pins the trim

| c15-M1: 601's shared check (handoff_beside_w) answering "not refused" at once | its own -k selection, at 6d861d4, in a separate clone | killed: 19 failed, 2 passed (the two unreadable cases away from W, which are the fail-closed part's and do not reach the check) |
| the code before each change of this round: 601, 602, 603, 604, 604's fast-forward, 732, the NITs, and 991 | its new tests, on the code before the change | they failed there (21, 15, 2, 5, 1, 1, 4 and 5), and pass after. The rest of each selection passed before too: cases of behaviour that was already right, among them 610's two (the checkout's lock during an S3-refused --prepend, the guard's exemption) |
| 9 one-line mutants of this round: 602's climb rule, 603's mapping, 604's commit, A57's guard against a commit in another clone of the register, 604's fast-forward, 732's lock-free status, 605, 607 (for [) and 609 | each against its own -k selection, at 60ddad3, in a separate clone | all 9 killed |
| 4 one-line mutants of 991 (decision 30): the question dropped in handoff-path, in set-restart-intent for the cell and for an absolute path, and the shared helper taking [ again | each against its own -k selection, at 7bc572c, in a separate clone | all 4 killed |
| the code before A62 and before A63 | their new tests, on the code before the change (red first) | A62: both forms failed (assert 0 <= lead, the line "until ... archived and dropped here" was not there), and pass after. A63: all four forms failed (assert 0 == 2), and pass after, with the 601, A44, A47 and 8(d) cases (27 passed in that selection). A64 is comments and two splits of composite asserts, so it has no red. |
| 7 one-line mutants of this round: S4.4's HEAD-is-this-write note offering the publish act again, its commit-on-top note offering it again, the first note saying "they are"; A63's refusal never firing, exiting 1, not saying the stamp line, and asked before 601's check | each against its own -k selection, at 2ff2c34, in a separate worktree | all 7 killed. A62's three: 1 of the case's 2 forms failed each time. A63's never-firing and exit-1: all 4 forms failed. The missing stamp line: the 2 --stamp forms failed. Asked before 601's check: 4 of the 6 cases of 601 failed (a clone beside W lost 601's words) |
The 31 stage-2 mutants scripted before the pre-review were not run as a battery; m2a's 60, the pre-review's 18 and the pins cover the same lines.

Your answers

  1. rename-lane's inlined publication (Q1): later. As you answered, it moves in a later slice, before the opt-in flips, and the lock matters more than the shared act. rename-lane will take the sidecar lock inside its checkout lock, from the restart check through the move and the stamp, and its stamp can then call handoff_publish_atomic, with the exit codes mapped to Harden legacy lane rename handoff publication against process crashes openRepoTools#158's 5. That is 1a's code, and it matters once the opt-in flips, so it is not in this round.
  2. Absolute paths in set-restart-intent --handoff (Q2): kept as given, now and after 2b (decision 10). Your caveat goes to 2b ("For slice 2b", above).
  3. Targets in W or <W>-lanes/ that are not this checkout's (Q3): refused by path, with 2. That is 601's change (decision 23).
  4. A handoffs/ file in another clone of the register that is not beside W (question 4): refused, with 2. That is A63 (decision 34): the check was there already (A44's test of the top level), and your reason is the one in the code: an uncommitted write there wedges that clone's lane at S3 step 4.

Not changed

Refs opensoft/openRepoTools#198

🤖 Generated with Claude Code

Brett Heap and others added 30 commits October 20, 2026 21:25
…ntation of the restart fences, the prior digest and the one-rename replace that `write-handoff` will share, and `publish-handoff` keeps its contract for every old caller (#198, slice 2a stage 1)

`handoff_publish_atomic <lane> <target> <prepared> <prior digest> [<op>
<gen> <attempt> <state> <transcript>]` is `publish-handoff`'s body, moved
whole: the target's links followed to the physical file, its directory
resolved with `cd -P`, the restart intent read and its five fences checked
(7 lost ownership, 2 not the bound handoff, 7 an active restart, 64 an
operation without its other four fences), the prior digest (7), then the
bytes. It runs under the lock its caller holds and every refusal is a `die`
whose exit trap releases it.

The bytes go #212's way, which was this act's own (`rename-lane`'s stamp
says it copied it from here): an exclusive temporary beside the target in
its physical directory, `cp -p` for the mode, the bytes, one `mv -f`. #212's
one refinement is taken: the temporary's name (`pbh_tmp`, which `cleanup`
removes on every handled exit) is cleared once the rename has landed, so the
exit trap never removes a name this process no longer owns. `rename-lane`
keeps its inlined copy: it already holds the checkout lock, and its
refusals are exit 5 under #158's tests.

`publish-handoff` keeps its arguments, flags, words, codes and its one line
on stdout, the physical file; it takes the workstation's sidecar lock alone,
as before, and compares the bound path as it is. The one difference is
invisible to a caller: a refusal's lock is released by the exit trap rather
than by a `release_lock` written before each `die`.

New: `tests/test_a23_write_handoff.py`, on slice 1b's box (imported from
`tests/test_a23_publish_replay.py`, not copied), with `publish-handoff`'s
contract: the physical file on stdout through a link, its mode kept, nothing
committed, no temporary left beside it; a changed handoff (7); and the
restart fences, owned, lost, active, half-given and unbound.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9f4f4f7db51bd260d0ebed6dfc87b91eab75923a)
… through, as a Rule 3 stamp, a new top block or a whole prepared file, under S3, the translation, the restart fences and S4, and `handoff-path` says where that is (#198, slice 2a stage 2)

`write-handoff <lane> (--stamp "<line>" | --prepend <block file> | --whole
<prepared> --expect-digest <sha>) --subject "<s>" [--target <path>]
[--restart <op> <gen> <attempt> <state> <transcript> [--expected-digest
<sha>]]` is routed (`write_lane_of` names its lane): the lane's own checkout
under the opt-in, the explicit `LANES_REPO` seam in place, W with the
opt-in off. Under the checkout's lock, in this order:

  1. S3, answered by the verb. `--stamp` writes nothing on a refusal,
     prints the line it would have written and exits with S3's code;
     `--prepend` and `--whole` write the file, commit nothing, print its
     path and exit with S3's code (R-A11-11: a swap is never left
     unwritten). S5, a fence, a digest or a failed proof writes nothing.
  2. The target: the row's cell from the register S3 just fetched, or
     `--target`, TRANSLATED, never followed (S2, `handoff_in_repo`). A
     relative cell is joined to the checkout; a `~/…` or absolute path
     whose physical parent is under W becomes the same path in the
     checkout; one already in the checkout stays itself; anything else is
     another repository's (Amendment 8(d)), written there and committed
     nowhere (0). A target that is physically W's, from a checkout that is
     not W, is refused (2, S5).
  3. S3 step 4 for the handoff itself: an uncommitted change to it is
     refused before a byte.
  4. The read, the splice and its proof, in the checkout: `lane-start`'s
     stamp under the header block (proven by taking it out again);
     `lane-handoff`'s shape for the block, with `---` and a blank line
     before the old file whole (proven by bytes); the prepared file whole,
     whose prior digest is `--expect-digest` (7 on a mismatch).
  5. The workstation's sidecar lock, taken INSIDE the checkout's and only
     for the fences and the replace: `handoff_publish_atomic`, with three
     knobs off for `publish-handoff`. An intent bound to W's spelling of the
     file still fences the lane's copy (`HPA_CHECKOUT`); the restart's bound
     digest is the pre-image (`--expected-digest`, 2, `lane-start`'s
     words); and a `--stamp` under `--restart` records the stamped file's
     digest as the intent's prepared digest BEFORE the bytes, in-process
     (`handoff_restart_reserve`, `lane-start`'s reservation). The sidecar
     lock is released before the push.
  6. S4: a commit by pathspec with a `Lane: <lane>` trailer
     (`CP_TRAILER`), the push, the read-back, and a refused push replayed
     on origin's new tip. A whole-file write replays only where origin's
     copy is still the one it started from, checked before the reset
     (`CP_WHOLE_REL`/`CP_WHOLE_BLOB`); otherwise its commit is kept on HEAD
     and named (6, S4.4).

Its one line on stdout, `<path><TAB><sha256>`, is printed by `cleanup`, so
a replay prints the last attempt's line only (`cleanup_for_exec` clears it).

On w1b's conditions for its `commit_publish`: the S4.4 check sits in the
refused-push branch after the read-back and before the origin-unmoved 6 and
the replay's reset; the trailer goes in as a second `-m` built in one array,
`cp_m`, at both commit sites, and a trailer with a control character or not
in `Key: value` form is refused (2) before anything is staged. Exit row 6
names the new cause in the code and, in one clause, in
`docs/README-lanes.md`.

`handoff-path <lane> [--create]` is a read and is not routed: the row's
cell, translated into the checkout `write-handoff` writes through, printed
whether or not that checkout exists yet; `--create` makes the lane's
checkout first. A lane with no row, or a row naming no handoff, is 8, and
nothing is cloned for it.

New helpers: `path_physical` (`ws_relative` now rests on it, unchanged in
what it answers), `handoff_in_repo` (`handoff_in_checkout` calls it),
`handoff_checkout_of`, `handoff_bound_is`, `handoff_restart_reserve`. The
sidecar lock's comment, the env doc and `cleanup` say which act nests it and
in which order. The unknown-subcommand list, the usage header and the exit
table's rows 2, 6, 7 and 8 name both verbs.

Tests, in `tests/test_a23_write_handoff.py`:
  * the stamp: spliced, committed with the trailer, pushed; refused by S3
    (a staged entry, a fetch that did not run) with nothing written and the
    line printed; replayed onto a peer's new content (another file, the
    handoff itself) with one stamp and one line; a splice that does not
    prove out;
  * the prepend: written and committed; refused by S3 and still written;
  * the whole file: a digest mismatch (7); replayed over an unchanged
    copy; kept on HEAD (6) where origin's copy changed;
  * the translation: an estate-spelled cell (seam, routed), a repository-
    relative cell (seam, routed, opt-in off), another repository's file, a
    link into W refused (S5);
  * the restart: the sidecar lock waited on inside the checkout's; lost,
    unbound and active fences; an intent bound to W's path; the prepared
    digest recorded before a replace that fails; a changed pre-image; only
    the stamp reserves; the sidecar lock released before the push;
  * `test_W_untouched`: routed, all three modes, and W's HEAD, tree and
    reflog do not move;
  * `handoff-path` in the three modes, `--create`, and a lane with no row;
  * usage (64).

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 357ec0765cd459e923c96ee4a46c86555499726e)
…managed-owner marker that lands between its first ask and S3's fetch is read, and the write is refused with nothing written (#198, slice 2a)

The pre-review of slice 1b found writes whose checks run before the lock, on
what was fetched before S3, and are never asked again on the tip S3
fast-forwards to (MUSTs 1 and 2: a rival's claim, a retired row). Slice 2a's
own code had one such check: `write-handoff` asked `managed_seam_refuse`
before its lock, on this checkout's origin/main as it last stood. It now asks
it again right after S3 succeeds, once `cs_flush` has dropped the cached
register, and a refusal there writes nothing. The row's handoff cell, the
pre-image, `--expect-digest`'s comparison and the S4.4 blob were already read
after S3, and the restart fences under the sidecar lock.

New: `test_a_managed_marker_landing_inside_the_s3_window_is_refused`. A peer's
row carrying managed-owner vocabulary is pushed by a hook at S3's own fetch;
the stamp is refused, the file and origin's copy are as they were, and the
lock is released. At 357ec07, without this change, the same case commits and
pushes the stamp.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 548095e95134d98ce84c0d9b0aa757db45048c57)
…write-handoff` joins `lane_row_gate` in the next commit (#198)

Slice 1b's head brings 7da3d61 (R4-1's `lane_row_gate`), B23 (the success
line stays `pushed origin/<branch> (attempt N)`), the merge of slice 1a's
fix rounds 2 and 3 (eead86d, 15c8e3f), B31 (supervised_restart's fixture no
longer exports `LANES_NO_FETCH=1`) and MUST 3 (`lane-worktrees sweep --yes`
writes its NOTED line with a fetch).

Where they meet slice 2a: `write-handoff` is a routed writer that needs its
lane's row, so it joins the gate's row group (A23, its own commit next);
B23's line is the one slice 2a's tests read; the S4.4 check and the trailer
sit in `commit_publish` where w1b's conditions put them, and B23 does not
touch either.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 43baa979d9516ae2378530baced53341ff1a5c8b)
…n a write for a lane with no row is refused before its checkout is cloned, after one fetch of W or at once under `LANES_NO_FETCH=1` (#198, slice 2a)

R4-1 (lane openRepoTools-3's re-check of #282) gave the routed writes a gate:
a write for a lane the published register does not know clones nothing.
`write-handoff` writes the handoff its lane's row names, so a lane with no
row has none. Before this, its routing cloned `<W>-lanes/<lane>/` and the
arm then refused 8. It now takes the gate's row group beside `set-row-state`,
`replace-in-row`, `append-session-id` and `rename-lane`. The gate's last
words name what each verb needs the row for ("edits that lane's row", or
"writes the handoff that lane's row names"); the four row verbs' words are
byte for byte as before. The explicit `LANES_REPO` seam and the opt-in off
are not gated, as for the other verbs, and keep `write-handoff`'s 8.

New: `test_routed_write_handoff_for_a_lane_with_no_row_clones_nothing`,
fetched and under `LANES_NO_FETCH=1`: 2, no checkout cloned, W and origin
as they were.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6ee2c117c10a5bbed2f551c5cdab3d4c2246fc2c)
…itory-relative `handoffs/…` cell and the estate's `~/…`, and binds them as the absolute path in the checkout the lane's writes go through, while an absolute path is still kept exactly as given (#198, slice 2a stage 3)

Until now `--handoff` took only an absolute path, and refused every other
spelling with 64. The row's own spellings of a handoff are a
repository-relative cell and the estate's `~/…` link (Amendment 4(e)).
They are now translated once the lane is known, by `handoff_in_repo`, into
the checkout that lane's writes go through (`handoff_checkout_of`: S1's
checkout under the opt-in, an explicit `LANES_REPO`, or W with the opt-in
off). The intent stores that absolute path, and the digest taken at
`pending` is that file's, so `write-handoff` fences on it directly. A note
says what the cell was bound as. A `~/…` that resolves into W is
translated into the checkout, never followed into W. A path that belongs
to another repository is stored as it is, with the same note.

An absolute path is kept exactly as given, W's included (A16). It is every
older caller's spelling, and each one binds the file it then publishes and
compares: W's, until slice 2b moves `lane-start` and `lane-handoff`
(`lane-start` compares it `-ef` with the file `hs_resolve` finds in W, and
the older `lane-handoff` publishes W's file through the strict
`publish-handoff`). Translating it would stop every `/ctx` under the
opt-in. `write-handoff` already translates a W-bound path on its own side
(A10).

Any other relative path, and a `.` or `..` component in a cell, is still
refused with 64, with nothing written. `--dir` keeps its rule, and `~` is
never expanded there.

New, in `tests/test_a23_write_handoff.py`:
- `test_set_restart_intent_binds_a_repository_relative_cell_in_the_checkout`,
  through the seam, routed and in default mode: the bound path, its digest,
  and the owned stamp fenced and reserved on it, with W's copy left alone
  where the checkout is not W;
- `test_set_restart_intent_binds_the_estate_spelling_in_the_checkout`;
- `test_set_restart_intent_keeps_an_absolute_path_as_given`;
- `test_set_restart_intent_refuses_any_other_relative_path` (four
  spellings).

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d5cabdcd943067f94888d739cf7da8b225097c36)
… causes, and both sides' new globals are kept (#198)

Slice 1b's head brings MUST 4 of rv1b's pre-review (c454247: a refused
push whose commit is no longer HEAD is not replayed, and both commits are
kept on HEAD and named, exit 6) and MUSTs 1 and 2 (10169f3: `claim` and
`add-row` run again from the top on origin's new tip when S3's fetch finds
it moved under their own checks, through `CS_RECHECK` and `cs_recheck`).

git stopped on three blocks, each an addition made on both sides:
- A30: `lanes-edit.sh`'s exit table, row 6, in the header comment. It now
  lists every cause from both sides: the replay's `reset --keep` over an
  uncommitted change, 1b's commit made on top of this one, a push refused
  with origin unmoved, and 2a's whole-file `write-handoff` whose copy
  origin changed meanwhile (S4.4).
- A31: the globals after `CP_REPLAY_KEEPS_EDIT`. Both are kept: 2a's
  `CP_TRAILER`, `CP_WHOLE_REL` and `CP_WHOLE_BLOB` first, then 1b's
  `CS_RECHECK=0`.
- A32: `docs/README-lanes.md`'s exit row 6, the same union as A30.

Checked with no conflict:
- In the refused-push branch, 2a's S4.4 check runs before `cp_replay`'s
  new HEAD check. Both exit 6 and reset nothing, so their order changes
  only which words are printed.
- `CS_RECHECK` is set by `claim` and `add-row` alone, and is 0 at load.
  So `cs_recheck`'s `exec` is never reached from `write-handoff`'s S3
  subshell. `write-handoff` keeps its own second `managed_seam_refuse`
  after S3 (A28).
- 1b's changes to `tests/test_a23_publish_replay.py` only add tests. The
  `Box` that `tests/test_a23_write_handoff.py` imports is unchanged.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit b02618c7a9518b9ab480a666b1e8c6d8c50276b7)
…off git does not track yet as a pending change, so a lost push race no longer drops it with the replayed commit, and a peer's untracked file is never added under this write's subject (#198, slice 2a)

The row can name a handoff the checkout has and git has never tracked: a
new lane's first, or a peer's. S3 step 4 asked `git diff HEAD`, which does
not see an untracked file, so the write committed it; and where the push
then lost a race, the replay's `reset --keep origin/main` dropped that
commit and the file with it, and the re-run exited 8, "Nothing was written",
with the new content only in a dropped commit the reflog names.

Now step 3 asks `git ls-files --error-unmatch` (literal pathspec, exact
case) beside `cs_own_pending`, and an untracked handoff is refused as S3
step 4 refuses any pending change (2): `--stamp` writes nothing and says its
line; `--prepend` and `--whole` write the file and commit nothing
(R-A11-11), and their recovery line adds it (`git add -- <rel> &&`) before
the pathspec commit. Skipped under LANES_NO_GIT=1, and for another
repository's file. A file that is not there is still 8.

Test: test_an_untracked_handoff_is_refused_and_never_dropped[stamp, prepend,
whole], rv2a's P1 with a peer's commit landing at the push. At b02618c all
three fail (rc 8, the file gone); with the fix all pass. Runs on this tree:
test_a23_write_handoff 63 passed; test_repo_hygiene 247 passed;
test_a23_guard_refusals 122 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6c2a9cc65b920a22fea3b4ca034af5290f1cb71f)
…le under `handoffs/` and nothing else, so a `--target` naming the register, or a row cell that climbs out to an object log, is refused before a byte instead of replaced and pushed (#198, slice 2a)

`write-handoff` took `--target` as given and turned any path inside the
checkout into the commit's pathspec. `--whole <file> --expect-digest <the
register's sha> --target lanes/LANES.md` replaced the register and pushed it
(exit 0), and a row cell `handoffs/../lanes/log/<lane>.md` made `--prepend`
push a top block into the head of the lane's object log, which Amendment
7(b) makes append-only. Amendment 23 S2 scopes these writers to
`handoffs/<estate>/<file>`, and Rule 9's one-line guard was bypassed.

Now, once the target is translated and found to be this checkout's, its
path relative to the checkout (taken from the PHYSICAL path, so `..` and a
link out of `handoffs/` are both resolved first) must be `handoffs/?*`, and
must not be `$LANES_PATH`, under `lanes/` or under `$LANES_PREFIX`, or end
in `/.` or `/..`. Otherwise: exit 2, "… is <rel> in the checkout <c>, which
is not a file under handoffs/ …. Nothing was written.", before S3 step 4,
the read, the splice and the sidecar lock, in every mode (`--stamp` says
its line). Another repository's file (Amendment 8(d)) is not this check's:
it is never committed. The header's exit row 2 and the arm's table say so.

Test: test_a_target_outside_handoffs_is_refused_and_nothing_written[target,
cell], rv2a's P2. At 6c2a9cc both fail (exit 0, "pushed origin/main
(attempt 1)"); with the fix both pass. Runs on this tree:
test_a23_write_handoff 65 passed; test_repo_hygiene 247 passed;
test_a23_guard_refusals 122 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 011f241b4e83de8660b2e2abbfbdaa1ee7718eab)
…naged seam again on what S3 fetched whatever S3 answered, so a managed-owner marker that lands at S3's fetch refuses a `--prepend` or `--whole` that S3's later refusal would still have written (#198, slice 2a)

A28's second `managed_seam_refuse` ran only where S3 passed. S3 can
refuse AFTER its fetch (a staged entry that is not this write's, a diverged
branch), and `--prepend` and `--whole` then still write the handoff
(R-A11-11), so a marker that S3's own fetch brought in was never read: rv2a's
P5 wrote a managed lane's handoff with the marker on origin. And M27 (the
first ask, before the lock, taken out) survived the module, because the
first ask mattered only on that path.

Now the two assignments that need a passing S3 stay under `if`, and
`cs_flush` and the second ask run after it unconditionally. Where S3
refused before any fetch, the second ask reads what the first read.

Tests:
- test_a_managed_marker_landing_at_S3s_fetch_refuses_even_where_S3_refused
  (rv2a's P5, with a marker that parses): at 011f241 it fails, the handoff
  written under S3's 2; with the fix the seam refuses (2, "owned by the
  managed ledger"), nothing is written or printed, the staged entry is kept.
- test_a_managed_marker_already_fetched_is_refused_before_the_lock, M27's
  pin: the marker is on the checkout's origin/main before the write, and the
  refusal comes with no network call at all, so S3's fetch never ran. It
  passes before and after the fix (it pins the first ask, which this commit
  does not change).

Runs on this tree: test_a23_write_handoff 67 passed; test_repo_hygiene 247
passed; test_a23_guard_refusals 122 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7561d5a08bcc8d96150955d27012fbc5e085683f)
…r checkout no longer stops the write of another repository's handoff, which no tool here commits, so an offline lane's Amendment 8(d) stamp is written and the verb exits 0 with the 8(d) note (#198, slice 2a)

`write-handoff` runs S3 before it knows the target, and a refusal then
stopped every mode: `--stamp --target <another repository's file>` under
`LANES_NO_FETCH=1` exited 2, "the stamp is NOT written", and `--prepend`
and `--whole` exited 2 with the file written. That file is never committed
(Amendment 8(d)), and today it is stamped through `publish-handoff` with no
fetch, so slice 2b moving that caller would lose the stamp whenever the
lane is offline.

Now, once the target is known to be another repository's, S3's refusal is
noted as this checkout's ("… does not stop this write: <file> is another
repository's handoff (Amendment 8(d)), which no tool here commits") and the
write goes on: the managed seam (already asked), the restart fences and the
digests still apply, the file is written, its line printed, and the verb
exits 0 with the 8(d) note. The arm's table says so.

Test: test_another_repositorys_handoff_is_written_when_S3_refuses_this_checkout
[stamp, prepend], rv2a's P3 with `LANES_NO_FETCH=1`: at 7561d5a both exit 2
(the stamp not written; the prepend written and "NOT committed"); with the
fix both are 0, written, the line printed, and the register checkout and
origin unchanged (`snapshot`). Runs on this tree: test_a23_write_handoff 69
passed; test_repo_hygiene 247 passed; test_a23_guard_refusals 122 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 346e403fb28b481201c2812b67f06f687c902cae)
… pin four behaviours that a one-line mutant used to leave alive, the exit 3 and `LANES_NO_GIT` paths, and carry no composite assert (#198, slice 2a)

Tests only; lanes-edit.sh is unchanged. rv2a's mutants M23, M27, M29 and M30
each survived the module, and two exits of the verb had no test. Each new
test passes on the code and fails on its mutant (one targeted run each, in a
clone at 346e403):

- M29 (`--whole` under S3 writes nothing): test_whole_refused_by_S3_still_writes_file_no_commit,
  sharing its body with the prepend case as `_written_uncommitted_under_S3`.
- M30 (`--target` ignored): test_target_names_the_file_written.
- M23 (an empty W takes the old rule): test_with_no_workspace_resolved_an_absolute_target_is_the_checkouts_own.
- M27 (the first managed-seam ask off): SHOULD 3's own
  test_a_managed_marker_already_fetched_is_refused_before_the_lock, which
  fails on S3's fetch showing in `net_ops()`.

Two exits had none: test_a_push_that_cannot_be_confirmed_is_3_and_its_line_printed
(the push lands, the read-back fetch fails: 3, "pushed, not confirmed", origin
holds it, the line printed) and
test_with_LANES_NO_GIT_the_handoff_is_written_and_nothing_committed (0, the
note, the file written, HEAD and origin unchanged, no net op).

The 22 composite asserts the review listed (Sonar S9073) are split into one
assert each, every message kept on the assert it belongs to.

Runs on this tree: test_a23_write_handoff 74 passed; test_repo_hygiene 247
passed; test_a23_guard_refusals 122 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 3cd2162f7553410a3f7b8f750d07f7e05d52caf7)
…ndoff-path` read the lane's row through a checked read, so a register that could not be read is 1 and never "no handoff", and a `--target` write for a lane with no row is 8 instead of a pushed commit carrying `Lane: <that lane>` (#198, slice 2a)

Both verbs read the handoff cell with `handoff_of_lane`, which answers a
failed register read as an empty cell. `handoff-path` then said 8, "no
handoff", over a register git could not show (R22: a read that could not
be performed is never 8). And `write-handoff` read the row only when no
`--target` was given, so through the explicit seam and in default mode,
where `lane_row_gate` does not ask, `--target` for a lane with no row went
on, committed the file with `Lane: <a lane with no row>` and pushed it.

Now both call the new `handoff_cell_of <lane>`, `handoff_of_lane`'s checked
twin: 0 with the cell (empty where the row names none), 8 where the
register was read and has no row, 1 where it could not be read.
`write-handoff` asks it right after the managed seam's second ask,
whatever `--target` says: 8 is "lane X has no row in the register, so it
has no handoff to write, and no commit may carry `Lane: X`", 1 is the R22
refusal, and each says the stamp line it would have written; `--target`
then replaces the cell. `handoff-path` answers 1 with the same R22 words,
and 8 "lane X has no handoff: there is no row for it in the register".
An empty cell keeps its 8 in both. The header's exit rows 1 and 8 and both
arms' tables say so.

In `write-handoff` the managed seam already refuses an unreadable register
(1, its UNKNOWN), so the new 1 there is reached only where the register
failed between the two reads; NIT 1 is observable through `handoff-path`.

Tests: test_a_target_for_a_lane_with_no_row_is_8_and_nothing_committed
(NIT 3) and test_handoff_path_over_an_unreadable_register_is_1_never_8
(NIT 1, a `git` shim first on PATH refusing `origin/main:lanes/LANES.md`);
test_write_handoff_for_a_lane_with_no_row_is_8 now reads the new words. At
3cd2162 all three failed: the `--target` write exited 0, "pushed
origin/main (attempt 1)"; `handoff-path` exited 8; the old test's words
were not the new ones. Runs on this tree: test_a23_write_handoff 76
passed; test_repo_hygiene 247 passed; test_a23_guard_refusals 122 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 96bf46471009a0cc00a58912e15ade4142f68be6)
… commit landed on top of a whole-file `write-handoff` while its push was in flight, the words name HEAD and say both commits are kept, instead of saying this write's commit is HEAD (#198, slice 2a)

S4.4 refuses to replay a whole-file write whose copy origin changed
meanwhile: exit 6, nothing reset, "the commit is local and kept on HEAD:
<sha> <subject>". It said that without reading HEAD. 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, and one landing on top of this write's
commit while its push is in flight left HEAD at that other commit, which
the words did not name. Nothing was lost (nothing is reset); only the
words were wrong. It runs before `cp_replay`'s MUST 4 HEAD check, so that
check's words never reached this case.

Now the block reads HEAD once the copy is known to have moved. Where HEAD
is this write's commit the words are as before. Where it is not, they
take MUST 4's shape: "HEAD is <top>, not this write's commit <sha>:
another commit was made on top of it … Nothing is reset, so both are
kept", "look at both: git -C … log --oneline <sha>^..HEAD", the publish
act's note in the plural, and the die "… this write's commit and the one
on top of it are local and kept, with HEAD at <top>: <sha> <subject>",
still exit 6. Found and left alone at the merge of 1b's 10169f3
(b02618c), so as not to make an evil merge.

Test: test_whole_refusal_names_HEAD_when_a_commit_landed_on_top, a
`--whole` whose push hook lands a peer's edit of the handoff and commits
an unlocked writer's stamp on top in the checkout. At 96bf464 it failed:
the die said "kept on HEAD: <this write's sha>" while HEAD was the other
commit. Now: exit 6, HEAD named, both commits unpublished, no replay, the
file's line printed. Runs on this tree: test_a23_write_handoff 77 passed;
test_repo_hygiene 247 passed; test_a23_guard_refusals 122 passed;
test_a23_publish_replay 32 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ea1d29300ddb4e41b9186797b59e16bbb0515a34)
…sides' new causes (#198)

Slice 1b's head brings, on top of 10169f3: 92fbe46 (SHOULD 1 and NIT 1 of
rv1b's pre-review: the manual's capture text, the carry and no-pull
rules), 3c6b240 (rv1b's second report), 6471fd1 (MUST-N and SHOULD B:
`claim` and `add-row` compare origin's tip with the tip their checks
read, `CS_SEEN` and `cs_seen_mark`), d9ff4e6 (SHOULD A and NITs 1-2: a
commit another writer rewrote is named as rewritten, `cp_rewritten` and
`cp_not_head`), c71621f (SHOULD C: the replay's drop is a compare-and-swap
of the branch from this write's own commit) and e6ea8f8 (stage 4, the
shell suite).

git stopped on three blocks, each a change made on both sides to the same
lines of an exit row:
- A38: `lanes-edit.sh`'s header exit row 2. 1b moved the list's last
  "and" to its new last item, `claim` or `add-row` meeting origin moved
  under its own checks on six attempts; 2a's clause (`write-handoff`'s
  S5, S2, restart and proof refusals, and S3's written-but-uncommitted
  handoff) now comes last and takes that "and". Both sides' words are
  otherwise kept whole.
- A39: `lanes-edit.sh`'s header exit row 6. 1b's rewritten row is kept
  whole, and 2a's S4.4 cause (a whole-file `write-handoff` whose copy
  origin changed meanwhile, which no replay may overwrite) joins its list
  of kept causes after the push refused with origin unmoved, before 1b's
  "Or another writer … REWROTE" and "named OFF HEAD" cases.
- A40: `docs/README-lanes.md`'s exit row 6, the same union as A39 (the one
  manual clause slice 2a edits).

The resolution was scripted (`resolve-1b-e6ea8f8.py`, all or nothing): it
checks each block's two sides against the prediction made with
`merge-tree --write-tree` on ea1d293, and the result equals its trial run
on the predicted files byte for byte.

Checked with no conflict:
- `checkout_sync`'s interface is unchanged. Its new comparison with
  `CS_SEEN` runs only under `CS_RECHECK=1`, which `claim` and `add-row`
  set and `write-handoff` never does, and `cs_seen_mark` in `log_sync`
  only records a tip.
- `cleanup_for_exec`, which A13's `HANDOFF_OUT` depends on, is not touched.
- 1b's `tests/test_a23_publish_replay.py` changes split asserts and add
  tests and helpers after the last test; the `Box` that
  `tests/test_a23_write_handoff.py` imports is unchanged.

Not resolved here, because git merged it cleanly and changing it in the
merge would make a change neither side made: 2a's S4.4 refusal, where
HEAD is not this write's commit, still says a commit was made on top of
it, where 1b's `cp_not_head` now tells a rewritten commit and an
unreadable HEAD apart. The next commit sends those two cases through
`cp_not_head`.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 287f6e5731b6407d3c11b6284129b19c4979f6bc)
… cannot be read, through slice 1b's `cp_not_head`, so a whole-file `write-handoff` never says its commit is kept when HEAD does not hold it (#198, slice 2a)

Where HEAD is not this write's commit, the S4.4 refusal said another
commit was made on top of it, and that "this write's commit and the one
on top of it are local and kept". Slice 1b's `cp_not_head`, merged in the
commit before this one, tells the cases apart: a commit on top (this
write's commit is an ancestor of HEAD), a commit another writer REWROTE
with no lock (a rebase, an amend: its sha is on no branch here), and a HEAD
that cannot be read. Its rule is that "kept" is said only where HEAD holds
the commit, read and never assumed. S4.4 runs before `cp_replay` asks, and
1b's own rewrite case makes the rewrite at the read-back fetch, which is
just before S4.4's check, so a rewritten whole-file write got the "kept"
words.

git merged the two cleanly, so this is not in the merge: there it would
have been a change neither side made.

Now S4.4's not-HEAD branch calls `cp_not_head "$cp_sha" "$msg" "while its
push was in flight"` where HEAD is unreadable or no longer holds this
write's commit. That gives 1b's words and exit 6, with nothing reset. Only
a commit on top keeps S4.4's own words, which name HEAD and say both are
kept.

Test: test_whole_refusal_names_a_rewritten_commit_as_rewritten, a `--whole`
whose push hook lands a peer's edit of the handoff and whose read-back
fetch hook amends this write's commit in the checkout (an amend cannot
conflict with the peer's edit, as a rebase over it would). At 287f6e5 it
failed: "this write's commit and the one on top of it are local and kept,
with HEAD at <the rewrite>". Now: exit 6, "rewrote this write's commit", no
"kept on HEAD", no replay, one commit waiting for the publish act, the
file's line printed. Runs on this tree: test_a23_write_handoff 78 passed;
test_repo_hygiene 253 passed; test_a23_guard_refusals 123 passed;
test_a23_publish_replay 39 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 1624f175d89a08db2fc06d169bf67937756ad5a9)
…all to `cp_not_head` keeps its three-argument form (#198)

Slice 1b's head brings, on top of e6ea8f8: 948e1e0 (NIT 3 of rv1b-c2's
re-check: cases for the carry form's compare-and-swap and a put-back that
cannot be made), 9683fd1 (NIT 4: migrate-state-cells' words for a log
held here under another case), 9cdf8ab (slice 2a's A37 built in 1b: the
`CP_KEEP_IF_GONE` global, set once at load and never from the
environment, and its check in `cp_replay`), 4b94f87 (B04: the replay's
HEAD check has a case only it can answer), 638b13a (the refusal with
origin unmoved reads HEAD before it says "kept on HEAD", through
`cp_not_head`'s new fourth argument) and 514b0c4 (migrate-state-cells'
case-pair words).

git merged it with no conflict. The tree equals the one `merge-tree
--write-tree` predicted on 1624f17 (49bda4c).

Checked where the two sides meet:
- A41's call in 2a's S4.4 refusal, `cp_not_head "$cp_sha" "$msg" "while
  its push was in flight"`, still matches the definition. 638b13a adds an
  optional fourth argument that only the origin-unmoved refusal gives;
  with three, `cp_not_head` and `cp_rewritten` print byte for byte what
  they printed at e6ea8f8. Its comment names 2a's S4.4 as a
  three-argument caller. S4.4 reaches `cp_not_head` only where HEAD is
  unreadable or does not hold this write's commit, and both of those die
  before the "dropping this write's commit" clause is printed.
- 638b13a's HEAD read sits after 2a's S4.4 block and is reached only
  where S4.4 did not refuse, so the two never both answer one push.
- 9cdf8ab's `CP_KEEP_IF_GONE` is empty at load, and no verb of 2a sets it
  yet, so this merge changes no 2a behaviour. The next commit makes
  `write-handoff` set it for `--stamp` and `--prepend` (A37).
- `tests/test_a23_write_handoff.py` imports `LANE` and `Box` from
  `tests/test_a23_publish_replay.py`. 1b's change there adds `REPO` to an
  import and appends tests and helpers, and leaves both names as they
  were.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e1a166aa2ce77fa42b0c6656c72b62bd55cb5b4a)
… in place in slice 1b's `CP_KEEP_IF_GONE`, so a peer that deletes it while the push is in flight leaves the write kept on HEAD and named, instead of replayed into nothing (#198, slice 2a)

`--stamp` and `--prepend` edit the lane's handoff in place. Where a peer
deleted that handoff on origin while this write's push was in flight,
the refused push was replayed: the drop took this write's commit, and
with it the file, onto origin's new tip, and the re-run found no file and
said "Nothing was written" (8). The write was then only in the commit the
replay had dropped. `--whole` was never affected, because S4.4 reads a
deleted copy as a copy that moved, and keeps the commit (6).

Slice 1b's 9cdf8ab, merged in the commit before this one, gives the
global `CP_KEEP_IF_GONE`, which is set once at load and never read from
the environment, and its check in `cp_replay`: where origin no longer has
the named file and this write's base did, the commit is kept on HEAD and
named (6), before anything is dropped. Now `write-handoff` sets it to the
handoff's path for `--stamp` and `--prepend`, beside `--whole`'s
`CP_WHOLE_REL` and `CP_WHOLE_BLOB`.

The verb's comment (step 7), the header's exit row 6 and the manual's
exit row 6 each gain this cause after the S4.4 one.

Tests, all through `write-handoff` itself:
- test_a_handoff_a_peer_deletes_mid_push_keeps_the_write_on_HEAD_exit_6,
  for `--stamp` and `--prepend`. In the deletes-it case the peer `git rm`s
  the handoff in a prepared commit, which lands at the push. At e1a166a
  it failed with exit 8: "push refused (attempt 1 of 6) ... is
  dropped", then "lane repoS4-1's handoff is not at
  <c>/handoffs/repoS4/repoS4-1.md, so there is nothing to write into.
  Nothing was written." Now: exit 6, "origin's copy of <handoff> was
  deleted while this write's push was in flight", kept on HEAD, no
  replay, the write in this checkout, no such file on origin, and one
  commit unpublished, this write's. In the keeps-it case the peer edits
  the handoff, and the write is replayed on the peer's copy and lands (0),
  as before.
- test_whole_over_a_handoff_a_peer_deletes_mid_push_is_S4_4s_exit_6: a
  `--whole` over the same deletion is S4.4's 6, in its own words and not
  in A37's.
- The third case of 1b's test, a named file the write's base never had,
  cannot be reached through `write-handoff` with a commit. S3 leaves no
  unpublished commit, so the base is origin's tip as S3 read it. A handoff
  git never tracked is refused before any commit (S3 step 4, MUST 1), and
  an absent one is 8. MUST 1's
  test_an_untracked_handoff_is_refused_and_never_dropped already drives
  that case in all three modes with a peer's commit at the push: nothing
  is committed or pushed, so there is nothing to keep.

Runs on this tree: test_a23_write_handoff 83 passed;
test_repo_hygiene 253 passed; test_a23_guard_refusals 127 passed;
test_a23_publish_replay 47 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 76a019bbb329738729b9d631b0851f0a87099d42)
…s carry form added (#198)

Slice 1b's head brings, on top of 514b0c4, one commit: 0c822f3, a case
for the carry form's refused tree step. The branch is put back on this
write's commit, and the commit is kept on HEAD and named (6). It changes
only tests/test_a23_publish_replay.py, where it appends one test.

git merged it with no conflict. The tree equals the one `merge-tree
--write-tree` predicted on 76a019b (20a75ce).

Checked where the two sides meet: `tests/test_a23_write_handoff.py`
imports `LANE` and `Box` from tests/test_a23_publish_replay.py, and 1b's
change leaves both as they were. No line of `lanes-edit.sh` changes, so
2a's S4.4 block, A41's call to `cp_not_head` and A37's
`CP_KEEP_IF_GONE` are as at 76a019b.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0d7d8530928b3804ef6ac5fa89cf3f9cc78ab6f4)
…he register too, so a `--target` or a row cell naming a sibling lane's register or object log is refused (2) instead of replaced as another repository's handoff (#198, slice 2a)

rv2a-c2's re-review of slice 2a at 0d7d853, SHOULD 1 (its probe N3).
SHOULD 1's rule (a file under `handoffs/`, never the register or
anything under `lanes/`) was asked only of a path in this checkout.
Every path outside it counted as another repository's handoff
(Amendment 8(d)). So a `--target`, or a row cell, naming
`<W>-lanes/<other lane>/lanes/LANES.md` had `--whole` replace that
clone's register in place, uncommitted, with exit 0, and that lane's
next write then refused at S3 step 4 over a change it never made.
`--prepend` did the same to an append-only object log.

Now a path outside this checkout whose work tree's top level also
carries the register (`$LANES_PATH`) counts as another clone of the
register. The same rule holds there, relative to that top level: 2,
nothing written, before a byte, and the stamp line said. The case tests
move into `whf_in_scope`, which both checks ask, and this checkout's
answers are unchanged.

A repository that carries no register is still 8(d)'s. Amendment 23 S2
says such a handoff "is unchanged", and
test_cell_in_another_repository_is_published_not_committed pins a
top-level `handoff.md` in another repository. A path in no work tree is
as it was. Whether a sibling clone's own `handoffs/` file should be
refused too is left to the PR's question 3.

The verb's comment (step 2) and the header's exit row 2 name the new
refusal.

Tests:
- test_a_target_in_another_clone_of_the_register_keeps_the_handoffs_rule,
  for whole-register, prepend-log and cell-register. The sibling is a
  clone at `<W>-lanes/repoS4-2/`. At 0d7d853 all three exited 0 with
  "... is another repository's handoff (Amendment 8(d))". Now each exits
  2 and writes nothing, and both checkouts are as they were.
- test_a_handoff_in_another_clone_of_the_register_is_still_another_repositorys:
  the bound. A sibling clone's `handoffs/` file is written as 8(d)'s,
  committed nowhere, with exit 0, as before.

Runs on this tree: test_a23_write_handoff 87 passed;
test_repo_hygiene 253 passed; test_a23_guard_refusals 127 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit a928dbb3e50ecd911a4d1a4e235fba4ff1984b3c)
…answered, so the recovery note names the `git add` an untracked handoff needs even where S3 refused for another reason (#198, slice 2a)

rv2a-c2's re-review of slice 2a at 0d7d853, NIT 1 (its probe N1).
`whf_untracked` was computed only where S3 passed. Where S3 refused for
a reason of its own, such as a foreign staged entry, `--prepend` and
`--whole` still wrote the handoff and exited 2 (R-A11-11), which is
right. But the note's `git -C <c> commit ... -- <handoff>`, run as
printed, then failed for an untracked handoff: "pathspec ... did not
match any file(s) known to git", because the `git add` was left out.

Now the `ls-files --error-unmatch` test runs wherever the handoff is
this checkout's and `LANES_NO_GIT` is not set, whatever S3 answered.
Only S3 step 4's refusal of an untracked handoff stays under a passing
S3, after `cs_own_pending`, as before. The step-6 note then names
`git -C <c> add -- <handoff> && ` first whatever made S3 refuse.

Tests:
- test_the_recovery_under_another_S3_refusal_names_git_add_for_an_untracked_handoff,
  for untracked and tracked. The untracked case: the row names a handoff
  git has never had, and a foreign staged entry makes S3 refuse. At
  a928dbb the note printed the commit alone. Now it prints the `add`
  first, and the two commands, run as printed, commit the handoff alone,
  with the foreign entry still staged. The tracked case is the pin: its
  note names no `add`, as before.

Runs on this tree: test_a23_write_handoff 89 passed;
test_repo_hygiene 253 passed; test_a23_guard_refusals 127 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 2fc394d9fbbc8c658ce4680037f84e4bc4eccdba)
… could not follow the handoff, so a handoff a peer moves with its row's cell mid-push is replayed into the moved file instead of kept (#198, slice 2a)

rv2a-c2's re-review of slice 2a at 0d7d853, NIT 2 (its probe N2). A37
kept the commit on HEAD (6) for any handoff origin no longer had once
this write's push was refused. A peer that `git mv`s the lane's handoff
and points the row's cell at the new name, in one commit landing while
the push is in flight, left exit 6: the commit was kept on HEAD while
editing a path origin no longer had, and every later write from that
checkout refused until a person published or archived it. Without
A37's line, the replay re-read the row, stamped the moved file, and
origin had it.

Now `write-handoff` records the cell it followed, `whf_keep_cell`, which
is empty under `--target`, whose re-run names the same file again. A new
function, `whf_keep_if_gone`, reads origin's register from the ref
`cp_confirm` just fetched. It reads that ref directly, because
`register_text_checked` keeps, for the whole process, the register it
read before the push. Its answer is 1 (replay) only where origin's row
for the lane names another cell, which the re-run reads and writes into.
It is 0 (keep, as A37 kept) where:
- the row still names the cell this write followed;
- `--target` named the file;
- origin has no row for the lane, so there is none to re-read
  (`rename-lane` renames a row together with its file);
- origin's register cannot be read (R22).

Slice 1b's `cp_replay` condition gains one clause, `&& whf_keep_if_gone`,
and a two-line comment. These are the only lines of 1b's code this
commit changes. With write-handoff's state unset, as in 1b's own test of
the global (a patched copy running `log NOTED`), the answer is 0, so 1b's
check is as it was there.

A row that now names a cell origin has no file for is replayed too, and
the re-run says 8 ("nothing to write into"): the row is what a replay
follows, and it names no file.

Text: the verb's comment (step 7), its header's step 7, the header's
exit row 6 and the manual's exit row 6 say that a handoff moved with its
row's cell is replayed into the moved file.

Tests:
- test_a_handoff_a_peer_moves_with_its_row_cell_mid_push_is_replayed_into_the_moved_file,
  for `--stamp` and `--prepend`. At 2fc394d both exited 6, kept on HEAD.
  Now each exits 0: the push is refused once, the write is replayed and
  lands in the moved file on origin, and nothing is left unpublished.
- test_a_moved_handoff_the_replay_cannot_follow_is_kept_on_HEAD_exit_6,
  for target and renamed: the bounds, where A37's keep still holds. With
  `--target` naming the old path, or the row renamed with the file, the
  commit is kept on HEAD and named (6) and nothing is replayed. Both
  passed before this change too.
- A37's tests (deletes-it and keeps-it, for both modes, and the `--whole`
  pin) pass unchanged: a deletion that leaves the row's cell as it was
  is still kept.

Runs on this tree: test_a23_write_handoff 93 passed;
test_a23_publish_replay 48 passed; test_repo_hygiene 253 passed;
test_a23_guard_refusals 127 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e2037b912e853c948739b6bd1d36b98b806cc12e)
…-target` or a row cell naming another clone's `.git/config`, or a bare clone's, is refused (2) instead of written as another repository's handoff and every git command there broken (#198, slice 2a)

rv2a-c3's re-check of slice 2a at e2037b9, new SHOULD 1 (its probe
N4). A44 holds the `handoffs/` rule in another clone of the register
by asking `rev-parse --show-toplevel` of the target's directory. Inside
a git directory that read fails, so the target counted as another
repository's handoff (Amendment 8(d)): written there, uncommitted, with
exit 0. `--stamp` put its line under `[core]` in a sibling lane's
`<W>-lanes/<lane>/.git/config`, `--whole` replaced the file, and every
git command in that clone then failed with 128, its own lane's S3 with
them, until someone repaired the config by hand. A bare clone's config
went the same way.

Now, for a path outside this checkout, `write-handoff` asks
`git -C <its directory> rev-parse --is-inside-git-dir` first, and where
it prints `true` refuses with 2 before a byte, the stamp line said. No
Amendment 8(d) handoff lives inside a git directory, so a top-level
`handoff.md` in another repository's work tree is written as before.
This checkout's own `.git/` was already refused by the `handoffs/`
rule. The verb's comment (step 2), its exit row 2 and the file header's
exit-2 list name the new refusal.

Tests:
- test_a_target_inside_another_clones_git_directory_is_refused_and_nothing_written,
  for a sibling clone at `<W>-lanes/repoS4-2/` and a bare clone, each in
  `--stamp` and `--whole`. At e2037b9 all four exited 0 and wrote the
  config. Now each exits 2 and writes nothing, that clone's git still
  reads its config, and this checkout and origin are as they were.

Runs on this tree: test_a23_write_handoff 97 passed;
test_repo_hygiene 253 passed; test_a23_guard_refusals 127 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5ad9b4a600794e32379b41aa3e690a9f7b112176)
… so a peer that empties the row's cell, or points it at a name origin has no file for or outside `handoffs/`, keeps the `--stamp` or `--prepend` on HEAD (6) instead of dropping it with nothing written (#198, slice 2a)

rv2a-c3's re-check of slice 2a at e2037b9, new NIT 1 (its probe N5).
A46's `whf_keep_if_gone` turned A37's keep into a replay wherever
origin's row for the lane named another cell than the one this write
followed. Its header says it keeps "only where a replay could not
follow the file", but it replayed even where the new cell names no
file the re-run can write. In one commit landing during this write's
push, a peer that deleted the handoff and pointed the cell at a name
not yet created, or emptied the cell, had the commit dropped, and the
re-run said 8, "Nothing was written". The write stayed only in the
reflog. At 0d7d853 the same race gave 6, with the commit kept on HEAD.

Now another cell is followed only where it translates into this
checkout (`handoff_in_repo`, as step 2 does), is in scope there
(`whf_in_scope`: under `handoffs/`, which also answers an emptied
cell), and names a file origin has (`git cat-file -e
refs/remotes/origin/<branch>:<cell>`). Any other cell answers keep:
an emptied one (the re-run's 8), one whose file origin lacks (8), one
outside `handoffs/` (the re-run's 2) and one in another repository.
The commit is then kept on HEAD and named (6), as A37 keeps it. The
third case goes one step past the report's two, because the re-run
refuses it with nothing written in the same way. A handoff moved with
its cell to a file origin has is replayed into it, as before.

Slice 1b's `cp_replay` is not touched again: its clause
`&& whf_keep_if_gone` is as A46 left it. The function's header, the
verb's step-7 comment and header, the file header's exit-6 list and
the manual's exit row 6 say the rule.

Tests:
- test_a_cell_a_peer_repoints_where_no_replay_can_write_keeps_the_write_on_HEAD_exit_6,
  for moved-no-file, emptied and moved-out-of-handoffs. At 5ad9b4a the
  three exited 8, 8 and 2, each after "push refused (attempt 1 of 6)"
  had dropped the commit. Now each exits 6: the stamp is in this
  checkout, one commit is unpublished (this write's), and nothing is
  replayed.

Runs on this tree: test_a23_write_handoff 100 passed;
test_a23_publish_replay 48 passed; test_repo_hygiene 253 passed;
test_a23_guard_refusals 127 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 68014b4154545b819c1281f1f4e0d28e8ea6e1d1)
…h` that m2a's mutant battery found no case for, each shown failing on its mutant: six usage values, an empty block or prepared file, a flag where the lane goes, four of `handoff-path`'s reads, a link out of the checkout, and the `handoffs/` rule's own cases (#198, slice 2a)

m2a-c1's whole-slice battery at e2037b9 ran 33 of its 60 one-line
mutants of slice 2a's own code: 18 were killed and 15 survived. Each
survivor is a check the code makes that no test supplies the bad
input for. This commit adds those tests and changes no code. Every
one passes on this tree and fails on its survivor's mutant, run in a
separate clone with the same one-line change.

- Usage, 64 with nothing written and no fetch made
  (`test_usage_is_64_and_writes_nothing`, six new ids):
  - `stamp-empty`: an empty `--stamp` (A02);
  - `subject-two-lines` (A04);
  - `whole-digest-not-hex` (A06);
  - `expect-digest-with-stamp` (A07);
  - `expected-digest-short` (A09);
  - `restart-empty-fence` (A10). Without that check, an empty
    operation left the write unfenced, and the fence the caller named
    was dropped.
- `test_an_empty_block_or_prepared_file_is_refused` [prepend|whole]
  (A11). An empty block put `---` and a blank line on top, and an
  empty prepared file emptied the handoff and pushed it. Both are now
  refused (1), as `publish-handoff` refuses an empty file.
- `test_routed_a_flag_where_the_lane_goes_is_usage_and_routes_nowhere`
  (A13). Routed, `write-handoff --stamp …` with no lane is 64. No
  checkout is cloned and no fetch is made.
- `handoff-path`:
  - `test_handoff_path_for_a_row_that_names_no_handoff_is_8` (B02):
    8 and nothing printed, and `--create` clones nothing;
  - `test_handoff_path_reads_the_cell_from_the_register_as_published`
    (B04): it fetches before it reads, so a cell a peer moved is
    answered;
  - `test_handoff_path_refuses_a_lane_path_that_holds_no_clone` (C02):
    1, nothing printed, and that directory left as it was;
  - `test_handoff_path_through_a_directory_inside_the_seam_names_its_top_level`
    (C03). The mutant is not equivalent, but `LANES_REPO` alone naming
    a directory inside the checkout is refused first (1, "registry not
    found"). So the raise to the top level is reached only where
    `LANES_FILE` or `LANES_DIR` names the register too, and the test
    sets `LANES_FILE`.
- `test_a_handoff_that_links_out_of_the_checkout_is_another_repositorys`
  (D08). The row's cell is a link in this checkout to another
  repository's file outside W: that file is written, as Amendment
  8(d)'s, 0, committed nowhere, and this checkout and origin are left
  as they were.
- `test_a_target_outside_handoffs_is_refused_and_nothing_written`
  gains three cases:
  - `tracked-file`, a tracked `docs/plan.md` (E01);
  - `git-config`, this checkout's own `.git/config` (E01). A47 does
    not cover it: A47's check is only for a path outside this checkout;
  - `dotdot`, a target ending in `/..` (E02). It was 2 on the code and
    1 on the mutant.

Runs on this tree: test_a23_write_handoff 117 passed;
test_repo_hygiene 253 passed; test_test_roots 39 passed, 1 skipped.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 2a41e8336cc6f45764cb67ef3b51737c61527f34)
…battery found no case for: an empty handoff, a row naming a file that is not there, `--expected-digest none`, a cell spelled otherwise whose file a peer deletes mid-push, a register a peer deletes mid-push, and a routed bind that must clone nothing (#198, slice 2a)

m2a-c2 ran the other 27 of m2a's 60 one-line mutants of slice 2a's
own code at e2037b9: 20 were killed and 7 survived (G05, G06, H03,
J01, J02, J03, K03). Each survivor is a check the code makes that no
test supplies the bad input for. This commit adds a case for each
and changes no code. Every case passes on this tree, and each was
run against its survivor's mutant in a separate clone with the same
one-line change.

- `test_stamp_into_an_empty_handoff_is_refused_1_and_says_the_line`
  (G05). An empty handoff has no header block to stamp under: 1, and
  the line it wants is said. Without the check, the splice went on
  and its proof refused it in other words (2, "did not prove out").
- `test_a_row_naming_a_handoff_the_checkout_does_not_have_is_8_and_nothing_written`
  [stamp|prepend|whole] (G06). The row names a file under
  `handoffs/` that is not there: 8, "nothing to write into", and
  never "unreadable" (1). The stamp line is said for `--stamp` only.
- `test_a_prepend_fenced_at_preparing_takes_expected_digest_none`
  (H03). `lane-handoff` binds its prepend at `preparing`, before the
  restart has a digest. `--expected-digest none` is no digest to
  compare, so the fenced write goes ahead (0) and reserves nothing.
  Compared as a digest, it was refused (2).
- `test_a_cell_spelled_otherwise_whose_file_a_peer_deletes_mid_push_keeps_the_write_on_HEAD_exit_6`
  [estate|link x stamp|prepend] (J01, J02). The row's cell is the
  estate's `~/...` spelling, or a link under `handoffs/` to the
  lane's handoff, and a peer deletes the file while this write's push
  is in flight: 6, the write kept on HEAD and named, never replayed
  into nothing.
  - J01 (A37's keep naming the cell's spelling rather than the path
    in this checkout) is killed by both spellings.
  - J02 (A46 recording the path in this checkout as the cell it
    followed) is equivalent for the estate spelling since A48: that
    cell translates to the deleted file, which origin lacks, so the
    answer is "keep" either way. The link kills it: A48 follows the
    link's own path, which origin still has, so the mutant replays
    the write, and the re-run says 8, "Nothing was written".
- `test_a_peer_that_deletes_the_handoff_and_the_register_mid_push_keeps_the_write_on_HEAD_exit_6`
  (J03). A peer's commit deletes the lane's handoff and the register
  while this write's push is in flight. Nothing between the refused
  push and the replay reads the register, so the keep is reached, and
  a register origin no longer has is never "replay" (R22): 6, one
  commit unpublished, nothing dropped.
- `test_a_routed_bind_of_the_rows_cell_clones_no_checkout` (K03).
  Routed, with no checkout for the lane yet, `set-restart-intent
  --handoff handoffs/...` binds the path that checkout will have, as
  `handoff-path` derives it without `--create`: 0, no checkout
  cloned, no clone made, and W as it was.

Runs on this tree: test_a23_write_handoff 128 passed;
test_repo_hygiene 253 passed; test_test_roots 39 passed, 1 skipped.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 37b000d5491a1660d59ed5e474e2ad2526217337)
…found no case for: the line a refused `--stamp` says, a cell this checkout cannot translate, and a cell padded otherwise (#198, slice 2a)

m2a-c4 ran the last 16 of the 35 one-line mutants of slice 2a's own
code at 2a41e83: 10 were killed and 6 survived (J02, J03, K03, J04b,
N03, N05). A51's cases kill J02, J03 and K03. This commit adds a case
for each of the other three and changes no code. Every case passes on
this tree, and each was run against its survivor's mutant in a
separate clone with the same one-line change.

- `test_a_target_inside_another_clones_git_directory_is_refused_and_nothing_written`
  gains one assertion (N03, A47). A `--stamp` refused for a target
  inside a git directory says the line it would have written, as
  every refusal of it does; `--whole` says none. Without the call,
  the stamp that is owed was owed to nobody.
- `test_a_cell_a_peer_repoints_where_no_replay_can_write_keeps_the_write_on_HEAD_exit_6`
  gains a fourth cell, `another-repository` (N05, A48). A peer
  deletes the handoff and points the row's cell at a path in a folder
  of another repository. This checkout cannot translate it, so the
  replay cannot follow it: 6, the write kept on HEAD and named,
  nothing replayed, and nothing written in that other repository.
  Where the translation failed, the mutant replayed the write, and
  the re-run said 8, "Nothing was written".
- `test_a_cell_a_peer_pads_otherwise_is_the_same_cell_and_the_write_is_kept_on_HEAD_exit_6`
  (J04b, A48). A peer moves the handoff to a file whose name ends in
  a blank and writes the row's cell unpadded, with that one trailing
  blank. Padding is no part of a cell, so the row still names the
  cell this write followed: 6, kept on HEAD, nothing written into the
  blank-ended name. Compared untrimmed, the cell was "another cell",
  a file origin has, and the write was replayed into it. Practically
  equivalent for the tool's own registers, which pad every cell the
  same way; the case pins the trim.

Runs on this tree: see the pull request body.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 1bb4f7566e0ed55e7a7a96d7dc1645587b7287f1)
…ns, and `cp_own_commit` given the whole message with the `Lane:` trailer (#198, review of #312, 606)

Lane openRepoTools-3's review of #312 at 1bb4f75 makes this merge a
landing condition: 1b's commit-race fix (#291, dba848a) was not in 2a's
base, and the race reproduced at 2a's head. Slice 1b's head brings, on top
of 0c822f3, its whole first-review fix round and its second round (32
commits, from 24efc8a to 59551dd), among them dba848a (291: a write knows
its own commit by what it is), af29be6/173f3c8 (`cp_under`), 40c57b9 (a
checkout removed while its writer waits for the lock), 126bccd (the
SC2093 disables) and 59551dd (a `LANES_REPO` below its checkout's top
level refuses with 2).

git stopped on four blocks, each a change made on both sides to the same
lines. Each is the union of both sides (A53):
- `lanes-edit.sh`'s header exit row 1: 1b's clause (a checkout, or the
  directory its lock lives in, removed while the write waited for that
  lock), then 2a's (`handoff-path` and `write-handoff`, a register that
  could not be read, never 8).
- `lanes-edit.sh`'s header exit row 6: 1b's rewritten list of kept
  causes whole, then 2a's S4.4 cause and its A37/A48 cause after the push
  refused with origin unmoved; the list's "or" moves to 2a's last item.
- `lane_row_gate`'s case lists: 2a's `write-handoff` in the row group,
  1b's `claim`, `release`, `retire-rows`, `archive-rows` and
  `migrate-state-cells` in the log group.
- `docs/README-lanes.md`'s exit row 6: the same union as the header's.

The resolution was scripted (`resolve-1b-59551dd.py`, all or nothing): it
checks each block's three sides against what was read before it writes.

606, on the merge, as the review asks. `cp_own_commit` (dba848a) tells
this write's commit by the message git stored, but `commit_publish` gave
it the subject alone, while `write-handoff` commits with `-m <subject>
-m "Lane: <lane>"`. Its commit could never match, so every race in that
window was `cp_untold`'s "cannot be told" (6, nothing named). It fails
safe, but says less than it knows. `commit_publish` now passes the whole
message, the subject and, where `CP_TRAILER` is set, its trailer as the
second paragraph (`cp_msg`); the words still print the subject. No other
writer sets `CP_TRAILER`, so for them `cp_msg` is the subject, as before.
The test for it is the next commit:
test_a_commit_landing_just_after_this_writes_commit_is_told_by_its_trailer_exit_6
failed on this merge without the change ("cannot be told") and passes
with it.

Runs on this tree: test_a23_write_handoff 130 passed;
test_a23_publish_replay 76 passed.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f43deb3c45e78f5e4c5746772f4475918a131b24)
…s just after: `write-handoff`'s own commit is told by its whole message, the `Lane:` trailer with it, and named under the other, both kept on HEAD (6) (#198, slice 2a, review of #312)

Lane openRepoTools-3's review of #312 at 1bb4f75, finding 606, fixed in
the merge of 1b at 59551dd (f43deb3): `cp_own_commit` was given the
subject alone, so `write-handoff`'s commit, made with `-m <subject> -m
"Lane: <lane>"`, never matched what git stored, and every race in the
`git commit` window was `cp_untold`'s "cannot be told" (6, nothing named).

test_a_commit_landing_just_after_this_writes_commit_is_told_by_its_trailer_exit_6
lands an unlocked writer's stamp just after this write's `git commit`
(slice 1b's `_git_shim` and `_stamp`, imported, as `Box` is), with a
lost push race: exit 6, this write's commit is the one under the stamp
and carries the trailer, the words name HEAD and this write's commit
("HEAD is <stamp>, not this write's commit <mine>"), nothing is
replayed, the other writer's file is still there, and both commits are
unpublished on HEAD. On f43deb3 without the change it failed with "this
write's commit cannot be told from another made in this checkout"; with
it, it passes.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ed63c171e78ac88a76158200826edfebb49ca734)
…handoff-path` and `set-restart-intent --handoff` refuse a handoff in W or in another checkout beside it (2, nothing written, no path printed), and a clone git cannot read is never "no repository" (1) (#198, slice 2a, review of #312)

Lane openRepoTools-3's correction for 601 (#312 6368669583) and finding
723 of its review of #316: with the refusal in `write-handoff` alone,
`handoff-path` still printed a sibling checkout's handoff, so `lane-start`
delivered that sibling's top block as this lane's first prompt.

- `handoff_beside_w`, beside `handoff_in_repo`, is the one check. It asks
  of the PHYSICAL path the translation gave, by path and asking git
  nothing: in W where W is not the checkout (S5), or in a `<W>-*`
  checkout that is not this one (a sibling lane's `<W>-lanes/<lane>/`, a
  per-profile `<W>-<name>/` clone). Any spelling lands where it lands:
  absolute, a cell climbing with `..`, the estate's `~/` cell.
- `write-handoff` calls it where w2a-c14's inline case was (after A44 and
  A47's checks, inside the other-repository block); its S5 refusal stays
  first. `handoff-path` calls it on the path it would print, and refuses
  with 2 and nothing on stdout. `set-restart-intent` calls it on a cell as
  translated, and on an absolute path that lands beside W; an absolute W
  path is still kept as given (every older caller's binding).
- Kept from w2a-c14: where git gives no top level but a `.git` is on the
  way up from the target, `write-handoff` refuses with 1 (R22), and git is
  asked in the nearest directory there is.
- Tests: w2a-c14's eight cases, the beside-W ones now with a climbing
  cell too (10); `handoff-path` refusing what `write-handoff` refuses (5:
  W by climbing, two checkouts beside W absolute and climbing); and
  `set-restart-intent --handoff` beside W (6: absolute, absolute with
  `..`, the `~/` cell). All 21 red at `ed63c17`, green here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6d861d413750ed4833f808bfc8ea8550c59081a2)
Brett Heap and others added 7 commits October 23, 2026 20:43
…components climb out of the checkout is refused by `write-handoff` and `handoff-path` (2, nothing written, no path printed), its spelling asked first (#198, slice 2a, review of #312)

Lane openRepoTools-3's review of #312, 602: Amendment 23 S2 joins a
relative cell to the lane's checkout, and `set-restart-intent --handoff`
already refused one with a `.` or `..` component (64), yet
`write-handoff` wrote `handoffs/../../outside/x.md`, and a relative
`--target ../outside/x.md`, as another repository's handoff (0), and
`handoff-path` printed it as the checkout's own.

- `handoff_rel_climbs_out <checkout> <cell>`, after `whf_in_scope`: a
  relative spelling (not `~`, `~/` or `/`) with a `.` or `..` component
  climbs out by its TEXT (a `..` above the top level, whether or not the
  directories it climbs through exist yet) or, where it resolves, by the
  FILE it names (a `..` after a link out). Not physical containment
  alone: D08's link out, with no such component, stays Amendment 8(d)'s.
- A55: the spelling is asked FIRST, in both verbs, before where the path
  lands (S5, 601's `handoff_beside_w`), as `set-restart-intent`'s 64
  already was. `handoff-path` asks the text before any checkout is made,
  so `--create` clones nothing for such a cell, then the file in the
  checkout there is.
- Tests: six spellings refused by `write-handoff` (outside by cell and by
  `--target`, into a sibling checkout, into W, through a link, and
  through `handoffs/notyet/`, a directory that does not exist yet, which
  said 8 and leaves no directory now); one that climbs within the checkout
  is its own handoff; `handoff-path` refuses two such cells, seam and
  routed (4). A54's climbing cases now read 602's words, and
  `handoff-path` gains W through a link in the checkout, which keeps
  601's S5 branch covered there. 15 red at `6d861d4`, green here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 31c61986dda9169bc6b95c7ae79186df9bde9740)
…2s (the bound file, the pre-image) with 7, so its 2 is never a restart fence's; `publish-handoff` keeps its documented 2 (#198, slice 2a, review of #312)

Lane openRepoTools-3's review of #312, 603 (and its correction, #312
6368669583): exit 2 meant both "S3 refused this checkout" and "the
restart fence refused", so slice 2b's `lane-start`, which under an
active restart dies on a fence's code and notes S3's 2 (act2-plan
§2.7), could not tell an offline checkout from a fence violation.

- `handoff_publish_atomic`'s two fence refusals that were 2 — the
  publication target is not the operation-bound handoff, and the restart
  handoff changed before its owned write (`--expected-digest`) — exit
  `"${HPA_FENCE_RC:-2}"`. `write-handoff` sets `HPA_FENCE_RC=7`;
  `publish-handoff` leaves it unset, so its contract is as it was.
- Not mapped: "restart ownership is malformed" (2 in both verbs). It is
  not a fence that did not match, and 7's one meaning is "you lost the
  race"; write-handoff's exit list names it under 2.
- The header's exit rows 2 and 7, `write-handoff`'s exit list, the shared
  act's comment, and the README's rows 2 and 7 say so.
- Tests: the unbound-file and pre-image refusals are 7 (both red at
  `31c6198`: 2 == 7), and S3's refusal under the same `--restart` stays 2
  (a bound case). `publish-handoff`'s bound-file case keeps its 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 36a25da84c0a986ce665a6f05e641badfdd10259)
…nd pushes an Amendment 8(d) handoff in its own repository, as `lane-start` and `lane-handoff` did at e2037b9, and never in another clone of the register (#198, slice 2a, review of #312)

Lane openRepoTools-3's review of #312, 604, ruled at #312 6368669583:
Amendment 23 S2 says an Amendment 8(d) handoff "is not the register's
and is unchanged", and unchanged is what `lane-start` and `lane-handoff`
did with it at e2037b9. Written and committed nowhere, every resume from
such a handoff would leave a dirty file in that repository once 2b moves
`lane-start` onto `write-handoff`.

- The 8(d) branch commits in the handoff's own top level, by
  `--literal-pathspecs` pathspec, with this write's subject and the
  `Lane:` trailer, and pushes the branch that repository is on (`main`
  wherever e2037b9's was).
- No rebasing pull: no line of this file may run or print one (#198,
  hygiene's `test_no_lane_writer_pulls_with_rebase`). So e2037b9's
  `pull --rebase` before the push is S3's way here: the branch is
  fetched and fast-forwarded BEFORE the commit, so the commit sits on
  origin's tip; a fast-forward that cannot be made is left to the push.
  Not S3 or S4 themselves: that repository is not the register, so no
  pending change there is asked about and nothing is replayed or read
  back.
- Its other outcomes keep the handoff WRITTEN (R-A11-11) and say the
  command that finishes it: in no git work tree, 0; LANES_NO_GIT, 0;
  HEAD on no branch (no commit mid-rebase), a failed add or commit, a
  fetch that failed, or a refused push (the commit kept on HEAD there,
  the look-first commands said), 6.
- NEVER IN A CLONE OF THE REGISTER: a top level that carries the register
  too (A44's test) is another lane's clone, not 8(d)'s, and a commit and
  push there would publish to the register's origin outside S3 and S4.
  Beside W, 601 refuses it; elsewhere it is written and NOT committed, as
  before (0, said). c16's draft refused it (2); lane 3 did not ask for
  that, and the commit needs only this guard, so it is a question to
  lane 3 in the body, not code.
- The header's exit row 6, `write-handoff`'s step 2 and exit list, the
  translation's comment, `handoff-path`'s note and README exit row 6 say
  so.
- Tests: the 8(d) case and D08's link out are committed and pushed
  there, their trees clean; where that origin moved first, by an
  unrelated commit, the write is fast-forwarded onto it, committed and
  pushed; no origin is 6 with the commit kept; under
  LANES_NO_GIT it is 0 and uncommitted; a register clone elsewhere is
  written, uncommitted, its HEAD and the register's origin as they were.
  Six red at `36a25da`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c962db78e22c1e8fa9f513ec66a49041369c064e)
…us` in W takes no optional lock, so the stray scan never rewrites W's index (#198, slice 2a, review of #316)

Lane openRepoTools-3's review of #316, 732 (1a's code, in this slice's
file `lanes-edit.sh`): the stray scan ran a plain `git status` in W,
which, finding the index stat-dirty (a tracked file touched, its bytes
the same), rewrote it under W's `index.lock`: a write in W, which
Amendment 23 S5 forbids, and 2b's launch runs the scan twice.

- It runs with `GIT_OPTIONAL_LOCKS=0`, as `ws_pending_refuse`'s status
  already does.
- Test: W's index is as it was after `refresh-workspace` (its mtime and
  its bytes), the stray still named, and a plain `git status` afterwards
  does rewrite it, so the case had a stat-dirty index to prove on. Red at
  `c962db7` ("W's index was rewritten").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7ffbcb04903468f6bf5d65d94b2b526351b315e6)
…repend` takes `--expected-digest none` only (605, 64), no glob character in a handoff's path (607, 2), S4.4 says "before or while" and names the commits under it (609), the cases 610 asks for, and the guard's comment names `write-handoff`'s exemption (611) (#198, slice 2a, review of #312)

- 605: a `--prepend` fenced on a real `--expected-digest` that lost its
  push race to a peer's change of the handoff was replayed onto a file
  that no longer had that digest, the re-run refused, and the block was
  left in the dropped commit, in the reflog only. `lane-handoff` binds
  its prepend at `preparing` with `none` and takes the digest again at
  `pending`, so no caller passes a sha there: it is usage (64), refused
  before the lock, nothing written and nothing fetched.
- 607: `commit_publish` stages and commits its pathspec as git reads one,
  a pattern, so a handoff named `x[1].md` also names `x1.md`. A glob
  character (`*`, `?` or `[`) in the path of a handoff in this checkout is
  refused (2, nothing written, the stamp line said), rather than
  changing slice 1b's pathspecs. No lane's handoff is named so.
- 609: S4.4's note says "before or while its push was in flight", as
  1b's 343 does; where commits that origin lacks sit under this write's
  kept commit, it counts them and says to stop and take them to the lane
  their subjects name, as 1b's 344 does.
- 610: cases for 605, 607 and 609, for the checkout lock held while an
  S3-refused `--prepend` waits on the sidecar lock and then writes, and
  for the exemption from the workstation guard.
- 611: the dispatcher guard's comment says that `write-handoff` is not in
  its list, on purpose (R-A11-11: a swap is never left unwritten).
- 608 is a decision in #312's body, not code.
- Red at `7ffbcb0`: four of the six cases (605 by 7 for 64, the old code
  reaching the fence; 607 by 0 for 2, its commit carrying `x1.md` under
  its subject and pushing it, which S3 step 4 did not refuse; 609's two by
  their words). The lock and exemption cases are bound cases and passed
  there too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 60ddad353b34f00061e013dbe6386cfd43d181b4)
…` refuse a handoff whose path in the checkout holds a glob character, as `write-handoff` does (607), by one shared question (#198, slice 2a, review of #311)

Lane openRepoTools-3's first review of #311 (slice 3), 991, 2a's to
close under 723 (its review of #316): at 60ddad3 `handoff-path` printed
`<checkout>/handoffs/<e>/x[1].md`, a path `write-handoff` refuses (607,
A59), for a restart to bind and a launch to read; and
`set-restart-intent --handoff` bound a restart to it.

- 607's question moves into ONE helper beside 601's `handoff_beside_w`,
  `handoff_rel_globbed <checkout> <path>`: the physical path, relative
  to the checkout, holds `*`, `?` or `[`. A path outside the checkout is
  not its rule's (Amendment 8(d)'s, committed by its own pathspec, A57).
- `write-handoff` asks it where it asked 607's case, with the same words
  and exit (2).
- `handoff-path` asks it after 601's check: 2, nothing on stdout. Routed,
  a read without `--create` makes no checkout; `--create` makes the
  lane's own first, as for 601.
- `set-restart-intent --handoff` asks it where it asks 601's: the row's
  cell translated into the checkout, and an absolute path in the lane's
  checkout beside W (the path `handoff-path` prints under the opt-in).
  2, no intent written. W's own absolute spelling is still kept as given
  (every older caller's binding, A54); `write-handoff` refuses it when it
  writes.
- Header exit row 2 and `handoff-path`'s exit list name it.
- Tests: `handoff-path` (the seam, routed, routed with `--create`) and
  `set-restart-intent` (the cell, an absolute path in the lane's
  checkout). Red at `60ddad3`: 5 failed, each `assert 0 == 2`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7bc572cdbf27afeb692a7f98aeeb095d52c5f38f)
…ings its round after d8859c8 (old 59551dd), slice 1a's merge of the code leg's main 577110e with 1b's resolution of three opensoft/openRepoTools#263 hunks, and the re-pin of that issue's register-hardening cases (opensoft/openRepoTools#198)

Slice 1b's head on the code leg, feat/198-a23-slice-1b-guard-publish at
34799b7, named by main at 2026-11-08T15:4xZ. It holds 32 commits past
d8859c8 (old 59551dd), the last 1b head merged into this slice, 8 of them
merges:
- 70edb34 to 07bf43c, slice 1b's own round after 59551dd, re-homed;
- 195fca9, slice 1a's merge 903d751 of openRepoTools-code main 577110e,
  where 1b's resolution of opensoft/openRepoTools#263's three hunks in
  lanes-edit.sh keeps main's change and 1b's (1b's B70: the header's exit
  rows 5 and 6, with 1b's row 6 whole);
- 34799b7, the re-pin of opensoft/openRepoTools#263's register-hardening
  cases to what holds under slice 1b (1b's B74).

Why: this slice stood on d8859c8, from before the cutover, so it carried
neither the code leg's main nor 1b's round. Slices 2b and 3 take them
through this merge.

The merge is clean: no hunk was resolved by hand. 1b kept its exit row 6,
so 2a's clauses there (S4.4, A37, A48 and 604's) merged beside it. git
merge-tree of 34799b7 into 1320847 gave tree
0802f5a before the merge was made.

Refs opensoft/openRepoTools#198

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 8, 2026
…le, so the case now sets `GIT_CONFIG_NOSYSTEM=1`, and skips, saying why, where git still reads it (opensoft/openRepoTools#198, slice 2a, #18)

CI on #18 at 2b1a232 ran git 2.56.0 and
failed `tests` and `tests-no-submodule`: 2 failed, 3652 passed. Both
were `test_a_clone_git_cannot_read_is_never_taken_for_no_repository`,
`[beside-W-other-owner]` and `[elsewhere-other-owner]`, at
`_unreadable`'s own probe, `git -C <clone> rev-parse --show-toplevel`,
which exited 0: "git can still read the clone". The `bad-config`
variants passed there. This workstation has git 2.43.0, where
`GIT_TEST_ASSUME_DIFFERENT_OWNER=1` refuses the clone (128, "dubious
ownership"), so the failure could not be seen here. Two causes fit:
(a) that git ignores the test hook, or (b) the runner's system config
(`/etc/gitconfig`) names `safe.directory = *`, which the box's own
`.gitconfig` does not override.

- `other-owner` sets `GIT_CONFIG_NOSYSTEM=1` beside the hook, in the
  environment `_unreadable` returns, so the probe, the check of the
  lane's own checkout and the write under test all run in it. The box
  scrubs `GIT_*` and sets HOME to its own, so `$HOME/.gitconfig` stays the
  global config git reads and names the box's repositories safe. That
  answers (b).
- Where the probe still reads the clone under `other-owner`, the case is
  skipped (`pytest.skip`), never passed: the reason has `git --version`
  and every `safe.directory` git reports in that environment with its
  file (`git config --show-origin --get-all safe.directory`), and names
  the cause, a `*` in the global config or a git that ignores the hook
  (a). CI's output then says which it was.
- `bad-config` is as it was.
- Shown on this workstation, git 2.43.0: both `other-owner` cases run
  and pass, not skipped; a scratch system config with `safe.directory =
  *` (`GIT_CONFIG_SYSTEM`) lets the probe read the clone without
  `GIT_CONFIG_NOSYSTEM` (exit 0, the CI failure) and refuses it with it
  (128); a `*` put into the box's own `.gitconfig`, and the hook given as
  0 to stand for a git that ignores it, each skip the case with their
  reason.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Nov 8, 2026
…wner case hold on CI's git 2.56.0) into slice 2b (opensoft/openRepoTools#198)

CI on #18 failed 2 of 3654 at 2b1a232, the
two `other-owner` cases of
`test_a_clone_git_cannot_read_is_never_taken_for_no_repository` in
tests/test_a23_write_handoff.py. 56a9b87 is 2a's fix for them: the case
sets `GIT_CONFIG_NOSYSTEM=1` beside git's test hook, and skips, saying
why, where git still reads the clone. It changes that one test file by 32
lines added and 2 removed, and nothing else.

This slice carried the same test file at a44b9d2, with 2b1a232's
version of it, so it would have failed the same two cases on CI's git.

The merge is clean: no hunk was resolved by hand. git merge-tree of
56a9b87 into this branch's a44b9d2 gave tree c17ffb5 before the
merge was made, and the merge commit's tree is that tree.

Lane: openreposhape-2 (openRepoShape-2)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@brettheap brettheap changed the title Amendment 23 act 2, slice 2a: write-handoff writes a lane's handoff in the checkout its writes go through, handoff-path says where, publish-handoff keeps its contract on one shared act, and set-restart-intent takes the row's spellings (opensoft/openRepoTools#198) Amendment 23 act 2, slice 2a: write-handoff writes a lane's handoff in the checkout its writes go through, handoff-path says where, publish-handoff keeps its contract on one shared act, and set-restart-intent takes the row's spellings Nov 8, 2026
Brett Heap and others added 4 commits November 8, 2026 19:39
…er offer the publish act, which refuses there (2), and give S6.3's archive as 1b's 705 does (opensoft/openRepoTools#198, slice 2a, review of #18, SHOULD 2)

Lane openRepoTools-3's review of #18 at
2b1a232 (#198 6726042267), SHOULD 2. `commit_publish`'s S4.4 refusal of
a whole-file `write-handoff` runs only after a push was refused because
origin moved. Its two notes, where this write's commit is HEAD and where
another commit sits on top of it, still said "until it is published -
LANES_LANE=<lane> lanes-edit.sh commit ..., the publish act - or
archived", and the first also offered a short `reset --keep
origin/main`. The publish act refuses there: after the refusal it exits 2,
"refusing to publish: origin/main has moved past the local commits ...
Nothing was pushed." 1b's round after d8859c8 (705, NIT 294) moved every
other origin-moved note to `cp_recovery_steps`, and `cp_not_head`'s
comment names 2a's S4.4 as such a caller; the merge of 1b (2b1a232)
brought the rule in without applying it to these two lines.

- The two notes are now `cp_recovery_steps "it is"` and
  `cp_recovery_steps "they are"`: what is kept must be archived and
  dropped, then S6.3 in order, the list, the STOP line, the archive on
  origin and the drop, with the tracking ref named in full
  (`reset --keep refs/remotes/origin/main`).
- The case runs both notes (`how`: this write's commit is HEAD, or an
  unlocked writer's stamp is on top of it), checks S6.3's order with
  test_a23_publish_replay's `_recovery_is_s63s_archive`, that no short
  `reset --keep origin/main` is printed, then runs the publish act exactly
  as the old note offered it: it exits 2, "moved past the local commits",
  "Nothing was pushed.", and origin and HEAD are as they were. The setup
  of the stamp-on-top race is one helper, shared with the older test.
- Red first, on the parent's lanes-edit.sh (56a9b87): both cases failed,
  `_recovery_is_s63s_archive`'s `assert 0 <= lead` (the "until ... archived
  and dropped here" line was not there); with the two lines, both pass.
- The docstrings and messages of four older S4.4 assertions said the
  commits wait "for the publish act"; they say "for the archive".

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…-handoff` refuses (2, nothing written) a file under handoffs/ of another clone of the register that is not beside W, instead of writing it there uncommitted (opensoft/openRepoTools#198, slice 2a, #18)

Question 4 of #18, answered by lane
openRepoTools-3 in its review at 2b1a232 (#198 6726042267): refuse it
with 2. Clones of the register outside W's siblings exist in this estate
(per-lane `LANES_REPO` clones), and an uncommitted write there wedges
that lane's next write at S3 step 4, which is 601's harm. A57 kept 604's
commit and push out of such a clone and wrote the file there, exit 0,
saying it was NOT committed; this round's first draft had refused it.

- A44's test of the top level (`whf_oreg`, now asked once) already finds
  the clone: a top level that carries the register, with the target under
  it. A file there that is not under `handoffs/` was refused already
  (A44). A file under `handoffs/` is refused now, after 601's check, so a
  clone beside W keeps 601's words and A44's keep theirs: 2, before a
  byte, the stamp line said, nothing on stdout, that clone, this checkout
  and origin as they were. By `--target` or by an absolute cell, in
  `--stamp`, `--prepend` and `--whole`.
- The branch of step 7 that wrote it there and did not commit it stays as
  a backstop for a top level that came to carry the register after the
  checks above, and its comment says so.
- The header's exit row 2 and the step-2 comment name the refusal. The
  manual's exit row 2 (`docs/README-lanes.md`, the spec leg's) names none
  of `write-handoff`'s S2 refusals (A44's, 601's, 602's, 607's, A47's), so
  it needs no change for this one.
- The case that pinned the old behaviour,
  `test_a_handoff_in_another_clone_of_the_register_elsewhere_is_written_never_committed`,
  is now `..._elsewhere_is_refused`, in four forms (`--stamp` and
  `--prepend` and `--whole` by `--target`, and `--stamp` by a cell).
  Red first, on the parent's lanes-edit.sh (0547408): all four failed,
  `assert 0 == 2` (the old code wrote the file and exited 0); with the
  refusal they pass, with the 601, A44, A47 and 8(d) cases.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… 8(d) clause of exit row 6 says its commit is in the handoff's own repository, the comment on 605 says A59, and two composite asserts are split (opensoft/openRepoTools#198, slice 2a, NITs 5, 6 and 7)

- NIT 5: the header's exit row 6 listed 604's Amendment 8(d) cause among
  the kept-on-HEAD causes, and the row ends "Every later write from that
  checkout refuses under S3 until the lane publishes or archives it". That
  is false for 8(d): the commit is in the handoff's own repository, and no
  write in the lane checkout refuses over it. The clause now ends "(that
  commit is in that repository; no write here refuses over it)", the words
  opensoft/openRepoTools-spec#16's exit row 6 takes (its NIT 5 commit), on
  one line so the two can be found together. No test compares the code's
  header with the manual's row 6: the hygiene test that does (the exit-3
  row) reads row 3, and a read of every test that opens the manual found
  none that quotes row 6 or 8(d).
- NIT 6: the comment on the fenced `--prepend` said "605; A58". The change
  is A59 (decision 27); A58 is 732.
- NIT 7: SonarCloud's S9073, composite asserts, at the test file's A46 and
  A52 cases: `assert _origin_has(box, MOVED_REL) and not _origin_has(box,
  HANDOFF_REL)` and `assert HANDOFF_REL not in names and odd in names` are
  each two asserts with a message. An AST scan of both test files for an
  `assert` whose test is an `and` or `or` found those two in
  tests/test_a23_write_handoff.py before, and none after.
  tests/test_handoff_skill_commit_block.py:911 (984) is slice 3's: it is
  not in this slice's file at all (483 lines here, 1207 at slice 3's f2340ad,
  where it is line 901, `assert block_file and block_file != "<none>",
  first.stdout`, in `test_block_1_block_2_and_step_4_each_in_its_own_shell_
  carry_what_step_2_did`), so this slice does not touch that file and
  slice 3's next merge of this head has nothing to conflict on there.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…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>
@sonarqubecloud

sonarqubecloud Bot commented Nov 8, 2026

Copy link
Copy Markdown

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