diff --git a/lanes-edit.sh b/lanes-edit.sh index 7c896b7..4f7508a 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 @@ -451,7 +458,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 @@ -478,9 +488,28 @@ # 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 file under `handoffs/` of any other +# clone of the register (A63: lane openRepoTools-3's answer to question +# 4 of #18; an uncommitted write there wedged that lane's next write at +# S3 step 4), 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 @@ -553,14 +582,22 @@ # 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 -# 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 -# another writer committed on the branch in between, the commit is -# named OFF HEAD, with `reflog -5`. Every later write from that +# 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) +# (that commit is in that repository; no write here refuses over it). 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 another writer committed on the branch in between, the +# commit is named OFF HEAD, with `reflog -5`. Every later write from that # checkout refuses under S3 until the lane publishes or archives it # 7 ANOTHER ACT GOT THERE FIRST, and this one wrote nothing. `claim`: # CLAIM-LOST, another lane's claim landed on main first. @@ -570,14 +607,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). @@ -696,8 +740,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 @@ -1317,6 +1362,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 @@ -1489,10 +1542,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 @@ -1617,6 +1673,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 @@ -1626,11 +1689,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" @@ -1663,7 +1727,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" @@ -3093,6 +3157,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 @@ -3146,15 +3224,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. @@ -3178,6 +3262,53 @@ 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" + # THE PUBLISH ACT REFUSES HERE ("moved past", 2): origin moved, so the + # recovery is S6.3's archive, as 1b's 705 made every other + # origin-moved note say (lane openRepoTools-3's review of #18 at + # 2b1a232, SHOULD 2; A62). + cp_recovery_steps "it is" + 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" + cp_recovery_steps "they are" + 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 @@ -3482,9 +3613,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" cp_recovery_steps "it is" @@ -6844,6 +6978,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 @@ -13806,6 +13953,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 @@ -16866,6 +17147,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 @@ -17010,8 +17296,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 @@ -17047,7 +17334,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 @@ -17061,7 +17348,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 @@ -17150,38 +17439,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 @@ -17273,8 +17749,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 @@ -17340,6 +17820,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 @@ -20458,38 +20944,593 @@ 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). So is a file + # under `handoffs/` of any other clone of the register, not beside W + # too (A63: lane openRepoTools-3's answer to question 4 of #18; an + # uncommitted write there wedged that lane's next write at S3 step 4). + # 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; no clone of the register is one (A57, + # A63). + # 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 + 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 + 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; A59). 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 || :)" + whf_oreg=0 + if [ -n "$whf_otop" ] && { [ -e "$whf_otop/$LANES_PATH" ] || [ -L "$whf_otop/$LANES_PATH" ]; } && + path_under "$whf_phys" "$whf_otop"; then + whf_oreg=1 + fi + if [ "$whf_oreg" = 1 ] && ! 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 + # AND A FILE UNDER handoffs/ OF ANOTHER CLONE OF THE REGISTER THAT IS NOT + # BESIDE W IS REFUSED TOO (lane openRepoTools-3's answer to question 4 of + # #18, REFUSE; A63). It is that clone's lane's file, not Amendment 8(d)'s + # "not the register's": 604's commit and push stay out of it (A57), and + # written there, uncommitted, it left a change its lane's next write + # refused over at S3 step 4, 601's harm. Per-lane `LANES_REPO` clones + # exist outside W's siblings, and A44's test of the top level is there + # already. After 601's check, so a clone beside W keeps 601's words. + if [ "$whf_oreg" = 1 ]; then + whf_stamp_said + die "lane $lane's handoff target $whf_path is ${whf_phys#"$whf_otop"/} in $whf_otop, 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 \"not the register's\": a lane's handoff is written in its own checkout only. A write there would leave a change that clone's lane refuses over at its next write (Amendment 23 S3 step 4), and a commit and push there would publish to the register outside S3 and S4. 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 - [ "$(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 + # 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 - release_lock - printf '%s\n' "$pbh_target" + 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 so did A63's + # (lane openRepoTools-3's answer to question 4 of #18), before a byte, so + # the branch below is a BACKSTOP for a top level that came to carry the + # register after those checks: the handoff is then written and NOT + # committed (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) @@ -20622,15 +21663,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) @@ -21428,6 +22522,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..826a2e8 --- /dev/null +++ b/tests/test_a23_write_handoff.py @@ -0,0 +1,2831 @@ +# 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, + _recovery_is_s63s_archive, _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) + + +@pytest.mark.parametrize("how", ["stamp-target", "stamp-cell", "prepend-target", "whole-target"]) +def test_a_handoff_in_another_clone_of_the_register_elsewhere_is_refused(box: Box, how: str): + """Question 4 of #18, answered by lane openRepoTools-3 (REFUSE, 2; A63). + 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". A57 kept 604's commit and push out of it and + wrote it there, uncommitted, exit 0: the change then wedged that lane's + next write at S3 step 4, which is 601's harm. A44's test of the top level + is there already, so the file is refused (2) before a byte, by `--target` + or by an absolute cell, in each of the three modes: that clone, this + checkout and the register's origin are as they were, the stamp line is + said, nothing is on stdout.""" + d = box.root / "elsewhere-clone" + if how == "stamp-cell": + box.cell_is(str(d / HANDOFF_REL)) + c = box.seam() + box.clone(d) + f = d / HANDOFF_REL + if how == "prepend-target": + args = ("--prepend", str(_block(box)), "--target", str(f)) + elif how == "whole-target": + args = ("--whole", str(_prepared(box)), "--expect-digest", sha(f), "--target", str(f)) + else: + args = ("--stamp", STAMP) + (("--target", str(f)) if how == "stamp-target" else ()) + text, theirs, mine = f.read_bytes(), box.snapshot(d), box.snapshot(c) + d_head = box.git("-C", str(d), "rev-parse", "HEAD").strip() + result = box.write_handoff(*args, "--subject", SUBJECT) + assert result.returncode == 2, result.stderr + assert (f"is {HANDOFF_REL} 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") in result.stderr + assert "Nothing was written." in result.stderr + assert "committed there" not in result.stderr + if how.startswith("stamp"): + 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.git("-C", str(d), "rev-parse", "HEAD").strip() == d_head + assert box.git("-C", str(d), "status", "--porcelain") == "", "nothing pending in that clone for its S3" + assert box.snapshot(d) == theirs + assert box.snapshot(c) == mine + assert result.stdout == "" + _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 _git_says(env: dict, *args: str, cwd: Path) -> str: + got = subprocess.run(["git", *args], env=env, cwd=str(cwd), capture_output=True, text=True, check=False) + return (got.stdout + got.stderr).strip() or "nothing" + + +def _skip_other_owner(box: Box, env: dict) -> None: + """Skip, saying why: git read a clone it was told to take for another + owner's. Either this git ignores `GIT_TEST_ASSUME_DIFFERENT_OWNER`, or a + `safe.directory = *` reaches it through the global config; the version + and every `safe.directory` it reports in this environment (each value + with the file it comes from) name which.""" + version = _git_says(env, "--version", cwd=box.root) + safe = _git_says(env, "config", "--show-origin", "--get-all", "safe.directory", cwd=box.root) + star = any(line.split("\t")[-1].strip() == "*" for line in safe.splitlines()) + cause = ("a `safe.directory = *` in the global config" if star + else "a git that ignores GIT_TEST_ASSUME_DIFFERENT_OWNER") + pytest.skip(f"{version}: with GIT_TEST_ASSUME_DIFFERENT_OWNER=1 and GIT_CONFIG_NOSYSTEM=1, git still reads " + f"a clone of another owner, which fits {cause}; safe.directory as git reports it in this " + f"environment: {safe.replace(chr(10), '; ')}") + + +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. + Another owner is git's test hook with `GIT_CONFIG_NOSYSTEM=1`, so that a + `safe.directory = *` in the machine's system config cannot name the clone + safe (on CI's git 2.56.0 the probe read the clone, and this case failed + there, with the box's `.gitconfig` as the global config): the box's + `.gitconfig` is the only config that names a directory safe, and the + write runs in the environment the probe ran in. Where git still reads the + clone, the case is skipped with the cause, never passed.""" + 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", "GIT_CONFIG_NOSYSTEM": "1"} + probe = subprocess.run(["git", "-C", str(d), "rev-parse", "--show-toplevel"], + env=dict(box.env, **extra), capture_output=True, text=True, check=False) + if how == "other-owner" and probe.returncode == 0: + _skip_other_owner(box, dict(box.env, **extra)) + 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 _whole_write_a_stamp_lands_on_top_of(box: Box) -> tuple: + """The setup S4.4's on-top cases share: a peer edits the handoff and lands + it at this write's push, and an unlocked writer's stamp lands on top of + this write's commit in the same moment. Returns the checkout and the + handoff in it.""" + 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") + return c, f + + +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 S6.3's archive.""" + c, f = _whole_write_a_stamp_lands_on_top_of(box) + 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 archive" + assert result.stdout == _line(f) + _released(box, c) + + +@pytest.mark.parametrize("how", ["head-is-this-write", "stamp-on-top"]) +def test_whole_kept_by_S4_4_offers_the_archive_not_the_publish_act_that_refuses(box: Box, how: str): + """lane openRepoTools-3's review of #18 at 2b1a232, SHOULD 2 (A62): S4.4's + two notes (this write's commit is HEAD, or another commit is on top of it) + run only after a push origin's move refused, and they offered the publish + act, with a short `reset --keep origin/main`. The publish act refuses + there ("moved past", 2), as 1b's 705 made every other origin-moved note + say. Now each says what is kept must be archived and dropped, then gives + S6.3's recovery: the list, the STOP line, the archive and the drop with + the tracking ref in full, never the publish act. The act is run after the + refusal, as the old note offered it: it refuses (2) and pushes nothing.""" + if how == "stamp-on-top": + c, f = _whole_write_a_stamp_lands_on_top_of(box) + else: + 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 + err = result.stderr + assert "is no longer the one this whole-file write started from" in err + _recovery_is_s63s_archive(err, c, "it is" if how == "head-is-this-write" else "they are") + assert "reset --keep origin/main" not in err, "the tracking ref is named in full" + tip, mine = box.origin_head(), box.head(c) + offered = box.helper("commit", "what it is", LANES_LANE=LANE) + assert offered.returncode == 2, offered.stderr + assert "origin/main has moved past the local commits" in offered.stderr + assert "Nothing was pushed." in offered.stderr + assert box.origin_head() == tip + assert box.head(c) == mine + 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 archive" + 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), "origin has the moved file" + assert not _origin_has(box, HANDOFF_REL), "origin no longer has the old name" + 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 archive.""" + 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, "origin no longer has the old name" + assert odd in names, "origin has the blank-ended name" + 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"