diff --git a/docs/README-lanes.md b/docs/README-lanes.md index 44dd0df..045db78 100644 --- a/docs/README-lanes.md +++ b/docs/README-lanes.md @@ -1218,12 +1218,12 @@ else. |---|---| | 0 | done — and for a write, **confirmed**: the commit was read back on origin before the writer said `pushed` (Amendment 23 S4) | | 1 | environment — no register, no writer | -| 2 | refusal — bad arguments, the object is held, an unknown alias, or `commit` refusing to publish local commits that are not register or handoff writes, from a checkout whose HEAD is off the branch, or that it cannot read (#194; it pushes none of them); and, Amendment 23 S3, every writer with nothing written, where its checkout cannot be brought to origin's tip: a fetch that failed (`LANES_NO_FETCH=1` is one, for a writer), an operation in progress or unmerged entries, HEAD off the branch, staged entries that are not the write's own, unpushed commits (the publish act excepted), a diverged branch, a pending change to a path the write commits, or a fast-forward git refused; a `LANES_REPO` naming a directory below its checkout's top level, from which git would read the write's pathspecs, before any edit; the withdrawn `--sweep`; the publish act over a stack origin has moved past (S4); and `claim` or `add-row` meeting origin moved under its own checks on each of six attempts, before any edit (S3, S4) | +| 2 | refusal — bad arguments, the object is held, an unknown alias, or `commit` refusing to publish local commits that are not register or handoff writes, from a checkout whose HEAD is off the branch, or that it cannot read (#194; it pushes none of them); and, Amendment 23 S3, every writer with nothing written, where its checkout cannot be brought to origin's tip: a fetch that failed (`LANES_NO_FETCH=1` is one, for a writer), an operation in progress or unmerged entries, HEAD off the branch, staged entries that are not the write's own, unpushed commits (the publish act excepted), a diverged branch, a pending change to a path the write commits, or a fast-forward git refused; a `LANES_REPO` naming a directory below its checkout's top level, from which git would read the write's pathspecs, before any edit; the withdrawn `--sweep`; the publish act over a stack origin has moved past (S4); and `claim` or `add-row` meeting origin moved under its own checks on each of six attempts, before any edit (S3, S4); and `publish-handoff`'s restart fence on its bound file or pre-image (`write-handoff` answers those 7, so its 2 is never a restart fence's) | | 3 | **pushed, not confirmed: ``** (Amendment 23 S4). The write committed and pushed — or its push timed out, and a hung push often lands — and the read-back could not confirm the commit on origin: the fetch failed or timed out, or origin does not hold it. Never success. The commit stays on HEAD — unless another writer in the checkout **rewrote** it while its push was in flight (an unlocked writer's rebase), which the words then say: it is not on HEAD at all, and what reached origin is read by content — and the next write's S3 finds it by content either way: published, there is nothing to do; not, every write refuses until the publish act sends it or an archive (S6.3) takes it | | 4 | the mutex could not be taken within 60s | | 5 | an edit moved more than one line and was refused | -| 6 | `git add` or `commit` failed — or the commit is **kept on HEAD and named** (sha, subject, `git show`): a sixth push refused because origin moved (Amendment 23 S4's bound; where S3 finds the sixth move before any edit, nothing is committed and it is 2), a replay's drop refused over an uncommitted change, a commit another writer made on top of this one in the checkout before the replay drops it, or under it (this write's commit is known by what it is, never by HEAD read after the fact, and the drop moves the branch only from that commit and only where it sits on origin's history, so it never takes another; `commit`'s carry form drops to its own commit's parent by design, so one under it stays, the hand edit goes back to the working tree with its copy kept, and the re-run's S3 refuses on it: 2, not 6), a commit made in the same moment as this write's that cannot be told from it (nothing is pushed), or a push (a write's, or the publish act's) refused with origin unmoved, which no replay can mend. Or another writer in the checkout **rewrote** this write's commit (a `pull --rebase` with no lock): it is not on HEAD at all, and what reached origin is read by content (`git cherry -v origin/main HEAD`). Or, where the drop's tree step was refused and another writer committed on the branch in between, the commit is named off HEAD, with `reflog -5`. Every later write from that checkout refuses until the lane publishes or archives it | -| 7 | another act got there first and this one wrote nothing: `claim`'s `CLAIM-LOST` (another lane's claim landed first), and `set-lane-state` / `set-lane-tree`'s lifecycle fence (openRepoTools#91, below) | +| 6 | `git add` or `commit` failed — or the commit is **kept on HEAD and named** (sha, subject, `git show`): a sixth push refused because origin moved (Amendment 23 S4's bound; where S3 finds the sixth move before any edit, nothing is committed and it is 2), a replay's drop refused over an uncommitted change, a commit another writer made on top of this one in the checkout before the replay drops it, or under it (this write's commit is known by what it is, never by HEAD read after the fact, and the drop moves the branch only from that commit and only where it sits on origin's history, so it never takes another; `commit`'s carry form drops to its own commit's parent by design, so one under it stays, the hand edit goes back to the working tree with its copy kept, and the re-run's S3 refuses on it: 2, not 6), a commit made in the same moment as this write's that cannot be told from it (nothing is pushed), a push (a write's, or the publish act's) refused with origin unmoved, which no replay can mend, or a whole-file `write-handoff` whose copy origin changed meanwhile, which no replay may overwrite (S4.4), or a `--stamp` or `--prepend` one whose handoff origin deleted meanwhile, which a replay would find nothing to write into (one moved with its row's cell, to a file under `handoffs/` that origin has, is replayed into the moved file), or an Amendment 8(d) handoff `write-handoff` wrote whose commit or push in its own repository failed (the command that finishes it is said). Or another writer in the checkout **rewrote** this write's commit (a `pull --rebase` with no lock): it is not on HEAD at all, and what reached origin is read by content (`git cherry -v origin/main HEAD`). Or, where the drop's tree step was refused and another writer committed on the branch in between, the commit is named off HEAD, with `reflog -5`. Every later write from that checkout refuses until the lane publishes or archives it | +| 7 | another act got there first and this one wrote nothing: `claim`'s `CLAIM-LOST` (another lane's claim landed first), and `set-lane-state` / `set-lane-tree`'s lifecycle fence (openRepoTools#91, below), and `publish-handoff` / `write-handoff`'s restart fence — `write-handoff` answers its bound file and pre-image 7 too, where `publish-handoff` keeps its 2 (Amendment 23, slice 2a) | | 9 | `CLAIM-LOST` — issue #30's own dead-lane verdict could not be reconfirmed before a `--force` takeover's push landed: the source lane resumed, a live session now backs it up, or that could not be read at all (`claim` only). Never 7 — that code is a RIVAL's claim, and this is the same lane the takeover was granted over | | 10 | `lane-state`: the lane's lifecycle snapshot is there and could not be read, or is of this schema and not a whole record, so nothing can be read out of it as a snapshot (openRepoTools#91 and #115, below) — never the 8 that means it has none. It was 9 until #61 spent 9 on `claim`. `lane-trees`: the lane's `trees` directory is there and could not be listed (#157 item 4e) — never the 8 that means it recorded no tree | | 8 | no record — and no other meaning | diff --git a/lanes-edit.sh b/lanes-edit.sh index b096334..8eda912 100755 --- a/lanes-edit.sh +++ b/lanes-edit.sh @@ -64,6 +64,13 @@ # lanes-edit.sh migrate-state-cells [--yes] # Amendment 13(e), once # lanes-edit.sh retire-rows … [--reason ""] [--writer ] # lanes-edit.sh archive-rows [--yes] # Amendment 19(d) +# lanes-edit.sh write-handoff (--stamp "" | --prepend +# | --whole --expect-digest ) --subject "" +# [--target ] [--restart +# [--expected-digest ]] +# (Amendment 23: the lane's handoff written in its own checkout, +# under S3-S4; `` on stdout) +# lanes-edit.sh handoff-path [--create] # where write-handoff writes it # # AMENDMENT 19 — A CLOSED OR DORMANT LANE LEAVES THE LISTING # lanes-edit.sh lanes [--closed] # CLOSED and DORMANT rows too @@ -448,7 +455,10 @@ # (every routed write, and `lane-checkout`), which is refused and never # deleted, moved or repaired; `refresh-workspace` with no W; a checkout, # or the directory its lock lives in, removed while the write waited for -# that lock (360: lane-end's sweep of a CLOSED lane), said at once +# that lock (360: lane-end's sweep of a CLOSED lane), said at once; +# `handoff-path` and `write-handoff`, a register that could not be read +# (R22: never 8), and `write-handoff`'s target under a repository that +# git could not read (R22: never "no repository") # 2 refusal: bad arguments, the object is held, an unknown alias, — # Amendment 15 — a register holding # two rows whose lane names differ only by case, which every writer and @@ -475,9 +485,25 @@ # branch, a pending change to a path the write commits (the register's, # for every writer but `commit`), or a fast-forward git refused; the # withdrawn `--sweep`; the publish act over a stack origin has moved -# past (S4, Open question 2); and `claim` or `add-row` meeting origin +# past (S4, Open question 2); `claim` or `add-row` meeting origin # moved under its own checks on each of six attempts (S3, S4), before any -# edit. EIGHT MEANINGS ON ONE NUMBER, and they +# edit; and — slice 2a — `write-handoff`'s handoff +# that resolves into W through a link (S5), a target in the checkout, or +# in another clone of the register, that is not a file under `handoffs/` +# (S2), a target inside a git directory, a target in W or in another +# checkout of the register beside it (`-lanes//`, `-/`; +# a lane's handoff is written in its own checkout only, and +# `handoff-path` and `set-restart-intent --handoff` refuse the same +# handoff by the same check, 601), a relative cell or `--target` whose +# `.` or `..` components climb out of the checkout (S2, 602; `handoff-path` +# refuses that cell too), a handoff whose path holds a glob character +# (607; `handoff-path` and `set-restart-intent --handoff` refuse it +# too, 991), or a splice that does not prove out, each with +# nothing written — a restart's bound file or pre-image that is not this +# one is `publish-handoff`'s 2 and `write-handoff`'s 7 (603: its 2 is +# never a restart fence's); while S3's refusal of a `--prepend` or a +# `--whole` has WRITTEN the handoff, uncommitted (R-A11-11), and printed +# its path. EIGHT MEANINGS ON ONE NUMBER, and they # stay readable only because of where each can occur: bad arguments to a # subcommand that predates Amendment 11; `register-row`'s "not lane-shaped"; # AN UNKNOWN SUBCOMMAND (the `*)` arm below), which is how a caller detects @@ -546,9 +572,16 @@ # commit's parent by design, so one under it stays, the hand edit # goes back to the working tree with its copy kept, and the re-run's # S3 refuses on it: 2, not 6), a commit made in the same moment as this -# write's that cannot be told from it (nothing is pushed), or a push (a +# write's that cannot be told from it (nothing is pushed), a push (a # write's, or the publish act's) -# refused with origin unmoved, which no replay can mend. Or another +# refused with origin unmoved, which no replay can mend, or a +# whole-file `write-handoff` whose copy origin changed meanwhile, which +# no replay may overwrite (S4.4), or a `--stamp` or `--prepend` one +# whose handoff origin deleted meanwhile, which a replay would find +# nothing to write into (one moved with its row's cell, to a file under +# `handoffs/` that origin has, is replayed into the moved file), or an +# Amendment 8(d) handoff `write-handoff` wrote whose commit or push in its +# own repository failed (604; the command that finishes it is said). Or another # writer in the checkout REWROTE this write's commit (a `pull --rebase` # with no lock): it is not on HEAD at all, and what reached origin is # read by content. Or, where the drop's tree step was refused and @@ -563,14 +596,21 @@ # `set-lane-tree`: the `--generation`/`--operation` this observation was # taken under is no longer the lane's — or the lane is `SWAPPED` and the # operation it names is finished (#114) — so a superseded poll cannot be -# filed over the current inventory. ONE MEANING ON THE NUMBER, in three -# places: you lost the race. +# filed over the current inventory. `publish-handoff` and `write-handoff`: +# the restart fence did not match, an active restart owns the handoff, or +# the handoff changed under the write — and in `write-handoff` alone, the +# restart's bound file or pre-image is not this one, which +# `publish-handoff` answers 2 (603) — and nothing was published. ONE +# MEANING ON THE NUMBER, in five places: you lost the race. # 8 no record — `who` found nothing; `lane-objects` has no log file for the # lane; `live-holder` READ this workstation's session records and none of # them holds it; `swapped` found no lane swapped on the workstation; # `history` READ the lane's log and it carries no `NOTED` or `RULED` line; # `window-session` found no live session in the window; `lane-checkout` -# found no checkout for the lane (Amendment 23). A read that could +# found no checkout for the lane (Amendment 23); `handoff-path` and +# `write-handoff` found no handoff for the lane — there is no row for it +# (`write-handoff` with `--target` too), its row names none, or +# (`write-handoff`) the file is not there — and wrote nothing. A read that could # not be performed is never 8 (R22). `session-start` never exits 8, or # anything but 0: it is a hook. `guard` never exits 8 either — it is a hook # too, and a BLOCKING one, so its two codes are 0 and 2 (see 2 above). @@ -682,8 +722,9 @@ # WORKSTATION's, taken by the lifecycle, restart and tree # sidecars, by `publish-handoff` and by `lane_state_follow`, # each ALONE: the follow-up runs after its write released -# the checkout lock, and no act holds both. No lock is taken -# in W then. Without the opt-in those acts take the +# the checkout lock. `write-handoff` alone holds both, the +# checkout's first and this one inside it, for its restart +# fence and replace only. No lock is taken in W then. Without the opt-in those acts take the # checkout's lock, as before. Each lock holds a `pid` file, # ` `: under LANES_ROUTE_WRITES=1 a # live holder in this container is never aged out, and @@ -1299,6 +1340,14 @@ CP_KEEP_IF_GONE="" # tree, which no argv can make again, so a replay gives it back as the hand # edit it was instead of dropping it with the commit (`cp_replay`). CP_REPLAY_KEEPS_EDIT=0 +# Set by `write-handoff` alone (slice 2a), never from the environment: +# CP_TRAILER the commit's second paragraph, `Lane: ` (Rule 5's +# trailer, as `lane-start` and `lane-handoff` commit a handoff); +# CP_WHOLE_REL a WHOLE-FILE write's path, and CP_WHOLE_BLOB the blob it +# CP_WHOLE_BLOB started from (empty for a file HEAD did not have): a refused +# push replays only where origin's copy is still that one +# (Amendment 23 S4.4), checked before any reset. +CP_TRAILER=""; CP_WHOLE_REL=""; CP_WHOLE_BLOB="" # Set by `claim` and `add-row` alone, whose own checks run BEFORE the lock on # what was last fetched: where S3's fetch then finds origin's tip moved, the # verb runs again from the top on the new tip before anything is written @@ -1468,10 +1517,13 @@ release_lock() { # snapshot, the restart intent, the tree records and `publish-handoff`'s fence # were serialised by W's mutex; a routed write takes no lock in W, and a lane's # checkout lock serialises one lane only, so they share this one beside the -# lane checkouts, on the mount every container sees. EACH TAKER TAKES IT ALONE -# (the review of #282, NIT 2): a sidecar verb, and `lane_state_follow`, which -# runs after its write released the checkout lock. No act holds both, so no -# order between them can deadlock. +# lane checkouts, on the mount every container sees. EVERY SIDECAR ACT TAKES IT +# ALONE (the review of #282, NIT 2): a sidecar verb, and `lane_state_follow`, +# which runs after its write released the checkout lock. ONE ACT NESTS IT: +# `write-handoff` (slice 2a) takes it INSIDE its checkout lock, for its restart +# fence and the replace alone, and releases it before it pushes. The order is +# always checkout lock, then sidecar lock, and no act takes them the other way +# round, so no order between them can deadlock (ort186 Q3, condition 1). # # WITHOUT THE OPT-IN it is the checkout's own lock, `$LOCK`, as it was before # Amendment 23: the sidecar verbs keep their `acquire_lock`, and the follow-up @@ -1596,6 +1648,13 @@ rename_undo() { # remove is set above, before the trap: `RL_*`, `LANES_IDX_DIR`, # `LANES_WT_IDX_DIR`, `LOCK` and `LOCK_HELD`. TMPD=""; pbh_tmp=""; rl_htmp=""; SE_CACHE_FILE="" +# `write-handoff`'s ONE LINE ON STDOUT, ``, printed by +# `cleanup` once this process has written the handoff — committed or not, so a +# refusal after the bytes (S3's for `--prepend` and `--whole`, a push not +# confirmed, the bound) still says what the file now is — and never by a run +# that `exec`s into its replay, which runs no EXIT trap: a replayed write +# prints one line, its last attempt's. +HANDOFF_OUT="" # The creation lock's heartbeat (`lane_checkout_ensure`), stopped by pid. create_heartbeat_stop() { [ -n "${CREATE_HB:-}" ] || return 0 @@ -1605,11 +1664,12 @@ create_heartbeat_stop() { return 0 } cleanup() { + if [ -n "${HANDOFF_OUT:-}" ]; then printf '%s\n' "$HANDOFF_OUT"; HANDOFF_OUT=""; fi [ -z "${pbh_tmp:-}" ] || rm -f -- "$pbh_tmp" [ -z "${rl_htmp:-}" ] || rm -f -- "$rl_htmp" # rename-lane's stamp before its replace (#158) rename_undo - sidecar_release # Amendment 23: never held with the - create_heartbeat_stop # checkout lock, so the order is free + sidecar_release # Amendment 23: released before the + create_heartbeat_stop # checkout lock, the reverse of its order [ -z "$CREATE_LOCK" ] || mutex_release "$CREATE_LOCK" if [ "$LOCK_HELD" = 1 ]; then release_lock; fi [ -n "${RL_SNAP:-}" ] && [ -d "${RL_SNAP:-}" ] && rm -rf -- "$RL_SNAP" @@ -1639,7 +1699,7 @@ cleanup() { cleanup_for_exec() { [ -z "${pbh_tmp:-}" ] || rm -f -- "$pbh_tmp" [ -z "${rl_htmp:-}" ] || rm -f -- "$rl_htmp" - pbh_tmp=""; rl_htmp=""; RL_ACTIVE=0 + pbh_tmp=""; rl_htmp=""; RL_ACTIVE=0; HANDOFF_OUT="" sidecar_release create_heartbeat_stop [ -z "$CREATE_LOCK" ] || mutex_release "$CREATE_LOCK" @@ -2921,6 +2981,20 @@ commit_publish() { msg="$1"; shift || : if [ "$#" -gt 0 ]; then CP_PATHS=("$@"); else CP_PATHS=("$LANES_PATH"); fi [ "$NO_GIT" = 1 ] && { note "LANES_NO_GIT=1 — not committing"; return 0; } + # RULE 5'S TRAILER, `write-handoff`'s alone (slice 2a, on w1b's conditions): + # a second paragraph of ONE `Key: value` line, or nothing is staged or + # committed (2). The arguments are one array that always has the subject, so + # it expands under `set -u` in bash 3.2. The publish act commits nothing and + # never reads it, and no verb sets it with CP_PUBLISH_AHEAD. + cp_m=(-m "$msg") + if [ -n "$CP_TRAILER" ]; then + cp_tk="${CP_TRAILER%%: *}" + case "$cp_tk" in ''|*[!A-Za-z0-9-]*) cp_tk="" ;; esac + if [ -z "$cp_tk" ] || [ "$cp_tk" = "$CP_TRAILER" ] || [ -z "${CP_TRAILER#*: }" ] || ! lane_restart_value_ok "$CP_TRAILER"; then + die "this write's commit trailer is not one \`Key: value\` line with no control character, so nothing was staged or committed: $(printf '%q' "$CP_TRAILER")" 2 + fi + cp_m+=(-m "$CP_TRAILER") + fi # THE OLD PATH IS A PATHSPEC FOR THE DIFFS AND NEVER FOR THE `add`: after the # index entry is force-removed it matches neither the index nor a directory # read that compares exactly, and `git add` FAILS on a pathspec that matches @@ -2974,15 +3048,21 @@ EOF if [ -n "$cp_extra" ]; then die "this commit carries a case-only rename of the lane's log, so it is made from the INDEX rather than from its pathspecs — and the index also holds$cp_extra, which is not this write's to commit. Stage-reset it — \`git -C $LANES_REPO restore --staged --\`$cp_extra — and re-run. Nothing was committed and the log is already renamed on disk." 6 fi - git -C "$LANES_REPO" -c "$CP_EXACT" commit -q -m "$msg" || die "git commit failed" 6 + git -C "$LANES_REPO" -c "$CP_EXACT" commit -q "${cp_m[@]}" || die "git commit failed" 6 # THE RENAME IS DONE AND IN HEAD: the old path is no longer a pathspec of # anything below, and the next `commit_publish` of this run is not a rename. CP_PATHS=(${cp_add[@]+"${cp_add[@]}"}) LOG_RENAMED_FROM="" else - git -C "$LANES_REPO" -c "$CP_EXACT" commit -q -m "$msg" -- "${CP_PATHS[@]}" || die "git commit failed" 6 - fi - cp_sha="$(cp_own_commit "$cp_parent" "$msg")" || cp_untold "$cp_parent" "$msg" + git -C "$LANES_REPO" -c "$CP_EXACT" commit -q "${cp_m[@]}" -- "${CP_PATHS[@]}" || die "git commit failed" 6 + fi + # THE MESSAGE AS GIT STORED IT (lane openRepoTools-3's review of #312, + # 606): a trailer is the commit's second paragraph, and `cp_own_commit` + # compares the whole message, so it is given the whole message; given the + # subject alone, `write-handoff`'s commit never matched, and every race + # here was "cannot be told" (6, nothing named). + cp_msg="$msg"; [ -z "$CP_TRAILER" ] || cp_msg="$(printf '%s\n\n%s' "$msg" "$CP_TRAILER")" + cp_sha="$(cp_own_commit "$cp_parent" "$cp_msg")" || cp_untold "$cp_parent" "$msg" note "committed: $msg" # ---- 2. THE PUSH: the branch as S3 left it plus this one commit, no pull. @@ -3006,6 +3086,49 @@ EOF 0) cp_pushed "$cp_sha"; return 0 ;; 2) cp_not_confirmed "$cp_sha" "$msg" "the push was refused, and $(cp_why_unconfirmed 2)" ;; esac + # S4.4 — A WHOLE-FILE WRITE REPLAYS ONLY OVER THE COPY IT STARTED FROM, and + # that is asked here, with origin just fetched, BEFORE the origin-unmoved + # refusal below and BEFORE the reset (w1b's placement): a replay + # would write the whole file again over whatever a peer put there. So where + # origin's copy has moved, the handoff stays written, its commit is kept and + # named, as at the bound, and nobody's change is undone (R-A11-11). + # HEAD IS READ, NOT ASSUMED (MUST 4's shape, which `cp_replay` asks after + # this): until slices 2b and 3 another writer can commit on top of this one in + # the same checkout while its push is in flight. Nothing is reset here either + # way, so both are kept; the words then name HEAD, and say both are kept, + # never that this write's commit is HEAD. A COMMIT ON TOP IS ONE CASE; A + # REWRITE IS ANOTHER (slice 1b's `cp_not_head`, rv1b-c's SHOULD A): where + # HEAD cannot be read, or no longer holds this write's commit at all (another + # writer rebased or amended it with no lock), nothing of it is kept here, and + # `cp_not_head` names that in 1b's words. + if [ -n "$CP_WHOLE_REL" ]; then + cpw_now="$(git -C "$LANES_REPO" rev-parse -q --verify "refs/remotes/origin/$LANES_BRANCH:$CP_WHOLE_REL" 2>/dev/null || :)" + if [ "$cpw_now" != "$CP_WHOLE_BLOB" ]; then + note "origin/$LANES_BRANCH's $CP_WHOLE_REL is ${cpw_now:-gone} now, and this whole-file write started from ${CP_WHOLE_BLOB:-no such file}." + cpw_head="$(git -C "$LANES_REPO" rev-parse -q --verify HEAD 2>/dev/null || :)" + if [ "$cpw_head" = "$cp_sha" ]; then + # THE COMMITS UNDER IT ARE NAMED TOO (lane openRepoTools-3's review of + # #312, 609, as 1b's 344 names them below): an unlocked writer's + # commit landing after S3 and before this write's `git commit` sits + # under it, and the `reset --keep` the next note offers would take it. + if git -C "$LANES_REPO" rev-parse -q --verify "$cp_sha^" >/dev/null 2>&1 && + ! git -C "$LANES_REPO" merge-base --is-ancestor "$cp_sha^" "refs/remotes/origin/$LANES_BRANCH" 2>/dev/null; then + cpw_un="$(git -C "$LANES_REPO" rev-list --count "refs/remotes/origin/$LANES_BRANCH..$cp_sha^" 2>/dev/null || printf 'some')" + note "under it are $cpw_un commit(s) that origin/$LANES_BRANCH lacks and this write did not make; list them: git -C $LANES_REPO log --oneline refs/remotes/origin/$LANES_BRANCH..HEAD; if any is not your own write, STOP and take it to the lane its subject names — never reset, stash or overwrite a peer's work" + fi + note "look at it: git -C $LANES_REPO show $cp_sha; and at origin's: git -C $LANES_REPO log -1 -p refs/remotes/origin/$LANES_BRANCH -- $CP_WHOLE_REL" + note "every later write from this checkout refuses (S3's unpushed row) until it is published — LANES_LANE= $SELF commit \"\", the publish act — or archived (S6.3) and \`git -C $LANES_REPO reset --keep origin/$LANES_BRANCH\` run." + die "the push was refused because origin/$LANES_BRANCH moved, and origin's copy of $CP_WHOLE_REL is no longer the one this whole-file write started from, so it does not replay over that change (Amendment 23 S4.4); the commit is local and kept on HEAD: $cp_sha $msg" 6 + fi + if [ -z "$cpw_head" ] || ! git -C "$LANES_REPO" merge-base --is-ancestor "$cp_sha" "$cpw_head" 2>/dev/null; then + cp_not_head "$cp_sha" "$msg" "before or while its push was in flight" + fi + note "HEAD is $cpw_head, not this write's commit $cp_sha: another commit was made on top of it in this checkout before or while its push was in flight (609, 1b's 343). Nothing is reset, so both are kept." + note "look at both: git -C $LANES_REPO log --oneline $cp_sha^..HEAD; and at origin's: git -C $LANES_REPO log -1 -p refs/remotes/origin/$LANES_BRANCH -- $CP_WHOLE_REL" + note "every later write from this checkout refuses (S3's unpushed row) until they are published — LANES_LANE= $SELF commit \"\", the publish act — or archived (S6.3)." + die "the push was refused because origin/$LANES_BRANCH moved, and origin's copy of $CP_WHOLE_REL is no longer the one this whole-file write started from, so it does not replay over that change (Amendment 23 S4.4); this write's commit and the one on top of it are local and kept, with HEAD at $cpw_head: $cp_sha $msg" 6 + fi + fi # A REFUSAL WITH ORIGIN UNMOVED IS NOT A LOST RACE: the replay would send the # same commit onto the same tip and meet the same refusal six times (a hook, # a permission, a protected branch). git's own words are above; the commit @@ -3297,9 +3420,12 @@ cp_replay() { # # the HEAD check, so "kept on HEAD" is said only of a commit HEAD is. Origin # was just fetched (`cp_confirm`), and a file its base had and origin lacks # means origin moved, so the origin-unmoved refusal can never be this one. + # Kept only where a replay could not follow the file (slice 2a's A46, + # `whf_keep_if_gone`): a handoff moved with its row's cell is replayed. if [ -n "$CP_KEEP_IF_GONE" ] && git -C "$LANES_REPO" cat-file -e "$1^:$CP_KEEP_IF_GONE" 2>/dev/null && - ! git -C "$LANES_REPO" cat-file -e "refs/remotes/origin/$LANES_BRANCH:$CP_KEEP_IF_GONE" 2>/dev/null; then + ! git -C "$LANES_REPO" cat-file -e "refs/remotes/origin/$LANES_BRANCH:$CP_KEEP_IF_GONE" 2>/dev/null && + whf_keep_if_gone; then note "origin/$LANES_BRANCH no longer has $CP_KEEP_IF_GONE, which this write edits in place and its base had: another writer deleted or moved it while this write's push was in flight." note "look at it: git -C $LANES_REPO show $1; and at origin's: git -C $LANES_REPO log -1 --stat refs/remotes/origin/$LANES_BRANCH -- $CP_KEEP_IF_GONE" note "every later write from this checkout refuses (S3's unpushed row) until it is published — LANES_LANE= $SELF commit \"\", the publish act — or archived (S6.3) and \`git -C $LANES_REPO reset --keep origin/$LANES_BRANCH\` run." @@ -6427,6 +6553,19 @@ handoff_of_lane() { row_cell "$hl_row" 7 | sed 's/^ *//; s/ *$//' } +# THE SAME CELL, AND WHETHER IT WAS READ (rv2a's pre-review of slice 2a, NITs +# 1 and 3), for `write-handoff` and `handoff-path`: 0 with the row's handoff +# cell on stdout (empty where the row names none); 8 where the register WAS +# read and has no row for the lane; 1 where it could not be read. The read +# above answers a failed read as an empty cell, which its callers take for "no +# handoff"; a read that failed is never that (R22). +handoff_cell_of() { # + hcl_reg="$(register_text_checked 2>/dev/null)" || return 1 + hcl_row="$(printf '%s\n' "$hcl_reg" | awk -v lane="$1" "$ROW_AWK")" || return 1 + [ -n "$hcl_row" ] || return 8 + row_cell "$hcl_row" 7 | sed 's/^ *//; s/ *$//' +} + short_ws() { printf '%s' "${1%%.*}" | tr 'A-Z' 'a-z'; } # Column 3 of the row is ` / / `. A lane recorded on @@ -13300,6 +13439,140 @@ lane_restart_put() { # +# [ ]` +# +# `publish-handoff`'s act, FACTORED OUT WHOLE, so the two acts that replace a +# lane's handoff — `publish-handoff`, which commits nothing, and +# `write-handoff`, which publishes under S3 and S4 — fence it and write its +# bytes with one implementation. In order, and every refusal is a `die` that +# says nothing was published: +# 1. the target's links are followed to the physical file (40 at most), its +# directory resolved with `cd -P`, and the file must be readable (1); +# 2. the lane's restart intent is read (1 where it cannot be, 2 where it is +# malformed). With an operation, the five fences must be the intent's own +# — operation, generation, attempt, state, transcript — or this writer +# lost ownership (7), and the intent's bound handoff must be this file +# (2; `write-handoff`'s 7, HPA_FENCE_RC). With none, an active restart +# owns the file (7); +# 3. the file's digest must still be the prior digest its caller read (7); +# 4. the bytes. THEY GO #212's WAY, which is this act's own (`rename-lane`'s +# stamp says it copied it from here): an exclusive temporary BESIDE the +# target, in its physical directory, so the one `mv -f` is a rename and +# never a copy across a mount; `cp -p` first, so the handoff keeps its +# mode; then the bytes; then the rename. A reader sees the handoff whole +# before or whole after, a full disk leaves it as it was, and a crash +# leaves `.resume.XXXXXX`, which no reader of `*.md` takes for a +# handoff. `pbh_tmp` is the name `cleanup` removes on every handled exit, +# and, #212's one refinement, it is CLEARED once the rename has landed: +# the name is no longer this process's to remove. +# +# RUN UNDER A LOCK THE CALLER HOLDS — the workstation's sidecar lock, or the +# checkout's where the opt-in is off (`sidecar_lock_dir`) — which the exit trap +# releases on every refusal. It sets HPA_TARGET, the file it published. +# +# FOUR MORE SETTINGS ARE `write-handoff`'s ALONE, each off unless it sets it, +# so `publish-handoff` is the act it always was: +# HPA_CHECKOUT the checkout the write goes through: the bound path also +# matches where its TRANSLATION into that checkout is the target +# (S2), so an intent an older `lane-handoff` bound to W's +# spelling still fences the lane's own copy (2a's "a /ctx in +# flight across the install"); +# HPA_EXPECTED the digest a restart bound: the prior digest must be it, or +# the handoff changed under the restart (HPA_FENCE_RC, +# `lane-start`'s refusal), and HPA_EXPECTED_FOR names what was +# owned; +# HPA_RESERVE the stamped file's digest, recorded in the intent as its +# prepared digest BEFORE the bytes (`handoff_restart_reserve`); +# HPA_FENCE_RC the code of the two restart fences that are 2 here — the +# bound file, the pre-image: 7 for `write-handoff` (lane +# openRepoTools-3's review of #312, 603), whose 2 is S3's +# refusal and so is never a fence's, as act2-plan §2.7 needs: +# under an active restart only a fence's code keeps +# `lane-start`'s die, and S3's 2 is a note. Unset, 2, +# `publish-handoff`'s documented code. +HPA_TARGET=""; HPA_CHECKOUT=""; HPA_EXPECTED=""; HPA_EXPECTED_FOR=""; HPA_RESERVE=""; HPA_FENCE_RC="" +# The intent's bound handoff is this target — the same file, never a spelling. +handoff_bound_is() { # + [ -n "$1" ] && [ "$1" != none ] || return 1 + [ "$1" -ef "$2" ] && return 0 + [ -n "$HPA_CHECKOUT" ] || return 1 + hbi_t="$(handoff_in_repo "$HPA_CHECKOUT" "$1")" || return 1 + [ "$hbi_t" -ef "$2" ] +} +# THE STAMP'S RESERVATION, `lane-start`'s (`:4436-4449`), MADE IN-PROCESS: the +# intent records the stamped file's digest as its PREPARED digest before the +# stamp's bytes are published, so an interruption on either side of the rename +# stays retryable — a retry finds the file at the bound digest or at the +# prepared one, and both are this operation's. In-process because it runs +# under the lock `handoff_publish_atomic` runs under, with the fence already +# checked there: a child `set-restart-intent` would wait for that lock, which +# is the checkout's own where the opt-in is off. Every other field is kept, as +# `set-restart-intent` keeps them for a transition of the same operation. +handoff_restart_reserve() { # + hrr_f="$(printf '%s\n' "$2" | awk -F'\t' '$1=="file" {print $2; exit}')" + [ -n "$hrr_f" ] && [ -f "$hrr_f" ] || + die "the restart intent this write is fenced on could not be found again to reserve its stamp; nothing was published" 1 + hrr_dig="$HPA_EXPECTED"; [ -n "$hrr_dig" ] || hrr_dig="$(lane_restart_sidecar_field "$hrr_f" digest)" + lane_restart_put "${hrr_f%/*}" "$1" \ + "$(lane_restart_sidecar_field "$hrr_f" state)" "$(lane_restart_sidecar_field "$hrr_f" generation)" \ + "$(lane_restart_sidecar_field "$hrr_f" operation)" "$(lane_restart_sidecar_field "$hrr_f" mode)" \ + "$(lane_restart_sidecar_field "$hrr_f" agent)" "$(lane_restart_sidecar_field "$hrr_f" profile)" \ + "$(lane_restart_sidecar_field "$hrr_f" dir)" "$(lane_restart_sidecar_field "$hrr_f" pane)" \ + "$(lane_restart_sidecar_field "$hrr_f" window)" "$(lane_restart_sidecar_field "$hrr_f" handoff)" \ + "$hrr_dig" "$(lane_restart_sidecar_field "$hrr_f" old_transcript)" \ + "$(lane_restart_sidecar_field "$hrr_f" new_transcript)" "$(lane_restart_sidecar_field "$hrr_f" attempt)" \ + "owned handoff stamp prepared" "$(lane_restart_sidecar_field "$hrr_f" created)" "$3" || + die "this write could not reserve its handoff stamp in $hrr_f (the shell's own error, if any, is above); nothing was published" 1 +} +handoff_publish_atomic() { # [ ] + hpa_lane="$1"; hpa_target="$2"; hpa_source="$3"; hpa_digest="$4" + hpa_op="${5-}"; hpa_gen="${6-}"; hpa_attempt="${7-}"; hpa_state="${8-}"; hpa_sid="${9-}" + HPA_TARGET="" + hpa_links=0 + while [ -L "$hpa_target" ]; do + hpa_links=$((hpa_links+1)); [ "$hpa_links" -lt 40 ] || die "the handoff link chain cannot be resolved" 2 + hpa_link="$(readlink "$hpa_target")" || die "the handoff link could not be read" 1 + case "$hpa_link" in /*) hpa_target="$hpa_link" ;; *) hpa_target="$(dirname -- "$hpa_target")/$hpa_link" ;; esac + done + hpa_parent="$( CDPATH=''; cd -P -- "$(dirname -- "$hpa_target")" && pwd -P )" || die "the handoff parent is missing" 1 + hpa_target="$hpa_parent/$(basename -- "$hpa_target")" + [ -f "$hpa_target" ] && [ -r "$hpa_target" ] || die "the handoff target is unreadable" 1 + hpa_irc=0; hpa_intent="$(lane_restart_read "$hpa_lane")" || hpa_irc=$? + case "$hpa_irc" in 0|8) : ;; *) die "restart ownership could not be read; no handoff was published" 1 ;; esac + hpa_current="$(printf '%s\n' "$hpa_intent" | awk -F'\t' '$1=="state" {print $2; exit}')" + case "$hpa_current" in UNKNOWN-*|INCOMPLETE) die "restart ownership is malformed; no handoff was published" 2 ;; esac + if [ -n "$hpa_op" ]; then + [ -n "$hpa_gen" ] && [ -n "$hpa_attempt" ] && [ -n "$hpa_state" ] && [ -n "$hpa_sid" ] || + die "operation publication needs generation, attempt, state and transcript fences" 64 + hpa_actual="$(printf '%s\n' "$hpa_intent" | awk -F'\t' ' + $1=="operation" {o=$2} $1=="generation" {g=$2} $1=="attempt" {a=$2} $1=="state" {s=$2} $1=="new_transcript" {t=$2} + END {printf "%s|%s|%s|%s|%s",o,g,a,s,t}')" + [ "$hpa_actual" = "$hpa_op|$hpa_gen|$hpa_attempt|$hpa_state|$hpa_sid" ] || + die "this handoff publisher lost restart ownership; nothing was published" 7 + hpa_bound="$(printf '%s\n' "$hpa_intent" | awk -F'\t' '$1=="handoff" {print $2; exit}')" + handoff_bound_is "$hpa_bound" "$hpa_target" || + die "the publication target is not the operation-bound handoff; nothing was published" "${HPA_FENCE_RC:-2}" + else + case "$hpa_current" in preparing|pending|starting) die "an active restart owns this handoff; nothing was published" 7 ;; esac + fi + [ "$(lane_handoff_digest "$hpa_target")" = "$hpa_digest" ] || die "the handoff changed before publication; nothing was published" 7 + if [ -n "$HPA_EXPECTED" ] && [ "$HPA_EXPECTED" != none ] && [ "$hpa_digest" != "$HPA_EXPECTED" ]; then + die "the restart handoff changed before ${HPA_EXPECTED_FOR:-its owned write}: $hpa_target digests $hpa_digest, and the restart bound $HPA_EXPECTED. Nothing was published." "${HPA_FENCE_RC:-2}" + fi + [ -z "$HPA_RESERVE" ] || handoff_restart_reserve "$hpa_lane" "$hpa_intent" "$HPA_RESERVE" + pbh_tmp="$(mktemp "$hpa_target.resume.XXXXXX")" || { pbh_tmp=""; die "the publication temporary file could not be made" 1; } + if ! cp -p -- "$hpa_target" "$pbh_tmp" || ! cat -- "$hpa_source" > "$pbh_tmp" || ! mv -f -- "$pbh_tmp" "$hpa_target"; then + rm -f -- "$pbh_tmp"; pbh_tmp="" + die "the handoff could not be published atomically" 1 + fi + pbh_tmp="" + HPA_TARGET="$hpa_target" + return 0 +} + # ------------------- AMENDMENT 19(c): THE SWEEP, ONE COMMIT OR NONE --------- # # `lane-end --retire-dormant ` is the word a person types, the dry run @@ -15974,6 +16247,11 @@ write_lane_of() { # set-row-state|replace-in-row|append-session-id) [ -n "${1-}" ] || return 1 printf '%s\n' "$1" ;; + # A LANE'S HANDOFF, WRITTEN AND COMMITTED (slice 2a): the lane is `$1`, and a + # flag where the lane should be is the arm's own usage refusal (64). + write-handoff) + case "${1-}" in ''|-*) return 1 ;; esac + printf '%s\n' "$1" ;; request-handoff) # THE ARM'S OWN PARSE (the review of #282, NIT 1): `--` ends the options, # and every word after it is the `--force` why — so a why that reads @@ -16118,8 +16396,9 @@ ws_pending_refuse() { # # refused, "expected exactly 1 row", leaving the clone. So where the lane has # no checkout yet and the verb's arm needs exactly one row of that lane (the # four routed `row_line` callers; `rename-lane` goes through its OLD name, -# whose row it needs), a lane with no row in the published register refuses -# first, and nothing is cloned. +# whose row it needs; `write-handoff`, slice 2a, writes the handoff the row +# names, so a lane with no row has none), a lane with no row in the +# published register refuses first, and nothing is cloned. # * AND THE WRITES THAT NAME A LANE WITH NO ROW (lane openRepoTools-3's # re-check of #282, R4-1, for slice 1b): `log`, `commit`, `request-handoff` # and `append-line` take a lane with no row — an archived lane keeps its @@ -16155,7 +16434,7 @@ lrg_known() { # <1 = its published log answers too> — 0 kno } lane_row_gate() { # case "$cmd" in - set-row-state|replace-in-row|append-session-id|rename-lane) lrg_log=0 ;; + set-row-state|replace-in-row|append-session-id|rename-lane|write-handoff) lrg_log=0 ;; log|commit|request-handoff|append-line|claim|release|retire-rows|archive-rows|migrate-state-cells) lrg_log=1 ;; *) return 0 ;; esac @@ -16169,7 +16448,9 @@ lane_row_gate() { # if [ "$lrg_log" = 1 ]; then die "lane $1 has no row and no object log on origin/$LANES_BRANCH (fetched just now into W), so no lane of that name was ever started or archived, and \`$cmd\` would write for it: its checkout $2/ was not cloned (Amendment 23 S1, S2). Nothing was written. Check the name — \`$SELF lanes\` lists every lane, and \`$SELF lanes --closed\` the closed ones — or start the lane first (lane-start; its \`add-row\` makes the row)." 2 fi - die "lane $1 has no row in the register on origin/$LANES_BRANCH (fetched just now into W), and \`$cmd\` edits that lane's row: there is nothing for it to edit, so its checkout $2/ was not cloned (Amendment 23 S1, S2). Nothing was written. Check the name — \`$SELF lanes\` lists every lane — or add the row first (\`add-row\`)." 2 + lrg_needs="edits that lane's row: there is nothing for it to edit" + [ "$cmd" != write-handoff ] || lrg_needs="writes the handoff that lane's row names: there is none to write" + die "lane $1 has no row in the register on origin/$LANES_BRANCH (fetched just now into W), and \`$cmd\` $lrg_needs, so its checkout $2/ was not cloned (Amendment 23 S1, S2). Nothing was written. Check the name — \`$SELF lanes\` lists every lane — or add the row first (\`add-row\`)." 2 } # S2, IN ITS ORDER, for a write: (1) an explicit `LANES_REPO` keeps its meaning @@ -16258,38 +16539,225 @@ lane_checkout_enter() { # # (`handoffs//`) is joined to the lane's checkout; a `~/…` or # absolute path whose physical parent is under W becomes the same path under # the lane's checkout; anything else is another repository's (Amendment 8(d)), -# published and never committed by these tools. The row's cell keeps its +# which `write-handoff` commits and pushes in its own repository (604) and +# `publish-handoff` never commits. The row's cell keeps its # spelling: this answers where the file is WRITTEN. -ws_relative() { # — 0 and its path relative to W; 1 and the real path - wsr_in="$1" - case "$wsr_in" in - '~') wsr_in="$HOME" ;; - '~/'*) wsr_in="$HOME/${wsr_in#'~/'}" ;; - /*) : ;; - *) printf '%s\n' "$wsr_in"; return 0 ;; - esac - wsr_n=0 - while [ -L "$wsr_in" ] && [ "$wsr_n" -lt 40 ]; do - wsr_l="$(readlink "$wsr_in")" || break - case "$wsr_l" in /*) wsr_in="$wsr_l" ;; *) wsr_in="$(dirname -- "$wsr_in")/$wsr_l" ;; esac - wsr_n=$((wsr_n + 1)) +# A path's physical spelling: `~` expanded, the leaf's links followed (40 at +# most), its directory resolved with `cd -P`. 1, and the path as far as it was +# followed, where that directory cannot be entered. +path_physical() { # + pp_in="$1" + case "$pp_in" in '~') pp_in="$HOME" ;; '~/'*) pp_in="$HOME/${pp_in#'~/'}" ;; esac + pp_n=0 + while [ -L "$pp_in" ] && [ "$pp_n" -lt 40 ]; do + pp_l="$(readlink "$pp_in")" || break + case "$pp_l" in /*) pp_in="$pp_l" ;; *) pp_in="$(dirname -- "$pp_in")/$pp_l" ;; esac + pp_n=$((pp_n + 1)) done - wsr_par="$(cd -P -- "$(dirname -- "$wsr_in")" 2>/dev/null && pwd -P)" || { printf '%s\n' "$wsr_in"; return 1; } - wsr_real="$wsr_par/$(basename -- "$wsr_in")" + pp_par="$(cd -P -- "$(dirname -- "$pp_in")" 2>/dev/null && pwd -P)" || { printf '%s\n' "$pp_in"; return 1; } + printf '%s/%s\n' "$pp_par" "$(basename -- "$pp_in")" +} +ws_relative() { # — 0 and its path relative to W; 1 and the real path + case "$1" in '~'|'~/'*|/*) : ;; *) printf '%s\n' "$1"; return 0 ;; esac + wsr_real="$(path_physical "$1")" || { printf '%s\n' "$wsr_real"; return 1; } case "$wsr_real" in "$LANES_WS_REAL"/*) printf '%s\n' "${wsr_real#"$LANES_WS_REAL"/}"; return 0 ;; esac printf '%s\n' "$wsr_real" return 1 } +# THE SAME TRANSLATION INTO WHICHEVER CHECKOUT THE WRITE GOES THROUGH (slice +# 2a): the lane's own under the opt-in, an explicit `LANES_REPO` (the seam), or +# W where the opt-in is off — and `write-handoff`, `handoff-path` and +# `set-restart-intent` translate into it. W's rule needs W: where none was +# resolved, an absolute path is only ever the checkout's own or another +# repository's (an empty W would read every absolute path as inside it). +handoff_in_repo() { # — 0 its path in that checkout; 3 another repository's + hir_c="$1"; hir_rel=""; hir_rc=0 + case "$2" in + '~'|'~/'*|/*) + if [ -n "$LANES_WS_REAL" ]; then + hir_rel="$(ws_relative "$2")" || hir_rc=$? + else + hir_rel="$(path_physical "$2")" || : + hir_rc=1 + fi ;; + *) hir_rel="$2" ;; + esac + if [ "$hir_rc" = 0 ]; then printf '%s/%s\n' "$hir_c" "$hir_rel"; return 0; fi + if path_under "$hir_rel" "$hir_c"; then printf '%s\n' "$hir_rel"; return 0; fi + printf '%s\n' "$hir_rel" + return 3 +} handoff_in_checkout() { # — 0 its path in the lane's checkout; 3 another repository's hic_p="$(lane_checkout_path "$1")" || return 1 - hic_rel=""; hic_rc=0 - hic_rel="$(ws_relative "$2")" || hic_rc=$? - if [ "$hic_rc" = 0 ]; then printf '%s/%s\n' "$hic_p" "$hic_rel"; return 0; fi - if path_under "$hic_rel" "$hic_p"; then printf '%s\n' "$hic_rel"; return 0; fi - printf '%s\n' "$hic_rel" - return 3 + handoff_in_repo "$hic_p" "$2" +} +# A LANE'S HANDOFF IS A FILE OF ITS OWN CHECKOUT, NEVER OF W OR OF ANOTHER +# CHECKOUT BESIDE IT (lane openRepoTools-3's review of #312, 601; its +# correction, #312 6368669583; finding 723 of its review of #316). The three +# verbs that translate a handoff — `write-handoff`, `handoff-path` and +# `set-restart-intent --handoff` — ask this ONE question of the path +# `handoff_in_repo` gave them, so whatever one refuses the others refuse: a +# path that is physically in W where W is not the checkout (S5, the readers' +# copy), or in a checkout beside W that is not this one — a sibling lane's +# `-lanes//` (S1), a per-profile `-/` clone. The PHYSICAL +# path is asked about, so any spelling — absolute, or climbing with `..` — +# lands where it lands; and it is asked BY PATH, asking git nothing, so a +# clone git cannot read is refused the same. In `write-handoff` alone, a +# sibling's `handoffs/` file was written there as another repository's, +# uncommitted, and that lane's next `--stamp` refused at S3 step 4 over a +# change it never made; while `lane-start` still delivered the sibling's top +# block as this lane's first prompt, because `handoff-path` printed its path. +# Sets HBW_WHY: the words that follow "is" in each verb's refusal. +HBW_WHY="" +handoff_beside_w() { # — 0 refused, HBW_WHY set; 1 not + HBW_WHY="" + [ -n "$LANES_WS_REAL" ] || return 1 + hbw_c="$(cd -P -- "$1" 2>/dev/null && pwd -P)" || hbw_c="$1" + hbw_p="$(path_physical "$2")" || hbw_p="$2" + if [ "$hbw_c" != "$LANES_WS_REAL" ] && path_under "$hbw_p" "$LANES_WS_REAL"; then + HBW_WHY="$hbw_p, inside the workspace checkout $LANES_WS_REAL, the readers' copy, where no tool writes (Amendment 23 S5), not in the checkout $hbw_c this lane's writes go through" + return 0 + fi + path_under "$hbw_p" "$hbw_c" && return 1 + case "$hbw_p" in + "$LANES_WS_REAL"-*/*) + HBW_WHY="$hbw_p, in another checkout of the register beside W ($LANES_WS_REAL), not in the checkout $hbw_c this lane's writes go through: a lane's handoff is written in its own checkout only (Amendment 23 S1, S2)" + return 0 ;; + esac + return 1 +} +# NO GLOB CHARACTER IN A HANDOFF'S PATH IN ITS CHECKOUT (lane +# openRepoTools-3's review of #312, 607; A59), ASKED BY ALL THREE VERBS (991 +# of its first review of #311, under 723; A60). `commit_publish` stages and +# commits its pathspec as git reads one, a pattern, so `handoffs//x[1].md` +# names `handoffs//x1.md` too, a file the write never wrote, which its +# commit could carry under its subject. No lane's handoff is named so (a lane +# name has none of `*?[`), so `write-handoff` refuses it; and where +# `handoff-path` printed that path, a restart was bound to a file the write +# then refused. So `write-handoff`, `handoff-path` and `set-restart-intent +# --handoff` ask this ONE question, as they ask 601's: of the PHYSICAL path +# (`path_physical`, as `write-handoff`'s scope check reads it), relative to +# the checkout. A path outside the checkout is not this rule's: another +# repository's handoff is Amendment 8(d)'s, committed by its own top level's +# pathspec (A57). Sets HRG_REL: the path in the checkout. +HRG_REL="" +handoff_rel_globbed() { # — 0 refused, HRG_REL set; 1 not + HRG_REL="" + hrg_p="$(path_physical "$2")" || hrg_p="$2" + path_under "$hrg_p" "$1" || return 1 + HRG_REL="${hrg_p#"$1"/}" + case "$HRG_REL" in *'*'*|*'?'*|*'['*) return 0 ;; esac + return 1 +} +# THE CHECKOUT A LANE'S HANDOFF IS WRITTEN THROUGH, for the two acts that answer +# it without writing (`handoff-path`, `set-restart-intent`): S1's path under the +# opt-in with no explicit `LANES_REPO` — made first with `create` 1, or checked +# where it already exists — and otherwise `LANES_REPO`'s top level, as every +# write goes through it there. Sets HCO_PATH; dies as `lane-checkout` does. +HCO_PATH="" +handoff_checkout_of() { # + HCO_PATH="" + if [ "$LANES_ROUTE_ON" = 1 ] && [ "$LANES_REPO_GIVEN" != 1 ] && [ -z "$LANES_ROUTED" ]; then + lanes_home_check + if [ "${2:-0}" = 1 ]; then + lane_checkout_ensure "$1" + HCO_PATH="$LCE_PATH" + return 0 + fi + HCO_PATH="$(lane_checkout_path "$1")" || die "$(lanes_workspace_why)" 1 + if { [ -e "$HCO_PATH" ] || [ -L "$HCO_PATH" ]; } && + ! { [ -d "$HCO_PATH" ] && [ ! -L "$HCO_PATH" ] && [ -z "$(ls -A -- "$HCO_PATH" 2>/dev/null)" ]; }; then + lane_checkout_verify "$HCO_PATH" || lane_checkout_refuse "$HCO_PATH" + fi + return 0 + fi + HCO_PATH="$(cd -P -- "$LANES_REPO" 2>/dev/null && pwd -P)" || + die "the checkout $LANES_REPO cannot be entered, so where this lane's handoff is written cannot be said." 1 + hco_top="$(git -C "$HCO_PATH" rev-parse --show-toplevel 2>/dev/null || :)" + hco_top="$( [ -n "$hco_top" ] && cd -P -- "$hco_top" 2>/dev/null && pwd -P || :)" + [ -z "$hco_top" ] || HCO_PATH="$hco_top" + return 0 +} +# A `--stamp` THAT WRITES NOTHING STILL SAYS ITS LINE (S3's `lane-start` note): +# a stamp that is owed is a stamp somebody writes by hand. +whf_stamp_said() { + [ "${whf_mode:-}" != stamp ] || note "the stamp line it would have written under the handoff's header block: ${whf_arg:-}" + return 0 +} +# A FILE `write-handoff` MAY REPLACE, relative to a clone of the register's top +# level (rv2a's pre-review of slice 2a, SHOULD 1): one under `handoffs/`, and +# never the register, an object log or anything else under `lanes/`. +whf_in_scope() { # — 0 in scope; 1 not + case "$1" in handoffs/?*) : ;; *) return 1 ;; esac + case "$1" in "$LANES_PATH"|lanes/*|*/.|*/..) return 1 ;; esac + if [ -n "$LANES_PREFIX" ]; then case "$1" in "$LANES_PREFIX"*) return 1 ;; esac; fi + return 0 +} +# A REPOSITORY-RELATIVE SPELLING NAMES A FILE OF THE CHECKOUT IT IS JOINED TO +# (Amendment 23 S2; lane openRepoTools-3's review of #312, 602), so one whose +# `.` or `..` components climb out of that checkout names no other +# repository's handoff: `write-handoff` and `handoff-path` refuse it (2), as +# `set-restart-intent --handoff` refuses the same spelling (64). It climbs out +# by its TEXT (a `..` above the checkout's top level, whether or not the +# directories it climbs through exist yet), or, where the checkout is given +# and the spelling resolves, by the FILE it names (a `..` after a link out). +# A spelling with no such component is not this rule's: a link that carries +# it out stays Amendment 8(d)'s (m2a's D08), and one that climbs within the +# checkout is the checkout's own file. THE SPELLING IS ASKED FIRST (A55), in +# both verbs, before where the path lands (S5, 601's `handoff_beside_w`): a +# relative spelling that climbs out is refused for what it is, wherever it +# lands, as `set-restart-intent`'s 64 already was, and `handoff-path` +# refuses it before `--create` clones anything. +handoff_rel_climbs_out() { # — 0 it climbs out; 1 not + case "$2" in '~'|'~/'*|/*) return 1 ;; esac + case "/$2/" in */../*|*/./*) : ;; *) return 1 ;; esac + hrco_d=0; hrco_rest="$2" + while [ -n "$hrco_rest" ]; do + case "$hrco_rest" in + */*) hrco_part="${hrco_rest%%/*}"; hrco_rest="${hrco_rest#*/}" ;; + *) hrco_part="$hrco_rest"; hrco_rest="" ;; + esac + case "$hrco_part" in + ''|.) : ;; + ..) hrco_d=$((hrco_d - 1)); [ "$hrco_d" -ge 0 ] || return 0 ;; + *) hrco_d=$((hrco_d + 1)) ;; + esac + done + [ -n "$1" ] || return 1 + hrco_p="$(path_physical "$1/$2")" || return 1 + path_under "$hrco_p" "$1" && return 1 + return 0 +} +# A37'S KEEP, ONLY WHERE A REPLAY COULD NOT FOLLOW THE FILE (rv2a-c2's +# re-review of slice 2a, NIT 2; A46). Slice 1b's `cp_replay` asks it before it +# keeps a write whose in-place file origin no longer has, of origin as +# `cp_confirm` just fetched it. 1, replay, only where origin's row for the lane +# names ANOTHER cell than the one this write followed, and that cell is a file +# under `handoffs/` in this checkout that origin has: a peer moved the handoff +# and pointed the cell at its new name, and the re-run reads that row and +# writes the moved file. 0, keep: the row still names that cell; `--target` +# named the file (the re-run names it again); origin has no row for the lane +# (none to re-read: `rename-lane` renames a row with its file); or origin's +# register cannot be read (R22). Read from the fetched ref itself, because +# `register_text_checked` keeps the register it read before the push. +# ANOTHER CELL IS FOLLOWED ONLY TO A FILE THE RE-RUN CAN WRITE (rv2a-c3's +# re-check of slice 2a, NIT 1; A48): an emptied cell (the re-run's 8, "names +# no handoff"), one whose file origin lacks (8, "nothing to write into"), one +# outside `handoffs/` (2) or in another repository is 0 too, so the write is +# kept on HEAD (6) and never dropped with nothing written. +whf_keep_if_gone() { + [ -n "${whf_keep_cell:-}" ] || return 0 + wkg_reg="$(git -C "$LANES_REPO" show "refs/remotes/origin/$LANES_BRANCH:$LANES_PATH" 2>/dev/null)" || return 0 + wkg_row="$(printf '%s\n' "$wkg_reg" | awk -v lane="${lane:-}" "$ROW_AWK")" || return 0 + [ -n "$wkg_row" ] || return 0 + wkg_cell="$(row_cell "$wkg_row" 7 | sed 's/^ *//; s/ *$//')" + [ "$wkg_cell" != "$whf_keep_cell" ] || return 0 + wkg_path="$(handoff_in_repo "$whf_c" "$wkg_cell")" || return 0 + wkg_rel="${wkg_path#"$whf_c"/}" + whf_in_scope "$wkg_rel" || return 0 + ! git -C "$LANES_REPO" cat-file -e "refs/remotes/origin/$LANES_BRANCH:$wkg_rel" 2>/dev/null } # S5 — W IS ONLY EVER FAST-FORWARDED, and `refresh-workspace` is the one act @@ -16381,8 +16849,12 @@ refresh_workspace() { # uncommitted, where nothing will commit it. Each one under `handoffs/` or # `lanes/` is named by path, and where a row's handoff cell names it, by the # lane whose handoff it is and the place in that lane's checkout it belongs. +# THE SCAN WRITES NOTHING IN W (lane openRepoTools-3's review of #316, 732): a +# plain `git status` that finds the index stat-dirty rewrites it under W's +# `index.lock`, a write S5 forbids, which 2b's launch would make twice. So it +# runs with `GIT_OPTIONAL_LOCKS=0`, as `ws_pending_refuse`'s status does. refresh_workspace_strays() { # - rws_list="$(git -C "$1" status --porcelain --untracked-files=all -- handoffs lanes 2>/dev/null || :)" + rws_list="$(GIT_OPTIONAL_LOCKS=0 git -C "$1" status --porcelain --untracked-files=all -- handoffs lanes 2>/dev/null || :)" [ -n "$rws_list" ] || return 0 rws_map="$(awk -F'|' 'substr($0,1,1) == "|" { p1 = index($0, "`"); if (p1 == 0) next @@ -16448,6 +16920,12 @@ fi # of them would come to disagree. The READS are untouched — a read in # front of every launch may not refuse, and one that answers nothing for a # workstation nobody configured is telling the truth. +# `write-handoff` (Amendment 23, slice 2a) IS NOT IN THE LIST, ON PURPOSE +# (lane openRepoTools-3's review of #312, 611; R-A11-11): a swap is never left +# unwritten, so a container with no workstation still gets its handoff written +# and published, and the commit's subject is the caller's, `lane-start`'s and +# `lane-handoff`'s, which spell the workstation themselves. So not EVERY writer +# refuses here: every writer in this list does. case "$cmd" in # `request-handoff` JOINS THE LIST BECAUSE IT WRITES (Amendment 18(c)/(e)). It # reaches `write_event` directly rather than through `log`, so the dispatcher @@ -19498,38 +19976,570 @@ EOF [ -f "$pbh_source" ] && [ -r "$pbh_source" ] && [ -s "$pbh_source" ] || die "the prepared handoff is unreadable or empty" 1 sidecar_lock_use; acquire_lock # the workstation's sidecar lock, and no other (Amendment 23 S2) managed_seam_refuse "$lane" "publish-handoff" - pbh_links=0 - while [ -L "$pbh_target" ]; do - pbh_links=$((pbh_links+1)); [ "$pbh_links" -lt 40 ] || die "the handoff link chain cannot be resolved" 2 - pbh_link="$(readlink "$pbh_target")" || die "the handoff link could not be read" 1 - case "$pbh_link" in /*) pbh_target="$pbh_link" ;; *) pbh_target="$(dirname -- "$pbh_target")/$pbh_link" ;; esac + # THE ACT ITSELF IS `handoff_publish_atomic`'s (Amendment 23 slice 2a), which + # `write-handoff` shares: the same fences, the same words and codes, the same + # bytes. Its contract here is the one every old caller has: the physical + # file on stdout, nothing committed, and the bound path compared as it is. + handoff_publish_atomic "$lane" "$pbh_target" "$pbh_source" "$pbh_digest" \ + "$pbh_op" "$pbh_gen" "$pbh_attempt" "$pbh_state" "$pbh_sid" + release_lock + printf '%s\n' "$HPA_TARGET" + ;; + + # ---------- AMENDMENT 23, SLICE 2A: A LANE'S HANDOFF, WRITTEN UNDER S3-S4 + # + # `write-handoff (--stamp "" | --prepend | + # --whole --expect-digest ) --subject "" + # [--target ] + # [--restart + # [--expected-digest ]]` + # + # The one act that `lane-start`'s Rule 3 stamp, `lane-handoff`'s top block + # and the skill's whole-file handoff call from slices 2b and 3 on, so that no + # caller holds a lock or commits a handoff by hand (plan §2.7; Open question + # 1's reading: the lock's holder is this process). In order: + # 1. THE CHECKOUT'S LOCK, then S3 (`checkout_sync`), run in a subshell so + # that its refusal is this arm's to answer (step 6); then the managed + # seam asked again on what S3 fetched, whatever S3 answered. + # 2. THE TARGET: the row's handoff cell in the published register, read + # after S3's fetch — or `--target`, the old callers' spelling — and + # TRANSLATED into the checkout this write goes through, never followed + # (`handoff_in_repo`, S2). One that still resolves into W, through a + # link, refuses (S5, 2), and so does one in the checkout, or in another + # clone of the register, that is not a file under `handoffs/` (S2: + # never the register or `lanes/`), and one inside a git directory — + # another clone's `.git/`, a bare clone — and any path in W or in a + # checkout beside it (`-lanes//`, `-/`): a lane's + # handoff is written in its own checkout only (601). A relative cell + # or `--target` whose `.` or `..` components climb out of the checkout + # is refused first, for its spelling, wherever it lands (602, A55). + # Where git cannot read a repository on the way up from the target, it + # refuses (1, R22). + # One in ANOTHER REPOSITORY (Amendment 8(d)) is written there, then + # committed and pushed in its own top level (604), and S3's refusal of + # this checkout does not stop it; one in another clone of the + # register not beside W is written and never committed (A57). + # 3. S3 step 4 for the handoff's own path (`cs_own_pending`), and for a + # handoff git does not track yet, which a commit would add and a lost + # race's replay would drop. + # 4. THE READ FOLLOWS THE WRITE: the file in that checkout, and `--stamp`'s + # splice (`lane-start`'s, unchanged: the line after the first blank line + # after line 1), `--prepend`'s (`lane-handoff`'s: the block, `---` and a + # blank line, then the old file) or `--whole`'s prepared file, each + # proven before it lands. + # 5. THE WORKSTATION'S SIDECAR LOCK, inside the checkout's, for this step + # only: `handoff_publish_atomic` — the restart fences (`--restart`, the + # bound path translated), the digests, `--stamp`'s reservation of its + # prepared digest, and the one-rename replace. + # 6. WHERE S3 REFUSED: `--stamp` wrote nothing and says its line; `--prepend` + # and `--whole` have written the handoff, commit nothing (R-A11-11: a + # swap is never left unwritten), and S3's own words are above. Exit 2. + # 7. `commit_publish`, by pathspec, with `Lane: ` as its trailer: the + # push, the read-back, and a refused push replays the whole verb on + # origin's new tip — a `--whole` only where origin's copy is still the + # one it read (S4.4), and a `--stamp` or `--prepend` only where origin + # still has the handoff it edits in place (A37, `CP_KEEP_IF_GONE`), or + # where origin's row now names another cell, a file under `handoffs/` + # that origin has, which the re-run writes (A46, A48); otherwise the + # commit is kept on HEAD and named (6). + # STDOUT: `` once the file is written (`HANDOFF_OUT`). + # 0 pushed — another repository's handoff in its own repository (8(d), + # 604) — or nothing to commit (a file in no git work tree, S3's refusal + # of this checkout or not; LANES_NO_GIT) + # 1 environment: a checkout that cannot be entered, an unreadable file, + # a register that could not be read (R22: never 8), a repository on the + # way up from the target that git could not read (R22) + # 2 refused: S3 (step 6), S5, a target that is not under handoffs/ (S2) or + # is inside a git directory, one in a checkout beside W that is not this + # one, a relative spelling that climbs out of the checkout (S2), a + # glob character in the handoff's path (607), a proof, a restart intent + # that is malformed — never a restart fence (603) + # 3 pushed, not confirmed · 4 a lock · 6 kept on HEAD, named — and an + # 8(d) handoff, written, whose commit or push in its own repository + # failed (the command that finishes it said) + # 7 a restart fence — the five fences, an active restart, and the + # restart's bound file and pre-image, which `publish-handoff` answers 2 + # (603) — or the handoff changed under the write + # 8 no handoff to write: no row for the lane (with `--target` too), the + # row names none, or its file is not there + # 64 usage + write-handoff) + whf_use="usage: write-handoff (--stamp \"\" | --prepend | --whole --expect-digest ) --subject \"\" [--target ] [--restart [--expected-digest ]]" + whf_lane="${1-}" + case "$whf_lane" in ''|-*) die "$whf_use" 64 ;; esac + shift + whf_mode=""; whf_arg=""; whf_expect=""; whf_subject=""; whf_target="" + whf_restart=0; whf_rop=""; whf_rgen=""; whf_ratt=""; whf_rstate=""; whf_rsid=""; whf_rexp="" + while [ "$#" -gt 0 ]; do + case "$1" in + --stamp|--prepend|--whole) + [ -z "$whf_mode" ] || die "write-handoff takes ONE of --stamp, --prepend and --whole. $whf_use" 64 + [ "$#" -ge 2 ] && [ -n "${2-}" ] || die "$1 needs a value. $whf_use" 64 + whf_mode="${1#--}"; whf_arg="$2"; shift 2 ;; + --expect-digest|--subject|--target|--expected-digest) + [ "$#" -ge 2 ] && [ -n "${2-}" ] || die "$1 needs a value. $whf_use" 64 + case "$1" in + --expect-digest) whf_expect="$2" ;; + --subject) whf_subject="$2" ;; + --target) whf_target="$2" ;; + --expected-digest) whf_rexp="$2" ;; + esac + shift 2 ;; + --restart) + [ "$#" -ge 6 ] || die "--restart takes five values: . $whf_use" 64 + whf_restart=1; whf_rop="$2"; whf_rgen="$3"; whf_ratt="$4"; whf_rstate="$5"; whf_rsid="$6"; shift 6 ;; + *) die "unknown write-handoff flag $1. $whf_use" 64 ;; + esac done - pbh_parent="$( CDPATH=''; cd -P -- "$(dirname -- "$pbh_target")" && pwd -P )" || die "the handoff parent is missing" 1 - pbh_target="$pbh_parent/$(basename -- "$pbh_target")" - [ -f "$pbh_target" ] && [ -r "$pbh_target" ] || die "the handoff target is unreadable" 1 - pbh_irc=0; pbh_intent="$(lane_restart_read "$lane")" || pbh_irc=$? - case "$pbh_irc" in 0|8) : ;; *) release_lock; die "restart ownership could not be read; no handoff was published" 1 ;; esac - pbh_current="$(printf '%s\n' "$pbh_intent" | awk -F'\t' '$1=="state" {print $2; exit}')" - case "$pbh_current" in UNKNOWN-*|INCOMPLETE) release_lock; die "restart ownership is malformed; no handoff was published" 2 ;; esac - if [ -n "$pbh_op" ]; then - [ -n "$pbh_gen" ] && [ -n "$pbh_attempt" ] && [ -n "$pbh_state" ] && [ -n "$pbh_sid" ] || { release_lock; die "operation publication needs generation, attempt, state and transcript fences" 64; } - pbh_actual="$(printf '%s\n' "$pbh_intent" | awk -F'\t' ' - $1=="operation" {o=$2} $1=="generation" {g=$2} $1=="attempt" {a=$2} $1=="state" {s=$2} $1=="new_transcript" {t=$2} - END {printf "%s|%s|%s|%s|%s",o,g,a,s,t}')" - [ "$pbh_actual" = "$pbh_op|$pbh_gen|$pbh_attempt|$pbh_state|$pbh_sid" ] || { release_lock; die "this handoff publisher lost restart ownership; nothing was published" 7; } - pbh_bound="$(printf '%s\n' "$pbh_intent" | awk -F'\t' '$1=="handoff" {print $2; exit}')" - [ -n "$pbh_bound" ] && [ "$pbh_bound" != none ] && [ "$pbh_bound" -ef "$pbh_target" ] || - { release_lock; die "the publication target is not the operation-bound handoff; nothing was published" 2; } - else - case "$pbh_current" in preparing|pending|starting) release_lock; die "an active restart owns this handoff; nothing was published" 7 ;; esac + [ -n "$whf_mode" ] || die "write-handoff needs one of --stamp, --prepend and --whole. $whf_use" 64 + [ -n "$whf_subject" ] || die "write-handoff needs --subject, the commit's subject line. $whf_use" 64 + for whf_v in "$whf_subject" "$whf_target" "$whf_rop" "$whf_rgen" "$whf_ratt" "$whf_rstate" "$whf_rsid"; do + lane_restart_value_ok "$whf_v" || + die "write-handoff's subject, target and restart fences are each one line with no control character. Nothing was written." 64 + done + if [ "$whf_mode" = stamp ] && ! lane_restart_value_ok "$whf_arg"; then + die "--stamp is ONE line with no control character: the line it puts under the handoff's header block. Nothing was written." 64 fi - [ "$(lane_handoff_digest "$pbh_target")" = "$pbh_digest" ] || { release_lock; die "the handoff changed before publication; nothing was published" 7; } - pbh_tmp="$(mktemp "$pbh_target.resume.XXXXXX")" || { release_lock; die "the publication temporary file could not be made" 1; } - if ! cp -p -- "$pbh_target" "$pbh_tmp" || ! cat -- "$pbh_source" > "$pbh_tmp" || ! mv -f -- "$pbh_tmp" "$pbh_target"; then - rm -f -- "$pbh_tmp"; release_lock; die "the handoff could not be published atomically" 1 + if [ "$whf_mode" = whole ]; then + [ "${#whf_expect}" = 64 ] || die "--whole requires --expect-digest, the complete SHA-256 of the handoff its prepared file was made from. $whf_use" 64 + case "$whf_expect" in *[!0-9a-fA-F]*) die "--expect-digest takes a SHA-256" 64 ;; esac + elif [ -n "$whf_expect" ]; then + die "--expect-digest goes with --whole: --stamp and --prepend read the handoff themselves, under the lock. $whf_use" 64 fi - release_lock - printf '%s\n' "$pbh_target" + if [ "$whf_restart" = 1 ]; then + [ -n "$whf_rop" ] && [ -n "$whf_rgen" ] && [ -n "$whf_ratt" ] && [ -n "$whf_rstate" ] && [ -n "$whf_rsid" ] || + die "--restart's five fences each need a value: . $whf_use" 64 + elif [ -n "$whf_rexp" ]; then + die "--expected-digest goes with --restart: it is the digest that restart bound. $whf_use" 64 + fi + if [ -n "$whf_rexp" ] && [ "$whf_rexp" != none ]; then + [ "${#whf_rexp}" = 64 ] || die "--expected-digest takes a SHA-256, or none" 64 + case "$whf_rexp" in *[!0-9a-fA-F]*) die "--expected-digest takes a SHA-256, or none" 64 ;; esac + fi + # A FENCED `--prepend` TAKES `--expected-digest none` ONLY (lane + # openRepoTools-3's review of #312, 605; A58). Fenced on a digest, a push + # that lost its race to a peer's change of the handoff was replayed onto a + # pre-image that was no longer the bound one, 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 here: one that does is usage, + # refused before the lock, and a swap's block is never lost to a replay. + if [ "$whf_mode" = prepend ] && [ -n "$whf_rexp" ] && [ "$whf_rexp" != none ]; then + die "--prepend takes --expected-digest none only: fenced on a digest, a replay after a lost push race would find the handoff changed, refuse, and leave the block in the reflog only (605; lane-handoff binds its prepend at preparing with none). Nothing was written." 64 + fi + if [ "$whf_mode" != stamp ]; then + [ -f "$whf_arg" ] && [ -r "$whf_arg" ] && [ -s "$whf_arg" ] || + die "the $whf_mode file $whf_arg is unreadable or empty. Nothing was written." 1 + fi + if [ "$whf_mode" = prepend ] && [ -n "$(tail -c 1 -- "$whf_arg")" ]; then + die "the block $whf_arg does not end with a newline, so the \`---\` under it would join its last line, and a caller's bytes are never altered. Nothing was written." 64 + fi + check_lane_name "$whf_lane" + lane="$(canon_lane "$whf_lane")" || exit 2 # Amendment 15 + managed_seam_refuse "$lane" "write-handoff" + acquire_lock + handoff_checkout_of "$lane" 0; whf_c="$HCO_PATH" + # ---- 1. S3, answered here. + whf_s3=0; whf_cs="" + whf_cs="$(checkout_sync write-handoff "$lane" write && printf 'ok%s' "$CS_NO_ORIGIN_BRANCH")" || whf_s3=$? + if [ "$whf_s3" = 0 ]; then CS_NO_ORIGIN_BRANCH="${whf_cs##*ok}"; LOG_SYNC_FETCH=yes; fi + # THE SEAM ASKED AGAIN, ON WHAT S3 JUST FETCHED (the pre-review of 1b, + # MUST 1's shape): the ask before the lock read this checkout's + # origin/$LANES_BRANCH as it last stood, and a managed-owner marker that + # landed since would otherwise go unread. WHATEVER S3 ANSWERED (rv2a's + # pre-review of slice 2a, SHOULD 3): S3 can refuse AFTER its fetch — a + # staged entry, a diverged branch — and `--prepend` and `--whole` still + # write there (step 6), so the marker that fetch brought is read before + # them too. Where S3 refused before any fetch, this reads what the first + # ask read. A refusal here writes nothing. + cs_flush + managed_seam_refuse "$lane" "write-handoff" + # ---- 2. the target, translated. THE ROW FIRST, WHATEVER `--target` + # SAYS (rv2a's pre-review of slice 2a, NITs 1 and 3): a lane with no row + # has no handoff to write, and no commit may carry `Lane: ` — through + # the seam and in default mode too, where no `lane_row_gate` asked before + # the lock — and a register that could not be read is 1, never "no + # handoff" (R22). The seam above refuses an unreadable register first (1), + # so that 1 is this read's own only where the register failed between them. + whf_hrc=0; whf_cell="$(handoff_cell_of "$lane")" || whf_hrc=$? + case "$whf_hrc" in + 0) : ;; + 8) whf_stamp_said + die "lane $lane has no row in the register, so it has no handoff to write, and no commit may carry \`Lane: $lane\`. Nothing was written." 8 ;; + *) whf_stamp_said + die "the register could not be read, so whether lane $lane's row names a handoff cannot be said — a read that failed is never 'no handoff' (R22). Nothing was written." 1 ;; + esac + if [ -n "$whf_target" ]; then whf_cell="$whf_target"; fi + if [ -z "$whf_cell" ]; then + whf_stamp_said + die "lane $lane's row names no handoff, so there is none to write. Nothing was written." 8 + fi + whf_other=0; whf_path="$(handoff_in_repo "$whf_c" "$whf_cell")" || whf_other=1 + whf_phys="$(path_physical "$whf_path")" || whf_phys="$whf_path" + if handoff_rel_climbs_out "$whf_c" "$whf_cell"; then + whf_stamp_said + die "lane $lane's handoff $whf_cell is a repository-relative spelling, joined to this write's checkout $whf_c, and its '.' or '..' components climb out of it (to $whf_phys): a relative cell or --target names a file of its checkout, never another repository's (Amendment 23 S2), as set-restart-intent --handoff refuses the same spelling. Nothing was written." 2 + fi + if [ -n "$LANES_WS_REAL" ] && [ "$whf_c" != "$LANES_WS_REAL" ] && path_under "$whf_phys" "$LANES_WS_REAL"; then + whf_stamp_said + die "lane $lane's handoff $whf_path resolves to $whf_phys, inside the workspace checkout $LANES_WS_REAL, the readers' copy, where no tool writes (Amendment 23 S5). Nothing was written. The lane's handoff is a file of its own checkout, $whf_c: make $whf_path one there, then run LANES_LANE=$lane $SELF write-handoff $lane … again." 2 + fi + [ "$whf_other" = 1 ] || path_under "$whf_phys" "$whf_c" || whf_other=1 + whf_rel=""; [ "$whf_other" = 1 ] || whf_rel="${whf_phys#"$whf_c"/}" + # ONLY A HANDOFF (rv2a's pre-review of slice 2a, SHOULD 1). This verb + # replaces a file whole and pushes it, so in this checkout it writes a file + # under `handoffs/` and nothing else: never the register, an object log or + # anything else under `lanes/`, which Rule 9's one-line guard and the logs' + # append-only rule (Amendment 7(b)) protect and this write would bypass + # (Amendment 23 S2 scopes it to `handoffs//`). `whf_rel` is + # the PHYSICAL path's, so a `--target`, a cell that climbs out through + # `..`, and a link out of `handoffs/` are all caught here, before a byte. + if [ "$whf_other" = 0 ] && ! whf_in_scope "$whf_rel"; then + whf_stamp_said + die "lane $lane's handoff target $whf_path is $whf_rel in the checkout $whf_c, which is not a file under handoffs/: write-handoff writes a lane's handoff and nothing else, never the register ($LANES_PATH) or anything under lanes/ (Amendment 23 S2). Nothing was written." 2 + fi + # NO GLOB CHARACTER IN THE HANDOFF'S PATH (lane openRepoTools-3's review + # of #312, 607; A59): the question `handoff-path` and `set-restart-intent + # --handoff` ask too (`handoff_rel_globbed`, 991; A60), nothing written. + if [ "$whf_other" = 0 ] && handoff_rel_globbed "$whf_c" "$whf_path"; then + whf_stamp_said + die "lane $lane's handoff $HRG_REL in the checkout $whf_c has a glob character ('*', '?' or '[') in its path, and git reads a pathspec as a pattern, so this write's commit could stage a file it never wrote (607). Nothing was written. Name the handoff without one." 2 + fi + # AND IN ANOTHER CLONE OF THE REGISTER (rv2a-c2's re-review of slice 2a, + # SHOULD 1). A path outside this checkout, in a git work tree whose top + # level carries the register too — a sibling lane's `-lanes//`, + # an older clone — is that clone's file, not another repository's: the + # same rule holds there, relative to that top level. Otherwise `--whole` + # replaced that clone's register in place, uncommitted, and its lane's + # next write refused at S3 step 4 over a change it never made. A + # repository that carries no register is Amendment 8(d)'s, and a path in + # no repository at all is as it was (A44). + # NEVER INSIDE A GIT DIRECTORY (rv2a-c3's re-check of slice 2a, SHOULD 1; + # A47). A path in another clone's `.git/`, or in a bare clone, is in no + # work tree, so the test below cannot see it, and it was written as + # another repository's handoff: `--stamp` put its line under `[core]` in + # that clone's `config`, `--whole` replaced it, and every git command + # there then failed (128), its own lane's S3 with them. No handoff of + # Amendment 8(d)'s lives in a git directory, so it is refused first. + if [ "$whf_other" = 1 ]; then + # git is asked in the NEAREST DIRECTORY THERE IS on the way up, so a + # target whose own directory is missing is still read where it would be. + whf_gd="$(dirname -- "$whf_phys")" + while [ ! -d "$whf_gd" ] && [ "$whf_gd" != / ] && [ "$whf_gd" != . ]; do whf_gd="$(dirname -- "$whf_gd")"; done + if [ "$(git -C "$whf_gd" rev-parse --is-inside-git-dir 2>/dev/null || :)" = true ]; then + whf_stamp_said + die "lane $lane's handoff target $whf_path is $whf_phys, inside a git directory ($whf_gd), where git keeps a repository's own files (another clone's .git/, or a bare clone): write-handoff writes a lane's handoff and nothing else, and no handoff lives in a git directory (Amendment 23 S2; Amendment 8(d)'s is a file of a work tree). Nothing was written." 2 + fi + whf_otop="$(git -C "$whf_gd" rev-parse --show-toplevel 2>/dev/null || :)" + whf_otop="$( [ -n "$whf_otop" ] && cd -P -- "$whf_otop" 2>/dev/null && pwd -P || :)" + if [ -n "$whf_otop" ] && { [ -e "$whf_otop/$LANES_PATH" ] || [ -L "$whf_otop/$LANES_PATH" ]; } && + path_under "$whf_phys" "$whf_otop" && ! whf_in_scope "${whf_phys#"$whf_otop"/}"; then + whf_stamp_said + die "lane $lane's handoff target $whf_path is ${whf_phys#"$whf_otop"/} in $whf_otop, another checkout of the register (its top level carries $LANES_PATH too), which is not a file under handoffs/: write-handoff writes a lane's handoff and nothing else, never a register or anything under lanes/, in this checkout or in another clone of it (Amendment 23 S2). Nothing was written." 2 + fi + # A LANE'S HANDOFF IS WRITTEN IN ITS OWN CHECKOUT ONLY (601; the check + # `handoff-path` and `set-restart-intent` share, `handoff_beside_w`): any + # path in a checkout beside W — a sibling lane's `-lanes//`, a + # per-profile `-/` clone — is refused by path, so a clone git + # cannot read is never A44's and A47's "no repository" there. W itself + # is S5's, above, wherever this checkout is not W. + if handoff_beside_w "$whf_c" "$whf_path"; then + whf_stamp_said + die "lane $lane's handoff target $whf_path is $HBW_WHY. Nothing was written." 2 + fi + # A READ THAT FAILED IS NEVER "NO REPOSITORY" (601's second half, R22). + # Where git gave no top level, the target is in no work tree only if no + # `.git` is on the way up from it; one that is there is a repository git + # could not read here (a broken config, another owner), whose register + # or git directory A44 and A47 could not see. Refused, nothing written. + if [ -z "$whf_otop" ]; then + whf_up="$whf_gd" + while :; do + if [ -e "$whf_up/.git" ] || [ -L "$whf_up/.git" ]; then + whf_gerr="$(git -C "$whf_gd" rev-parse --show-toplevel 2>&1 >/dev/null | head -n 1)" + whf_stamp_said + die "lane $lane's handoff target $whf_path is $whf_phys, under $whf_up, which holds a git repository that git could not read here (${whf_gerr:-no top level}), so whether it is another clone of the register, or its git directory, cannot be said: a read that failed is never 'no repository' (R22). Nothing was written." 1 + fi + [ "$whf_up" != / ] && [ "$whf_up" != . ] || break + whf_up="$(dirname -- "$whf_up")" + done + fi + fi + # ANOTHER REPOSITORY'S HANDOFF IS NOT THIS CHECKOUT'S WRITE (rv2a's + # pre-review of slice 2a, SHOULD 2). It is written, committed and pushed + # in its own repository (Amendment 8(d); 604), so S3's refusal of the + # register checkout — an + # offline lane's fetch, a staged entry — does not stop it, as the old + # `publish-handoff` never fetched for it. The managed seam (asked above), + # the restart fences and the digests still apply. + if [ "$whf_other" = 1 ] && [ "$whf_s3" != 0 ]; then + note "the refusal above is this checkout's (Amendment 23 S3) and does not stop this write: $whf_phys is another repository's handoff (Amendment 8(d)), which is never committed through this checkout" + whf_s3=0 + fi + # ---- 3. S3 step 4 for the handoff itself: a change to it nobody committed, + # and a handoff git does not track yet (rv2a's pre-review of slice 2a, + # MUST 1). An untracked file — a new lane's first handoff, or a peer's — + # would be ADDED by this write's commit, under its subject; and where the + # push then lost a race, the replay's `reset --keep` dropped that commit + # and the file with it, and the re-run found nothing to write into (8). So + # it is a pending change like any other, and whoever made it adds and + # commits it as its own write. WHETHER IT IS TRACKED IS ASKED WHATEVER S3 + # ANSWERED (rv2a-c2's re-review of slice 2a, NIT 1; A45): where S3 refused + # for a reason of its own, `--prepend` and `--whole` still write it (step + # 6), and the commit that note prints needs the `git add` first. + whf_untracked=0 + if [ "$whf_other" = 0 ] && [ "$NO_GIT" != 1 ] && { [ -e "$whf_phys" ] || [ -L "$whf_phys" ]; } && + ! git --literal-pathspecs -C "$whf_c" -c "$CP_EXACT" ls-files --error-unmatch -- "$whf_rel" >/dev/null 2>&1; then + whf_untracked=1 + fi + if [ "$whf_s3" = 0 ] && [ "$whf_other" = 0 ]; then + ( cs_own_pending write-handoff "$lane" "$whf_rel" ) || whf_s3=$? + if [ "$whf_s3" = 0 ] && [ "$whf_untracked" = 1 ]; then + ( cs_refuse write-handoff "$whf_rel, this write's handoff, is not tracked yet: git has never had it (a new lane's first handoff, or a peer's), so this write's commit would add it under its own subject, and a replay after a lost race would drop that commit and the file with it (Amendment 23 S3 step 4)" \ + "read it: git -C $LANES_REPO status --short -- $whf_rel" \ + "whoever made it adds and commits it by pathspec, as its own write: git -C $LANES_REPO add -- $whf_rel && git -C $LANES_REPO commit -m \"()\" -- $whf_rel, then LANES_LANE= $SELF commit \"\" (the publish act)" \ + "never commit a file you did not make; then re-run this write" ) || whf_s3=$? + fi + fi + if [ "$whf_s3" != 0 ] && [ "$whf_mode" = stamp ]; then + note "the stamp is NOT written: the checkout refused above (Amendment 23 S3), and a Rule 3 stamp the checkout cannot take is written by nobody, and never blocks a launch." + whf_stamp_said + exit "$whf_s3" + fi + # ---- 4. the read, the splice and its proof. + if [ ! -e "$whf_phys" ] && [ ! -L "$whf_phys" ]; then + whf_stamp_said + die "lane $lane's handoff is not at $whf_phys, so there is nothing to write into. Nothing was written." 8 + fi + [ -f "$whf_phys" ] && [ -r "$whf_phys" ] || die "the handoff target is unreadable" 1 + TMPD="$(mktemp -d)" || die "no temporary directory could be made under ${TMPDIR:-/tmp} for the handoff's write. Nothing was written." 1 + whf_pre="$TMPD/pre"; whf_out="$TMPD/out" + cat -- "$whf_phys" > "$whf_pre" 2>/dev/null || die "the handoff at $whf_phys could not be read. Nothing was written." 1 + whf_prior="$(lane_handoff_digest "$whf_pre")" + case "$whf_mode" in + stamp) + [ -s "$whf_pre" ] || + die "the handoff at $whf_phys is empty, so it has no header block to stamp under, and Rule 3's stamp is not written. The line it wants: $whf_arg" 1 + whf_at="$(awk 'NR > 1 && /^[[:space:]]*$/ { print NR; exit }' "$whf_pre" || :)" + if [ -z "$whf_at" ]; then + whf_at=1 + note "$whf_phys has no blank line after line 1, so it has no header block to stamp under — the stamp goes directly under line 1" + fi + { head -n "$whf_at" -- "$whf_pre" + printf '%s\n' "$whf_arg" + tail -n "+$((whf_at + 1))" -- "$whf_pre" + } > "$whf_out" || die "the stamped copy of the handoff could not be made in $TMPD. Nothing was written." 1 + # THE PROOF, `lane-start`'s: the added line is the stamp, at the ruled + # place, and taking it out again gives the file back byte for byte. + if [ "$(sed -n -e "$((whf_at + 1))p" "$whf_out")" != "$whf_arg" ] || + ! { head -n "$whf_at" -- "$whf_out"; tail -n "+$((whf_at + 2))" -- "$whf_out"; } | cmp -s - "$whf_pre"; then + die "the splice of the stamp into $whf_phys did not prove out (one line added, none changed), so NOTHING was written to it. Add this line under its header block yourself: $whf_arg" 2 + fi ;; + prepend) + printf -- '---\n\n' > "$TMPD/sep" && + { cat -- "$whf_arg"; cat -- "$TMPD/sep"; cat -- "$whf_pre"; } > "$whf_out" || + die "the new top block could not be put in front of the handoff in $TMPD. Nothing was written." 1 + # THE PROOF, by bytes: the block, then `---` and a blank line, then the + # old file whole — which is also what a last line with no newline needs. + whf_bn="$(wc -c < "$whf_arg" | tr -d ' ')" + if ! head -c "$whf_bn" -- "$whf_out" | cmp -s - "$whf_arg" || + ! head -c "$((whf_bn + 5))" -- "$whf_out" | tail -c 5 | cmp -s - "$TMPD/sep" || + ! tail -c "+$((whf_bn + 6))" -- "$whf_out" | cmp -s - "$whf_pre"; then + die "the new top block for $whf_phys did not prove out (the old file must survive it byte for byte), so NOTHING was written to it." 2 + fi ;; + whole) + cat -- "$whf_arg" > "$whf_out" || die "the prepared handoff could not be read. Nothing was written." 1 ;; + esac + whf_blob="" + [ "$whf_other" = 1 ] || whf_blob="$(git -C "$whf_c" rev-parse -q --verify "HEAD:$whf_rel" 2>/dev/null || :)" + # ---- 5. under the workstation's sidecar lock, inside the checkout's. + sidecar_try 60 || + die "could not acquire the workstation's sidecar lock $(sidecar_lock_dir 2>/dev/null) after 60s — a restart, lifecycle or tree act holds it (holder pid $(cat -- "$(sidecar_lock_dir 2>/dev/null)/pid" 2>/dev/null || printf 'unrecorded')). Nothing was written." 4 + HPA_CHECKOUT="$whf_c" + HPA_FENCE_RC=7 # 603: this verb's 2 is S3's, so a restart fence's 2 is 7 here + whf_fence=() + if [ "$whf_restart" = 1 ]; then + whf_fence=("$whf_rop" "$whf_rgen" "$whf_ratt" "$whf_rstate" "$whf_rsid") + HPA_EXPECTED="$whf_rexp"; HPA_EXPECTED_FOR="this owned write" + if [ "$whf_mode" = stamp ]; then + HPA_EXPECTED_FOR="its owned resume stamp" + HPA_RESERVE="$(lane_handoff_digest "$whf_out")" + fi + fi + whf_prior_is="$whf_prior"; [ "$whf_mode" != whole ] || whf_prior_is="$whf_expect" + handoff_publish_atomic "$lane" "$whf_phys" "$whf_out" "$whf_prior_is" ${whf_fence[@]+"${whf_fence[@]}"} + sidecar_release + HANDOFF_OUT="$HPA_TARGET $(lane_handoff_digest "$HPA_TARGET")" + # ---- 6. a refused checkout: written, and not committed. + if [ "$whf_s3" != 0 ]; then + note "the handoff is WRITTEN and NOT committed: the checkout refused above (Amendment 23 S3; R-A11-11 — a swap is never left unwritten, so the handoff is written, no commit is made, and git's own words are above). It is $HPA_TARGET." + if [ "$whf_other" = 0 ]; then + whf_add=""; [ "$whf_untracked" = 0 ] || whf_add="git -C $whf_c add -- $whf_rel && " + note "once the refusal is answered, commit it by pathspec and publish it: ${whf_add}git -C $whf_c commit -m \"$whf_subject\" -m \"Lane: $lane\" -- $whf_rel, then LANES_LANE=$lane $SELF commit \"\" (the publish act). Never write it again with --prepend: that puts a second block on top." + fi + exit "$whf_s3" + fi + # ANOTHER REPOSITORY'S HANDOFF IS COMMITTED AND PUSHED THERE (lane + # openRepoTools-3's review of #312, 604, ruled at #312 6368669583; A57). + # 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: one commit in the handoff's OWN top level, by + # pathspec, with this write's subject and the `Lane:` trailer, then a + # `push` of the branch that repository is on, which is `main` wherever + # theirs was. Their `pull --rebase` before the push is S3's way here, as + # no line of this file pulls (#198): the branch is fetched and + # fast-forwarded BEFORE the commit, so the commit sits on origin's tip, + # and a fast-forward that cannot be made (a local commit, or origin's + # change to this very file) is left to the push, which is refused, and the + # commit kept (6). Not S3 or S4 themselves: that repository is not the + # register, so no pending change there is asked about, nothing is + # replayed and nothing is read back. NEVER IN + # A CLONE OF THE REGISTER (A57): a top level that carries the register + # too (A44's test) is another lane's clone, not 8(d)'s "not the + # register's", and 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 refused it above, and elsewhere it is written and + # NOT committed, as before 604 (0, said; its lane commits it). A step + # that fails there leaves the handoff WRITTEN, says what is left with the + # command that finishes it, and is 6 (`git add` or `commit` failed, or the + # commit is kept on HEAD and named); a file in no git work tree is written + # and commits nowhere (0), as `lane-start` said; LANES_NO_GIT commits + # nothing (0). + if [ "$whf_other" = 1 ]; then + whf_8d="$HPA_TARGET is another repository's handoff (Amendment 8(d)): it is written there" + whf_8top="$(git -C "$(dirname -- "$HPA_TARGET")" rev-parse --show-toplevel 2>/dev/null || :)" + whf_8top="$( [ -n "$whf_8top" ] && cd -P -- "$whf_8top" 2>/dev/null && pwd -P || :)" + if [ -z "$whf_8top" ] || ! path_under "$HPA_TARGET" "$whf_8top"; then + note "$whf_8d, and it is in no git work tree, so nothing commits it from here." + exit 0 + fi + if [ -e "$whf_8top/$LANES_PATH" ] || [ -L "$whf_8top/$LANES_PATH" ]; then + note "$HPA_TARGET is in $whf_8top, another clone of the register (its top level carries $LANES_PATH too), not this write's checkout $whf_c, so it is not Amendment 8(d)'s: it is written there and NOT committed, since a commit and push there would publish to the register outside Amendment 23 S3 and S4. That clone's lane commits it." + exit 0 + fi + whf_8rel="${HPA_TARGET#"$whf_8top"/}" + whf_8cmd="git --literal-pathspecs -C $whf_8top add -- $whf_8rel && git --literal-pathspecs -C $whf_8top commit -m \"$whf_subject\" -m \"Lane: $lane\" -- $whf_8rel" + if [ "$NO_GIT" = 1 ]; then + note "$whf_8d, and NOT committed: LANES_NO_GIT=1. Commit it there by pathspec, then push it: $whf_8cmd" + exit 0 + fi + whf_8br="$(git -C "$whf_8top" symbolic-ref -q --short HEAD 2>/dev/null || :)" + if [ -z "$whf_8br" ]; then + note "$whf_8d, and NOT committed: $whf_8top's HEAD is on no branch (a rebase in progress, or a detached checkout), and no commit is made there in the middle of one. Once it is on its branch: $whf_8cmd, then push it." + exit 6 + fi + whf_8look="look first: git -C $whf_8top fetch origin $whf_8br && git -C $whf_8top log --oneline origin/$whf_8br -3, since a push that timed out may have landed; only if it is not there, put it on origin's tip (git -C $whf_8top rebase origin/$whf_8br) and push it: git -C $whf_8top push origin $whf_8br. Never force." + whf_8ff=0 + if git_net -C "$whf_8top" fetch -q origin "$whf_8br" >&2 && + git -C "$whf_8top" rev-parse -q --verify "refs/remotes/origin/$whf_8br" >/dev/null 2>&1; then + whf_8ff=1 + git -C "$whf_8top" -c gc.auto=0 -c maintenance.auto=false merge --ff-only -q "refs/remotes/origin/$whf_8br" >/dev/null 2>&1 || : + fi + if ! git --literal-pathspecs -C "$whf_8top" add -- "$whf_8rel" >&2 || + ! git --literal-pathspecs -C "$whf_8top" commit -q -m "$whf_subject" -m "Lane: $lane" -- "$whf_8rel" >&2; then + note "$whf_8d, and NOT committed: git's own words are above. Commit it there by pathspec, as this write's own, then push it: $whf_8cmd" + exit 6 + fi + if [ "$whf_8ff" = 0 ]; then + note "$whf_8d and committed there, on $whf_8br, \"$whf_subject\", and NOT pushed: origin/$whf_8br could not be fetched there first (no origin, no reachable origin, or a timeout). The commit is kept on HEAD there — $whf_8look" + exit 6 + fi + if ! git_net -C "$whf_8top" push -q origin "$whf_8br" >&2; then + note "$whf_8d and committed there, on $whf_8br, \"$whf_subject\", and the push was refused or timed out (a hung push often lands). The commit is kept on HEAD there — $whf_8look" + exit 6 + fi + note "$whf_8d, committed there and pushed to origin/$whf_8br: \"$whf_subject\" (Lane: $lane)." + exit 0 + fi + # ---- 7. the commit, the push, the read-back. + CP_TRAILER="Lane: $lane" + # A WRITE IN PLACE IS NEVER REPLAYED INTO NOTHING (A37, through slice 1b's + # `CP_KEEP_IF_GONE`): `--stamp` and `--prepend` edit the handoff in place, + # so where another writer deleted it while this write's push was in + # flight, the replay's re-run would find no file (8, "Nothing was + # written") and the write would be only in the commit it dropped; the + # commit is kept on HEAD and named instead (6). `--whole` is S4.4's: a + # copy origin deleted is a copy that moved. A HANDOFF MOVED WITH ITS + # ROW'S CELL IS REPLAYED (A46): `whf_keep_cell` is the cell this write + # followed (none under `--target`), and where origin's row names another + # one, a file under `handoffs/` that origin has, the re-run reads it and + # writes the moved file. Any other cell keeps the commit (6, A48). + case "$whf_mode" in + whole) CP_WHOLE_REL="$whf_rel"; CP_WHOLE_BLOB="$whf_blob" ;; + *) CP_KEEP_IF_GONE="$whf_rel" + whf_keep_cell=""; [ -n "$whf_target" ] || whf_keep_cell="$whf_cell" ;; + esac + commit_publish "$whf_subject" "$whf_rel" + ;; + + # ---------- AMENDMENT 23, SLICE 2A: WHERE A LANE'S HANDOFF IS WRITTEN + # + # `handoff-path [--create]`, a read: the row's handoff cell, out of the + # published register, TRANSLATED into the checkout `write-handoff` writes it + # through — S1's `-lanes//` under the opt-in with no explicit + # `LANES_REPO`, otherwise that checkout — for a caller that binds a restart + # intent to it before it writes (plan §2.7). The path is derived whether or + # not the checkout is there yet; `--create` makes the lane's checkout first. + # Not routed: it writes nothing. + # 0 the path (another repository's too, said on stderr) · 8 no row, or a + # row that names no handoff, and nothing cloned · 1 a checkout there that + # is not a clone, or a register that could not be read (R22: never 8) · + # 2 a name refused, a handoff in W or in another checkout beside it + # (601, as `write-handoff` refuses it; nothing on stdout), a relative + # cell whose `.` or `..` components climb out of the checkout (602, S2), + # a path in the checkout that holds a glob character (607, as + # `write-handoff` refuses it, 991), or the clone failed · 4 the creation + # lock · 64 usage + handoff-path) + hp_lane=""; hp_create=0 + while [ $# -gt 0 ]; do + case "$1" in + --create) hp_create=1; shift ;; + --) shift ;; + -*) die "unknown option '$1' for handoff-path (usage: handoff-path [--create])" 64 ;; + *) [ -z "$hp_lane" ] || die "handoff-path takes ONE lane (usage: handoff-path [--create])" 64 + hp_lane="$1"; shift ;; + esac + done + [ -n "$hp_lane" ] || die "usage: handoff-path [--create]" 64 + check_lane_name "$hp_lane" + log_sync + hp_lane="$(canon_lane "$hp_lane")" || exit $? + check_lane_name "$hp_lane" + # THE CELL FIRST: a lane with no row, or a row with no handoff, has nothing + # to write, and `--create` clones nothing for it (R4-1's rule for writes). + hp_hrc=0; hp_cell="$(handoff_cell_of "$hp_lane")" || hp_hrc=$? + case "$hp_hrc" in + 0) : ;; + 8) note "lane $hp_lane has no handoff: there is no row for it in the register" + exit 8 ;; + *) die "the register could not be read, so whether lane $hp_lane's row names a handoff cannot be said — a read that failed is never 'no handoff' (R22). Nothing was cloned." 1 ;; + esac + if [ -z "$hp_cell" ]; then + note "lane $hp_lane's row names no handoff" + exit 8 + fi + # A RELATIVE CELL THAT CLIMBS OUT OF THE CHECKOUT (602): refused as + # `write-handoff` refuses it, and first, as there (A55) — by its text + # before any checkout is made, then by the file it names in the checkout + # there is. + hp_climbs="lane $hp_lane's handoff cell $hp_cell is a repository-relative spelling, joined to the checkout its writes go through, and its '.' or '..' components climb out of it: a relative cell names a file of that checkout, never another repository's (Amendment 23 S2), so write-handoff refuses it too. Nothing was printed." + ! handoff_rel_climbs_out "" "$hp_cell" || die "$hp_climbs" 2 + handoff_checkout_of "$hp_lane" "$hp_create" + ! handoff_rel_climbs_out "$HCO_PATH" "$hp_cell" || die "$hp_climbs" 2 + hp_rc=0; hp_path="$(handoff_in_repo "$HCO_PATH" "$hp_cell")" || hp_rc=$? + # WHAT `write-handoff` REFUSES, NO PATH IS PRINTED FOR (finding 723 of + # lane openRepoTools-3's review of #316): a caller binds a restart to this + # path and reads its top block as the lane's first prompt, so a handoff in + # W or in another checkout beside it is refused here by the same check. + if handoff_beside_w "$HCO_PATH" "$hp_path"; then + die "lane $hp_lane's handoff $hp_cell is $HBW_WHY: \`write-handoff\` refuses it, so no path is printed for a restart to bind or a launch to read (601). Nothing was written." 2 + fi + if handoff_rel_globbed "$HCO_PATH" "$hp_path"; then + die "lane $hp_lane's handoff $hp_cell is $HRG_REL in the checkout $HCO_PATH, with a glob character ('*', '?' or '[') in its path: \`write-handoff\` refuses it (607), so no path is printed for a restart to bind or a launch to read (991). Nothing was written. Name the handoff without one." 2 + fi + [ "$hp_rc" = 0 ] || note "$hp_path is another repository's handoff (Amendment 8(d)): \`write-handoff\` writes it there, and commits and pushes it in that repository (604), never in another clone of the register" + printf '%s\n' "$hp_path" ;; legacy-restart-check) @@ -19662,15 +20672,68 @@ EOF # the handoff: the reader of this file is a DIFFERENT PROCESS IN A # DIFFERENT DIRECTORY — the supervisor in a respawned pane — and a relative # path resolved against its cwd is a path to somewhere else. `~` is the - # writing shell's and is never expanded here. + # writing shell's and is never expanded in `--dir`. + # + # …EXCEPT THE ROW'S OWN SPELLINGS OF A HANDOFF (Amendment 23 S2, slice + # 2a): a repository-relative `handoffs/…` cell, and the estate's `~/…`, + # are TRANSLATED once the lane is known — into the checkout that lane's + # writes go through (`handoff_checkout_of`: its own under the opt-in, an + # explicit `LANES_REPO`, W with the opt-in off) — and stored as that + # absolute path, so the digest below is that file's and `write-handoff` + # fences on it directly. An ABSOLUTE path is kept exactly as given: it + # is every older caller's, and each binds the file it then publishes and + # compares — W's, until slice 2b moves `lane-start` and `lane-handoff` + # (`write-handoff` translates a W-bound path on its own side). Any other + # relative path, and a `.` or `..` component, is still refused. case "$sri_dir" in ''|/*) : ;; *) die "--dir takes an ABSOLUTE path: this record is read by the supervisor in a pane whose working directory is not this one, and a relative path there names a different place. Got '$sri_dir'." 64 ;; esac - case "$sri_hf" in ''|/*) : ;; *) die "--handoff takes an ABSOLUTE path, for the same reason --dir does: the launch that reads it back stands somewhere else. Got '$sri_hf'." 64 ;; esac + sri_hf_cell="" + case "$sri_hf" in + ''|/*) : ;; + handoffs/*|'~/'*) + case "/$sri_hf/" in + */../*|*/./*) die "--handoff's repository-relative cell is joined to a checkout, so it names no '.' or '..' component. Got '$sri_hf'." 64 ;; + esac + sri_hf_cell="$sri_hf" ;; + *) die "--handoff takes an ABSOLUTE path, for the same reason --dir does: the launch that reads it back stands somewhere else — or the row's own spelling, a repository-relative handoffs/… cell or ~/…, which is translated into the lane's checkout. Got '$sri_hf'." 64 ;; + esac for sri_exact in "$sri_dir" "$sri_pane" "$sri_win" "$sri_hf" "$sri_old" "$sri_new" "$sri_dig" "$sri_prepared"; do lane_restart_value_ok "$sri_exact" || die "restart launch facts cannot contain control characters. Nothing was written." 64 done check_lane_name "$lane" log_sync lane="$(canon_lane "$lane")" || exit 2 # Amendment 15 + if [ -n "$sri_hf_cell" ]; then + handoff_checkout_of "$lane" 0 + sri_hf="$(handoff_in_repo "$HCO_PATH" "$sri_hf_cell")" || : + case "$sri_hf" in + /*) note "--handoff $sri_hf_cell is bound as $sri_hf: the row's spelling, translated into the checkout lane $lane's writes go through, or another repository's file as it is (Amendment 23 S2)" ;; + *) die "--handoff $sri_hf_cell could not be made an absolute path in the checkout lane $lane's writes go through (got '$sri_hf'). Nothing was written." 1 ;; + esac + if handoff_beside_w "$HCO_PATH" "$sri_hf"; then + die "--handoff $sri_hf_cell is $HBW_WHY: \`write-handoff\` refuses it, so no restart is bound to it (601). Nothing was written." 2 + fi + if handoff_rel_globbed "$HCO_PATH" "$sri_hf"; then + die "--handoff $sri_hf_cell is $HRG_REL in the checkout $HCO_PATH, with a glob character ('*', '?' or '[') in its path: \`write-handoff\` refuses it (607), so no restart is bound to it (991). Nothing was written." 2 + fi + elif [ -n "$sri_hf" ] && [ -n "$LANES_WS_REAL" ]; then + # AN ABSOLUTE PATH IS KEPT AS GIVEN (W's spelling is every older + # caller's, and `write-handoff` translates it on its side), but one that + # lands in a checkout beside W is asked the same questions as a cell + # (601): only the lane's own checkout there is its handoff's, and no + # glob character in its path there (607, 991) — the path `handoff-path` + # prints under the opt-in. W's own spelling is not asked (A60). + sri_hf_phys="$(path_physical "$sri_hf")" || sri_hf_phys="$sri_hf" + case "$sri_hf_phys" in + "$LANES_WS_REAL"-*/*) + handoff_checkout_of "$lane" 0 + if handoff_beside_w "$HCO_PATH" "$sri_hf"; then + die "--handoff $sri_hf is $HBW_WHY: \`write-handoff\` refuses it, so no restart is bound to it (601). Nothing was written." 2 + fi + if handoff_rel_globbed "$HCO_PATH" "$sri_hf"; then + die "--handoff $sri_hf is $HRG_REL in the checkout $HCO_PATH, with a glob character ('*', '?' or '[') in its path: \`write-handoff\` refuses it (607), so no restart is bound to it (991). Nothing was written." 2 + fi ;; + esac + fi # Select the physical intent under the CAS mutex: a waiting writer must # see a reservation another writer established before releasing the lock. sidecar_lock_use; acquire_lock # the workstation's sidecar lock, and no other (Amendment 23 S2) @@ -20409,6 +21472,6 @@ EOF ;; *) - die "unknown subcommand '$cmd' (verify-row|set-row-state|append-row-status|replace-in-row|append-session-id|append-line|add-row|retire-rows|archive-rows|migrate-state-cells|commit|rename-lane|log|claim|release|who|history|swapped|session-start|guard|idle-holders|live-holder|window-session|transcript-holders|binding|request-handoff|session-lane|window-lane|lane-dir|lane-profile|lane-agent|lane-transcript|lane-last|workspace-root|last-session|forks|duplicate-holder|workstation|fetch-age|lanes|lane-groups|next-free|sibling-filter|resolve-repo|lane-objects|register-row|register-row-local|retired-identity|canon-lane|resolve-home|managed-projection|lane-state|set-lane-state|lane-trees|set-lane-tree|lane-tree-now|project-legs|lane-reconcile|lane-holders|legacy-restart-check|publish-handoff|restart-intent|set-restart-intent|worktrees|pathspec-check|lane-checkout|refresh-workspace)" 2 + die "unknown subcommand '$cmd' (verify-row|set-row-state|append-row-status|replace-in-row|append-session-id|append-line|add-row|retire-rows|archive-rows|migrate-state-cells|commit|rename-lane|log|claim|release|who|history|swapped|session-start|guard|idle-holders|live-holder|window-session|transcript-holders|binding|request-handoff|session-lane|window-lane|lane-dir|lane-profile|lane-agent|lane-transcript|lane-last|workspace-root|last-session|forks|duplicate-holder|workstation|fetch-age|lanes|lane-groups|next-free|sibling-filter|resolve-repo|lane-objects|register-row|register-row-local|retired-identity|canon-lane|resolve-home|managed-projection|lane-state|set-lane-state|lane-trees|set-lane-tree|lane-tree-now|project-legs|lane-reconcile|lane-holders|legacy-restart-check|publish-handoff|restart-intent|set-restart-intent|worktrees|pathspec-check|lane-checkout|refresh-workspace|write-handoff|handoff-path)" 2 ;; esac diff --git a/tests/test_a23_write_handoff.py b/tests/test_a23_write_handoff.py new file mode 100644 index 0000000..640cc1f --- /dev/null +++ b/tests/test_a23_write_handoff.py @@ -0,0 +1,2741 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Amendment 23, act 2, slice 2a: the handoff verbs. + +`publish-handoff` keeps its contract for every old caller, and its act — the +link followed to the physical file, the restart fences, the prior digest, and +the bytes replaced by one rename beside the file — is `handoff_publish_atomic`, +which `write-handoff` shares. + +`write-handoff (--stamp | --prepend | --whole) --subject …` writes the +lane's handoff in the checkout its writes go through (the lane's own under the +opt-in, the explicit `LANES_REPO` seam, W with the opt-in off), under the +checkout's lock: S3 first, the row's cell translated (S2), the read in that +checkout, the splice proven, the restart fences and the one-rename replace under +the workstation's sidecar lock nested inside, then S4 — commit by pathspec with +the `Lane:` trailer, push, read back, and a refused push replayed on origin's +new tip (a whole-file write only over the copy it started from). S3's refusal +leaves `--stamp` unwritten and `--prepend`/`--whole` written and uncommitted +(R-A11-11). `handoff-path` answers where the handoff is written. + +The sandbox is the real `lanes-edit.sh`; nothing reaches a real remote. The box +is slice 1b's (`tests/test_a23_publish_replay.py`: two rows, a peer clone, and +a push-time `race` hook), imported rather than copied, with the lane's handoff +published on origin, W brought to it, and a restart state root of its own. +""" + +from __future__ import annotations + +import hashlib +import os +import shlex +import shutil +import stat +import subprocess +import time +from pathlib import Path + +import pytest + +from a23_box import ME, REGISTER +from conftest import WINDOWS_SKIP +from test_a23_publish_replay import LANE, NEIGHBOUR, Box as ReplayBox, _git_shim, _stamp + +pytestmark = [pytest.mark.skipif(shutil.which("bash") is None, + reason="lanes-edit.sh is a bash script"), + pytest.mark.skipif(shutil.which("git") is None, + reason="the register is a git repository"), + WINDOWS_SKIP] + +#: The handoff cell `_row` gives the lane, and the file origin carries there: +#: a header line, the blank that ends the header block, then the block. +HANDOFF_REL = f"handoffs/repoS4/{LANE}.md" +HANDOFF = (f"Lane: {LANE} (profile p, session s) — single-use resume prompt\n" + "\n" + "## RESUME PROMPT — the block the next session reads\n" + "\n" + "Preserved task.\n") + + +SUBJECT = f"handoff({LANE}@Eagle): RESUMED by a23-tester" +STAMP = f"RESUMED by a23-tester (lane {LANE}) at 2026-10-20T21:00:00Z — lane-start on Eagle" +BLOCK = (f"Lane: {LANE} (profile p, session s) — single-use resume prompt\n" + "\n" + "## RESUME PROMPT — PAUSED, the newer block\n" + "\n") +#: The fences `--restart` names, and the intent `restart()` makes to match. +FENCE = ("op-a23", "3", "1", "starting", "sid-a23") +#: How long a waiter is watched to prove it is still waiting (it polls at 1 s). +WATCH = 4 + + +def sha(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def sha_of(text: str) -> str: + return hashlib.sha256(text.encode("utf-8")).hexdigest() + + +def stamped(text: str, line: str = STAMP) -> str: + """`lane-start`'s splice: the line after the first blank line after line + 1, else directly under line 1.""" + lines = text.splitlines(keepends=True) + at = next((i + 1 for i, l in enumerate(lines) if i > 0 and not l.strip()), 1) + return "".join(lines[:at]) + line + "\n" + "".join(lines[at:]) + + +def prepended(text: str, block: str = BLOCK) -> str: + return block + "---\n\n" + text + + +class Box(ReplayBox): + """Slice 1b's box, with the lane's handoff on origin, W brought to it (the + S6.5 state), and the restart intents under a state root of the box's own.""" + + def __init__(self, root: Path, **kw) -> None: + root = Path(os.path.realpath(root)) + state = root / "state" + env = dict(kw.pop("env", None) or {}, LANES_LANE_STATE_ROOT=str(state)) + super().__init__(root, env=env, **kw) + self.state = state + self.peer_pushes(HANDOFF_REL, HANDOFF) + self.w_current() + + def w_current(self) -> None: + """W fast-forwarded by the fixture, as `refresh-workspace` would.""" + self.git("-C", str(self.w), "fetch", "-q", "origin") + self.git("-C", str(self.w), "merge", "-q", "--ff-only", "refs/remotes/origin/main") + + def write_handoff(self, *args: str, **extra: str) -> subprocess.CompletedProcess: + return self.helper("write-handoff", LANE, *args, **extra) + + def restart(self, handoff: Path) -> None: + """The operation FENCE names, as a `/ctx` and its supervisor leave it: + `pending` bound to `handoff` (its digest taken there), then `starting` + with the attempt and the transcript the launch prepares.""" + self.intent("pending", "--expect", "none", "--operation", FENCE[0], "--generation", FENCE[1], + "--handoff", str(handoff)) + self.intent("starting", "--expect", "pending", "--expect-operation", FENCE[0], + "--attempt", FENCE[2], "--new-transcript", FENCE[4]) + + def cell_is(self, cell: str) -> None: + """The peer publishes the lane's row with another handoff cell.""" + self.peer_edits(REGISTER, f"| {HANDOFF_REL} |", f"| {cell} |", "a peer respells the handoff cell") + + def last_message(self) -> list: + return self.git("--git-dir", str(self.origin), "log", "-1", "--format=%B", "main").splitlines() + + def w_reflog(self) -> str: + return self.git("-C", str(self.w), "reflog", "--format=%H %gs", "HEAD") + + def intent(self, state: str, *extra: str) -> None: + made = self.helper("set-restart-intent", LANE, state, *extra) + assert made.returncode == 0, (made.stdout, made.stderr) + + def intent_field(self, key: str) -> str: + text = (self.state / LANE / "restart-intent.yaml").read_text(encoding="utf-8") + return next(line.split(": ", 1)[1] for line in text.splitlines() + if line.startswith(f"{key}: ")) + + +@pytest.fixture +def box(tmp_path: Path) -> Box: + return Box(tmp_path) + + +def _no_temporary_beside(path: Path) -> None: + assert [p.name for p in path.parent.iterdir() if ".resume." in p.name] == [] + + +# ------------------------------------------------- publish-handoff's contract + +def test_publish_handoff_prints_the_physical_file_and_keeps_its_mode(box: Box): + """Through a link, the physical file is replaced by one rename beside it: + it is the one line on stdout, it keeps its mode, the link stays a link, + nothing is committed, and no temporary is left beside it.""" + c = box.seam() + real = c / HANDOFF_REL + real.chmod(0o640) + link = box.root / "estate" / "handoff.md" + link.parent.mkdir() + link.symlink_to(real) + prepared = box.root / "prepared.md" + prepared.write_text("# published whole\n", encoding="utf-8") + head, tip = box.head(c), box.origin_head() + result = box.helper("publish-handoff", LANE, str(link), str(prepared), + "--expect-digest", sha(real)) + assert result.returncode == 0, result.stderr + assert result.stdout == f"{real}\n" + assert real.read_text(encoding="utf-8") == "# published whole\n" + assert stat.S_IMODE(real.stat().st_mode) == 0o640 + assert link.is_symlink() + _no_temporary_beside(real) + assert box.head(c) == head, "publish-handoff commits nothing" + assert box.origin_head() == tip, "publish-handoff pushes nothing" + + +def test_publish_handoff_refuses_a_handoff_that_changed_exit_7(box: Box): + c = box.seam() + real = c / HANDOFF_REL + prepared = box.root / "prepared.md" + prepared.write_text("# published whole\n", encoding="utf-8") + result = box.helper("publish-handoff", LANE, str(real), str(prepared), + "--expect-digest", "0" * 64) + assert result.returncode == 7, result.stderr + assert "the handoff changed before publication; nothing was published" in result.stderr + assert real.read_text(encoding="utf-8") == HANDOFF + assert result.stdout == "" + _no_temporary_beside(real) + + +FENCES = { + # name: (the intent's state, the publish's fence flags, exit, words) + "owned": ("preparing", ("--operation", "op-a23", "--generation", "3", "--attempt", "0", + "--state", "preparing", "--transcript", "none"), 0, ""), + "lost": ("preparing", ("--operation", "op-a23", "--generation", "4", "--attempt", "0", + "--state", "preparing", "--transcript", "none"), 7, + "this handoff publisher lost restart ownership; nothing was published"), + "active": ("pending", (), 7, "an active restart owns this handoff; nothing was published"), + "half": ("preparing", ("--operation", "op-a23"), 64, + "operation publication needs generation, attempt, state and transcript fences"), +} + + +@pytest.mark.parametrize("case", FENCES) +def test_publish_handoff_restart_fences(box: Box, case: str): + """The five fences are the intent's own or nothing is published: a lost + operation is 7, an active restart with no fences is 7, and an operation + without its other four fences is 64.""" + c = box.seam() + real = c / HANDOFF_REL + state, flags, code, words = FENCES[case] + box.intent(state, "--expect", "none", "--operation", "op-a23", "--generation", "3", + "--handoff", str(real)) + prepared = box.root / "prepared.md" + prepared.write_text("# fenced\n", encoding="utf-8") + result = box.helper("publish-handoff", LANE, str(real), str(prepared), + "--expect-digest", sha(real), *flags) + assert result.returncode == code, result.stderr + assert words in result.stderr + assert real.read_text(encoding="utf-8") == ("# fenced\n" if code == 0 else HANDOFF) + _no_temporary_beside(real) + + +def test_publish_handoff_refuses_a_file_the_restart_did_not_bind(box: Box): + """Under an operation, the file published must be the one the intent + binds — compared as the same file, never by spelling (2).""" + c = box.seam() + real = c / HANDOFF_REL + other = c / "handoffs" / "README.md" + box.intent("preparing", "--expect", "none", "--operation", "op-a23", "--generation", "3", + "--handoff", str(other)) + prepared = box.root / "prepared.md" + prepared.write_text("# fenced\n", encoding="utf-8") + result = box.helper("publish-handoff", LANE, str(real), str(prepared), + "--expect-digest", sha(real), "--operation", "op-a23", + "--generation", "3", "--attempt", "0", "--state", "preparing", + "--transcript", "none") + assert result.returncode == 2, result.stderr + assert "the publication target is not the operation-bound handoff" in result.stderr + assert real.read_text(encoding="utf-8") == HANDOFF + + +# ------------------------------------------------------------ write-handoff + +def _released(box: Box, c: Path) -> None: + assert not box.lock_of(c).exists(), "the checkout's lock is released" + assert not (box.lanes_root / ".lane-state.lock").exists(), "the sidecar lock is released" + + +def _line(path: Path) -> str: + """write-handoff's one line on stdout for the file it wrote.""" + return f"{path}\t{sha(path)}\n" + + +def _staged_foreign(box: Box, c: Path) -> None: + box.write(c, "handoffs/README.md", "# handoffs\na peer's staged line\n") + box.git("-C", str(c), "add", "--", "handoffs/README.md") + + +# ------------------------------------------------- --stamp + +def test_stamp_spliced_committed_pushed_with_lane_trailer(box: Box): + """`lane-start`'s splice, moved: the stamp under the header block, proven, + committed by pathspec with the `Lane:` trailer, pushed and read back.""" + c = box.seam() + f = c / HANDOFF_REL + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 0, result.stderr + assert f.read_text(encoding="utf-8") == stamped(HANDOFF) + assert box.origin_file(HANDOFF_REL) == stamped(HANDOFF) + msg = box.last_message() + assert msg[0] == SUBJECT, msg + assert f"Lane: {LANE}" in msg, msg + assert box.git("--git-dir", str(box.origin), "show", "--name-only", "--format=", "main").split() == [HANDOFF_REL] + assert result.stdout == _line(f) + assert "pushed origin/main" in result.stderr + assert box.head(c) == box.origin_head() + assert box.unpublished(c) == [] + _released(box, c) + + +@pytest.mark.parametrize("row", ["staged", "no-fetch"]) +def test_stamp_refused_by_S3_writes_nothing_prints_line(box: Box, row: str): + """S3 refuses (a foreign staged entry; a fetch that did not run): the + stamp is written by nobody, the line it would have written is printed, and + the checkout and origin are as they were (2).""" + c = box.seam() + extra = {} + if row == "staged": + _staged_foreign(box, c) + words = "staged entries this write did not make: handoffs/README.md" + else: + extra = {"LANES_NO_FETCH": "1"} + words = "LANES_NO_FETCH=1 is set" + before = box.snapshot(c) + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, **extra) + assert result.returncode == 2, result.stderr + assert words in result.stderr + assert "Nothing was written." in result.stderr + assert f"the stamp line it would have written under the handoff's header block: {STAMP}" in result.stderr + assert result.stdout == "" + assert box.snapshot(c) == before + assert (c / HANDOFF_REL).read_text(encoding="utf-8") == HANDOFF + _released(box, c) + + +@pytest.mark.parametrize("peer", ["another-file", "the-handoff"]) +def test_stamp_replays_on_new_content(box: Box, peer: str): + """A peer's commit lands at the moment of the push: the stamp's commit is + dropped and the verb runs again on origin's new tip, splicing the stamp + into the new content. One stamp reaches origin, and one line is printed.""" + c = box.seam() + base = HANDOFF + if peer == "another-file": + box.peer_prepares("handoffs/README.md", None, "# handoffs, by a peer\n", "a peer writes another file") + else: + base = HANDOFF.replace("Preserved task.\n", "Preserved task, and a peer's line.\n") + box.peer_prepares(HANDOFF_REL, "Preserved task.\n", "Preserved task, and a peer's line.\n", + "a peer edits the handoff") + box.race(prepared=True) + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 0, result.stderr + assert "push refused (attempt 1 of 6)" in result.stderr + published = box.origin_file(HANDOFF_REL) + assert published == stamped(base) + assert published.count(STAMP) == 1 + f = c / HANDOFF_REL + assert result.stdout == _line(f), "one line: the last attempt's" + assert box.subjects()[0] == SUBJECT + assert box.head(c) == box.origin_head() + assert box.unpublished(c) == [] + _released(box, c) + + +def test_stamp_that_does_not_prove_out_writes_nothing(box: Box): + """A one-line handoff with no newline would have the stamp glued to it: + the proof fails, and nothing is written (2).""" + c = box.seam() + box.write(c, HANDOFF_REL, "one line and no newline") + box.commit(c, "a handoff of one unterminated line", HANDOFF_REL) + box.git("-C", str(c), "push", "-q", "origin", "main") + tip = box.origin_head() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 2, result.stderr + assert "did not prove out" in result.stderr + assert STAMP in result.stderr + assert (c / HANDOFF_REL).read_text(encoding="utf-8") == "one line and no newline" + assert box.origin_head() == tip + assert result.stdout == "" + _released(box, c) + + +def test_stamp_over_an_uncommitted_change_to_the_handoff_is_refused(box: Box): + """S3 step 4 for the handoff: a change nobody committed would ride the + stamp's commit under its subject. Refused (2), and the change is left as + it was, unstamped.""" + c = box.seam() + f = c / HANDOFF_REL + edited = HANDOFF + "A line somebody wrote and did not commit.\n" + f.write_text(edited, encoding="utf-8") + tip = box.origin_head() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 2, result.stderr + assert f"these paths this write commits hold an uncommitted change: {HANDOFF_REL}" in result.stderr + assert f.read_text(encoding="utf-8") == edited + assert box.origin_head() == tip + assert result.stdout == "" + _released(box, c) + + +# ------------------------------------------------- --prepend + +def _block(box: Box) -> Path: + block = box.root / "block.md" + block.write_text(BLOCK, encoding="utf-8") + return block + + +def test_prepend_written_committed(box: Box): + """`lane-handoff`'s shape, moved: the block, `---` and a blank line, then + the old file whole; committed with the trailer, pushed, read back.""" + c = box.seam() + f = c / HANDOFF_REL + subject = f"handoff({LANE}@Eagle): PAUSED, a23" + result = box.write_handoff("--prepend", str(_block(box)), "--subject", subject) + assert result.returncode == 0, result.stderr + assert f.read_text(encoding="utf-8") == prepended(HANDOFF) + assert box.origin_file(HANDOFF_REL) == prepended(HANDOFF) + msg = box.last_message() + assert msg[0] == subject + assert f"Lane: {LANE}" in msg + assert result.stdout == _line(f) + assert box.unpublished(c) == [] + _released(box, c) + + +def _written_uncommitted_under_S3(box: Box, mode: str) -> None: + """S3 refuses the commit (a foreign staged entry); the handoff is written + all the same, nothing is committed or pushed, S3's words are printed, and + so is the path (2).""" + c = box.seam() + f = c / HANDOFF_REL + if mode == "prepend": + args, after = ("--prepend", str(_block(box))), prepended(HANDOFF) + else: + args, after = ("--whole", str(_prepared(box)), "--expect-digest", sha(f)), "# the handoff, written whole\n" + _staged_foreign(box, c) + head, tip = box.head(c), box.origin_head() + result = box.write_handoff(*args, "--subject", "handoff: PAUSED") + assert result.returncode == 2, result.stderr + assert "staged entries this write did not make: handoffs/README.md" in result.stderr + assert "the handoff is WRITTEN and NOT committed" in result.stderr + assert f.read_text(encoding="utf-8") == after + assert result.stdout == _line(f) + assert box.head(c) == head + assert box.origin_head() == tip + assert box.git("-C", str(c), "diff", "--cached", "--name-only").split() == ["handoffs/README.md"] + _released(box, c) + + +def test_prepend_refused_by_S3_still_writes_file_no_commit(box: Box): + """R-A11-11: a swap is never left unwritten, so S3's refusal leaves the + new top block written and uncommitted.""" + _written_uncommitted_under_S3(box, "prepend") + + +def test_whole_refused_by_S3_still_writes_file_no_commit(box: Box): + """The same for the skill's whole-file write (R-A11-11's path): the + prepared handoff is written, uncommitted, with S3's words and the path.""" + _written_uncommitted_under_S3(box, "whole") + + +# ------------------------------------------------- --whole + +def _prepared(box: Box, text: str = "# the handoff, written whole\n") -> Path: + prepared = box.root / "prepared.md" + prepared.write_text(text, encoding="utf-8") + return prepared + + +def test_whole_expect_digest_mismatch_exit_7_nothing_published(box: Box): + c = box.seam() + tip = box.origin_head() + result = box.write_handoff("--whole", str(_prepared(box)), "--expect-digest", "0" * 64, + "--subject", "handoff: PAUSED") + assert result.returncode == 7, result.stderr + assert "the handoff changed before publication; nothing was published" in result.stderr + assert (c / HANDOFF_REL).read_text(encoding="utf-8") == HANDOFF + assert result.stdout == "" + assert box.origin_head() == tip + _released(box, c) + + +def test_whole_replays_only_when_origin_copy_unchanged(box: Box): + """Origin moved under the push, and its copy of the handoff is still the + one this write started from: the whole-file write replays and lands.""" + c = box.seam() + f = c / HANDOFF_REL + box.peer_prepares("handoffs/README.md", None, "# handoffs, by a peer\n", "a peer writes another file") + box.race(prepared=True) + result = box.write_handoff("--whole", str(_prepared(box)), "--expect-digest", sha(f), + "--subject", "handoff: PAUSED") + assert result.returncode == 0, result.stderr + assert "push refused (attempt 1 of 6)" in result.stderr + assert box.origin_file(HANDOFF_REL) == "# the handoff, written whole\n" + assert box.origin_file("handoffs/README.md") == "# handoffs, by a peer\n" + assert result.stdout == _line(f) + _released(box, c) + + +def test_whole_refuses_with_commit_kept_exit_6_when_origin_changed_it(box: Box): + """S4.4: origin's copy moved under the push, so the whole-file write does + not replay over that change. The handoff stays written, its commit is kept + on HEAD and named (6), and origin keeps the peer's version.""" + c = box.seam() + f = c / HANDOFF_REL + box.peer_prepares(HANDOFF_REL, "Preserved task.\n", "Preserved task, and a peer's line.\n", + "a peer edits the handoff") + box.race(prepared=True) + result = box.write_handoff("--whole", str(_prepared(box)), "--expect-digest", sha(f), + "--subject", "handoff: PAUSED") + assert result.returncode == 6, result.stderr + kept = box.head(c) + assert "is no longer the one this whole-file write started from" in result.stderr + assert f"kept on HEAD: {kept}" in result.stderr + assert "push refused (attempt" not in result.stderr, "no replay" + assert f.read_text(encoding="utf-8") == "# the handoff, written whole\n" + assert "and a peer's line" in box.origin_file(HANDOFF_REL) + assert [line.split()[1] for line in box.unpublished(c)] == [kept] + assert result.stdout == _line(f) + _released(box, c) + + +# ------------------------------------------------- the translation (S2) + +def _estate_link(box: Box) -> str: + """`~/projects/repoS4/handoffs`, the estate link into W, and the cell + spelled through it (Amendment 4(e)).""" + estate = box.home / "projects" / "repoS4" + estate.mkdir(parents=True) + (estate / "handoffs").symlink_to(box.w / "handoffs" / "repoS4") + return f"~/projects/repoS4/handoffs/{LANE}.md" + + +@pytest.mark.parametrize("how", ["seam", "routed"]) +def test_estate_spelled_cell_is_written_in_checkout(box: Box, how: str): + """A cell spelled through the estate link resolves into W, and is + TRANSLATED, never followed: the lane's checkout's copy is written and + committed, and W's is left alone.""" + box.cell_is(_estate_link(box)) + c = box.seam() if how == "seam" else box.checkout(LANE) + w_file = box.w / HANDOFF_REL + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 0, result.stderr + assert (c / HANDOFF_REL).read_text(encoding="utf-8") == stamped(HANDOFF) + assert box.origin_file(HANDOFF_REL) == stamped(HANDOFF) + assert w_file.read_text(encoding="utf-8") == HANDOFF, "W's copy is never written" + assert result.stdout == _line(c / HANDOFF_REL) + _released(box, c) + + +@pytest.mark.parametrize("how", ["seam", "routed", "default"]) +def test_repo_relative_cell(tmp_path: Path, how: str): + """The repository-relative cell is joined to the checkout the write goes + through: the lane's own routed, the seam's, and W's with the opt-in off — + where every writer goes through W, as before Amendment 23.""" + box = Box(tmp_path, route=(how != "default")) + c = {"seam": box.seam, "routed": lambda: box.checkout(LANE), "default": lambda: box.w}[how]() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 0, result.stderr + assert result.stdout == _line(c / HANDOFF_REL) + assert box.origin_file(HANDOFF_REL) == stamped(HANDOFF) + if how != "default": + assert (box.w / HANDOFF_REL).read_text(encoding="utf-8") == HANDOFF + _released(box, c) + + +def _other_repository(box: Box, *, origin: bool = True) -> Path: + """Another repository, not the register: `/other`, its handoff + committed on `main`, and (with `origin`) a bare origin `main` tracks.""" + other = box.root / "other" + box.git("init", "-q", "-b", "main", str(other)) + box.write(other, "handoff.md", HANDOFF) + box.commit(other, "another repository's handoff", "handoff.md") + if origin: + bare = box.root / "other.git" + box.git("init", "-q", "--bare", "-b", "main", str(bare)) + box.git("-C", str(other), "remote", "add", "origin", str(bare)) + box.git("-C", str(other), "push", "-q", "-u", "origin", "main") + return other + + +def _committed_and_pushed_there(box: Box, other: Path, before: str, rel: str = "handoff.md") -> None: + """604's outcome: one commit on `main` in the handoff's own repository, + by pathspec, with this write's subject and `Lane:` trailer; its tree + clean; its origin holding it.""" + o = ["-C", str(other)] + assert box.git(*o, "status", "--porcelain") == "" + assert box.git(*o, "rev-parse", "HEAD~1").strip() == before + assert box.git(*o, "log", "-1", "--format=%B").strip().splitlines() == [SUBJECT, "", f"Lane: {LANE}"] + assert box.git(*o, "diff", "--name-only", "HEAD~1", "HEAD").split() == [rel] + assert (box.git("--git-dir", str(box.root / "other.git"), "rev-parse", "main").strip() + == box.git(*o, "rev-parse", "HEAD").strip()) + + +def test_a_cell_in_another_repository_is_committed_and_pushed_there(box: Box): + """Amendment 8(d), as lane openRepoTools-3 ruled 604 (#312 6368669583): + Amendment 23 S2 says another repository's handoff "is not the register's + and is unchanged", so it is committed and pushed in its own top level, + as `lane-start` and `lane-handoff` did at e2037b9 — by pathspec, under + this write's subject and `Lane:` trailer — and its tree is left clean. + It was written there and committed nowhere, ` M` for good. This checkout + and the register's origin are as they were.""" + other = _other_repository(box) + box.cell_is(str(other / "handoff.md")) + c = box.seam() + other_head = box.git("-C", str(other), "rev-parse", "HEAD").strip() + head, tip = box.head(c), box.origin_head() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 0, result.stderr + assert (f"{other / 'handoff.md'} is another repository's handoff (Amendment 8(d)): it is written " + "there, committed there and pushed to origin/main") in result.stderr + assert (other / "handoff.md").read_text(encoding="utf-8") == stamped(HANDOFF) + _committed_and_pushed_there(box, other, other_head) + assert box.head(c) == head + assert box.origin_head() == tip + assert result.stdout == _line(other / "handoff.md") + _released(box, c) + + +@pytest.mark.parametrize("how", ["no-origin", "no-git"]) +def test_an_8d_handoff_that_cannot_be_committed_or_pushed_there_is_written_and_said(box: Box, how: str): + """604's other outcomes, each with the handoff WRITTEN (R-A11-11): where + its repository has no origin to push to, the commit is made and kept on + HEAD there, and the look-first commands said (6); under LANES_NO_GIT + nothing is committed, and the command that commits it is said (0).""" + other = _other_repository(box, origin=False) + box.cell_is(str(other / "handoff.md")) + c = box.seam() + other_head = box.git("-C", str(other), "rev-parse", "HEAD").strip() + head, tip = box.head(c), box.origin_head() + extra = {"LANES_NO_GIT": "1"} if how == "no-git" else {} + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, **extra) + assert (other / "handoff.md").read_text(encoding="utf-8") == stamped(HANDOFF) + o = ["-C", str(other)] + if how == "no-origin": + assert result.returncode == 6, result.stderr + assert "and committed there, on main" in result.stderr + assert f"git -C {other} fetch origin main && git -C {other} log --oneline origin/main -3" in result.stderr + assert box.git(*o, "status", "--porcelain") == "" + assert box.git(*o, "rev-parse", "HEAD~1").strip() == other_head + assert box.git(*o, "log", "-1", "--format=%B").strip().splitlines() == [SUBJECT, "", f"Lane: {LANE}"] + else: + assert result.returncode == 0, result.stderr + assert "and NOT committed: LANES_NO_GIT=1" in result.stderr + assert f"git --literal-pathspecs -C {other} add -- handoff.md" in result.stderr + assert box.git(*o, "rev-parse", "HEAD").strip() == other_head + assert box.git(*o, "status", "--porcelain").split() == ["M", "handoff.md"] + assert box.head(c) == head + assert box.origin_head() == tip + assert result.stdout == _line(other / "handoff.md") + _released(box, c) + + +def test_an_8d_handoff_whose_origin_moved_is_fast_forwarded_first_then_committed_and_pushed(box: Box): + """604 without a rebasing pull (no line of `lanes-edit.sh` pulls, #198): + where the handoff's repository's origin moved first, by a commit that + does not touch the handoff, the branch is fetched and fast-forwarded + BEFORE the commit, as S3 does, so this write's commit sits on that + commit and the push goes through (0), its tree clean.""" + other = _other_repository(box) + peer = box.root / "other-peer" + box.git("clone", "-q", str(box.root / "other.git"), str(peer)) + box.write(peer, "notes.md", "a peer's note\n") + box.commit(peer, "a peer's unrelated commit", "notes.md") + box.git("-C", str(peer), "push", "-q", "origin", "main") + moved = box.git("-C", str(peer), "rev-parse", "HEAD").strip() + box.cell_is(str(other / "handoff.md")) + c = box.seam() + head, tip = box.head(c), box.origin_head() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 0, result.stderr + assert "committed there and pushed to origin/main" in result.stderr + assert (other / "handoff.md").read_text(encoding="utf-8") == stamped(HANDOFF) + _committed_and_pushed_there(box, other, moved) + assert (other / "notes.md").read_text(encoding="utf-8") == "a peer's note\n" + assert box.head(c) == head + assert box.origin_head() == tip + _released(box, c) + + +def test_a_handoff_in_another_clone_of_the_register_elsewhere_is_written_never_committed(box: Box): + """A57, because of 604: the 8(d) branch now commits and pushes in the + handoff's own top level, but never in one that carries the register + (A44's test). A file under `handoffs/` of another clone of the register + that is NOT beside W (601 refuses one beside W) is that clone's lane's, + not Amendment 8(d)'s "not the register's", and a commit and push there + would publish to the register's origin outside S3 and S4, from a clone + this lane does not hold. So it is written there and NOT committed, as + before 604 (0), and said: that clone's HEAD, this checkout and the + register's origin are as they were. Whether it should be refused + instead is put to lane openRepoTools-3 (the body's Q4).""" + c = box.seam() + d = box.clone(box.root / "elsewhere-clone") + f = d / HANDOFF_REL + text = f.read_text(encoding="utf-8") + d_head = box.git("-C", str(d), "rev-parse", "HEAD").strip() + mine, tip = box.snapshot(c), box.origin_head() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, "--target", str(f)) + assert result.returncode == 0, result.stderr + assert (f"{f} is in {d}, another clone of the register (its top level carries {REGISTER} too), " + f"not this write's checkout {c}, so it is not Amendment 8(d)'s: it is written there and " + "NOT committed") in result.stderr + assert "committed there" not in result.stderr + assert f.read_text(encoding="utf-8") == stamped(text) + assert box.git("-C", str(d), "rev-parse", "HEAD").strip() == d_head + assert box.git("-C", str(d), "status", "--porcelain").split() == ["M", HANDOFF_REL] + assert box.origin_head() == tip + assert box.snapshot(c) == mine + assert result.stdout == _line(f) + _released(box, c) + + +def test_a_cell_through_a_link_into_W_is_refused_S5(box: Box): + """S5: a handoff in the lane's checkout that is a link into W would write + W's file. Refused before a byte (2), naming the checkout and the lane.""" + c = box.seam() + f = c / HANDOFF_REL + f.unlink() + f.symlink_to(box.w / HANDOFF_REL) + result = box.write_handoff("--prepend", str(_block(box)), "--subject", "handoff: PAUSED") + assert result.returncode == 2, result.stderr + assert "the readers' copy, where no tool writes (Amendment 23 S5)" in result.stderr + assert f"LANES_LANE={LANE}" in result.stderr + assert (box.w / HANDOFF_REL).read_text(encoding="utf-8") == HANDOFF + assert result.stdout == "" + _released(box, c) + + +# ------------------------------------------------- the restart (Amendment 17) + +def test_restart_fences_under_sidecar_lock(box: Box): + """The restart's fence and the replace run under the workstation's sidecar + lock, taken INSIDE the checkout's: while another act holds it, the write + waits holding its checkout lock and writes nothing; then it lands.""" + c = box.seam() + f = c / HANDOFF_REL + box.restart(f) + sidecar = box.lanes_root / ".lane-state.lock" + holder = subprocess.Popen(["sleep", "300"]) + try: + sidecar.mkdir(parents=True) + (sidecar / "pid").write_text(f"{holder.pid} {ME}\n", encoding="utf-8") + waiter = box.popen("write-handoff", LANE, "--stamp", STAMP, "--subject", SUBJECT, + "--restart", *FENCE) + time.sleep(WATCH) + assert waiter.poll() is None, "it waits on the workstation's sidecar lock" + assert box.lock_of(c).is_dir(), "…holding its checkout lock, taken first" + assert f.read_text(encoding="utf-8") == HANDOFF + finally: + holder.kill() + holder.wait() + out, err = waiter.communicate(timeout=120) + assert waiter.returncode == 0, err + assert f.read_text(encoding="utf-8") == stamped(HANDOFF) + assert out == _line(f) + _released(box, c) + + +REFUSED_FENCES = { + "lost": (("--restart", "op-a23", "4", "1", "starting", "sid-a23"), 7, + "this handoff publisher lost restart ownership"), + "unbound": (("--restart", *FENCE), 7, "the publication target is not the operation-bound handoff"), + "active": ((), 7, "an active restart owns this handoff"), +} + + +@pytest.mark.parametrize("case", REFUSED_FENCES) +def test_restart_fence_refusals_write_nothing(box: Box, case: str): + c = box.seam() + f = c / HANDOFF_REL + box.restart(c / "handoffs" / "README.md" if case == "unbound" else f) + flags, code, words = REFUSED_FENCES[case] + tip = box.origin_head() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, *flags) + assert result.returncode == code, result.stderr + assert words in result.stderr + assert f.read_text(encoding="utf-8") == HANDOFF + assert result.stdout == "" + assert box.origin_head() == tip + assert box.intent_field("prepared_digest") == "none", "nothing was reserved" + _released(box, c) + + +def test_intent_bound_to_W_path_is_translated(box: Box): + """An intent an older `lane-handoff` bound to W's spelling of the file + still fences the lane's own copy: the bound path is TRANSLATED into the + checkout before it is compared. `publish-handoff` keeps the strict test.""" + w_file = box.w / HANDOFF_REL + box.restart(w_file) + made = box.helper("set-row-state", LANE, "LIVE · the checkout is made") + assert made.returncode == 0, made.stderr + c = box.checkout(LANE) + strict = box.helper("publish-handoff", LANE, str(c / HANDOFF_REL), str(_prepared(box)), + "--expect-digest", sha(c / HANDOFF_REL), "--operation", FENCE[0], + "--generation", FENCE[1], "--attempt", FENCE[2], "--state", FENCE[3], + "--transcript", FENCE[4]) + assert strict.returncode == 2, strict.stderr + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, "--restart", *FENCE) + assert result.returncode == 0, result.stderr + assert (c / HANDOFF_REL).read_text(encoding="utf-8") == stamped(HANDOFF) + assert w_file.read_text(encoding="utf-8") == HANDOFF + assert box.intent_field("handoff") == str(w_file), "the intent itself is not rewritten" + _released(box, c) + + +def test_prepared_digest_recorded_before_publication(box: Box): + """`lane-start`'s reservation, in-process: the stamped file's digest is the + intent's prepared digest BEFORE the bytes are replaced — here the replace + fails (no temporary can be made beside the file), and the reservation is + already there, with the bound digest and the operation kept.""" + if os.geteuid() == 0: + pytest.skip("a directory this process cannot write needs an unprivileged runner") + c = box.seam() + f = c / HANDOFF_REL + box.restart(f) + bound = box.intent_field("digest") + assert bound == sha(f) + f.parent.chmod(0o555) + try: + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, "--restart", *FENCE, + "--expected-digest", bound) + finally: + f.parent.chmod(0o755) + assert result.returncode == 1, result.stderr + assert "the publication temporary file could not be made" in result.stderr + assert f.read_text(encoding="utf-8") == HANDOFF + assert box.intent_field("prepared_digest") == sha_of(stamped(HANDOFF)) + assert box.intent_field("digest") == bound + assert box.intent_field("reason") == "owned handoff stamp prepared" + assert (box.intent_field("operation"), box.intent_field("state")) == (FENCE[0], FENCE[3]) + again = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, "--restart", *FENCE, + "--expected-digest", bound) + assert again.returncode == 0, again.stderr + assert sha(f) == box.intent_field("prepared_digest") + _released(box, c) + + +def test_restart_pre_image_must_be_the_bound_digest(box: Box): + """The handoff changed under the restart: `lane-start`'s refusal, and + nothing written or reserved. A restart fence, so 7 (603): this verb's 2 + is S3's refusal.""" + c = box.seam() + f = c / HANDOFF_REL + box.restart(f) + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, "--restart", *FENCE, + "--expected-digest", "1" * 64) + assert result.returncode == 7, result.stderr + assert "the restart handoff changed before its owned resume stamp" in result.stderr + assert f.read_text(encoding="utf-8") == HANDOFF + assert box.intent_field("prepared_digest") == "none" + _released(box, c) + + +def test_under_a_restart_2_is_the_checkouts_refusal_and_a_fence_is_7(box: Box): + """603 of lane openRepoTools-3's review of #312: `write-handoff`'s 2 meant + both "S3 refused this checkout" and "the restart fence refused" (its bound + file, its pre-image), so slice 2b's `lane-start`, which notes the first + and dies on the second under an active restart (act2-plan §2.7), could + not tell them apart. This verb answers the shared act's two fence 2s with + 7 (the cases above), and S3's refusal under the same `--restart` is 2 + still: the stamp not written, its line said, nothing reserved. + `publish-handoff` keeps its documented 2 for the bound file + (`test_publish_handoff_refuses_a_file_the_restart_did_not_bind`).""" + c = box.seam() + f = c / HANDOFF_REL + box.restart(f) + bound = box.intent_field("digest") + _staged_foreign(box, c) + tip = box.origin_head() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, "--restart", *FENCE, + "--expected-digest", bound) + assert result.returncode == 2, result.stderr + assert "the stamp is NOT written" in result.stderr + assert f"the stamp line it would have written under the handoff's header block: {STAMP}" in result.stderr + assert "nothing was published" not in result.stderr.lower() + assert f.read_text(encoding="utf-8") == HANDOFF + assert box.intent_field("prepared_digest") == "none" + assert box.origin_head() == tip + assert result.stdout == "" + _released(box, c) + + +def test_only_the_stamp_reserves(box: Box): + """`lane-handoff`'s prepend binds at `preparing` and the digest is taken + again at `pending`: a fenced prepend writes, and reserves nothing.""" + c = box.seam() + f = c / HANDOFF_REL + box.intent("preparing", "--expect", "none", "--operation", FENCE[0], "--generation", FENCE[1], + "--handoff", str(f)) + result = box.write_handoff("--prepend", str(_block(box)), "--subject", "handoff: PAUSED", + "--restart", FENCE[0], FENCE[1], "0", "preparing", "none") + assert result.returncode == 0, result.stderr + assert f.read_text(encoding="utf-8") == prepended(HANDOFF) + assert box.intent_field("prepared_digest") == "none" + _released(box, c) + + +def test_the_sidecar_lock_is_released_before_the_push(box: Box): + """The workstation's lock is held for the fence and the replace only; the + push and the read-back run under the checkout's lock alone.""" + c = box.seam() + sidecar = shlex.quote(str(box.lanes_root / ".lane-state.lock")) + checkout_lock = shlex.quote(str(box.lock_of(c))) + seen = box.root / "at-push" + box.use_net('[ "$1" = push ] || exit 0\n' + f"{{ [ -d {sidecar} ] && echo sidecar; [ -d {checkout_lock} ] && echo checkout; }} " + f">> {shlex.quote(str(seen))}\nexit 0\n") + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 0, result.stderr + assert seen.read_text(encoding="utf-8").split() == ["checkout"] + _released(box, c) + + +# ------------------------------------------------- always: W + +def test_W_untouched(box: Box): + """Routed, every mode writes the lane's own checkout and origin. W is the + readers' copy: its HEAD, its working tree and its reflog do not move.""" + w_before, reflog = box.w_state(), box.w_reflog() + stamp = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert stamp.returncode == 0, stamp.stderr + c = box.checkout(LANE) + prepend = box.write_handoff("--prepend", str(_block(box)), "--subject", "handoff: PAUSED") + assert prepend.returncode == 0, prepend.stderr + whole = box.write_handoff("--whole", str(_prepared(box)), "--expect-digest", sha(c / HANDOFF_REL), + "--subject", "handoff: whole") + assert whole.returncode == 0, whole.stderr + assert box.origin_file(HANDOFF_REL) == "# the handoff, written whole\n" + assert box.subjects()[:3] == ["handoff: whole", "handoff: PAUSED", SUBJECT] + assert box.w_state() == w_before + assert box.w_reflog() == reflog + assert (box.w / HANDOFF_REL).read_text(encoding="utf-8") == HANDOFF + assert box.head(c) == box.origin_head() + assert box.unpublished(c) == [] + _released(box, c) + + +# ------------------------------------------------- handoff-path + +@pytest.mark.parametrize("how", ["routed", "seam", "default"]) +def test_handoff_path_names_where_write_handoff_writes(tmp_path: Path, how: str): + """A read: the row's cell translated into the checkout `write-handoff` + writes through. Routed, the path is derived before the checkout exists, + and `--create` makes it.""" + box = Box(tmp_path, route=(how != "default")) + c = {"seam": box.seam, "routed": lambda: box.checkout(LANE), "default": lambda: box.w}[how]() + result = box.helper("handoff-path", LANE) + assert result.returncode == 0, result.stderr + assert result.stdout == f"{c / HANDOFF_REL}\n" + if how == "routed": + assert not c.exists(), "a read makes no checkout" + made = box.helper("handoff-path", LANE, "--create") + assert made.returncode == 0, made.stderr + assert made.stdout == f"{c / HANDOFF_REL}\n" + assert (c / HANDOFF_REL).is_file() + for argv in (("repoS4-9",), ("repoS4-9", "--create")): + none = box.helper("handoff-path", *argv) + assert none.returncode == 8, none.stderr + assert "no row for it in the register" in none.stderr + assert none.stdout == "" + assert not box.checkout("repoS4-9").exists(), "a lane with no row gets no checkout" + + +# ------------------------------------------------- usage + +@pytest.mark.parametrize("argv", [ + ("--subject", SUBJECT), + ("--stamp", STAMP), + ("--stamp", STAMP, "--prepend", "x", "--subject", SUBJECT), + ("--whole", "x", "--subject", SUBJECT), + ("--stamp", STAMP, "--subject", SUBJECT, "--expected-digest", "0" * 64), + ("--stamp", "two\nlines", "--subject", SUBJECT), + ("--stamp", STAMP, "--subject", SUBJECT, "--restart", "op"), + ("--stamp", "", "--subject", SUBJECT), + ("--stamp", STAMP, "--subject", "two\nlines"), + ("--whole", "x", "--expect-digest", "g" * 64, "--subject", SUBJECT), + ("--stamp", STAMP, "--subject", SUBJECT, "--expect-digest", "0" * 64), + ("--stamp", STAMP, "--subject", SUBJECT, "--restart", *FENCE, "--expected-digest", "abc"), + ("--stamp", STAMP, "--subject", SUBJECT, "--restart", "", *FENCE[1:]), +], ids=["no-mode", "no-subject", "two-modes", "whole-no-digest", "expected-no-restart", + "stamp-two-lines", "restart-short", "stamp-empty", "subject-two-lines", + "whole-digest-not-hex", "expect-digest-with-stamp", "expected-digest-short", + "restart-empty-fence"]) +def test_usage_is_64_and_writes_nothing(box: Box, argv): + """Every argument is checked before the lock, and a bad one is 64 with + nothing written and no fetch made. m2a's battery found six checks no case + supplied the bad value for (its A02-A10): an empty stamp, a subject of two + lines, an `--expect-digest` that is not hex, one beside `--stamp`, an + `--expected-digest` that is not 64 long, and an empty fence, which left + the operation empty, so the write went out unfenced.""" + c = box.seam() + box.use_net() + before = box.snapshot(c) + result = box.write_handoff(*argv) + assert result.returncode == 64, result.stderr + assert box.snapshot(c) == before + assert box.net_ops() == [] + + +def test_write_handoff_for_a_lane_with_no_row_is_8(box: Box): + """No row, no handoff cell: nothing to write (8), and the stamp it would + have written is said.""" + c = box.seam() + before = box.snapshot(c) + result = box.helper("write-handoff", "repoS4-9", "--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 8, result.stderr + assert "lane repoS4-9 has no row in the register" in result.stderr + assert f"the stamp line it would have written under the handoff's header block: {STAMP}" in result.stderr + assert result.stdout == "" + assert box.snapshot(c) == before + _released(box, c) + + +def test_a_block_with_no_last_newline_is_refused(box: Box): + c = box.seam() + block = box.root / "block.md" + block.write_text("a block whose last line has no newline", encoding="utf-8") + result = box.write_handoff("--prepend", str(block), "--subject", "handoff: PAUSED") + assert result.returncode == 64, result.stderr + assert (c / HANDOFF_REL).read_text(encoding="utf-8") == HANDOFF + + +# ------------------------------------------------- the seam, asked on S3's tip (A28) + +def test_a_managed_marker_landing_inside_the_s3_window_is_refused(box: Box): + """The managed seam is asked before the lock on this checkout's + origin/main as it last stood, and AGAIN on what S3 fetched: a peer's row + carrying managed-owner vocabulary, landed at the moment of S3's fetch, is + read, and the stamp is refused with nothing written.""" + c = box.seam() + f = c / HANDOFF_REL + box.peer_prepares(REGISTER, f"| handoffs/repoS4/{LANE}.md | LIVE · seeded |", + f"| handoffs/repoS4/{LANE}.md | LIVE · managed-owner: daemon-a23 |", + "a peer's row names a managed owner") + peer = shlex.quote(str(box.peer)) + count = shlex.quote(str(box.count)) + box.use_net('[ "$1" = fetch ] || exit 0\n' + f'[ -e {count} ] && exit 0\n' + f"echo 1 > {count}\n" + f"git -C {peer} push -q origin main\n" + "exit 0\n") + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode in (1, 2), result.stderr + assert "managed ledger" in result.stderr + assert f.read_text(encoding="utf-8") == HANDOFF, "nothing written" + assert "managed-owner: daemon-a23" in box.origin_register(), "the peer's row landed" + assert box.origin_file(HANDOFF_REL) == HANDOFF + assert result.stdout == "" + assert box.net_ops()[0] == "fetch", "the marker landed at S3's fetch, after the first ask" + _released(box, c) + + +# ------------------------------------------------- a lane with no row, routed (A23) + +@pytest.mark.parametrize("fetch", ["fetched", "no-fetch"]) +def test_routed_write_handoff_for_a_lane_with_no_row_clones_nothing(box: Box, fetch: str): + """R4-1's rule for this verb: routed, a lane with no row has no handoff, + and it is refused (2) before its checkout is cloned — after one fetch of + W, or at once under `LANES_NO_FETCH=1`, which a writer cannot run on.""" + extra = {"LANES_NO_FETCH": "1"} if fetch == "no-fetch" else {} + w_before, tip = box.w_state(), box.origin_head() + result = box.helper("write-handoff", "repoS4-9", "--stamp", STAMP, "--subject", SUBJECT, **extra) + assert result.returncode == 2, result.stderr + if fetch == "fetched": + assert "writes the handoff that lane's row names: there is none to write" in result.stderr + else: + assert "LANES_NO_FETCH=1 is set" in result.stderr + assert "was not cloned" in result.stderr + assert not box.checkout("repoS4-9").exists(), "nothing is cloned for a lane with no row" + assert box.w_state() == w_before + assert box.origin_head() == tip + assert result.stdout == "" + + +# ------------------------------------------------- set-restart-intent --handoff + +def _bound(box: Box, cell: str) -> subprocess.CompletedProcess: + """A `/ctx`'s `pending`, bound to `cell` as the caller spells it.""" + return box.helper("set-restart-intent", LANE, "pending", "--expect", "none", "--operation", FENCE[0], + "--generation", FENCE[1], "--handoff", cell) + + +@pytest.mark.parametrize("how", ["seam", "routed", "default"]) +def test_set_restart_intent_binds_a_repository_relative_cell_in_the_checkout(tmp_path: Path, how: str): + """The row's repository-relative cell is translated into the checkout the + lane's writes go through and stored as that absolute path, with that + file's digest; the owned stamp then lands on it, fenced and reserved.""" + box = Box(tmp_path, route=(how != "default")) + if how == "routed": + made = box.helper("handoff-path", LANE, "--create") + assert made.returncode == 0, made.stderr + c = {"seam": box.seam, "routed": lambda: box.checkout(LANE), "default": lambda: box.w}[how]() + f = c / HANDOFF_REL + made = _bound(box, HANDOFF_REL) + assert made.returncode == 0, made.stderr + assert f"--handoff {HANDOFF_REL} is bound as {f}" in made.stderr + assert box.intent_field("handoff") == str(f) + assert box.intent_field("digest") == sha(f) + box.intent("starting", "--expect", "pending", "--expect-operation", FENCE[0], + "--attempt", FENCE[2], "--new-transcript", FENCE[4]) + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, "--restart", *FENCE, + "--expected-digest", box.intent_field("digest")) + assert result.returncode == 0, result.stderr + assert f.read_text(encoding="utf-8") == stamped(HANDOFF) + assert box.intent_field("prepared_digest") == sha(f) + if how != "default": + assert (box.w / HANDOFF_REL).read_text(encoding="utf-8") == HANDOFF + + +def test_set_restart_intent_binds_the_estate_spelling_in_the_checkout(box: Box): + """`~/…` through the estate link is W's file, and is bound as the + checkout's copy of it, never followed into W.""" + cell = _estate_link(box) + c = box.seam() + made = _bound(box, cell) + assert made.returncode == 0, made.stderr + assert box.intent_field("handoff") == str(c / HANDOFF_REL) + assert box.intent_field("digest") == sha(c / HANDOFF_REL) + + +def test_set_restart_intent_keeps_an_absolute_path_as_given(box: Box): + """Every older caller's spelling: an absolute path is stored exactly as + given, W's included — `lane-start` compares it with the file it resolves + in W until slice 2b, and the older `lane-handoff` publishes that file.""" + w_file = box.w / HANDOFF_REL + made = _bound(box, str(w_file)) + assert made.returncode == 0, made.stderr + assert box.intent_field("handoff") == str(w_file) + assert box.intent_field("digest") == sha(w_file) + assert "is bound as" not in made.stderr + + +@pytest.mark.parametrize("cell", ["notes/handoff.md", "handoffs/../lanes/LANES.md", + "handoffs/./repoS4/repoS4-1.md", "~user/handoff.md"]) +def test_set_restart_intent_refuses_any_other_relative_path(box: Box, cell: str): + """Only the row's own spellings are translated; any other relative path + is still refused (64) and nothing is written.""" + made = _bound(box, cell) + assert made.returncode == 64, made.stderr + assert "--handoff" in made.stderr + assert not (box.state / LANE / "restart-intent.yaml").exists() + + +# ------------------------------------------------- rv2a's pre-review of slice 2a + +#: A handoff the checkout has and git has never tracked: a new lane's first, or +#: a peer's. +NEW_REL = "handoffs/repoS4/new-untracked.md" + + +@pytest.mark.parametrize("mode", ["stamp", "prepend", "whole"]) +def test_an_untracked_handoff_is_refused_and_never_dropped(box: Box, mode: str): + """MUST 1: the row names a handoff the checkout has and git has never + tracked, and a peer's commit lands at the push. This write's commit would + add the file under its own subject, and the lost race's replay dropped that + commit and the file with it (8, "Nothing was written"). S3 step 4 counts it + as a pending change: refused (2) before any commit, `--stamp` writing + nothing and `--prepend`/`--whole` writing it, uncommitted (R-A11-11). The + file is still there in every mode, still untracked, and nothing is pushed.""" + c = box.seam() + box.cell_is(NEW_REL) + f = c / NEW_REL + f.write_text(HANDOFF, encoding="utf-8") + box.race() + if mode == "stamp": + args, after = ("--stamp", STAMP), HANDOFF + elif mode == "prepend": + args, after = ("--prepend", str(_block(box))), prepended(HANDOFF) + else: + args, after = ("--whole", str(_prepared(box)), "--expect-digest", sha(f)), "# the handoff, written whole\n" + result = box.write_handoff(*args, "--subject", "handoff: PAUSED") + assert result.returncode == 2, result.stderr + assert f"{NEW_REL}, this write's handoff, is not tracked yet" in result.stderr + assert f.read_text(encoding="utf-8") == after + assert box.git("-C", str(c), "status", "--porcelain", "--", NEW_REL).split() == ["??", NEW_REL] + assert box.head(c) == box.origin_head(), "S3's fast-forward, and no commit of this write" + assert "push" not in box.net_ops() + assert NEW_REL not in box.git("--git-dir", str(box.origin), "ls-tree", "-r", "--name-only", "main").split() + if mode == "stamp": + assert result.stdout == "" + assert f"the stamp line it would have written under the handoff's header block: {STAMP}" in result.stderr + else: + assert result.stdout == _line(f) + assert f"git -C {c} add -- {NEW_REL} && git -C {c} commit" in result.stderr + _released(box, c) + + +@pytest.mark.parametrize("handoff", ["untracked", "tracked"]) +def test_the_recovery_under_another_S3_refusal_names_git_add_for_an_untracked_handoff(box: Box, handoff: str): + """rv2a-c2's NIT 1 (its probe N1): S3 refuses for a foreign staged entry, + and the row names a handoff git does not track. `--prepend` writes it, + uncommitted (2), and the recovery's commit by pathspec needs the `git + add` first: git refuses a pathspec it does not know. The note left the + `add` out wherever S3 had refused for a reason of its own. It names it + now (A45), and the two commands, run as printed, commit the handoff + alone, the foreign entry still staged. A tracked handoff's note names no + `add`, as before.""" + c = box.seam() + rel = NEW_REL if handoff == "untracked" else HANDOFF_REL + if handoff == "untracked": + box.cell_is(NEW_REL) + (c / NEW_REL).write_text(HANDOFF, encoding="utf-8") + _staged_foreign(box, c) + result = box.write_handoff("--prepend", str(_block(box)), "--subject", "handoff: PAUSED") + err = result.stderr + assert result.returncode == 2, err + assert "staged entries this write did not make: handoffs/README.md" in err + assert "the handoff is WRITTEN and NOT committed" in err + add = f"git -C {c} add -- {rel}" + commit = f'git -C {c} commit -m "handoff: PAUSED" -m "Lane: {LANE}" -- {rel}' + if handoff == "tracked": + assert f"publish it: {commit}, then" in err, err + assert f"add -- {rel}" not in err + _released(box, c) + return + assert f"publish it: {add} && {commit}, then" in err, err + for printed in (add, commit): + box.git(*shlex.split(printed)[1:]) + assert box.git("-C", str(c), "show", "--name-only", "--format=", "HEAD").split() == [NEW_REL] + assert (c / NEW_REL).read_text(encoding="utf-8") == prepended(HANDOFF) + assert box.git("-C", str(c), "diff", "--cached", "--name-only").split() == ["handoffs/README.md"] + _released(box, c) + + +@pytest.mark.parametrize("how", ["target", "cell", "tracked-file", "git-config", "dotdot"]) +def test_a_target_outside_handoffs_is_refused_and_nothing_written(box: Box, how: str): + """SHOULD 1: this verb replaces a file and pushes it, so in the checkout + it writes a file under `handoffs/` and nothing else. A `--target` naming + the register, or a row cell that climbs out of `handoffs/` to the lane's + append-only object log, is refused (2) before a byte: the checkout's copy + and origin's are as they were, and nothing is printed on stdout. The rule + is `handoffs/` itself, not only the register's and `lanes/`'s exclusions + (m2a's E01 and E02): a tracked file elsewhere in the tree, this + checkout's own `.git/config`, and a target whose last part is `..` are + refused the same way.""" + if how == "tracked-file": + box.peer_pushes("docs/plan.md", "# a plan, tracked outside handoffs/\n") + c = box.seam() + if how == "target": + rel = REGISTER + args = ("--whole", str(_prepared(box, "# not a register any more\n")), + "--expect-digest", sha(c / REGISTER), "--target", REGISTER) + elif how == "cell": + rel = f"lanes/log/{LANE}.md" + box.cell_is(f"handoffs/../{rel}") + args = ("--prepend", str(_block(box))) + elif how == "tracked-file": + rel = "docs/plan.md" + args = ("--stamp", STAMP, "--target", str(c / rel)) + elif how == "git-config": + rel = ".git/config" + args = ("--whole", str(_prepared(box, "[core\n")), "--expect-digest", sha(c / rel), + "--target", str(c / rel)) + else: + rel = "handoffs/repoS4/.." + args = ("--stamp", STAMP, "--target", str(c / rel)) + watched = c / (HANDOFF_REL if how == "dotdot" else rel) + before, tip = watched.read_bytes(), box.origin_head() + published = None if how in ("git-config", "dotdot") else box.origin_file(rel) + result = box.write_handoff(*args, "--subject", "handoff: PAUSED") + assert result.returncode == 2, result.stderr + assert f"is {rel} in the checkout {c}, which is not a file under handoffs/" in result.stderr + assert "Nothing was written." in result.stderr + assert watched.read_bytes() == before + if published is not None: + assert box.origin_file(rel) == published + assert box.origin_head() == tip + assert box.git("-C", str(c), "config", "gc.auto").strip() == "0", "git still reads the checkout's config" + assert result.stdout == "" + _released(box, c) + + +@pytest.mark.parametrize("how", ["whole-register", "prepend-log", "cell-register"]) +def test_a_target_in_another_clone_of_the_register_keeps_the_handoffs_rule(box: Box, how: str): + """rv2a-c2's SHOULD 1 (its probe N3): the `handoffs/` rule held only in + this checkout, so a `--target`, or a row cell, naming a file of ANOTHER + clone of the register — a sibling lane's checkout, `-lanes//` — + was taken for another repository's handoff (8(d)): `--whole` replaced + that clone's register in place and `--prepend` put a block on its + append-only object log, uncommitted, exit 0, and that lane's next write + then refused at S3 step 4 over a change it never made. The rule holds + relative to that clone's own top level (A44): 2, nothing written there or + here, nothing on stdout.""" + d = box.lanes_root / NEIGHBOUR + rel = f"lanes/log/{NEIGHBOUR}.md" if how == "prepend-log" else REGISTER + if how == "cell-register": + box.cell_is(str(d / REGISTER)) + c = box.seam() + box.clone(d) + if how == "whole-register": + args = ("--whole", str(_prepared(box, "# not a register any more\n")), + "--expect-digest", sha(d / REGISTER), "--target", str(d / REGISTER)) + elif how == "prepend-log": + args = ("--prepend", str(_block(box)), "--target", str(d / rel)) + else: + args = ("--stamp", STAMP) + text, theirs, mine = (d / rel).read_bytes(), box.snapshot(d), box.snapshot(c) + result = box.write_handoff(*args, "--subject", "handoff: PAUSED") + assert result.returncode == 2, result.stderr + assert f"is {rel} in {d}, another checkout of the register" in result.stderr + assert "Nothing was written." in result.stderr + assert "another repository's handoff" not in result.stderr + assert (d / rel).read_bytes() == text + assert box.snapshot(d) == theirs + assert box.snapshot(c) == mine + assert result.stdout == "" + _released(box, c) + + +def _beside_w_dir(box: Box, where: str) -> Path: + """Where another checkout of the register BESIDE W is: a sibling lane's + `-lanes//` (S1), or a per-profile `-/` clone.""" + if where == "lanes-sibling": + return box.lanes_root / NEIGHBOUR + return box.w.parent / f"{box.w.name}-p2" + + +def _beside_w(box: Box, where: str) -> Path: + """Another checkout of the register beside W, cloned.""" + return box.clone(_beside_w_dir(box, where)) + + +def _climb(box: Box, f: Path) -> str: + """`f` spelled as a repository-relative cell of the seam `/c` that + climbs out of it with `..` (lane 3's 723 scenario's second spelling).""" + return "handoffs/../" + os.path.relpath(f, box.root / "c") + + +@pytest.mark.parametrize("how", ["target", "abs-cell", "climb-cell"]) +@pytest.mark.parametrize("where", ["lanes-sibling", "profile-clone"]) +def test_a_handoff_in_another_checkout_of_the_register_beside_W_is_refused(box: Box, where: str, how: str): + """601 (MUST) and Q3 of lane openRepoTools-3's review of #312. A file + under `handoffs/` in another checkout of the register beside W was taken + for another repository's handoff (8(d)): written there, uncommitted, + exit 0, and that lane's own next `--stamp` refused at S3 step 4 over a + change it never made, its next `--prepend` printed a recovery that would + commit this lane's line under its own trailer. This test pinned that as + `test_a_handoff_in_another_clone_of_the_register_is_still_another_repositorys` + until lane 3's ruling. A lane's handoff is written in its own checkout + only, so the path refuses it (2), by `--target` or by an absolute cell: + nothing written there or here, the stamp line said, nothing on stdout. + A relative cell that climbs there with `..` is refused the same (2), by + 602's rule, which asks the spelling first (A55).""" + if how == "abs-cell": + box.cell_is(str(_beside_w_dir(box, where) / HANDOFF_REL)) + if how == "climb-cell": + box.cell_is(_climb(box, _beside_w_dir(box, where) / HANDOFF_REL)) + c = box.seam() + d = _beside_w(box, where) + f = d / HANDOFF_REL + args = ("--target", str(f)) if how == "target" else () + text, theirs, mine = f.read_bytes(), box.snapshot(d), box.snapshot(c) + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, *args) + assert result.returncode == 2, result.stderr + if how == "climb-cell": + assert (f"lane {LANE}'s handoff {_climb(box, f)} is a repository-relative spelling, joined to " + f"this write's checkout {c}, and its '.' or '..' components climb out of it (to {f})" + ) in result.stderr + else: + assert f"is {f}, in another checkout of the register beside W ({box.w})" in result.stderr + assert "Nothing was written." in result.stderr + assert "another repository's handoff" not in result.stderr + assert f"the stamp line it would have written under the handoff's header block: {STAMP}" in result.stderr + assert f.read_bytes() == text + assert box.snapshot(d) == theirs, "that lane's checkout is as it was: nothing pending for its S3" + assert box.snapshot(c) == mine + assert result.stdout == "" + _released(box, c) + + +def _unreadable(box: Box, d: Path, how: str, c: Path) -> dict: + """Make the clone at `d` one git cannot read: a broken line in its + config, or another owner (git's `dubious ownership`, with this box's own + repositories named safe). Returns the environment the write runs with.""" + extra = {} + if how == "bad-config": + with open(d / ".git" / "config", "a", encoding="utf-8") as fh: + fh.write("[broken\n") + else: + safe = (c, box.w, box.origin, box.seed, box.peer) + (box.home / ".gitconfig").write_text( + "[safe]\n" + "".join(f"\tdirectory = {x}\n" for x in safe), encoding="utf-8") + extra = {"GIT_TEST_ASSUME_DIFFERENT_OWNER": "1"} + probe = subprocess.run(["git", "-C", str(d), "rev-parse", "--show-toplevel"], + env=dict(box.env, **extra), capture_output=True, text=True, check=False) + assert probe.returncode != 0, ("git can still read the clone", probe.stdout) + mine = subprocess.run(["git", "-C", str(c), "rev-parse", "--show-toplevel"], + env=dict(box.env, **extra), capture_output=True, text=True, check=False) + assert mine.returncode == 0, ("git cannot read this checkout either", mine.stderr) + return extra + + +@pytest.mark.parametrize("how", ["bad-config", "other-owner"]) +@pytest.mark.parametrize("where", ["beside-W", "elsewhere"]) +def test_a_clone_git_cannot_read_is_never_taken_for_no_repository(box: Box, where: str, how: str): + """601's second half: A44 and A47 asked git about the other clone and + read a failed answer as "no repository" (against R22), so where git + could not read that clone — a broken config line, or another owner — + `--whole` replaced its register in place, exit 0, which A44 exists to + refuse. Beside W the path refuses it (2); anywhere else, a `.git` on the + way up from the target that git gave no answer for refuses (1, R22). + Nothing written there or here, nothing on stdout.""" + c = box.seam() + d = box.clone(box.lanes_root / NEIGHBOUR if where == "beside-W" else box.root / "elsewhere-clone") + reg = d / REGISTER + before, mine = reg.read_bytes(), box.snapshot(c) + extra = _unreadable(box, d, how, c) + result = box.write_handoff("--whole", str(_prepared(box, "# not a register any more\n")), + "--expect-digest", sha(reg), "--subject", "handoff: PAUSED", + "--target", str(reg), **extra) + if where == "beside-W": + assert result.returncode == 2, result.stderr + assert f"is {reg}, in another checkout of the register beside W ({box.w})" in result.stderr + else: + assert result.returncode == 1, result.stderr + assert f"is {reg}, under {d}, which holds a git repository that git could not read here" in result.stderr + assert "a read that failed is never 'no repository' (R22)" in result.stderr + assert "Nothing was written." in result.stderr + assert "another repository's handoff" not in result.stderr + assert reg.read_bytes() == before + assert box.snapshot(c) == mine + assert result.stdout == "" + _released(box, c) + + +HANDOFF_PATH_REFUSED = [("W", "climb-cell"), ("W", "link-cell"), ("lanes-sibling", "abs-cell"), + ("lanes-sibling", "climb-cell"), ("profile-clone", "abs-cell"), + ("profile-clone", "climb-cell")] + + +@pytest.mark.parametrize("where,how", HANDOFF_PATH_REFUSED) +def test_handoff_path_refuses_what_write_handoff_refuses(box: Box, where: str, how: str): + """Finding 723 of lane openRepoTools-3's review of #316, and its + correction for 601 (#312 6368669583): with the refusal in `write-handoff` + alone, `handoff-path` still printed a sibling checkout's handoff, and + `lane-start` delivered that sibling's top block as this lane's first + prompt. The three verbs now share one check, so `handoff-path` refuses + what `write-handoff` refuses (2): a cell that lands in W (S5), or in + another checkout of the register beside W, absolute or climbing with + `..`. No path on stdout, the refusal named, nothing written there or + here. W's own absolute spelling is still translated into the checkout + (S2), so W is reached here only by climbing, or through a link in the + checkout. A relative cell that climbs out is refused by 602's rule, + which asks the spelling first (A55), as `write-handoff` does.""" + if where == "W": + box.w_current() + d = box.w + else: + d = _beside_w(box, where) + f = d / HANDOFF_REL + assert f.is_file() + if how != "link-cell": + box.cell_is(str(f) if how == "abs-cell" else _climb(box, f)) + c = box.seam() + if how == "link-cell": + (c / HANDOFF_REL).unlink() + (c / HANDOFF_REL).symlink_to(f) + theirs, mine = box.snapshot(d), box.snapshot(c) + result = box.helper("handoff-path", LANE) + assert result.returncode == 2, result.stderr + if how == "climb-cell": + assert (f"lane {LANE}'s handoff cell {_climb(box, f)} is a repository-relative spelling, joined to " + "the checkout its writes go through, and its '.' or '..' components climb out of it" + ) in result.stderr + assert "so write-handoff refuses it too. Nothing was printed." in result.stderr + elif where == "W": + assert f"is {f}, inside the workspace checkout {box.w}, the readers' copy" in result.stderr + else: + assert f"is {f}, in another checkout of the register beside W ({box.w}), not in the checkout {c}" in result.stderr + if how != "climb-cell": + assert "`write-handoff` refuses it, so no path is printed" in result.stderr + assert "another repository's handoff" not in result.stderr + assert result.stdout == "" + assert box.snapshot(d) == theirs + assert box.snapshot(c) == mine + + +@pytest.mark.parametrize("how", ["abs", "abs-climb", "estate-cell"]) +@pytest.mark.parametrize("where", ["lanes-sibling", "profile-clone"]) +def test_set_restart_intent_refuses_a_handoff_beside_W_that_is_not_the_lanes(box: Box, where: str, how: str): + """601 in the third verb that translates a handoff: `set-restart-intent + --handoff` binds a restart to a file `write-handoff` would then refuse, + so it refuses the same file by the same check (2), whether the path is + absolute, absolute and climbing with `..`, or the estate's `~/` cell: + no intent written, the refusal named, nothing on stdout. W's absolute + path is still kept as given (every older caller's binding).""" + c = box.seam() + d = _beside_w(box, where) + f = d / HANDOFF_REL + assert f.is_file() + if how == "abs": + spelled = str(f) + elif how == "abs-climb": + spelled = str(c / "handoffs" / ".." / os.path.relpath(f, c)) + else: + spelled = "~/" + os.path.relpath(f, box.home) + theirs, mine = box.snapshot(d), box.snapshot(c) + made = _bound(box, spelled) + assert made.returncode == 2, made.stderr + assert (f"--handoff {spelled} is {f}, in another checkout of the register beside W ({box.w}), " + f"not in the checkout {c}") in made.stderr + assert "Nothing was written." in made.stderr + assert made.stdout == "" + assert not (box.state / LANE / "restart-intent.yaml").exists() + assert box.snapshot(d) == theirs + assert box.snapshot(c) == mine + + +@pytest.mark.parametrize("mode", ["stamp", "whole"]) +@pytest.mark.parametrize("where", ["sibling-clone", "bare-clone"]) +def test_a_target_inside_another_clones_git_directory_is_refused_and_nothing_written( + box: Box, where: str, mode: str): + """rv2a-c3's SHOULD 1 (its probe N4): a path inside ANOTHER clone's git + directory — a sibling lane's `-lanes//.git/config` — or inside a + bare clone is in no work tree, so A44's test could not see it, and it was + written as another repository's handoff (8(d)), exit 0: `--stamp` put its + line under `[core]`, `--whole` replaced the file, and every git command in + that clone then failed (128), its own lane's S3 with them. No handoff + lives in a git directory (A47): 2 before a byte, nothing written there or + here, that clone's git still reads its config, and nothing on stdout. + A `--stamp` refused here says the line it would have written, as every + refusal of it does (m2a's N03): a stamp that is owed is a stamp somebody + writes by hand.""" + c = box.seam() + if where == "sibling-clone": + d = box.clone(box.lanes_root / NEIGHBOUR) + gd = d / ".git" + else: + d = None + gd = box.root / "bare.git" + box.git("clone", "-q", "--bare", str(box.origin), str(gd)) + box.quiet(gd, bare=True) + cfg = gd / "config" + if mode == "stamp": + args = ("--stamp", STAMP) + else: + args = ("--whole", str(_prepared(box, "[core\n")), "--expect-digest", sha(cfg)) + before, mine = cfg.read_bytes(), box.snapshot(c) + theirs = box.snapshot(d) if d is not None else None + result = box.write_handoff(*args, "--subject", "handoff: PAUSED", "--target", str(cfg)) + assert result.returncode == 2, result.stderr + assert f"is {cfg}, inside a git directory ({gd})" in result.stderr + assert "Nothing was written." in result.stderr + assert "another repository's handoff" not in result.stderr + said = f"the stamp line it would have written under the handoff's header block: {STAMP}" in result.stderr + assert said == (mode == "stamp"), result.stderr + assert cfg.read_bytes() == before + assert box.git("--git-dir", str(gd), "config", "core.bare").strip() == ( + "true" if d is None else "false"), "that clone's git still reads its config" + if d is not None: + assert box.snapshot(d) == theirs + assert box.snapshot(c) == mine + assert result.stdout == "" + _released(box, c) + + +def _managed_row(box: Box) -> tuple: + """The lane's row as a peer republishes it, with a managed-owner marker + that parses, so the seam's answer is "owned", not "unknown".""" + return (f"| handoffs/repoS4/{LANE}.md | LIVE · seeded |", + f"| handoffs/repoS4/{LANE}.md | LIVE · 2026-10-21T00:00:00Z · managed-owner mode=managed " + f"daemon=ledger-a23 generation=3 bound-lane={LANE} |") + + +def test_a_managed_marker_landing_at_S3s_fetch_refuses_even_where_S3_refused(box: Box): + """SHOULD 3: the seam is asked again on what S3 fetched WHATEVER S3 + answered. A peer's managed-owner marker lands at S3's fetch, and S3 then + refuses (a foreign staged entry): a `--prepend`, which S3's refusal leaves + written (R-A11-11), is refused by the managed ledger instead, with nothing + written, nothing printed, and the staged entry left as it was.""" + c = box.seam() + f = c / HANDOFF_REL + box.peer_prepares(REGISTER, *_managed_row(box), "a peer's row names a managed owner") + peer = shlex.quote(str(box.peer)) + count = shlex.quote(str(box.count)) + box.use_net('[ "$1" = fetch ] || exit 0\n' + f'[ -e {count} ] && exit 0\n' + f"echo 1 > {count}\n" + f"git -C {peer} push -q origin main\n" + "exit 0\n") + _staged_foreign(box, c) + result = box.write_handoff("--prepend", str(_block(box)), "--subject", "handoff: PAUSED") + assert result.returncode == 2, result.stderr + assert "is owned by the managed ledger (owner ledger-a23" in result.stderr + assert "the handoff is WRITTEN" not in result.stderr + assert f.read_text(encoding="utf-8") == HANDOFF, "a managed lane's handoff is never written" + assert "daemon=ledger-a23" in box.origin_register(), "the peer's row landed at S3's fetch" + assert result.stdout == "" + assert box.git("-C", str(c), "diff", "--cached", "--name-only").split() == ["handoffs/README.md"] + _released(box, c) + + +def test_a_managed_marker_already_fetched_is_refused_before_the_lock(box: Box): + """The FIRST ask, before the lock: a marker this checkout's origin/main + already carries refuses the write before S3 runs, so no fetch is made and + nothing is written.""" + box.peer_edits(REGISTER, *_managed_row(box), "a peer's row names a managed owner") + c = box.seam() + f = c / HANDOFF_REL + box.use_net() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 2, result.stderr + assert "is owned by the managed ledger (owner ledger-a23" in result.stderr + assert box.net_ops() == [], "refused before the lock: S3's fetch never ran" + assert f.read_text(encoding="utf-8") == HANDOFF + assert result.stdout == "" + _released(box, c) + + +@pytest.mark.parametrize("mode", ["stamp", "prepend"]) +def test_another_repositorys_handoff_is_written_when_S3_refuses_this_checkout(box: Box, mode: str): + """SHOULD 2: a handoff in another repository is never committed + (Amendment 8(d)), so S3's refusal of the REGISTER checkout — here no + fetch can run, `LANES_NO_FETCH=1` — does not stop its write, as the old + `publish-handoff` never fetched for it. Written, said, its line printed, + exit 0; the register checkout and origin are as they were.""" + c = box.seam() + other = box.root / "otherrepo" / "h.md" + other.parent.mkdir(parents=True) + other.write_text(HANDOFF, encoding="utf-8") + args, after = (("--stamp", STAMP), stamped(HANDOFF)) if mode == "stamp" else \ + (("--prepend", str(_block(box))), prepended(HANDOFF)) + before = box.snapshot(c) + result = box.write_handoff(*args, "--subject", SUBJECT, "--target", str(other), LANES_NO_FETCH="1") + assert result.returncode == 0, result.stderr + assert "LANES_NO_FETCH=1 is set" in result.stderr + assert f"does not stop this write: {other} is another repository's handoff (Amendment 8(d))" in result.stderr + assert "another repository's handoff (Amendment 8(d)): it is written there" in result.stderr + assert "the stamp is NOT written" not in result.stderr + assert "the handoff is WRITTEN and NOT committed" not in result.stderr + assert other.read_text(encoding="utf-8") == after + assert result.stdout == _line(other) + assert box.snapshot(c) == before + _released(box, c) + + +def test_target_names_the_file_written(box: Box): + """`--target`, the old callers' spelling, is the file written: a tracked + handoff that is not the row's is stamped, committed and pushed, and the + row's own handoff is left as it was, in the checkout and on origin.""" + rel = "handoffs/repoS4/old-callers.md" + box.peer_pushes(rel, HANDOFF) + c = box.seam() + f = c / rel + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, "--target", str(f)) + assert result.returncode == 0, result.stderr + assert f.read_text(encoding="utf-8") == stamped(HANDOFF) + assert box.origin_file(rel) == stamped(HANDOFF) + assert (c / HANDOFF_REL).read_text(encoding="utf-8") == HANDOFF + assert box.origin_file(HANDOFF_REL) == HANDOFF + assert result.stdout == _line(f) + _released(box, c) + + +def test_with_no_workspace_resolved_an_absolute_target_is_the_checkouts_own(tmp_path: Path): + """A5's rule for an unresolved W: with no workspace checkout, an absolute + path is only ever the checkout's own or another repository's — an empty W + would read every absolute path as inside it and strip its leading `/`. + Through the seam, a `--target` spelled absolute is written there.""" + box = Box(tmp_path, route=False) + (box.home / ".agents" / "workspace.yaml").write_text( + f"repository: {box.origin}\npath: {box.root / 'no-such-workspace'}\n", encoding="utf-8") + c = box.seam() + f = c / HANDOFF_REL + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, "--target", str(f)) + assert result.returncode == 0, result.stderr + assert f.read_text(encoding="utf-8") == stamped(HANDOFF) + assert box.origin_file(HANDOFF_REL) == stamped(HANDOFF) + assert result.stdout == _line(f) + _released(box, c) + + +def test_a_push_that_cannot_be_confirmed_is_3_and_its_line_printed(box: Box): + """S4's read-back: the push lands and the fetch that would confirm it + fails. 3, `pushed, not confirmed: `, never the success line; the + handoff is written, its line printed (A13), and origin does hold it.""" + c = box.seam() + f = c / HANDOFF_REL + mark = shlex.quote(str(box.root / "pushed")) + box.use_net(f'[ "$1" = push ] && {{ touch {mark}; exit 0; }}\n' + f'[ "$1" = fetch ] && [ -e {mark} ] && {{ echo "fatal: no read-back" >&2; exit 128; }}\n' + "exit 0\n") + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 3, result.stderr + sent = box.head(c) + assert f"pushed, not confirmed: {sent}" in result.stderr + assert "pushed origin/main (attempt" not in result.stderr, "never the success line" + assert box.origin_head() == sent, "it did land: 3 never says it did not" + assert f.read_text(encoding="utf-8") == stamped(HANDOFF) + assert result.stdout == _line(f) + _released(box, c) + + +def test_with_LANES_NO_GIT_the_handoff_is_written_and_nothing_committed(box: Box): + """`LANES_NO_GIT=1`: the handoff is written and proven as ever, and no git + act follows — no fetch, no commit, no push. 0, said, its line printed.""" + c = box.seam() + f = c / HANDOFF_REL + box.use_net() + head, tip = box.head(c), box.origin_head() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, LANES_NO_GIT="1") + assert result.returncode == 0, result.stderr + assert "LANES_NO_GIT=1 — not committing" in result.stderr + assert f.read_text(encoding="utf-8") == stamped(HANDOFF) + assert result.stdout == _line(f) + assert box.head(c) == head + assert box.origin_head() == tip + assert box.net_ops() == [] + _released(box, c) + + +def test_a_target_for_a_lane_with_no_row_is_8_and_nothing_committed(box: Box): + """NIT 3: the row is needed whatever `--target` says. Through the seam + (and in default mode) a `--target` write for a lane with no row went on + and committed `Lane: `; now it is 8 with nothing + written, as the opt-in's gate refuses it before the clone.""" + c = box.seam() + before = box.snapshot(c) + result = box.helper("write-handoff", "repoS4-9", "--stamp", STAMP, "--subject", SUBJECT, + "--target", str(c / HANDOFF_REL)) + assert result.returncode == 8, result.stderr + assert "lane repoS4-9 has no row in the register" in result.stderr + assert result.stdout == "" + assert box.snapshot(c) == before + assert (c / HANDOFF_REL).read_text(encoding="utf-8") == HANDOFF + _released(box, c) + + +def test_handoff_path_over_an_unreadable_register_is_1_never_8(box: Box): + """NIT 1 (R22): a register read that fails is never "no handoff". With + the published register unreadable, `handoff-path` is 1 and prints + nothing, where it answered 8.""" + box.seam() + real = shutil.which("git", path=os.environ.get("PATH")) + shim = box.fakebin / "git" + shim.write_text('#!/usr/bin/env bash\ncase " $* " in\n' + f' *"origin/main:{REGISTER}"*) echo "test shim: git $* refused" >&2; exit 128 ;;\n' + f'esac\nexec "{real}" "$@"\n', encoding="utf-8") + shim.chmod(0o755) + result = box.helper("handoff-path", LANE) + assert result.returncode == 1, result.stderr + assert "a read that failed is never 'no handoff' (R22)" in result.stderr + assert result.stdout == "" + + +def test_whole_refusal_names_HEAD_when_a_commit_landed_on_top(box: Box): + """S4.4's refusal where another writer's commit landed on top of this + write's while its push was in flight: HEAD is that commit, so the words + name it and say both are kept — never "kept on HEAD" for this write's. + Nothing is reset (6), and both commits wait for the publish act.""" + c = box.seam() + f = c / HANDOFF_REL + box.peer_prepares(HANDOFF_REL, "Preserved task.\n", "Preserved task, and a peer's line.\n", + "a peer edits the handoff") + peer = shlex.quote(str(box.peer)) + cq = shlex.quote(str(c)) + count = shlex.quote(str(box.count)) + box.use_net('[ "$1" = push ] || exit 0\n' + f"[ -e {count} ] && exit 0\n" + f"echo 1 > {count}\n" + f"git -C {peer} push -q origin main\n" + f"printf 'stamp' > {cq}/handoffs/stamp.md\n" + f"git -C {cq} add -- handoffs/stamp.md\n" + f"git -C {cq} commit -q -m 'a stamp from an unlocked writer' -- handoffs/stamp.md\n" + "exit 0\n") + result = box.write_handoff("--whole", str(_prepared(box)), "--expect-digest", sha(f), + "--subject", "handoff: PAUSED") + assert result.returncode == 6, result.stderr + top = box.head(c) + mine = box.git("-C", str(c), "rev-parse", "HEAD~1").strip() + assert box.subject(c) == "a stamp from an unlocked writer" + assert box.subject(c, "HEAD~1") == "handoff: PAUSED" + assert f"HEAD is {top}, not this write's commit {mine}" in result.stderr + assert ("another commit was made on top of it in this checkout before or while its push was " + "in flight") in result.stderr, "609: 1b's 343 words" + assert f"kept, with HEAD at {top}: {mine} handoff: PAUSED" in result.stderr + assert f"kept on HEAD: {mine}" not in result.stderr + assert "push refused (attempt" not in result.stderr, "no reset, no replay" + assert len(box.unpublished(c)) == 2, "both kept, for the publish act" + assert result.stdout == _line(f) + _released(box, c) + + +def test_whole_refusal_names_a_rewritten_commit_as_rewritten(box: Box): + """S4.4's refusal where another writer REWROTE this write's commit while + its push was in flight (slice 1b's SHOULD A case, which S4.4 meets before + `cp_replay` does): HEAD does not hold this write's sha at all, so nothing + is said to be kept, on HEAD or under it, and slice 1b's `cp_not_head` + names the rewrite. The rewrite here is an amend at the read-back fetch, + which cannot conflict with the peer's edit as a rebase over it would.""" + c = box.seam() + f = c / HANDOFF_REL + box.peer_prepares(HANDOFF_REL, "Preserved task.\n", "Preserved task, and a peer's line.\n", + "a peer edits the handoff") + peer = shlex.quote(str(box.peer)) + cq = shlex.quote(str(c)) + pushed = shlex.quote(str(box.root / "pushed")) + done = shlex.quote(str(box.root / "rewritten")) + box.use_net(f'if [ "$1" = push ] && [ ! -e {pushed} ]; then echo 1 > {pushed}\n' + f" git -C {peer} push -q origin main\n" + " exit 0\n" + "fi\n" + f'if [ "$1" = fetch ] && [ -e {pushed} ] && [ ! -e {done} ]; then echo 1 > {done}\n' + f" git -C {cq} commit -q --amend -m 'handoff: PAUSED, reworded by an unlocked writer'\n" + "fi\n" + "exit 0\n") + result = box.write_handoff("--whole", str(_prepared(box)), "--expect-digest", sha(f), + "--subject", "handoff: PAUSED") + assert result.returncode == 6, result.stderr + assert "rewrote this write's commit" in result.stderr + assert "kept on HEAD" not in result.stderr, "this write's sha is on no branch here" + assert "local and kept, with HEAD at" not in result.stderr, "no commit was made on top" + assert "push refused (attempt" not in result.stderr, "no reset, no replay" + assert box.subject(c) == "handoff: PAUSED, reworded by an unlocked writer" + assert len(box.unpublished(c)) == 1, "the rewritten copy waits for the publish act" + assert result.stdout == _line(f) + _released(box, c) + + +# ------------------------------------------------- a handoff a peer deletes mid-push (A37) + +def _peer_deletes_the_handoff(box: Box) -> None: + """The peer clone commits the deletion of the lane's handoff WITHOUT + pushing it: the race hook lands it at the moment this write pushes. The + row still names the file.""" + box.git("-C", str(box.peer), "fetch", "-q", "origin") + box.git("-C", str(box.peer), "merge", "-q", "--ff-only", "refs/remotes/origin/main") + box.git("-C", str(box.peer), "rm", "-q", "--", HANDOFF_REL) + box.git("-C", str(box.peer), "commit", "-q", "-m", f"a peer deletes {HANDOFF_REL}") + + +def _origin_has(box: Box, rel: str) -> bool: + return rel in box.git("--git-dir", str(box.origin), "ls-tree", "-r", "--name-only", "main").split() + + +@pytest.mark.parametrize("mode", ["stamp", "prepend"]) +@pytest.mark.parametrize("peer", ["deletes-it", "keeps-it"]) +def test_a_handoff_a_peer_deletes_mid_push_keeps_the_write_on_HEAD_exit_6(box: Box, peer: str, mode: str): + """A37: `--stamp` and `--prepend` edit the handoff IN PLACE. A peer that + deleted it while this write's push was in flight left the replay nothing + to write into: the re-run said "Nothing was written" (8), and the write + was only in the commit the replay had dropped. `write-handoff` now names + the handoff in slice 1b's `CP_KEEP_IF_GONE`, so the commit is kept on + HEAD and named (6) before anything is dropped: the write is in this + checkout, origin has no such file, and one commit is unpublished, this + write's. A peer's commit that KEEPS the handoff, and changes it, is an + ordinary lost race: the write is replayed on the peer's copy and lands + (0).""" + c = box.seam() + f = c / HANDOFF_REL + if mode == "stamp": + args, written = ("--stamp", STAMP), stamped + else: + args, written = ("--prepend", str(_block(box))), prepended + if peer == "deletes-it": + _peer_deletes_the_handoff(box) + else: + box.peer_prepares(HANDOFF_REL, "Preserved task.\n", "Preserved task, and a peer's line.\n", + "a peer edits the handoff") + box.race(prepared=True) + result = box.write_handoff(*args, "--subject", SUBJECT) + err = result.stderr + if peer == "deletes-it": + assert result.returncode == 6, err + kept = box.head(c) + assert f"origin's copy of {HANDOFF_REL} was deleted while this write's push was in flight" in err, err + assert f"kept on HEAD: {kept}" in err + assert "push refused (attempt" not in err, "nothing is dropped or replayed" + assert box.subject(c) == SUBJECT + assert f.read_text(encoding="utf-8") == written(HANDOFF), "the write is in this checkout" + assert not _origin_has(box, HANDOFF_REL), "origin has no such file" + assert [line.split()[1] for line in box.unpublished(c)] == [kept], "one commit unpublished, this write's" + else: + base = HANDOFF.replace("Preserved task.\n", "Preserved task, and a peer's line.\n") + assert result.returncode == 0, err + assert "push refused (attempt 1 of 6)" in err + assert "was deleted while this write's push was in flight" not in err + assert box.origin_file(HANDOFF_REL) == written(base) + assert f.read_text(encoding="utf-8") == written(base) + assert box.unpublished(c) == [] + assert result.stdout == _line(f) + _released(box, c) + + +def test_whole_over_a_handoff_a_peer_deletes_mid_push_is_S4_4s_exit_6(box: Box): + """`--whole` never names its file in `CP_KEEP_IF_GONE` (A37 is + `--stamp`'s and `--prepend`'s): a handoff origin deleted is a copy that + is no longer the one it started from, so S4.4 answers, before the + replay, and keeps the commit on HEAD (6) in its own words.""" + c = box.seam() + f = c / HANDOFF_REL + _peer_deletes_the_handoff(box) + box.race(prepared=True) + result = box.write_handoff("--whole", str(_prepared(box)), "--expect-digest", sha(f), + "--subject", "handoff: PAUSED") + err = result.stderr + assert result.returncode == 6, err + kept = box.head(c) + assert f"origin/main's {HANDOFF_REL} is gone now" in err, err + assert "is no longer the one this whole-file write started from" in err + assert f"kept on HEAD: {kept}" in err + assert "was deleted while this write's push was in flight" not in err + assert "push refused (attempt" not in err, "no replay" + assert f.read_text(encoding="utf-8") == "# the handoff, written whole\n" + assert not _origin_has(box, HANDOFF_REL) + assert [line.split()[1] for line in box.unpublished(c)] == [kept] + assert result.stdout == _line(f) + _released(box, c) + + +#: Where a peer moves the lane's handoff to (rv2a-c2's probe N2). +MOVED_REL = "handoffs/repoS4/moved-handoff.md" + + +def _peer_moves_the_handoff(box: Box, row: str) -> None: + """The peer clone `git mv`s the lane's handoff to MOVED_REL in a commit + it does NOT push: the race hook lands it at this write's push. In the + same commit the register's row for the lane either follows the file + ("follows": the cell names MOVED_REL) or is renamed with it ("renamed": + the lane is `repoS4-9` now, as `rename-lane` renames a row and moves its + handoff), so origin has no row for this write's lane.""" + p = box.peer + box.git("-C", str(p), "fetch", "-q", "origin") + box.git("-C", str(p), "merge", "-q", "--ff-only", "refs/remotes/origin/main") + box.git("-C", str(p), "mv", "--", HANDOFF_REL, MOVED_REL) + reg = p / REGISTER + text = reg.read_text(encoding="utf-8") + assert text.count(f"| {HANDOFF_REL} |") == 1 + new = text.replace(f"| {HANDOFF_REL} |", f"| {MOVED_REL} |") + if row == "renamed": + assert new.count(f"| `{LANE}` |") == 1 + new = new.replace(f"| `{LANE}` |", "| `repoS4-9` |") + reg.write_text(new, encoding="utf-8") + box.git("-C", str(p), "add", "--", REGISTER) + box.git("-C", str(p), "commit", "-q", "-m", f"a peer moves {HANDOFF_REL} and its row ({row})") + + +@pytest.mark.parametrize("mode", ["stamp", "prepend"]) +def test_a_handoff_a_peer_moves_with_its_row_cell_mid_push_is_replayed_into_the_moved_file(box: Box, mode: str): + """rv2a-c2's NIT 2 (its probe N2): a peer moves the lane's handoff and + points the row's cell at its new name, in one commit that lands while + this write's push is in flight. A37 kept the commit on HEAD (6) for any + handoff origin no longer has, though the replay's re-run reads the row + again and writes the moved file. It is kept now only where origin's row + still names the cell this write followed (A46): here the replay goes + ahead, the write lands in the moved file on origin (0), and nothing is + left unpublished.""" + c = box.seam() + if mode == "stamp": + args, written = ("--stamp", STAMP), stamped + else: + args, written = ("--prepend", str(_block(box))), prepended + _peer_moves_the_handoff(box, "follows") + box.race(prepared=True) + result = box.write_handoff(*args, "--subject", SUBJECT) + err = result.stderr + assert result.returncode == 0, err + assert "push refused (attempt 1 of 6)" in err + assert "was deleted while this write's push was in flight" not in err + assert box.origin_file(MOVED_REL) == written(HANDOFF) + assert not _origin_has(box, HANDOFF_REL) + assert (c / MOVED_REL).read_text(encoding="utf-8") == written(HANDOFF) + assert box.unpublished(c) == [] + assert result.stdout == _line(c / MOVED_REL) + _released(box, c) + + +@pytest.mark.parametrize("how", ["target", "renamed"]) +def test_a_moved_handoff_the_replay_cannot_follow_is_kept_on_HEAD_exit_6(box: Box, how: str): + """A46's bounds, where A37's keep still holds (6): the replay's re-run + could not follow the move. A `--target` names the old path again, so + the re-run would find nothing there; and where the row was renamed with + the file, origin has no row for this lane to re-read, so the re-run + would be 8 ("no row"). The write is in this checkout and kept on HEAD, + one commit unpublished, nothing replayed.""" + c = box.seam() + f = c / HANDOFF_REL + _peer_moves_the_handoff(box, "follows" if how == "target" else "renamed") + box.race(prepared=True) + extra = ("--target", str(f)) if how == "target" else () + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, *extra) + err = result.stderr + assert result.returncode == 6, err + kept = box.head(c) + assert f"origin's copy of {HANDOFF_REL} was deleted while this write's push was in flight" in err, err + assert f"kept on HEAD: {kept}" in err + assert "push refused (attempt" not in err, "nothing is dropped or replayed" + assert f.read_text(encoding="utf-8") == stamped(HANDOFF) + assert _origin_has(box, MOVED_REL) and not _origin_has(box, HANDOFF_REL) + assert [line.split()[1] for line in box.unpublished(c)] == [kept] + assert result.stdout == _line(f) + _released(box, c) + + +#: Where a peer moves the lane's handoff OUT of `handoffs/` (A48's third case). +OUTSIDE_REL = "notes/moved-handoff.md" + + +def _elsewhere(box: Box) -> Path: + """A file in another repository's folder, which nobody wrote.""" + return box.root / "elsewhere" / "handoff.md" + + +def _peer_repoints_the_cell(box: Box, cell: str) -> None: + """In one commit the peer clone does NOT push (the race hook lands it at + this write's push), the lane's handoff leaves origin and the row's cell + stops naming it (rv2a-c3's probe N5): "moved-no-file" deletes it and + points the cell at MOVED_REL, which origin has no file for; "emptied" + deletes it and empties the cell; "moved-out-of-handoffs" `git mv`s it to + OUTSIDE_REL and points the cell there; "another-repository" deletes it + and points the cell at a path in a folder of another repository, which + this checkout cannot translate (m2a's N05).""" + p = box.peer + box.git("-C", str(p), "fetch", "-q", "origin") + box.git("-C", str(p), "merge", "-q", "--ff-only", "refs/remotes/origin/main") + if cell == "moved-out-of-handoffs": + (p / OUTSIDE_REL).parent.mkdir(parents=True, exist_ok=True) + box.git("-C", str(p), "mv", "--", HANDOFF_REL, OUTSIDE_REL) + else: + box.git("-C", str(p), "rm", "-q", "--", HANDOFF_REL) + if cell == "another-repository": + _elsewhere(box).parent.mkdir() # the folder is there, the file is not + new = {"moved-no-file": MOVED_REL, "emptied": "", "moved-out-of-handoffs": OUTSIDE_REL, + "another-repository": str(_elsewhere(box))}[cell] + reg = p / REGISTER + text = reg.read_text(encoding="utf-8") + assert text.count(f"| {HANDOFF_REL} |") == 1 + reg.write_text(text.replace(f"| {HANDOFF_REL} |", f"| {new} |"), encoding="utf-8") + box.git("-C", str(p), "add", "--", REGISTER) + box.git("-C", str(p), "commit", "-q", "-m", f"a peer repoints the lane's handoff cell ({cell})") + + +@pytest.mark.parametrize("cell", ["moved-no-file", "emptied", "moved-out-of-handoffs", "another-repository"]) +def test_a_cell_a_peer_repoints_where_no_replay_can_write_keeps_the_write_on_HEAD_exit_6(box: Box, cell: str): + """rv2a-c3's NIT 1 (its probe N5): A46 replayed wherever origin's row + named another cell, though the re-run writes only a handoff that is + there. A peer that deleted the handoff and pointed the cell at a name + origin has no file for, or emptied it, while this write's push was in + flight had the commit dropped and the re-run say "Nothing was written" + (8): the write was only in the reflog. Another cell is followed now only + to a file under `handoffs/` that origin has (A48); otherwise — no file, + no cell, a file the re-run refuses (2, outside `handoffs/`), or a path + this checkout cannot translate, in another repository (m2a's N05) — + A37's keep holds: 6, the write in this checkout, one commit unpublished, + nothing replayed, and nothing written in that other repository either.""" + c = box.seam() + f = c / HANDOFF_REL + _peer_repoints_the_cell(box, cell) + box.race(prepared=True) + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + err = result.stderr + assert result.returncode == 6, err + kept = box.head(c) + assert f"origin's copy of {HANDOFF_REL} was deleted while this write's push was in flight" in err, err + assert f"kept on HEAD: {kept}" in err + assert "push refused (attempt" not in err, "nothing is dropped or replayed" + assert box.subject(c) == SUBJECT + assert f.read_text(encoding="utf-8") == stamped(HANDOFF), "the write is in this checkout" + assert not _origin_has(box, HANDOFF_REL) + assert [line.split()[1] for line in box.unpublished(c)] == [kept], "one commit unpublished, this write's" + assert not _elsewhere(box).exists(), "nothing was written in another repository either" + assert result.stdout == _line(f) + _released(box, c) + + +# ------------------------------------------------- m2a's battery: what no case asked + +@pytest.mark.parametrize("mode", ["prepend", "whole"]) +def test_an_empty_block_or_prepared_file_is_refused(box: Box, mode: str): + """m2a's A11: an empty `--prepend` block would put `---` and a blank + line on top of the handoff, and an empty `--whole` file would empty the + handoff and push that. Each is refused (1) before the lock, as + `publish-handoff` refuses an empty prepared file: nothing written, + committed or pushed, no fetch made, nothing on stdout.""" + c = box.seam() + f = c / HANDOFF_REL + empty = box.root / "empty.md" + empty.write_text("", encoding="utf-8") + args = (("--prepend", str(empty)) if mode == "prepend" else + ("--whole", str(empty), "--expect-digest", sha(f))) + box.use_net() + before = box.snapshot(c) + result = box.write_handoff(*args, "--subject", "handoff: PAUSED") + assert result.returncode == 1, result.stderr + assert f"the {mode} file {empty} is unreadable or empty. Nothing was written." in result.stderr + assert f.read_text(encoding="utf-8") == HANDOFF + assert box.snapshot(c) == before + assert box.net_ops() == [] + assert result.stdout == "" + _released(box, c) + + +def test_routed_a_flag_where_the_lane_goes_is_usage_and_routes_nowhere(box: Box): + """m2a's A13: routed, the lane is `write-handoff`'s first word, and a + flag there is the arm's own usage refusal (64), never a lane to route + to: no checkout is cloned for it, no fetch is made, and W and origin are + as they were.""" + box.use_net() + w_before, tip = box.w_state(), box.origin_head() + result = box.helper("write-handoff", "--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 64, result.stderr + assert "usage: write-handoff " in result.stderr + assert box.net_ops() == [] + assert not box.checkout("--stamp").exists() + assert box.w_state() == w_before + assert box.origin_head() == tip + assert result.stdout == "" + + +def test_handoff_path_for_a_row_that_names_no_handoff_is_8(box: Box): + """m2a's B02: a row whose handoff cell is empty names no file, so there + is no path to answer: 8, said, nothing on stdout (never the checkout's + own directory), and routed, `--create` clones nothing for it.""" + box.cell_is("") + for argv in ((LANE,), (LANE, "--create")): + result = box.helper("handoff-path", *argv) + assert result.returncode == 8, result.stderr + assert f"lane {LANE}'s row names no handoff" in result.stderr + assert result.stdout == "" + assert not box.checkout(LANE).exists(), "a row that names no handoff gets no checkout" + + +def test_handoff_path_reads_the_cell_from_the_register_as_published(box: Box): + """m2a's B04: `handoff-path` fetches before it reads the row, as + `write-handoff` does, so a peer that points the lane's cell at another + file after this checkout last fetched is answered with that file.""" + c = box.seam() + box.cell_is(MOVED_REL) + result = box.helper("handoff-path", LANE) + assert result.returncode == 0, result.stderr + assert result.stdout == f"{c / MOVED_REL}\n" + + +def test_handoff_path_refuses_a_lane_path_that_holds_no_clone(box: Box): + """m2a's C02: routed, the lane's checkout path is a directory that is not + a clone of the register. The path it would print is not one a write can + go through: refused (1), nothing on stdout, and nothing there deleted, + moved or written.""" + d = box.checkout(LANE) + d.mkdir(parents=True) + (d / "notes.md").write_text("a person's notes\n", encoding="utf-8") + result = box.helper("handoff-path", LANE) + assert result.returncode == 1, result.stderr + assert f"{d} is not a clone of the register: it is a directory that is not a git checkout" in result.stderr + assert result.stdout == "" + assert [p.name for p in d.iterdir()] == ["notes.md"] + assert (d / "notes.md").read_text(encoding="utf-8") == "a person's notes\n" + + +def test_handoff_path_through_a_directory_inside_the_seam_names_its_top_level(box: Box): + """m2a's C03: an explicit `LANES_REPO` that names a directory inside the + checkout is raised to that checkout's top level, so the row's + repository-relative cell is joined to the top level and never to the + directory as given. `LANES_REPO` alone would name no register there (1, + "registry not found"), so `LANES_FILE` names the checkout's.""" + c = box.seam() + box.env["LANES_REPO"] = str(c / "handoffs") + box.env["LANES_FILE"] = str(c / REGISTER) + result = box.helper("handoff-path", LANE) + assert result.returncode == 0, result.stderr + assert result.stdout == f"{c / HANDOFF_REL}\n" + + +def test_a_handoff_that_links_out_of_the_checkout_is_another_repositorys(box: Box): + """m2a's D08: the row's cell names a file of this checkout, and that file + is a link to another repository's file outside W. The path is followed + to the physical file, and that file is another repository's (Amendment + 8(d)): written there, said, its line printed, 0, and committed and + pushed in that repository (604), its tree clean. This checkout and + origin are as they were, the link still a link.""" + other = _other_repository(box) + other_head = box.git("-C", str(other), "rev-parse", "HEAD").strip() + seed = box.seed + box.git("-C", str(seed), "pull", "-q", "--ff-only", "origin", "main") + (seed / HANDOFF_REL).unlink() + (seed / HANDOFF_REL).symlink_to(other / "handoff.md") + box.commit(seed, "the lane's handoff is a link into another repository", HANDOFF_REL) + box.git("-C", str(seed), "push", "-q", "origin", "main") + c = box.seam() + assert (c / HANDOFF_REL).is_symlink() + before = box.snapshot(c) + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 0, result.stderr + assert "another repository's handoff (Amendment 8(d))" in result.stderr + assert (other / "handoff.md").read_text(encoding="utf-8") == stamped(HANDOFF) + _committed_and_pushed_there(box, other, other_head) + assert (c / HANDOFF_REL).is_symlink() + assert box.snapshot(c) == before + assert result.stdout == _line(other / "handoff.md") + _released(box, c) + + +# ------------------------------------------------- 602: a relative spelling that climbs out + +CLIMBS = { + # name: (the spelling, where it is given, the file it reaches from the seam `/c`); + # None: the box's own file, spelled by `_climb` (a sibling checkout's handoff, W's) + "cell-outside": ("handoffs/../../outside/x.md", "cell", "outside/x.md"), + "target-outside": ("../outside/x.md", "target", "outside/x.md"), + "cell-sibling": (None, "cell", None), + "cell-W": (None, "cell", None), + "cell-through-a-link": ("handoffs/lnk/../x.md", "cell", "elsewhere/x.md"), + "cell-not-there-yet": ("handoffs/notyet/../../../outside/x.md", "cell", "outside/x.md"), +} + + +@pytest.mark.parametrize("case", CLIMBS) +def test_a_relative_spelling_that_climbs_out_of_the_checkout_is_refused(box: Box, case: str): + """602 of lane openRepoTools-3's review of #312: a repository-relative + cell, or a relative `--target`, is joined to the checkout (S2), and one + whose `.` or `..` components climb out of it was written as another + repository's handoff, exit 0, where `set-restart-intent --handoff` + refuses the same cell (64). It names no other repository's handoff: + refused (2), nothing written anywhere, the stamp line said. Climbing out + is read from the text, and where the spelling resolves, from the file it + names: `handoffs/lnk/../x.md`, with `lnk` a link out, stays in the + checkout by its text and leaves it by its file. A link with no such + component is still Amendment 8(d)'s (D08's test). The spelling is asked + first (A55), so a cell that climbs into W or a checkout beside it is + refused for its spelling too. And one that climbs out THROUGH A + DIRECTORY NOT THERE YET (`handoffs/notyet/..`), whose file neither + `path_physical` nor the kernel can resolve, so no check by path can say + where it lands, is refused by its text (it said 8, "not there"), and no + directory is made for it.""" + spelling, given, rel = CLIMBS[case] + if case == "cell-sibling": + f = box.lanes_root / NEIGHBOUR / HANDOFF_REL + elif case == "cell-W": + f = box.w / HANDOFF_REL + else: + f = box.root / rel + if spelling is None: + spelling = _climb(box, f) + if given == "cell": + box.cell_is(spelling) + c = box.seam() + if case == "cell-sibling": + box.clone(box.lanes_root / NEIGHBOUR) + elif case != "cell-W": + f.parent.mkdir(parents=True, exist_ok=True) + f.write_text(HANDOFF, encoding="utf-8") + if case == "cell-through-a-link": + (box.root / "elsewhere" / "a").mkdir() + (c / "handoffs" / "lnk").symlink_to(box.root / "elsewhere" / "a") + assert f.is_file() + args = ("--target", spelling) if given == "target" else () + text, mine, w = f.read_bytes(), box.snapshot(c), box.w_state() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, *args) + assert result.returncode == 2, result.stderr + assert (f"lane {LANE}'s handoff {spelling} is a repository-relative spelling, joined to this " + f"write's checkout {c}, and its '.' or '..' components climb out of it") in result.stderr + assert "Nothing was written." in result.stderr + assert "another repository's handoff" not in result.stderr + assert f"the stamp line it would have written under the handoff's header block: {STAMP}" in result.stderr + assert f.read_bytes() == text + assert box.snapshot(c) == mine + assert box.w_state() == w + assert not (c / "handoffs" / "notyet").exists() + assert result.stdout == "" + _released(box, c) + + +def test_a_relative_spelling_that_stays_in_the_checkout_is_its_own_handoff(box: Box): + """602's bound: the rule is a spelling that LEAVES the checkout, not any + `..`. A cell that climbs within it names the lane's own handoff, which + is written, committed and pushed as the plain cell's is.""" + box.cell_is(f"handoffs/../{HANDOFF_REL}") + c = box.seam() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 0, result.stderr + assert box.origin_file(HANDOFF_REL) == stamped(HANDOFF) + assert result.stdout == _line(c / HANDOFF_REL) + _released(box, c) + + +@pytest.mark.parametrize("cell", ["handoffs/../../outside/x.md", "handoffs/notyet/../../../outside/x.md"]) +@pytest.mark.parametrize("how", ["seam", "routed"]) +def test_handoff_path_refuses_a_relative_cell_that_climbs_out_of_the_checkout(box: Box, how: str, cell: str): + """602 in `handoff-path`: it printed `/handoffs/../../outside/x.md` + as this checkout's own file, with no note, for a cell `write-handoff` + now refuses, and the same for a cell that climbs out through a directory + not there yet. It answers where that write goes, so it refuses the cell + too (2), prints nothing, and routed, `--create` clones nothing for it: + the text is read before any checkout is made.""" + f = box.root / "outside" / "x.md" + f.parent.mkdir(parents=True, exist_ok=True) + f.write_text(HANDOFF, encoding="utf-8") + box.cell_is(cell) + c = box.seam() if how == "seam" else box.checkout(LANE) + for argv in ((LANE,), (LANE, "--create")): + result = box.helper("handoff-path", *argv) + assert result.returncode == 2, result.stderr + assert (f"lane {LANE}'s handoff cell {cell} is a repository-relative " + "spelling, joined to the checkout its writes go through, and its '.' or '..' " + "components climb out of it") in result.stderr + assert result.stdout == "" + if how == "routed": + assert not c.exists(), "a cell that climbs out gets no checkout" + else: + assert not (c / "handoffs" / "notyet").exists() + + +# ------------------------------------------------- 732: 1a's stray scan, carried in 2a's file + +def test_refresh_workspaces_stray_scan_never_rewrites_Ws_index(box: Box): + """732 of lane openRepoTools-3's review of #316 (1a's code, in this + slice's file): `refresh_workspace_strays` 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 S5 + forbids, and 2b's launch runs the scan twice. It runs with + `GIT_OPTIONAL_LOCKS=0`, as `ws_pending_refuse`'s status does: W's index + is as it was, its mtime and its bytes, and the stray is still named. A + plain `git status` afterwards does rewrite it, so the index was + stat-dirty.""" + w = box.w + index = w / ".git" / "index" + reg = w / REGISTER + st = reg.stat() + os.utime(reg, ns=(st.st_atime_ns, st.st_mtime_ns + 5_000_000_000)) + box.write(w, "handoffs/repoS4/stray.md", "# a stray\n") + time.sleep(1.1) + before = (index.stat().st_mtime_ns, index.read_bytes()) + result = box.helper("refresh-workspace", timeout=120) + assert result.returncode == 0, result.stderr + assert "a stray in W: handoffs/repoS4/stray.md (??)" in result.stderr + assert (index.stat().st_mtime_ns, index.read_bytes()) == before, "W's index was rewritten" + plain = subprocess.run(["git", "-C", str(w), "status", "--porcelain"], env=box.env, + capture_output=True, text=True, check=False) + assert plain.returncode == 0, plain.stderr + assert index.stat().st_mtime_ns != before[0], "the index was not stat-dirty: the case proves nothing" + + +# ------------------------------------------------- the NITs of lane 3's review of #312 + +def test_a_fenced_prepend_with_a_bound_digest_is_usage_64(box: Box): + """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, refused, and its block was left in the + reflog only. `lane-handoff` binds its prepend at `preparing` with `none` + and takes the digest again at `pending`, so a sha there has no caller: + usage (64), before the lock, nothing written and no fetch made. `none` + is still taken (`test_a_prepend_fenced_at_preparing_takes_expected_digest_none`).""" + c = box.seam() + f = c / HANDOFF_REL + block = _block(box) + box.use_net() + before = box.snapshot(c) + result = box.write_handoff("--prepend", str(block), "--subject", "handoff: PAUSED", + "--restart", FENCE[0], FENCE[1], "0", "preparing", "none", + "--expected-digest", sha(f)) + assert result.returncode == 64, result.stderr + assert "--prepend takes --expected-digest none only" in result.stderr + assert "Nothing was written." in result.stderr + assert f.read_text(encoding="utf-8") == HANDOFF + assert box.snapshot(c) == before + assert box.net_ops() == [] + assert result.stdout == "" + + +def test_a_handoff_whose_path_holds_a_glob_character_is_refused(box: Box): + """607: `commit_publish` stages and commits its pathspec as git reads + one, a pattern, so a handoff named `x[1].md` names `x1.md` too, here a + file nobody committed. At 7ffbcb0 S3 step 4 did not refuse it (it is + not the handoff), and the write's commit carried `x1.md` under its + subject and pushed it (0). A lane's handoff is never named so: refused + (2), nothing written, staged or pushed, the stamp line said.""" + glob_rel = "handoffs/repoS4/x[1].md" + box.peer_pushes(glob_rel, HANDOFF) + box.cell_is(glob_rel) + c = box.seam() + box.write(c, "handoffs/repoS4/x1.md", "# a file nobody committed\n") + mine, tip = box.snapshot(c), box.origin_head() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 2, result.stderr + assert (f"lane {LANE}'s handoff {glob_rel} in the checkout {c} has a glob character ('*', '?' " + "or '[') in its path") in result.stderr + assert "Nothing was written." in result.stderr + assert f"the stamp line it would have written under the handoff's header block: {STAMP}" in result.stderr + assert (c / glob_rel).read_text(encoding="utf-8") == HANDOFF + assert box.snapshot(c) == mine + assert box.origin_head() == tip + assert result.stdout == "" + _released(box, c) + + +#: 607's handoff: a path whose part in the checkout holds a glob character. +GLOB_REL = "handoffs/repoS4/x[1].md" + + +@pytest.mark.parametrize("how,argv", [("seam", ()), ("routed", ()), ("routed", ("--create",))]) +def test_handoff_path_refuses_a_glob_character_as_write_handoff_does(tmp_path: Path, how: str, argv: tuple): + """991 of lane openRepoTools-3's first review of #311, 2a's to close + under 723: at 60ddad3 `handoff-path` printed `/handoffs/repoS4/x[1].md`, + a path `write-handoff` refuses (607), for a restart to bind and a launch + to read. The verbs now ask one question of the physical path in the + checkout (`handoff_rel_globbed`, A60), so `handoff-path` refuses it too + (2): no path on stdout, the refusal named, nothing written. Routed, a + read without `--create` makes no checkout, and `--create` makes the + lane's own (S1's) before the question is asked, as 601's refusal does.""" + box = Box(tmp_path, route=(how == "routed")) + box.peer_pushes(GLOB_REL, HANDOFF) + box.cell_is(GLOB_REL) + c = box.seam() if how == "seam" else box.checkout(LANE) + mine = box.snapshot(c) if how == "seam" else None + result = box.helper("handoff-path", LANE, *argv) + assert result.returncode == 2, result.stderr + assert (f"lane {LANE}'s handoff {GLOB_REL} is {GLOB_REL} in the checkout {c}, with a glob character " + "('*', '?' or '[') in its path: `write-handoff` refuses it (607), so no path is printed for " + "a restart to bind or a launch to read (991)") in result.stderr + assert "Nothing was written." in result.stderr + assert "another repository's handoff" not in result.stderr + assert result.stdout == "" + if how == "seam": + assert box.snapshot(c) == mine + elif argv: + assert (c / ".git").exists(), "--create made the lane's own checkout first" + else: + assert not c.exists(), "a read makes no checkout" + + +@pytest.mark.parametrize("how", ["cell", "abs-in-lane-checkout"]) +def test_set_restart_intent_refuses_a_glob_character_as_write_handoff_does(tmp_path: Path, how: str): + """991 in the third verb that translates a handoff: `set-restart-intent + --handoff` bound a restart to `x[1].md`, a file `write-handoff` then + refuses (607). It asks the same question where it asks 601's (A60): of + the row's cell translated into the checkout, and of an absolute path in + the lane's own checkout beside W, the path `handoff-path` prints under + the opt-in. Refused (2): no intent written, nothing on stdout. W's own + absolute spelling is still kept as given (every older caller's binding, + A54), and `write-handoff` refuses it when it writes.""" + box = Box(tmp_path, route=True) + if how == "cell": + c = box.seam() + spelled = GLOB_REL + else: + c = box.checkout(LANE) + spelled = str(c / GLOB_REL) + made = _bound(box, spelled) + assert made.returncode == 2, made.stderr + assert (f"--handoff {spelled} is {GLOB_REL} in the checkout {c}, with a glob character ('*', '?' or " + "'[') in its path: `write-handoff` refuses it (607), so no restart is bound to it (991)" + ) in made.stderr + assert "Nothing was written." in made.stderr + assert made.stdout == "" + assert not (box.state / LANE / "restart-intent.yaml").exists() + + +def test_whole_kept_by_S4_4_names_the_commits_under_it(box: Box): + """609: S4.4's refusal did not list the commits under this write's, as + 1b's 344 does. An unlocked writer's stamp landing just before this + whole-file write's commit sits under it, and the `reset --keep` S4.4's + note offers would take it, so they are counted and the STOP line said. + Origin's copy moved, so nothing is replayed: kept on HEAD (6), both + commits there for the publish act.""" + c = box.seam() + f = c / HANDOFF_REL + box.peer_prepares(HANDOFF_REL, "Preserved task.\n", "Preserved task, and a peer's line.\n", + "a peer edits the handoff") + box.race(prepared=True) + env = _git_shim(box, '*" commit -q -m handoff("*', before=_stamp(c)) + result = box.write_handoff("--whole", str(_prepared(box)), "--expect-digest", sha(f), + "--subject", SUBJECT, **env) + assert (box.root / "fired").exists(), "the stamp landed just before this write's commit" + assert result.returncode == 6, result.stderr + kept = box.head(c) + assert box.subject(c) == SUBJECT + assert box.subject(c, "HEAD~1") == "handoff(repoS4-2@Eagle): a stamp from an unlocked writer" + assert "is no longer the one this whole-file write started from" in result.stderr + assert f"kept on HEAD: {kept}" in result.stderr + assert ("under it are 1 commit(s) that origin/main lacks and this write did not make" + in result.stderr), result.stderr + assert "STOP and take it to the lane its subject names" in result.stderr + assert "push refused (attempt" not in result.stderr, "no replay" + assert len(box.unpublished(c)) == 2, "both kept on HEAD" + assert result.stdout == _line(f) + _released(box, c) + + +def test_a_prepend_S3_refused_writes_under_the_checkouts_lock(box: Box): + """610: no case showed that a `--prepend` S3 refused, which still writes + the handoff (R-A11-11), writes it under the checkout's lock. While another + act holds the workstation's sidecar lock, the write waits HOLDING the + checkout's lock, the handoff unwritten; then it writes it, uncommitted + (2), and releases both.""" + c = box.seam() + f = c / HANDOFF_REL + block = _block(box) + _staged_foreign(box, c) + sidecar = box.lanes_root / ".lane-state.lock" + holder = subprocess.Popen(["sleep", "300"]) + try: + sidecar.mkdir(parents=True) + (sidecar / "pid").write_text(f"{holder.pid} {ME}\n", encoding="utf-8") + waiter = box.popen("write-handoff", LANE, "--prepend", str(block), "--subject", "handoff: PAUSED") + time.sleep(WATCH) + assert waiter.poll() is None, "it waits on the workstation's sidecar lock" + assert box.lock_of(c).is_dir(), "...holding its checkout lock, though S3 refused" + assert f.read_text(encoding="utf-8") == HANDOFF + finally: + holder.kill() + holder.wait() + out, err = waiter.communicate(timeout=120) + assert waiter.returncode == 2, err + assert "the handoff is WRITTEN and NOT committed" in err + assert f.read_text(encoding="utf-8") == prepended(HANDOFF) + assert out == _line(f) + _released(box, c) + + +def test_write_handoff_is_exempt_from_the_workstation_guard(box: Box): + """610 and 611: in a container with no `LANES_WORKSTATION`, every writer + in the dispatcher's guard refuses (2, R-A11-14), and `write-handoff` is + not in that guard, on purpose (R-A11-11: a swap is never left unwritten, + and the commit's subject is its caller's). It writes, commits and + pushes the handoff; `set-row-state` beside it is refused.""" + c = box.seam() + unset = {"LANES_WORKSTATION": "", "LANES_IN_CONTAINER": "1"} + guarded = box.helper("set-row-state", LANE, "LIVE · in a container", **unset) + assert guarded.returncode == 2, guarded.stderr + assert "this is a container and $LANES_WORKSTATION is not set" in guarded.stderr + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, **unset) + assert result.returncode == 0, result.stderr + assert "$LANES_WORKSTATION is not set" not in result.stderr + assert box.origin_file(HANDOFF_REL) == stamped(HANDOFF) + assert result.stdout == _line(c / HANDOFF_REL) + _released(box, c) + + +# ------------------------------------------------- m2a's battery, its second half + +def test_stamp_into_an_empty_handoff_is_refused_1_and_says_the_line(box: Box): + """m2a's G05: an empty handoff has no header block to stamp under, so the + stamp is refused (1) and the line it wants is said, as `lane-start` said + it. Without that check the splice went on, and its proof refused it in + other words (2, "did not prove out"). Nothing written, committed or + pushed.""" + c = box.seam() + f = c / HANDOFF_REL + box.write(c, HANDOFF_REL, "") + box.commit(c, "an empty handoff", HANDOFF_REL) + box.git("-C", str(c), "push", "-q", "origin", "main") + tip = box.origin_head() + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + assert result.returncode == 1, result.stderr + assert f"the handoff at {f} is empty, so it has no header block to stamp under" in result.stderr + assert f"The line it wants: {STAMP}" in result.stderr + assert "did not prove out" not in result.stderr + assert f.read_text(encoding="utf-8") == "" + assert box.origin_head() == tip + assert result.stdout == "" + _released(box, c) + + +#: A handoff the row names under `handoffs/` and nobody ever wrote. +GONE_REL = "handoffs/repoS4/no-such-handoff.md" + + +@pytest.mark.parametrize("mode", ["stamp", "prepend", "whole"]) +def test_a_row_naming_a_handoff_the_checkout_does_not_have_is_8_and_nothing_written(box: Box, mode: str): + """m2a's G06: the row names a file under `handoffs/` that is not there. + There is nothing to write into: 8, the path said (and, for `--stamp`, the + line it would have written), never "unreadable" (1). Nothing is made, + committed or pushed.""" + box.cell_is(GONE_REL) + c = box.seam() + gone = c / GONE_REL + args = {"stamp": ("--stamp", STAMP), + "prepend": ("--prepend", str(_block(box))), + "whole": ("--whole", str(_prepared(box)), "--expect-digest", "0" * 64)}[mode] + before = box.snapshot(c) + result = box.write_handoff(*args, "--subject", SUBJECT) + err = result.stderr + assert result.returncode == 8, err + assert f"lane {LANE}'s handoff is not at {gone}, so there is nothing to write into. Nothing was written." in err + assert "unreadable" not in err + said = f"the stamp line it would have written under the handoff's header block: {STAMP}" in err + assert said == (mode == "stamp"), err + assert not gone.exists() + assert not gone.is_symlink() + assert box.snapshot(c) == before + assert result.stdout == "" + _released(box, c) + + +def test_a_prepend_fenced_at_preparing_takes_expected_digest_none(box: Box): + """m2a's H03: `lane-handoff` binds its prepend at `preparing`, before the + restart has a digest, and `--expected-digest none` says so: `none` is no + digest to compare, so the fenced write goes ahead (0) and reserves + nothing. Compared as a digest, it was refused (2, "the restart handoff + changed before this owned write").""" + c = box.seam() + f = c / HANDOFF_REL + box.intent("preparing", "--expect", "none", "--operation", FENCE[0], "--generation", FENCE[1], + "--handoff", str(f)) + result = box.write_handoff("--prepend", str(_block(box)), "--subject", "handoff: PAUSED", + "--restart", FENCE[0], FENCE[1], "0", "preparing", "none", + "--expected-digest", "none") + assert result.returncode == 0, result.stderr + assert "the restart handoff changed before" not in result.stderr + assert f.read_text(encoding="utf-8") == prepended(HANDOFF) + assert box.origin_file(HANDOFF_REL) == prepended(HANDOFF) + assert box.intent_field("prepared_digest") == "none" + assert result.stdout == _line(f) + _released(box, c) + + +#: A link under `handoffs/` to the lane's handoff, which a row's cell may name. +LINK_REL = "handoffs/repoS4/link-to-handoff.md" + + +def _cell_spelled(box: Box, spelling: str) -> None: + """The peer points the lane's cell at its handoff in another spelling than + the checkout's own path: the estate's `~/...` through the estate link, or + a link under `handoffs/` to the lane's handoff, published first.""" + if spelling == "estate": + box.cell_is(_estate_link(box)) + return + seed = box.seed + box.git("-C", str(seed), "pull", "-q", "--ff-only", "origin", "main") + (seed / LINK_REL).symlink_to(Path(HANDOFF_REL).name) + box.commit(seed, "a link to the lane's handoff", LINK_REL) + box.git("-C", str(seed), "push", "-q", "origin", "main") + box.cell_is(LINK_REL) + + +@pytest.mark.parametrize("mode", ["stamp", "prepend"]) +@pytest.mark.parametrize("spelling", ["estate", "link"]) +def test_a_cell_spelled_otherwise_whose_file_a_peer_deletes_mid_push_keeps_the_write_on_HEAD_exit_6( + box: Box, spelling: str, mode: str): + """m2a's J01 and J02: A37's keep names the file by its path in this + checkout, and A46 compares origin's cell with the cell this write + followed, each in its own spelling. Where the cell is not that path (the + estate's `~/...`, or a link under `handoffs/` to the lane's handoff) and a + peer deletes the file while this write's push is in flight, the row left + as it was, the write is kept on HEAD (6) and never replayed into nothing + (8, the write only in the reflog).""" + _cell_spelled(box, spelling) + c = box.seam() + f = c / HANDOFF_REL + if mode == "stamp": + args, written = ("--stamp", STAMP), stamped + else: + args, written = ("--prepend", str(_block(box))), prepended + _peer_deletes_the_handoff(box) + box.race(prepared=True) + result = box.write_handoff(*args, "--subject", SUBJECT) + err = result.stderr + assert result.returncode == 6, err + kept = box.head(c) + assert f"origin's copy of {HANDOFF_REL} was deleted while this write's push was in flight" in err, err + assert f"kept on HEAD: {kept}" in err + assert "push refused (attempt" not in err, "nothing is dropped or replayed" + assert box.subject(c) == SUBJECT + assert f.read_text(encoding="utf-8") == written(HANDOFF), "the write is in this checkout" + assert not _origin_has(box, HANDOFF_REL) + assert [line.split()[1] for line in box.unpublished(c)] == [kept], "one commit unpublished, this write's" + assert result.stdout == _line(f) + _released(box, c) + + +def test_a_peer_that_deletes_the_handoff_and_the_register_mid_push_keeps_the_write_on_HEAD_exit_6(box: Box): + """m2a's J03: where origin's register cannot be read at the replay, A46 + cannot say whether the row names another cell, and a read that failed is + never "replay" (R22). A peer's commit that deletes the lane's handoff and + the register lands while this write's push is in flight: the write is + kept on HEAD (6), one commit unpublished, nothing dropped.""" + c = box.seam() + f = c / HANDOFF_REL + p = box.peer + box.git("-C", str(p), "fetch", "-q", "origin") + box.git("-C", str(p), "merge", "-q", "--ff-only", "refs/remotes/origin/main") + box.git("-C", str(p), "rm", "-q", "--", HANDOFF_REL, REGISTER) + box.git("-C", str(p), "commit", "-q", "-m", "a peer deletes the handoff and the register") + box.race(prepared=True) + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + err = result.stderr + assert result.returncode == 6, err + kept = box.head(c) + assert f"origin's copy of {HANDOFF_REL} was deleted while this write's push was in flight" in err, err + assert f"kept on HEAD: {kept}" in err + assert "push refused (attempt" not in err, "nothing is dropped or replayed" + assert f.read_text(encoding="utf-8") == stamped(HANDOFF), "the write is in this checkout" + assert not _origin_has(box, REGISTER) + assert [line.split()[1] for line in box.unpublished(c)] == [kept], "one commit unpublished, this write's" + assert result.stdout == _line(f) + _released(box, c) + + +def test_a_cell_a_peer_pads_otherwise_is_the_same_cell_and_the_write_is_kept_on_HEAD_exit_6(box: Box): + """m2a's J04b: A46 compares origin's cell with the cell this write + followed once the blanks round it are trimmed, as the register's readers + take it: padding is no part of a cell. A peer that moves the handoff to a + file whose name ends in a blank and writes the row's cell unpadded, with + that one trailing blank, has not pointed the cell anywhere else, so the + write is kept on HEAD (6) and is not replayed into the file whose name + ends in a blank (compared untrimmed, it was "another cell", a file origin + has: the commit dropped and the re-run writing there).""" + c = box.seam() + f = c / HANDOFF_REL + p = box.peer + odd = HANDOFF_REL + " " + box.git("-C", str(p), "fetch", "-q", "origin") + box.git("-C", str(p), "merge", "-q", "--ff-only", "refs/remotes/origin/main") + box.git("-C", str(p), "mv", "--", HANDOFF_REL, odd) + reg = p / REGISTER + text = reg.read_text(encoding="utf-8") + assert text.count(f"| {HANDOFF_REL} |") == 1 + reg.write_text(text.replace(f"| {HANDOFF_REL} |", f"|{HANDOFF_REL} |"), encoding="utf-8") + box.git("-C", str(p), "add", "--", REGISTER) + box.git("-C", str(p), "commit", "-q", "-m", "a peer renames the file to a blank-ended name, cell unpadded") + box.race(prepared=True) + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT) + err = result.stderr + assert result.returncode == 6, err + kept = box.head(c) + assert f"origin's copy of {HANDOFF_REL} was deleted while this write's push was in flight" in err, err + assert f"kept on HEAD: {kept}" in err + assert "push refused (attempt" not in err, "nothing is dropped or replayed" + assert f.read_text(encoding="utf-8") == stamped(HANDOFF), "the write is in this checkout" + names = box.git("--git-dir", str(box.origin), "ls-tree", "-r", "--name-only", "main").splitlines() + assert HANDOFF_REL not in names and odd in names + assert not (c / odd).exists(), "nothing was written into the blank-ended name" + assert [line.split()[1] for line in box.unpublished(c)] == [kept], "one commit unpublished, this write's" + assert result.stdout == _line(f) + _released(box, c) + + +def test_a_routed_bind_of_the_rows_cell_clones_no_checkout(box: Box): + """m2a's K03: routed, the lane has no checkout yet, and + `set-restart-intent --handoff ` binds the path that + checkout will have, as `handoff-path` derives it without `--create`. A + bind writes the sidecar and nothing else: no checkout is cloned for it, + and W is as it was.""" + box.use_net() + c = box.checkout(LANE) + w_before = box.w_state() + made = _bound(box, HANDOFF_REL) + assert made.returncode == 0, made.stderr + assert f"--handoff {HANDOFF_REL} is bound as {c / HANDOFF_REL}" in made.stderr + assert box.intent_field("handoff") == str(c / HANDOFF_REL) + assert not c.exists(), "a bind makes no checkout" + assert "clone" not in box.net_ops() + assert box.w_state() == w_before + + +# ------------------------------------------- lane openRepoTools-3's review of #312 + +def test_a_commit_landing_just_after_this_writes_commit_is_told_by_its_trailer_exit_6(box: Box): + """606: slice 1b's 291 tells this write's commit by its message where an + unlocked writer's lands just after `git commit`. This write's message has + the `Lane:` trailer as its second paragraph, and the subject alone never + matched what git stored, so every such race was "cannot be told" (6, + nothing named). Given the whole message, this write's commit is found + under the stamp and named, nothing is replayed, and both are kept on HEAD + (6), as `set-row-state`'s are.""" + c = box.seam() + box.race() + env = _git_shim(box, '*" commit -q -m handoff("*', after=_stamp(c)) + result = box.write_handoff("--stamp", STAMP, "--subject", SUBJECT, **env) + assert (box.root / "fired").exists(), "the stamp landed just after this write's commit" + assert result.returncode == 6, result.stderr + err = result.stderr + stamp, mine = box.head(c), box.git("-C", str(c), "rev-parse", "HEAD~1").strip() + assert box.subject(c, "HEAD~1") == SUBJECT, "this write's commit is under the stamp" + assert box.git("-C", str(c), "log", "-1", "--format=%B", mine).strip().splitlines()[1:] == ["", f"Lane: {LANE}"] + assert "cannot be told from another made in this checkout" not in err, err + assert f"HEAD is {stamp}, not this write's commit {mine}" in err, err + assert "push refused (attempt 1 of 6)" not in err, "nothing is replayed" + assert (c / "handoffs" / "stamp.md").is_file(), "the other writer's file is still there" + assert len(box.unpublished(c)) == 2, "both kept on HEAD"