From 98e2ec083ac3f906dfa8b71c4fbe0b8fd72ef093 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Thu, 22 Oct 2026 13:38:52 +0000 Subject: [PATCH 01/17] Amendment 23 act 2, slice 3: the handoff skill defines its file through `handoff-path` and writes it with ONE `write-handoff --whole` call, so it spells no checkout and commits, pulls and pushes nothing of its own, and step 4's `append-line` and `set-row-state` carry `LANES_LANE` (#198) Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 (cherry picked from commit b599ed9f4f90afa8f911b4274b1d80943f0ce6d7) --- skills/handoff/SKILL.md | 160 +++-- tests/test_handoff_skill_commit_block.py | 871 +++++++++++++---------- 2 files changed, 588 insertions(+), 443 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index aba1d6d..f769112 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -268,19 +268,15 @@ Only the second branch leaves anything blank, and it leaves it blank because not ## 2. Refresh the handoff -Manual preservation and restart/resume-stamp publication is serialized by `lanes-edit.sh publish-handoff`. -Read the current file's SHA-256, write semantic updates to a temporary file, then -publish that complete prepared file with: - -```sh -"$L" publish-handoff "$lane" "$handoff_file" "$prepared_file" --expect-digest "$prior_sha256" -``` - -The helper checks the expected bytes and atomically replaces the resolved target -while holding the workspace writer mutex. It refuses an active restart owned by -another operation. Do not edit or redirect into the canonical handoff directly; -perform the existing explicit-pathspec commit/push after successful publication. -Legacy `rename-lane` is a separate four-file transaction under the same mutex; +The handoff is written by ONE call, `lanes-edit.sh write-handoff --whole` (Amendment 23, S2–S4), and this +skill holds no lock and runs no `git` of its own. Under the lane checkout's lock the verb brings that +checkout to origin, checks that the file still has the bytes this session read, replaces it atomically +under the workstation's sidecar lock, commits it by its own pathspec with the Rule 5 `Lane:` trailer, +pushes with no pull, reads origin back, and replays the one write on a refused push. It refuses an active +restart owned by another operation. So the order below is: say where the handoff is and read its SHA-256, +write the complete new file to a temporary file, then make the one call under "THE WRITE IS ONE CALL". Do +not edit or redirect into the canonical handoff directly, and do not commit it: the verb is the commit. +Legacy `rename-lane` is a separate four-file transaction under the workspace's writer mutex; it refuses unfinished/failed restart ownership and uses EXIT rollback. Its stamp now atomically replaces the handoff through a temporary file beside it (#158), but the four-file rename itself is still not crash-atomic (#224). @@ -332,9 +328,49 @@ if [[ -n "$row" && -z "$uuid" ]]; then fi ``` -If `$handoff` came back empty, **stop here** — this row does not split the way `row_cell` expects, a -`git add ""` below commits nothing while looking like it worked, and Amendment 6(e) exists for exactly this -step. Read the row's own text by hand and take its handoff token; do not guess one. +If `$handoff` came back empty, **stop here** — this row does not split the way `row_cell` expects, the call +under "THE WRITE IS ONE CALL" would name no handoff while looking like it worked, and Amendment 6(e) exists +for exactly this step. Read the row's own text by hand and take its handoff token; do not guess one. + +**WHERE THE HANDOFF IS, AND THE BYTES THIS WRITE IS MADE FROM.** `$handoff` is the row's own spelling, and the +check above is all this skill asks of it. The file `write-handoff` writes is `handoff-path`'s answer: that cell +translated into the lane's OWN checkout (Amendment 23 S2), derived here and never spelled. No checkout path is +written anywhere in this step, and none is taken from the environment or from the helper's workspace root as a +fallback. A read that failed is no answer (Amendment 7(d)), so nothing is guessed in its place. `--create` +makes that checkout first where this is its first use, so that there is a file to read. The SHA-256 of the +file as it stands is the digest the verb holds this write to: it replaces the file only while the file still +has those bytes (exit 7 where it does not), so the new file is made from what was read here and never from a +copy that has moved. Where the block below prints `HANDOFF NOT FOUND` there is no file to write into: the call +under "THE WRITE IS ONE CALL" is not made, and the swap goes on with the gap in the report (`R-A11-11`, *a +swap is never left unwritten*). + +```sh +# WHERE THE HANDOFF IS: derived, never spelled. `handoff-path` only reads, and `--create` makes the lane's own +# checkout first where this is its first use. +hf_rc=0; handoff_file=""; prior_sha256=""; prepared_file="" +handoff_file="$("$L" handoff-path "$lane" --create)" || hf_rc=$? +if [[ "$hf_rc" == 0 && -n "$handoff_file" && -f "$handoff_file" ]]; then + # THE DIGEST OF WHAT IS THERE NOW, by the two tools the helper's own `lane_handoff_digest` tries: + # `sha256sum` on Linux, `shasum -a 256` on macOS. + if command -v sha256sum >/dev/null 2>&1; then + prior_sha256="$(sha256sum -- "$handoff_file" | awk '{print $1; exit}')" + elif command -v shasum >/dev/null 2>&1; then + prior_sha256="$(shasum -a 256 -- "$handoff_file" | awk '{print $1; exit}')" + fi + sha_re='^[0-9a-f]{64}$' + [[ "$prior_sha256" =~ $sha_re ]] || prior_sha256="" + # THE COMPLETE NEW FILE goes in this one, whole: the top block below, a `---` line and a blank line, then + # every byte of the old file. The verb refuses an empty file, so a file never written is a refusal and not a + # blank handoff. + prepared_file="$(mktemp "${TMPDIR:-/tmp}/handoff-prepared.XXXXXX")" || prepared_file="" + printf 'handoff_file=%s\nprior_sha256=%s\nprepared_file=%s\n' \ + "$handoff_file" "${prior_sha256:-}" "${prepared_file:-}" +else + printf 'HANDOFF NOT FOUND: `%s handoff-path %s --create` exited %s and named %s, so there is no handoff to write into and the call under "THE WRITE IS ONE CALL" is NOT made. The swap goes on (R-A11-11); the report carries this line.\n' \ + "$L" "$lane" "$hf_rc" "${handoff_file:-no file}" + handoff_file="" +fi +``` `$uuid` is this session's own id — `$CLAUDE_CODE_SESSION_ID`, which a running session always has — else the **last** id in the session cell, the only one Amendment 6(b) calls the lane's. Rewrite the handoff's state line, **every word given and not yet executed**, and the resume prompt; @@ -410,56 +446,54 @@ made, and that the row still reads as it did — so the next session knows the r handoff rather than believing the lane never paused. This commit is in the LANE'S OWN repository and is not one of the writes that stop. -**COMMIT IN THE CHECKOUT THE HANDOFF WAS PUBLISHED IN, BY ITS OWN PATH, AND NOTHING ELSE** (Amendment 4(d): -*"with explicit pathspecs — never `git add -A`, never a bare `git commit`"*; Amendment 5(c) and (d)). The -checkout is RESOLVED, never spelled: `$LANES_REPO` first, then the helper's own `workspace-root`, which is the -order `lane-handoff` uses — a lane window exports `LANES_REPO` to the lane's own clone, and step 4's writes go -there. Step 2's publication target `$handoff_file` is the same file (`$LANES_REPO/$handoff` where that exists, -else the workspace root's), so the file published is the file committed. A STAGED ENTRY THIS HANDOFF DID NOT -MAKE IS A REFUSAL, read before the `git add`: a bare commit would publish it, and with a pathspec the pull would -refuse on it. So is a rebase, merge, cherry-pick or revert in progress. Then the Rule 5 `Lane:` trailer, -pull-rebase and push: +**THE WRITE IS ONE CALL, AND IT COMMITS THE HANDOFF'S OWN PATH AND NOTHING ELSE** (Amendment 4(d): +*"with explicit pathspecs — never `git add -A`, never a bare `git commit`"*; Amendment 5(c) and (d); Amendment 23 +S3–S5). The block under this paragraph is the whole of it, and it is the only act in this step that writes. +THERE IS NO CHECKOUT FOR THIS SKILL TO RESOLVE AND NONE FOR IT TO SPELL: no `cd`, no commit, no pull and no push of +its own, and no fallback from one checkout to another. The verb writes in the lane's own checkout, under that +checkout's lock, and `LANES_LANE` names the lane. (This block used to commit in whichever checkout held the +file, the shared workspace checkout among them, with no lock, and to rebase on a pull before its push; finding +282 of the review of opensoft/openRepoTools#307 names it one of the three writers that did.) A STAGED ENTRY +THIS HANDOFF DID NOT MAKE and an operation in progress are the verb's refusals now, made before anything is +committed (2); a handoff that moved after its digest was read is exit 7, and nothing is published. +`$hf_subject` is the commit's one-line subject: the lane, the workstation (`none` where step 1 found none) and +the why in the operator's words, `handoff` where there is none. ```sh -# THE CHECKOUT IS RESOLVED, NEVER SPELLED: `$LANES_REPO` first, then the -# helper's own `workspace-root`, the order `lane-handoff` uses. Lane windows -# export `LANES_REPO` to the lane's own clone, and step 4's writes go there. -hc_root=""; hc_why=""; hc_staged="" -for hc_base in "${LANES_REPO:-}" "$("$L" workspace-root 2>/dev/null)"; do - if [ -n "$hc_base" ] && [ -f "$hc_base/$handoff" ]; then hc_root="$hc_base"; break; fi -done -if [ -z "$hc_root" ]; then - hc_why="$handoff is under neither \$LANES_REPO nor the workspace root" -else - hc_git="$(git -C "$hc_root" rev-parse --absolute-git-dir)" - for hc_op in rebase-merge rebase-apply MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD; do - [ ! -e "$hc_git/$hc_op" ] || hc_why="$hc_root has an operation in progress ($hc_op)" - done - # A STAGED ENTRY THIS HANDOFF DID NOT MAKE, read BEFORE the add: a bare - # commit would publish it, and with a pathspec the pull would refuse on it. - hc_staged="$(git -C "$hc_root" diff --cached --name-only | grep -vxF -- "$handoff")" - [ -z "$hc_staged" ] || hc_why="${hc_why:+$hc_why; }the index of $hc_root holds $(printf '%s\n' "$hc_staged" | wc -l | tr -d ' ') staged path(s) this handoff did not make" -fi -if [ -n "$hc_why" ]; then - printf 'HANDOFF NOT COMMITTED: %s. The file is written; nothing was committed or pushed. Never commit staged entries you did not make.\n' "$hc_why" - [ -z "$hc_staged" ] || printf '%s\n' "$hc_staged" | head -n 20 | sed 's/^/ staged: /' -elif ! { git -C "$hc_root" add -- "$handoff" && - git -C "$hc_root" commit -m "handoff(@): PAUSED, " -m "Lane: " -- "$handoff"; }; then - printf 'HANDOFF NOT COMMITTED: git refused the add or the commit in %s (its words are above).\n' "$hc_root" -elif git -C "$hc_root" pull --rebase origin "${LANES_BRANCH:-main}" && - git -C "$hc_root" push origin "${LANES_BRANCH:-main}"; then - echo "handoff pushed from $hc_root" +# A CONTROL CHARACTER IS REMOVED, NOT REFUSED OVER: the verb exits 64 on one in the subject and writes nothing, +# and a why with a stray tab must not cost the swap its handoff. +hf_subject="handoff($lane@${ws:-none}): PAUSED, ${why:-handoff}" +hf_subject="$(printf '%s' "$hf_subject" | LC_ALL=C tr -d '[:cntrl:]')" +hf_rc=0; hf_out="" +if [[ -n "$handoff_file" && -n "$prior_sha256" && -s "${prepared_file:-}" ]]; then + hf_out="$(LANES_LANE="$lane" "$L" write-handoff "$lane" --whole "$prepared_file" \ + --expect-digest "$prior_sha256" --subject "$hf_subject")" || hf_rc=$? + # ITS STDOUT IS `` ONCE THE FILE IS WRITTEN, whatever the exit; its own words are on stderr. + hf_path="${hf_out%%$'\t'*}" + hf_wrote="nothing was written"; [[ -z "$hf_out" ]] || hf_wrote="the file is written at $hf_path" + case "$hf_rc" in + 0) printf 'handoff written: %s (pushed from the lane checkout; a handoff in another repository is written and never committed, and its note above says so)\n' "$hf_path" ;; + 2) if [[ -n "$hf_out" ]]; then + printf 'HANDOFF NOT COMMITTED: write-handoff refused the commit (exit 2; its words are above) and the file is written at %s, uncommitted. Nothing was pushed. Do not call it again: the file no longer has the bytes the digest names, so a second call is refused (7). Answer the refusal, then commit and publish the file as the note above says.\n' "$hf_path" + else + printf 'HANDOFF NOT WRITTEN: write-handoff refused (exit 2; its words are above) and wrote nothing.\n' + fi ;; + 3) printf 'HANDOFF PUSHED, NOT CONFIRMED: the push went, or timed out, and origin could not be read back to show the commit on it (exit 3; the note above names it). Do not call it published until it is read back.\n' ;; + 6) printf 'HANDOFF NOT PUSHED: its commit is kept on HEAD of the lane checkout and named above (exit 6), and every later write from that checkout refuses until it is published; the note above names the act. Never force.\n' ;; + 7) printf 'HANDOFF NOT WRITTEN: the handoff is no longer the bytes digest %s names, or a restart owns it (exit 7; its words above say which), and nothing was published. If it moved, read it again, make the new file from what is there now, and make the call once more; if a restart owns it, do not retry, because that restart writes this handoff.\n' "$prior_sha256" ;; + *) printf 'HANDOFF NOT PUBLISHED: write-handoff exited %s (its words are above); %s.\n' "$hf_rc" "$hf_wrote" ;; + esac else - git -C "$hc_root" rebase --abort 2>/dev/null || : - printf 'HANDOFF NOT PUSHED: it is a local commit in %s, any rebase is aborted, and git said why above. Never force; look first:\n git -C %s fetch origin && git -C %s cherry -v origin/%s HEAD\n' \ - "$hc_root" "$hc_root" "$hc_root" "${LANES_BRANCH:-main}" + printf 'HANDOFF NOT WRITTEN: there is no handoff located, no digest read or no prepared file written, so write-handoff was NOT called. The swap goes on (R-A11-11); the report carries this line.\n' fi ``` A refusal, a conflict or a rejected push does not stop the swap (`R-A11-11`, *a swap is never left -unwritten*): the handoff file is written, the block prints what it did NOT do, and the report carries that -line. On a rebase conflict the block aborts and leaves the tree clean. Never force, and never commit staged -entries you did not make. +unwritten*): where the lane's checkout refuses the commit (2), the verb has still written the handoff, commits +nothing and prints git's own words; where its push is refused it replays the write on origin's new tip if +origin's copy of the handoff is still the one this write was made from, and otherwise keeps its commit on HEAD +and names it (6). The block prints what it did NOT do, and the report carries that line. Never force, never +`git add -A`, and never commit staged entries you did not make. ## 3. Subagents and worktrees — the one question, and the sweep that fills WRITERS @@ -740,7 +774,11 @@ LANES_LANE="$lane" LANES_SESSION="$uuid" "$L" log PAUSED "lane:$lane" \ # is never left unwritten, so a lane whose object line was refused still gets # this line and the row's state cell, with the gap named in the handoff — EXCEPT # where there is no uuid to name, which is the `$uuid_missing` that skips both. -"$L" append-line "PAUSED — lane $lane, session $uuid@$ws, $(date -u +%Y-%m-%dT%H:%M:%SZ), $why_text; handoff refreshed" +# `LANES_LANE` NAMES THE LANE ON THIS WRITE AND ON (c)'S AS IT DOES ON (a)'S (Amendment +# 23 S2): a register write goes through the lane's own checkout, and `append-line` finds +# that lane from this variable first, so a line that names no lane with a row is not a +# write with no lane. +LANES_LANE="$lane" "$L" append-line "PAUSED — lane $lane, session $uuid@$ws, $(date -u +%Y-%m-%dT%H:%M:%SZ), $why_text; handoff refreshed" fi fi # (c) the row: ONE WRITE, which sets the whole state cell (Amendment 13(a)). @@ -783,7 +821,7 @@ if [[ -n "$ws_missing" ]]; then row_write_refused=1 echo "NOT WRITTEN: the row's state cell stays as it is, because every register write from a container with no \$LANES_WORKSTATION stops (clause (k) rule (d)). The handoff refreshed in step 2 names this gap, and step 5 prints the restart command with --lane, because a restart cannot resolve this lane from a row that was never set." else -"$L" set-row-state "$lane" "PAUSED · $hs_line" || row_write_refused=1 +LANES_LANE="$lane" "$L" set-row-state "$lane" "PAUSED · $hs_line" || row_write_refused=1 fi ``` diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index 43c952a..b641d81 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -1,96 +1,88 @@ # SPDX-License-Identifier: Apache-2.0 -"""`skills/handoff/SKILL.md`'s step-2 handoff commit, held to -opensoft/openRepoTools#197, in seconds rather than the shell suite's hour. +"""`skills/handoff/SKILL.md`'s step-2 write: ONE call to `write-handoff`, held to +Amendment 23 (opensoft/openRepoTools#198, slice 3) in seconds rather than the +shell suite's hour. -The block a session runs after publishing its handoff was +The skill used to publish its handoff and then COMMIT it itself: - cd ~/projects/brett-wip + cd git add "$handoff" - git commit -m "handoff(@): PAUSED, " -m "Lane: " - git pull --rebase && git push - -and its own paragraph said "Commit with an explicit pathspec" and "On a -rebase conflict: abort". A bare `git commit` takes the WHOLE index, so in a -checkout holding another lane's staged entry it published that entry; the -spelled path is not the checkout a lane window writes (`LANES_REPO` names the -lane's own clone); and a same-file conflict left the checkout mid-rebase. -Amendment 4(d) requires *"explicit pathspecs — never `git add -A`, never a bare -`git commit` … on a rebase conflict the writer ABORTS, leaves the checkout -clean and reports"*, and Amendment 5(c) bans a bare `git commit` too. - -THE BLOCK IS RUN, NOT READ. It is extracted from the skill by the commit -message it writes, which the shipped block and the fixed one BOTH carry, so -every behavioural case below runs whichever block the file holds: each RED -case fails against the shipped block for the reason #197 measured, and passes -with the fix. Each runs under `bash`, and under `zsh` where one is installed, -because the Bash tool runs a skill's block under the user's own shell. - -NOTHING HERE READS THE CALLER'S ENVIRONMENT. The block READS `LANES_REPO`, -and a lane window exports it to that lane's REAL clone, so the environment is -an explicit dict, never `os.environ` plus changes: no `LANES_*`, no `TMUX*`, -`HOME` under `tmp_path`, no system git config. Every repository is under -`tmp_path`: a bare origin, the "shared" checkout at -`$HOME/projects/brett-wip` (which the stub `lanes-edit.sh` names as the -workspace root), the lane's own clone, and a peer clone that lands other -lanes' work. `maintenance.auto=false` and `gc.auto=0` keep git from starting -background work in the fixtures (#599's cleanup flake). + git commit -m "handoff(@): PAUSED, " -m "Lane: " -- "$handoff" + git pull --rebase origin main && git push origin main + +opensoft/openRepoTools#197 made that block resolve its checkout (`$LANES_REPO`, +then the helper's `workspace-root`), refuse a staged entry it did not make and +an operation in progress, and abort a conflicted rebase. It was still one of +the three writers that commit to a checkout with NO LOCK and then pull and push +(finding 282 of the review of #307, with `lane-start`'s stamp and +`lane-handoff`'s PAUSED commit), and its fallback could land in W, the readers' +copy (Amendment 23 S5). Slice 2a gave the one act that replaces all of it: + + handoff-path --create where the file is + write-handoff --whole --expect-digest --subject + +which takes the lane checkout's lock, brings the checkout to origin, checks the +digest, replaces the file atomically, commits it by its own pathspec with the +`Lane:` trailer, pushes, reads origin back and replays a refused push. So this +file holds the skill's two blocks to what is left of them: derive the path and +the digest, send the call with exactly those arguments, say what each exit +means and never stop the swap (`R-A11-11`), and spell no checkout of its own. + +THE BLOCKS ARE RUN, NOT READ, under `bash` and under `zsh` where one is +installed, because the Bash tool runs a skill's block under the user's own +shell. Two layers: + + * against a STUB `lanes-edit.sh` that answers `handoff-path` and + `write-handoff`, logs every argv and the `LANES_LANE` it was called under, + and exits as each case says: the arguments, the words and the exits; + * against the REAL `lanes-edit.sh` in slice 2a's sandbox + (`tests/test_a23_write_handoff.py`'s `Box`: a bare origin, W, the lane's + row and handoff, `LANES_ROUTE_WRITES=1`): the handoff is pushed from the + lane's own checkout C, and W is untouched. + +NOTHING HERE READS THE CALLER'S ENVIRONMENT OR TOUCHES A REAL REGISTER, W OR +GITHUB. Every environment is an explicit dict (no `LANES_*`, no `TMUX*`, `HOME` +under `tmp_path`), the stub helper and `tmux` live under `tmp_path`, and the +real helper runs only in the sandbox, whose `gh` and `tmux` refuse. """ from __future__ import annotations +import hashlib +import json import os import re import shlex import shutil import stat import subprocess +import sys from pathlib import Path -from typing import Dict, List, Optional import pytest from conftest import REPO, WINDOWS_SKIP +from test_a23_write_handoff import BLOCK, HANDOFF, HANDOFF_REL, Box as RealBox, prepended pytestmark = [WINDOWS_SKIP, pytest.mark.skipif( shutil.which("bash") is None or shutil.which("git") is None, - reason="the skill's blocks are shell, and they run git")] + reason="the skill's blocks are shell, and the sandbox runs git")] SKILL = REPO / "skills/handoff/SKILL.md" +HELPER = REPO / "lanes-edit.sh" -#: THE COMMIT MESSAGE, which the shipped block and the fixed one both write and -#: no other fenced block of the skill carries: it is how the block is found. -MARKER = "PAUSED, " - -HANDOFF = "handoffs/h1.md" # this lane's handoff: the row's own cell -PEER = "handoffs/h2.md" # another lane's handoff -REGISTER = "lanes/LANES.md" - -H1_V1, H1_V2 = "h1 v1\n", "h1 v2 (this lane PAUSED)\n" -H2_V1, H2_V2 = "h2 v1\n", "h2 v2 (peer P1)\n" -ROWS = "| row1 | a |\n| row2 | a |\n| row3 | a |\n| row4 | {} |\n" - -GITCONFIG = """[user] -\tname = sandbox -\temail = sandbox@example.invalid -[init] -\tdefaultBranch = main -[maintenance] -\tauto = false -[gc] -\tauto = 0 -[advice] -\tdetachedHead = false -[commit] -\tgpgsign = false -""" +#: Each carried by exactly one shell block of the skill: how the blocks are found. +B1 = 'handoff_file="$("$L" handoff-path "$lane" --create)"' +B2 = '--whole "$prepared_file"' +STEP4 = "# (c) the row: ONE WRITE" -#: The stub helper: `workspace-root` is the only verb the block calls. -STUB = """#!/bin/sh -case "$1" in - workspace-root) printf '%s\\n' "$HOME/projects/brett-wip" ;; - *) exit 2 ;; -esac -""" +LANE = "repoHS-1" +UUID = "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d" +OLD = ("Lane: repoHS-1 (p, session s) — single-use resume prompt\n\n" + "## RESUME PROMPT — PAUSED, the old block\n\nthe old state\n") +NEW = ("Lane: repoHS-1 (p, session s) — single-use resume prompt\n\n" + "## RESUME PROMPT — PAUSED, the new block\n\n---\n\n" + OLD) +OLD_DIGEST = hashlib.sha256(OLD.encode("utf-8")).hexdigest() SHELLS = [ pytest.param("bash", id="bash"), @@ -99,385 +91,500 @@ ] +# --- the skill's text, as the file holds it ---------------------------------- + def _text() -> str: return SKILL.read_text(encoding="utf-8") -def _blocks(text: str) -> List[str]: +def _blocks(text: str) -> list[str]: return re.findall(r"^```(?:sh|bash)\n(.*?)\n```$", text, re.DOTALL | re.MULTILINE) -def _block() -> str: - found = [b for b in _blocks(_text()) if MARKER in b] +def _block(marker: str) -> str: + found = [b for b in _blocks(_text()) if marker in b] assert len(found) == 1, ( f"expected exactly one fenced block of {SKILL.name} carrying " - f"{MARKER!r}, found {len(found)}: this test is stale") + f"{marker!r}, found {len(found)}: this test is stale") return found[0] def _section(heading: str) -> str: - """The prose-and-code of one numbered `## . …` step of the skill.""" + """The prose-and-code of one numbered `## . …` step of the skill; it + ends at the next NUMBERED step, because the handoff's own template opens + with a `## RESUME PROMPT` of its own.""" text = _text() start = text.index(heading) end = re.compile(r"\n## \d+\. ").search(text, start + 1) return text[start:end.start() if end else len(text)] -class Sandbox: - """A bare origin, the clones a lane meets, and a hermetic environment.""" +def _prose(heading: str) -> list[str]: + """A step's paragraphs with their fences cut out and their lines joined.""" + prose = re.sub(r"```.*?```", "", _section(heading), flags=re.DOTALL) + return [" ".join(p.split()) for p in prose.split("\n\n") if p.strip()] + + +def _code_lines(blocks: list[str]) -> list[str]: + return [line for block in blocks for line in block.splitlines() + if not line.lstrip().startswith("#")] + + +# --- a stub helper that logs what it is asked, and answers what a case says --- + +#: The interpreter that is running pytest, so the stub needs nothing on PATH. +STUB = f"""#!{sys.executable} +import json, os, sys +argv = sys.argv[1:] +rec = {{"argv": argv, "lane": os.environ.get("LANES_LANE")}} +if argv[:1] == ["write-handoff"] and "--whole" in argv: + try: + with open(argv[argv.index("--whole") + 1], "rb") as f: + rec["prepared"] = f.read().decode("utf-8") + except (OSError, IndexError): + rec["prepared"] = None +with open(os.environ["STUB_LOG"], "a", encoding="utf-8") as log: + log.write(json.dumps(rec) + "\\n") +verb = argv[0] if argv else "" +if verb == "handoff-path": + if os.environ.get("STUB_HP_OUT"): + print(os.environ["STUB_HP_OUT"]) + sys.exit(int(os.environ.get("STUB_HP_RC", "0"))) +if verb == "write-handoff": + if os.environ.get("STUB_WH_OUT"): + print(os.environ["STUB_WH_OUT"]) + sys.exit(int(os.environ.get("STUB_WH_RC", "0"))) +if verb in ("log", "append-line", "set-row-state"): + sys.exit(0) +sys.exit(8 if verb == "lane-dir" else 2) +""" + + +class Estate: + """A stub helper, a handoff file, and a hermetic environment.""" def __init__(self, root: Path) -> None: + root.mkdir(parents=True, exist_ok=True) self.root = root - self.home = root / "home" - (self.home / "projects").mkdir(parents=True) - (self.root / "tmp").mkdir() - (self.home / ".gitconfig").write_text(GITCONFIG, encoding="utf-8") self.bin = root / "bin" self.bin.mkdir() + self.home = root / "home" + self.home.mkdir() + self.tmp = root / "tmp" + self.tmp.mkdir() + self.log = root / "calls.jsonl" + self.log.write_text("", encoding="utf-8") self.helper = self.bin / "lanes-edit.sh" self.helper.write_text(STUB, encoding="utf-8") - self.helper.chmod(self.helper.stat().st_mode | stat.S_IXUSR) - self.origin = root / "origin.git" - self.shared = self.home / "projects" / "brett-wip" - self.own = self.home / "projects" / "brett-wip-own" - self.peer = root / "peer" - dirs: List[str] = [str(self.bin)] - for tool in ("git", "bash", "zsh"): - found = shutil.which(tool) - if found and os.path.dirname(found) not in dirs: - dirs.append(os.path.dirname(found)) - for fallback in ("/usr/bin", "/bin"): - if fallback not in dirs: - dirs.append(fallback) - self.env: Dict[str, str] = { - "HOME": str(self.home), "PATH": os.pathsep.join(dirs), - "TMPDIR": str(self.root / "tmp"), "LC_ALL": "C", "TZ": "UTC", - "GIT_CONFIG_NOSYSTEM": "1", "GIT_TERMINAL_PROMPT": "0", - "GIT_EDITOR": "true", - } - self._seed() - - # --- git, always in the sandbox's own environment --------------------- - - def git(self, *args: str, cwd: Optional[Path] = None, - check: bool = True) -> subprocess.CompletedProcess: - proc = subprocess.run(["git", *args], cwd=str(cwd or self.root), - env=self.env, capture_output=True, text=True, - stdin=subprocess.DEVNULL, check=False, - timeout=120) - if check and proc.returncode != 0: - raise AssertionError(f"git {' '.join(args)} in {cwd} failed:\n" - f"{proc.stderr}{proc.stdout}") - return proc - - def out(self, *args: str, cwd: Optional[Path] = None) -> str: - return self.git(*args, cwd=cwd).stdout.strip() - - def _seed(self) -> None: - self.git("init", "-q", "--bare", str(self.origin)) - seed = self.root / "seed" - self.git("init", "-q", str(seed)) - (seed / "handoffs").mkdir() - (seed / "lanes").mkdir() - (seed / HANDOFF).write_text(H1_V1, encoding="utf-8") - (seed / PEER).write_text(H2_V1, encoding="utf-8") - (seed / REGISTER).write_text(ROWS.format("a"), encoding="utf-8") - self.git("add", "--", HANDOFF, PEER, REGISTER, cwd=seed) - self.git("commit", "-q", "-m", "seed", "--", HANDOFF, PEER, REGISTER, - cwd=seed) - self.git("remote", "add", "origin", str(self.origin), cwd=seed) - self.git("push", "-q", "origin", "main", cwd=seed) - - def clone(self, dest: Path) -> Path: - self.git("clone", "-q", str(self.origin), str(dest)) - return dest - - def land(self, path: str, content: str, message: str) -> None: - """Another lane lands `path` on origin, from its own clone.""" - if not self.peer.exists(): - self.clone(self.peer) - self.git("pull", "-q", "--rebase", "origin", "main", cwd=self.peer) - (self.peer / path).write_text(content, encoding="utf-8") - self.git("commit", "-q", "-m", message, "--", path, cwd=self.peer) - self.git("push", "-q", "origin", "main", cwd=self.peer) - - def regime2(self, checkout: Path) -> None: - """The shared checkout's state from 2026-10-11T20:07Z (#197): a peer's - v2 of `h2` lands, then `main` moves to it WITHOUT the index (a linked - worktree's `checkout -B`), so the index still holds `h2` v1 — a - STAGED reversal of the peer's work.""" - self.land(PEER, H2_V2, "P1: peer handoff v2") - self.git("fetch", "-q", "origin", cwd=checkout) - self.git("update-ref", "refs/heads/main", "origin/main", cwd=checkout) - - # --- reads ------------------------------------------------------------- - - def head(self, checkout: Path) -> str: - return self.out("rev-parse", "HEAD", cwd=checkout) - - def origin_head(self) -> str: - return self.out("--git-dir", str(self.origin), "rev-parse", "main") - - def origin_file(self, path: str) -> str: - return self.git("--git-dir", str(self.origin), "show", - f"main:{path}").stdout - - def staged(self, checkout: Path) -> List[str]: - return self.out("diff", "--cached", "--name-only", - cwd=checkout).splitlines() - - def counts(self, checkout: Path) -> tuple: - """(ahead, behind) against origin's main, after a fetch.""" - self.git("fetch", "-q", "origin", cwd=checkout) - row = self.out("rev-list", "--left-right", "--count", - "HEAD...origin/main", cwd=checkout).split() - return int(row[0]), int(row[1]) - - def in_progress(self, checkout: Path) -> List[str]: - gitdir = Path(self.out("rev-parse", "--absolute-git-dir", cwd=checkout)) - return [op for op in ("rebase-merge", "rebase-apply", "MERGE_HEAD", - "CHERRY_PICK_HEAD", "REVERT_HEAD") - if (gitdir / op).exists()] - - # --- the block ---------------------------------------------------------- - - def run(self, shell: str, lanes_repo: Optional[Path] = None, - block: Optional[str] = None) -> subprocess.CompletedProcess: - script = "\n".join([f"L={shlex.quote(str(self.helper))}", - f"handoff={shlex.quote(HANDOFF)}", - block if block is not None else _block()]) - env = dict(self.env) - if lanes_repo is not None: - env["LANES_REPO"] = str(lanes_repo) - argv = [shutil.which(shell) or shell] - if shell == "zsh": - argv.append("-f") - return subprocess.run([*argv, "-c", script], cwd=str(self.home), - env=env, capture_output=True, text=True, - stdin=subprocess.DEVNULL, check=False, - timeout=120) + # A `tmux` that answers nothing, so the host's own server is never read. + (self.bin / "tmux").write_text("#!/bin/sh\nexit 1\n", encoding="utf-8") + for tool in (self.helper, self.bin / "tmux"): + tool.chmod(tool.stat().st_mode | stat.S_IXUSR) + self.handoff = root / "checkout" / "handoffs" / "estate" / f"{LANE}.md" + self.handoff.parent.mkdir(parents=True) + self.handoff.write_text(OLD, encoding="utf-8") + + def run(self, shell: str, script: str, **extra: str) -> subprocess.CompletedProcess: + env = {"HOME": str(self.home), "TMPDIR": str(self.tmp), "STUB_LOG": str(self.log), + "PATH": os.pathsep.join([str(self.bin), "/usr/bin", "/bin"]), + "STUB_HP_OUT": str(self.handoff), **extra} + argv = ["bash", "-c", script] if shell == "bash" else \ + [shutil.which("zsh") or "zsh", "-f", "-c", script] + return subprocess.run(argv, env=env, cwd=str(self.home), capture_output=True, + text=True, check=False, timeout=120) + + def calls(self) -> list[dict]: + return [json.loads(line) for line in + self.log.read_text(encoding="utf-8").splitlines()] + + def verbs(self) -> list[str]: + return [c["argv"][0] for c in self.calls()] + + def step2(self, *, ws: str = "Eagle", why: str = "'a usage reset'", + prepare: bool = True) -> str: + """Step 2 as a session runs it: the first block, the session's own act + of writing the complete new file, then the one call. `ws` and `why` are + shell words, so a case can hand the block an ANSI-C string.""" + write = (f'[ -z "$prepared_file" ] || printf %s {shlex.quote(NEW)} > "$prepared_file"' + if prepare else ":") + return "\n".join([f"L={shlex.quote(str(self.helper))}", f"lane={LANE}", f"ws={ws}", + f"why={why}", _block(B1), write, _block(B2), + 'printf "AFTER\\n"']) + + def step4(self, *, ws_missing: str = "", uuid_missing: str = "") -> str: + return "\n".join([f"L={shlex.quote(str(self.helper))}", f"lane={LANE}", "ws=Eagle", + 'why="a usage reset"', f'uuid="{UUID}"', f'ws_missing="{ws_missing}"', + f'uuid_missing="{uuid_missing}"', _block(STEP4), + 'printf "row_write_refused=[%s]\\n" "$row_write_refused"']) @pytest.fixture -def sb(tmp_path: Path) -> Sandbox: - return Sandbox(tmp_path) +def estate(tmp_path: Path) -> Estate: + return Estate(tmp_path) -def _said(proc: subprocess.CompletedProcess) -> str: - return f"--- stdout\n{proc.stdout}--- stderr\n{proc.stderr}" +def _write_call(estate: Estate) -> dict: + found = [c for c in estate.calls() if c["argv"][0] == "write-handoff"] + assert len(found) == 1, estate.calls() + return found[0] -# --- RED 1: a foreign staged entry is never committed ----------------------- +# --- the call: the arguments, and nothing else ------------------------------- @pytest.mark.parametrize("shell", SHELLS) -def test_a_foreign_staged_entry_is_refused_and_nothing_is_published(sb, shell): - shared = sb.clone(sb.shared) - sb.regime2(shared) - sb.land(REGISTER, ROWS.format("P2"), "P2: peer row4") # another FILE moves - (shared / HANDOFF).write_text(H1_V2, encoding="utf-8") - assert sb.staged(shared) == [PEER], "the precondition: a staged reversal" - head, origin = sb.head(shared), sb.origin_head() - - proc = sb.run(shell) - - assert sb.origin_file(PEER) == H2_V2, ( - "origin's copy of the PEER's handoff was reverted: the commit took a " - f"staged entry this handoff did not make, and pushed it\n{_said(proc)}") - assert sb.origin_head() == origin, f"origin moved\n{_said(proc)}" - assert sb.head(shared) == head, f"a commit was made\n{_said(proc)}" - assert "HANDOFF NOT COMMITTED" in proc.stdout, _said(proc) - assert f"staged: {PEER}" in proc.stdout, ( - f"the refusal must name the staged path\n{_said(proc)}") - assert sb.staged(shared) == [PEER], "the peer's entry is left as found" - assert (shared / HANDOFF).read_text(encoding="utf-8") == H1_V2, ( - "the handoff file stays written") +def test_block_calls_write_handoff_with_whole_and_expect_digest(estate, shell): + """The handoff's place is asked of `handoff-path --create`, its digest is + read from the bytes there, and `write-handoff` is called ONCE with the + prepared file the session wrote, that digest, and the subject: the lane, the + workstation and the why. `LANES_LANE` is the lane.""" + proc = estate.run(shell, estate.step2(), STUB_WH_OUT=f"{estate.handoff}\tdeadbeef") + assert proc.returncode == 0, proc.stderr + assert estate.verbs() == ["handoff-path", "write-handoff"], estate.calls() + assert estate.calls()[0]["argv"] == ["handoff-path", LANE, "--create"] + call = _write_call(estate) + argv = call["argv"] + assert argv[:3] == ["write-handoff", LANE, "--whole"], argv + assert argv[4:] == ["--expect-digest", OLD_DIGEST, "--subject", + f"handoff({LANE}@Eagle): PAUSED, a usage reset"], argv + assert call["prepared"] == NEW, "the file sent is the one the session wrote, whole" + assert Path(argv[3]).parent == estate.tmp, "the prepared file is a temporary one" + assert call["lane"] == LANE + assert f"handoff written: {estate.handoff}" in proc.stdout, proc.stdout + assert proc.stdout.endswith("AFTER\n") + assert estate.handoff.read_text(encoding="utf-8") == OLD, ( + "the skill never writes the handoff itself: the verb does") -# --- RED 2 and 3: the checkout is RESOLVED, `LANES_REPO` first -------------- +@pytest.mark.parametrize("shell", SHELLS) +def test_the_digest_is_of_the_bytes_that_are_there(estate, shell): + """Not of a copy and not of the row's spelling: the file `handoff-path` + named, read as it stands.""" + other = "a different handoff\nwith other bytes\n" + estate.handoff.write_text(other, encoding="utf-8") + estate.run(shell, estate.step2()) + argv = _write_call(estate)["argv"] + assert argv[argv.index("--expect-digest") + 1] == hashlib.sha256(other.encode()).hexdigest() + @pytest.mark.parametrize("shell", SHELLS) -def test_lanes_repo_is_where_the_handoff_is_committed_and_the_shared_one_is_untouched( - sb, shell): - shared, own = sb.clone(sb.shared), sb.clone(sb.own) - sb.regime2(shared) - sb.git("pull", "-q", "--rebase", "origin", "main", cwd=own) - (own / HANDOFF).write_text(H1_V2, encoding="utf-8") - shared_head = sb.head(shared) - - proc = sb.run(shell, lanes_repo=own) - - assert sb.origin_file(PEER) == H2_V2, ( - "origin's copy of the peer's handoff was reverted from the SHARED " - f"checkout, which this lane did not write\n{_said(proc)}") - assert sb.origin_file(HANDOFF) == H1_V2, ( - f"the lane's handoff never reached origin\n{_said(proc)}") - assert f"handoff pushed from {own}" in proc.stdout, _said(proc) - assert sb.git("status", "--porcelain", cwd=own).stdout == "", ( - "the lane's own clone is left with its handoff uncommitted") - assert sb.counts(own) == (0, 0) - assert sb.head(shared) == shared_head, "the shared checkout was committed in" - assert sb.staged(shared) == [PEER], "the shared checkout's index was touched" +@pytest.mark.parametrize("ws, why, subject", [ + ("Eagle", "'a usage reset'", f"handoff({LANE}@Eagle): PAUSED, a usage reset"), + ("''", "''", f"handoff({LANE}@none): PAUSED, handoff"), + ("Eagle", "$'a\\tb\\x1bc'", f"handoff({LANE}@Eagle): PAUSED, abc"), + ("Eagle", "'reset — switch'", f"handoff({LANE}@Eagle): PAUSED, reset — switch"), +], ids=["a-why", "no-workstation-no-why", "control-characters-removed", "a-dash-is-kept"]) +def test_the_subject_is_one_line_with_no_control_character(estate, shell, ws, why, subject): + """The verb exits 64 on a control character in the subject and writes + nothing, so a why with a stray tab would cost the swap its handoff: it is + removed, not refused over. A why the operator did not give is `handoff`, + and a missing workstation is `none`.""" + estate.run(shell, estate.step2(ws=ws, why=why)) + assert _write_call(estate)["argv"][-1] == subject @pytest.mark.parametrize("shell", SHELLS) -def test_lanes_repo_is_used_when_the_shared_checkout_is_clean(sb, shell): - shared, own = sb.clone(sb.shared), sb.clone(sb.own) - sb.land(PEER, H2_V2, "P1: peer handoff v2") - for checkout in (shared, own): - sb.git("pull", "-q", "--rebase", "origin", "main", cwd=checkout) - (own / HANDOFF).write_text(H1_V2, encoding="utf-8") - shared_head = sb.head(shared) +def test_no_spelled_checkout_and_no_workspace_root_fallback(estate, shell): + """No checkout path is written anywhere in the skill, none is taken from + the environment, and the helper's `workspace-root` is never asked: a caller's + `LANES_REPO` changes nothing about what is called.""" + text = _text() + for spelled in ("~/projects/brett-wip", "workspace-root", "LANES_REPO", "LANES_BRANCH"): + assert spelled not in text, f"{SKILL.name} still says {spelled!r}" + blocks = _blocks(text) + git_writes = [line.strip() for line in _code_lines(blocks) + if re.search(r"\bgit\b[^\n]*\b(add|commit|pull|push|rebase|merge|reset|" + r"checkout|stash|cherry-pick|clean|restore)\b", line)] + assert not git_writes, f"a shell block of the skill runs a git write: {git_writes}" + step2 = [b for b in blocks if b in (_block(B1), _block(B2))] + assert not [line for line in _code_lines(step2) if re.match(r"\s*cd\b", line)], ( + "step 2 changes directory: it has no checkout of its own") + def shape() -> list[dict]: + """The calls, the temporary file's random name aside.""" + out = [] + for call in estate.calls(): + argv = list(call["argv"]) + if argv[0] == "write-handoff": + argv[3] = "" + out.append(dict(call, argv=argv)) + return out + + estate.run(shell, estate.step2(), STUB_WH_OUT=f"{estate.handoff}\tx") + plain = shape() + estate.log.write_text("", encoding="utf-8") + estate.run(shell, estate.step2(), STUB_WH_OUT=f"{estate.handoff}\tx", + LANES_REPO=str(estate.root / "elsewhere"), LANES_BRANCH="not-main") + assert "workspace-root" not in estate.verbs(), estate.calls() + assert shape() == plain, "an ambient LANES_REPO changed the call" + + +def test_the_three_variables_the_old_call_named_are_defined_in_a_block(): + """`publish-handoff "$lane" "$handoff_file" "$prepared_file" --expect-digest + "$prior_sha256"` used its three variables without defining any of them.""" + code = "\n".join(_code_lines([_block(B1)])) + for name in ("handoff_file", "prior_sha256", "prepared_file"): + assert re.search(rf"(?m)^\s*{name}=", code), f"no block defines ${name}" + assert "publish-handoff" not in "\n".join(_blocks(_text())), ( + "the old call is gone: write-handoff is the one act") + + +def test_the_skill_never_pulls_with_rebase_outside_a_comment(): + """Amendment 23 S3-S4 (`tests/test_repo_hygiene.py`'s no-pull rule, whose + file list takes this file at the merge): no line of the skill that is not a + comment runs, or tells a person to run, a rebasing pull.""" + pull = re.compile(r"\bpull\b[^\n]*?(?:--rebase\b|\s-r\b)") + found = [n for n, line in enumerate(_text().splitlines(), 1) + if not line.lstrip().startswith("#") and pull.search(line)] + assert not found, f"{SKILL.name} line(s) {found} run or print a rebasing pull" + + +def test_the_blocks_parse_under_bash(tmp_path): + for name, marker in (("first", B1), ("second", B2)): + script = tmp_path / f"{name}.sh" + script.write_text(_block(marker) + "\n", encoding="utf-8") + proc = subprocess.run(["bash", "-n", str(script)], capture_output=True, + text=True, check=False) + assert proc.returncode == 0, proc.stderr + + +# --- step 4's three register writes carry `LANES_LANE` ----------------------- + +@pytest.mark.parametrize("shell", SHELLS) +@pytest.mark.parametrize("ambient", [None, "some-other-lane"], ids=["unset", "another-lane"]) +def test_step4_writes_carry_LANES_LANE(estate, shell, ambient): + """`log`, `append-line` and `set-row-state` each run under the lane being + swapped, whatever the caller's environment says: a register write goes + through that lane's checkout and attributes its commit to it (Amendment 23 + S2), and `append-line` finds its lane from the variable first.""" + extra = {} if ambient is None else {"LANES_LANE": ambient} + proc = estate.run(shell, estate.step4(), **extra) + assert proc.returncode == 0, proc.stderr + writes = {c["argv"][0]: c["lane"] for c in estate.calls() + if c["argv"][0] in ("log", "append-line", "set-row-state")} + assert writes == {"log": LANE, "append-line": LANE, "set-row-state": LANE}, estate.calls() + + +def test_every_register_write_and_the_handoff_write_names_its_lane_in_the_text(): + """The text holds the same line for the next verb that joins the list.""" + calls = [line.strip() for line in _code_lines(_blocks(_text())) + if re.search(r'"\$L" (log|append-line|set-row-state|write-handoff)\b', line)] + assert len(calls) == 4, calls + for call in calls: + assert 'LANES_LANE="$lane"' in call, f"this call does not name its lane: {call}" + + +# --- the words: what each exit says, and that the swap goes on --------------- + +def _says(estate: Estate) -> dict: + path = str(estate.handoff) + return { + "pushed": (0, True, [f"handoff written: {path}"], ["HANDOFF NOT"]), + "refused-after-writing": (2, True, [ + "HANDOFF NOT COMMITTED", f"written at {path}, uncommitted", "Nothing was pushed", + "refused (7)"], ["HANDOFF NOT WRITTEN"]), + "refused-before-writing": (2, False, [ + "HANDOFF NOT WRITTEN", "wrote nothing"], ["HANDOFF NOT COMMITTED"]), + "pushed-not-confirmed": (3, True, ["HANDOFF PUSHED, NOT CONFIRMED", "(exit 3"], []), + "kept-on-head": (6, True, ["HANDOFF NOT PUSHED", "kept on HEAD", "(exit 6)", + "Never force"], []), + "moved-or-owned": (7, False, [ + "HANDOFF NOT WRITTEN", f"digest {OLD_DIGEST}", "(exit 7;", "once more", + "if a restart owns it, do not retry"], []), + "no-handoff": (8, False, ["HANDOFF NOT PUBLISHED", "exited 8", "nothing was written"], []), + "environment": (1, False, ["HANDOFF NOT PUBLISHED", "exited 1", "nothing was written"], []), + "lock": (4, False, ["HANDOFF NOT PUBLISHED", "exited 4", "nothing was written"], []), + } + + +CASES = ["pushed", "refused-after-writing", "refused-before-writing", "pushed-not-confirmed", + "kept-on-head", "moved-or-owned", "no-handoff", "environment", "lock"] - proc = sb.run(shell, lanes_repo=own) - assert sb.origin_file(HANDOFF) == H1_V2, ( - "the block exited without committing the handoff the lane wrote in " - f"its own clone\n{_said(proc)}") - assert sb.counts(own) == (0, 0), _said(proc) - assert sb.head(shared) == shared_head +@pytest.mark.parametrize("shell", SHELLS) +@pytest.mark.parametrize("case", CASES) +def test_each_exit_of_the_verb_is_said_and_the_swap_goes_on(estate, shell, case): + rc, written, must, never = _says(estate)[case] + out = f"{estate.handoff}\tdeadbeef" if written else "" + proc = estate.run(shell, estate.step2(), STUB_WH_RC=str(rc), STUB_WH_OUT=out) + assert proc.returncode == 0, "the block never stops the swap (R-A11-11)" + assert proc.stdout.endswith("AFTER\n"), proc.stdout + for words in must: + assert words in proc.stdout, f"{case}: {words!r} missing from\n{proc.stdout}" + for words in never: + assert words not in proc.stdout, f"{case}: {words!r} must not be said\n{proc.stdout}" + assert estate.verbs().count("write-handoff") == 1, "one call, and it is never repeated" + + +def test_refusal_text_keeps_R_A11_11(): + """`R-A11-11`, *a swap is never left unwritten*, is still the text's rule + about a refusal, a conflict or a rejected push, and says what the verb does + in each: the file is written and not committed on S3's refusal (2), a refused + push is replayed or kept and named (6), nothing is forced and no staged + entry that is not the handoff's is ever committed.""" + held = [p for p in _prose("## 2. Refresh the handoff") + if "does not stop the swap" in p] + assert len(held) == 1, held + paragraph = held[0] + for words in ("R-A11-11", "a swap is never left unwritten", "(2)", "has still written the handoff", + "commits nothing", "git's own words", "replays the write", "(6)", + "report carries that line", "Never force", "never commit staged entries you did not make"): + assert words in paragraph, f"the paragraph no longer says {words!r}:\n{paragraph}" + + +# --- where the handoff is not found, and the file the session never wrote ---- +@pytest.mark.parametrize("shell", SHELLS) +@pytest.mark.parametrize("how, extra", [ + ("exit 8: no row, or a row that names no handoff", {"STUB_HP_RC": "8", "STUB_HP_OUT": ""}), + ("exit 1: a register that could not be read", {"STUB_HP_RC": "1", "STUB_HP_OUT": ""}), + ("exit 0 and a path that is not a file", {"STUB_HP_OUT": "/nowhere/at/all.md"}), + ("exit 0 and no path", {"STUB_HP_OUT": ""}), +], ids=["no-handoff", "unreadable", "not-a-file", "no-path"]) +def test_a_handoff_that_is_not_found_is_said_and_write_handoff_is_never_called( + estate, shell, how, extra): + proc = estate.run(shell, estate.step2(), **extra) + assert proc.returncode == 0, "the swap goes on" + assert "HANDOFF NOT FOUND" in proc.stdout, proc.stdout + assert "HANDOFF NOT WRITTEN" in proc.stdout and "was NOT called" in proc.stdout, proc.stdout + assert estate.verbs() == ["handoff-path"], estate.calls() + assert proc.stdout.endswith("AFTER\n") -# --- RED 4: a same-file conflict is aborted, never left mid-rebase ---------- @pytest.mark.parametrize("shell", SHELLS) -def test_a_conflict_is_aborted_and_the_commit_stays_local(sb, shell): - shared = sb.clone(sb.shared) - sb.land(HANDOFF, "h1 v2 (someone else)\n", "P3: conflicting h1") - (shared / HANDOFF).write_text(H1_V2, encoding="utf-8") - origin = sb.origin_head() +def test_a_prepared_file_the_session_never_wrote_is_never_sent(estate, shell): + """`mktemp` makes an empty file; the verb refuses an empty `--whole` file, + and the block does not even ask.""" + proc = estate.run(shell, estate.step2(prepare=False)) + assert proc.returncode == 0 + assert "HANDOFF NOT WRITTEN" in proc.stdout and "was NOT called" in proc.stdout, proc.stdout + assert estate.verbs() == ["handoff-path"], estate.calls() - proc = sb.run(shell) - assert sb.in_progress(shared) == [], ( - f"the checkout was left mid-operation\n{_said(proc)}") - assert "HANDOFF NOT PUSHED" in proc.stdout, _said(proc) - assert sb.origin_head() == origin - assert sb.counts(shared) == (1, 1), _said(proc) - assert sb.out("show", "--name-only", "--format=", "HEAD", - cwd=shared).splitlines() == [HANDOFF] - assert sb.git("status", "--porcelain", cwd=shared).stdout == "" +# --- the real helper: the handoff is pushed from C, and W is untouched -------- +REAL_NEW = prepended(HANDOFF, BLOCK) +REAL_LANE = "repoS4-1" +REAL_WHY = "a usage reset" -# --- RED 5: an operation in progress is a refusal --------------------------- -@pytest.mark.parametrize("shell", SHELLS) -@pytest.mark.parametrize("operation", ["rebase", "merge"]) -def test_an_operation_in_progress_is_refused_before_any_commit(sb, shell, operation): - shared = sb.clone(sb.shared) - (shared / HANDOFF).write_text("h1 local\n", encoding="utf-8") - sb.git("commit", "-q", "-m", "local h1", "--", HANDOFF, cwd=shared) - sb.land(HANDOFF, "h1 peer\n", "P3: conflicting h1") - if operation == "rebase": - sb.git("pull", "-q", "--rebase", "origin", "main", cwd=shared, check=False) - else: - sb.git("fetch", "-q", "origin", cwd=shared) - sb.git("merge", "-q", "--no-edit", "origin/main", cwd=shared, check=False) - before = sb.in_progress(shared) - assert before, f"the precondition: a {operation} stopped on its conflict" - (shared / HANDOFF).write_text(H1_V2, encoding="utf-8") - head, origin = sb.head(shared), sb.origin_head() - - proc = sb.run(shell) - - assert sb.head(shared) == head, ( - f"a commit was made in the middle of a {operation}\n{_said(proc)}") - assert sb.origin_head() == origin, _said(proc) - found = re.search(r"has an operation in progress \(([A-Za-z_-]+)\)", - proc.stdout) - assert found and found.group(1) in before, ( - f"the refusal must name the operation ({before})\n{_said(proc)}") - assert "HANDOFF NOT COMMITTED" in proc.stdout - assert sb.in_progress(shared) == before, "the block finished what it did not start" - - -# --- RED 6: the text -------------------------------------------------------- - -def test_the_skill_never_spells_the_shared_checkout(): - spelled = [n + 1 for n, line in enumerate(_text().splitlines()) - if "~/projects/brett-wip" in line] - assert not spelled, ( - "the handoff's checkout is resolved (`$LANES_REPO`, then the helper's " - f"`workspace-root`), never spelled; these lines spell it: {spelled}") - - -#: A git INVOCATION of `add` or `commit`, with or without `-C ` — and not -#: a message that only says the words ("git refused the add or the commit"). -_GIT_VERB = r'\bgit(?:\s+-C\s+"[^"]*")?\s+{}\b' - - -def test_the_add_and_the_commit_name_the_handoff_by_its_own_path(): - lines = _block().splitlines() - commits = [line for line in lines if re.search(_GIT_VERB.format("commit"), line)] - adds = [line for line in lines if re.search(_GIT_VERB.format("add"), line)] - assert commits and adds, "the block no longer adds and commits" - for line in commits + adds: - assert '-- "$handoff"' in line, ( - "every `git add` and `git commit` in the block carries the handoff's " - f"own pathspec (Amendment 4(d): never a bare `git commit`): {line.strip()}") - - -def test_the_publication_target_is_named_as_the_committed_file(): - """`publish-handoff` writes `$handoff_file`, which no block defines; the - prose must say it is the file the commit block resolves.""" - prose = re.sub(r"```.*?```", "", _section("## 2. Refresh the handoff"), - flags=re.DOTALL) - assert "$handoff_file" in prose, ( - "step 2's prose never says which checkout `$handoff_file` is in, so the " - "file published and the file committed can differ") - assert "$LANES_REPO/$handoff" in prose - - -def test_the_block_parses_under_bash(tmp_path): - """A keeper: the shipped block parsed too.""" - script = tmp_path / "block.sh" - script.write_text(_block() + "\n", encoding="utf-8") - proc = subprocess.run([shutil.which("bash") or "bash", "-n", str(script)], - capture_output=True, text=True, check=False) - assert proc.returncode == 0, proc.stderr +def _real_script(shell_between: str = "", *, lane: str = REAL_LANE, new: str = REAL_NEW) -> str: + return "\n".join([ + f"L={shlex.quote(str(HELPER))}", f"lane={lane}", "ws=Eagle", f"why={shlex.quote(REAL_WHY)}", + _block(B1), shell_between, + f'[ -z "$prepared_file" ] || printf %s {shlex.quote(new)} > "$prepared_file"', + _block(B2)]) -# --- where the handoff is nowhere the block looks --------------------------- +def _real_run(box: RealBox, shell: str, script: str) -> subprocess.CompletedProcess: + argv = ["bash", "-c", script] if shell == "bash" else \ + [shutil.which("zsh") or "zsh", "-f", "-c", script] + return subprocess.run(argv, env=dict(box.env), cwd=str(box.root), capture_output=True, + text=True, check=False, timeout=240) -@pytest.mark.parametrize("shell", SHELLS) -def test_a_handoff_under_neither_root_is_refused(sb, shell): - """`$LANES_REPO` names no checkout and the workspace root has none: the - block says so rather than running git wherever the shell stands.""" - origin = sb.origin_head() - proc = sb.run(shell, lanes_repo=sb.root / "nowhere") - assert "HANDOFF NOT COMMITTED" in proc.stdout, _said(proc) - assert "under neither" in proc.stdout, _said(proc) - assert sb.origin_head() == origin + +def _said(proc: subprocess.CompletedProcess) -> str: + return f"--- stdout\n{proc.stdout}--- stderr\n{proc.stderr}" -# --- keepers: what the shipped block already did, still done ---------------- +def _tree(box: RealBox) -> list[str]: + return box.git("--git-dir", str(box.origin), "ls-tree", "-r", "--name-only", "main").split() + + +@pytest.fixture +def real(tmp_path: Path) -> RealBox: + return RealBox(tmp_path) + @pytest.mark.parametrize("shell", SHELLS) -def test_a_clean_current_checkout_is_committed_and_pushed(sb, shell): - shared = sb.clone(sb.shared) - (shared / HANDOFF).write_text(H1_V2, encoding="utf-8") - proc = sb.run(shell) - assert sb.origin_file(HANDOFF) == H1_V2, _said(proc) - assert sb.counts(shared) == (0, 0) - assert sb.out("log", "-1", "--format=%B", "origin/main", - cwd=shared).splitlines()[-1] == "Lane: " +@pytest.mark.parametrize("how", ["routed", "seam"]) +def test_the_handoff_is_pushed_from_the_lane_checkout_and_W_is_untouched(real, shell, how): + """Step 2, whole, against the real helper. The lane's checkout is made by + the first block where it is not there yet, the handoff is written, committed + by its own pathspec with the `Lane:` trailer and pushed from it, and W, the + readers' copy, does not move: not its HEAD, its working tree, its reflog, and + not the entry another lane left STAGED there, which the old block committed + and published.""" + if how == "seam": + c = real.seam() + else: + c = real.checkout(REAL_LANE) + assert not c.exists(), "the precondition: the lane has no checkout yet" + real.write(real.w, "handoffs/staged-in-w.md", "another lane's entry, staged in W\n") + real.git("-C", str(real.w), "add", "--", "handoffs/staged-in-w.md") + w_before, reflog, tip = real.w_state(), real.w_reflog(), real.origin_head() + + proc = _real_run(real, shell, _real_script()) + + assert proc.returncode == 0, _said(proc) + assert f"handoff written: {c / HANDOFF_REL}" in proc.stdout, _said(proc) + assert real.origin_file(HANDOFF_REL) == REAL_NEW, _said(proc) + assert real.origin_head() != tip + assert real.subjects()[0] == f"handoff({REAL_LANE}@Eagle): PAUSED, {REAL_WHY}" + assert real.git("--git-dir", str(real.origin), "log", "-1", "--format=%B", + "main").strip().splitlines()[-1] == f"Lane: {REAL_LANE}" + assert (c / HANDOFF_REL).read_text(encoding="utf-8") == REAL_NEW + assert real.head(c) == real.origin_head() and real.unpublished(c) == [] + assert real.git("-C", str(c), "status", "--porcelain") == "" + assert real.w_state() == w_before, "W moved" + assert real.w_reflog() == reflog, "W's reflog moved: something committed or fast-forwarded it" + assert (real.w / HANDOFF_REL).read_text(encoding="utf-8") == HANDOFF + assert "handoffs/staged-in-w.md" not in _tree(real), "a staged entry of W's reached origin" + assert real.git("-C", str(real.w), "diff", "--cached", "--name-only").split() == [ + "handoffs/staged-in-w.md"] + assert not real.lock_of(c).exists(), "the checkout's lock is released" @pytest.mark.parametrize("shell", SHELLS) -def test_the_handoff_already_staged_by_an_earlier_attempt_is_its_own(sb, shell): - shared = sb.clone(sb.shared) - (shared / HANDOFF).write_text(H1_V2, encoding="utf-8") - sb.git("add", "--", HANDOFF, cwd=shared) - proc = sb.run(shell) - assert sb.origin_file(HANDOFF) == H1_V2, _said(proc) - assert sb.counts(shared) == (0, 0) +def test_a_handoff_that_moved_after_its_digest_was_read_is_refused_and_nothing_is_published( + real, shell): + """The digest is of the bytes the new file was made from. A peer's edit that + lands between the read and the call is exit 7: the verb fetches the lane's + checkout, finds other bytes, and publishes nothing; the block says so and + tells the session to read it again.""" + peer = shlex.quote(str(real.seed)) + rel = shlex.quote(HANDOFF_REL) + between = (f"git -C {peer} pull -q --ff-only origin main && " + f"printf '%s\\n' 'a peer line' >> {peer}/{rel} && " + f"git -C {peer} add -- {rel} && " + f"git -C {peer} commit -q -m 'a peer edits the handoff' && " + f"git -C {peer} push -q origin main") + proc = _real_run(real, shell, _real_script(between)) + assert proc.returncode == 0, _said(proc) + assert "HANDOFF NOT WRITTEN" in proc.stdout and "once more" in proc.stdout, _said(proc) + assert "nothing was published" in proc.stderr, _said(proc) + assert real.subjects()[0] == "a peer edits the handoff" + assert "a peer line" in real.origin_file(HANDOFF_REL) + assert real.origin_file(HANDOFF_REL) != REAL_NEW + + +@pytest.mark.parametrize("shell", SHELLS) +def test_a_staged_entry_in_the_lane_checkout_leaves_the_handoff_written_and_uncommitted( + real, shell): + """S3's refusal (2) with `--whole`: the file is written (`R-A11-11`), nothing + is committed or pushed, the entry that is not the handoff's stays staged and + unpublished, and the block says exactly that.""" + c = real.seam() + real.write(c, "handoffs/foreign.md", "someone else's entry\n") + real.git("-C", str(c), "add", "--", "handoffs/foreign.md") + tip, head = real.origin_head(), real.head(c) + + proc = _real_run(real, shell, _real_script()) + + assert proc.returncode == 0, _said(proc) + assert "HANDOFF NOT COMMITTED" in proc.stdout, _said(proc) + assert f"written at {c / HANDOFF_REL}, uncommitted" in proc.stdout, _said(proc) + assert (c / HANDOFF_REL).read_text(encoding="utf-8") == REAL_NEW + assert real.origin_head() == tip and real.head(c) == head + assert real.git("-C", str(c), "diff", "--cached", "--name-only").split() == [ + "handoffs/foreign.md"] + assert "handoffs/foreign.md" not in _tree(real) @pytest.mark.parametrize("shell", SHELLS) -def test_a_lanes_repo_without_the_handoff_falls_back_to_the_workspace_root(sb, shell): - shared = sb.clone(sb.shared) - (sb.root / "elsewhere").mkdir() - (shared / HANDOFF).write_text(H1_V2, encoding="utf-8") - proc = sb.run(shell, lanes_repo=sb.root / "elsewhere") - assert sb.origin_file(HANDOFF) == H1_V2, _said(proc) - assert sb.counts(shared) == (0, 0) +def test_a_lane_with_no_row_has_nothing_to_write_and_nothing_is_cloned(real, shell): + tip = real.origin_head() + proc = _real_run(real, shell, _real_script(lane="repoS4-9")) + assert proc.returncode == 0, _said(proc) + assert "HANDOFF NOT FOUND" in proc.stdout and "was NOT called" in proc.stdout, _said(proc) + assert not real.checkout("repoS4-9").exists() + assert real.origin_head() == tip From 502b0608ac36156bf9f096d7cc8b5b0034814d20 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Fri, 23 Oct 2026 15:32:36 +0000 Subject: [PATCH 02/17] Slice 3, rv3's MUST 1: the handoff skill's first step-2 block tells `handoff-path`'s exits apart, so only its 8 is HANDOFF NOT FOUND and every other exit is a HANDOFF READ FAILED that quotes the code, an older helper is told to update `lanes-edit.sh`, a held lock to run step 2 again, and the session keeps its new top block in the report (#198) The block read every non-zero exit of `handoff-path --create` as "no handoff" and dropped the session's new top block with nothing said of it. A 2 is now asked again without `--create` and with only its stderr kept, as `sread` asks, to tell an unknown-subcommand 2 from a refusal. The module's case at the old line 439 (exit 1 expected as NOT FOUND) is gone; a read that failed has its own cases, six exits in bash and zsh. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 019074b50d1949cc86e04f513b890b517a227479) --- skills/handoff/SKILL.md | 36 +++++++++-- tests/test_handoff_skill_commit_block.py | 78 ++++++++++++++++++++++-- 2 files changed, 104 insertions(+), 10 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index f769112..33d7f8c 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -340,15 +340,27 @@ fallback. A read that failed is no answer (Amendment 7(d)), so nothing is guesse makes that checkout first where this is its first use, so that there is a file to read. The SHA-256 of the file as it stands is the digest the verb holds this write to: it replaces the file only while the file still has those bytes (exit 7 where it does not), so the new file is made from what was read here and never from a -copy that has moved. Where the block below prints `HANDOFF NOT FOUND` there is no file to write into: the call -under "THE WRITE IS ONE CALL" is not made, and the swap goes on with the gap in the report (`R-A11-11`, *a -swap is never left unwritten*). +copy that has moved. Where the block below prints `HANDOFF NOT FOUND` (`handoff-path`'s 8: no row, or a row +that names no handoff) there is no file to write into. Where it prints `HANDOFF READ FAILED`, the read +failed, and that is never "no handoff": every exit but 0 and 8 is one, and the line quotes it. A 2 whose words +say "unknown subcommand" is a `lanes-edit.sh` predating `handoff-path`, to be updated; a 4 is the lock that +makes the lane's checkout, and step 2 is run again. In either case the call under "THE WRITE IS ONE CALL" is +not made, the new top block is still written and kept whole in the report, so that it can be put at the head +of the handoff by hand, and the swap goes on with the gap in the report (`R-A11-11`, *a swap is never left +unwritten*). ```sh # WHERE THE HANDOFF IS: derived, never spelled. `handoff-path` only reads, and `--create` makes the lane's own # checkout first where this is its first use. -hf_rc=0; handoff_file=""; prior_sha256=""; prepared_file="" +hf_rc=0; handoff_file=""; prior_sha256=""; prepared_file=""; hf_said="" handoff_file="$("$L" handoff-path "$lane" --create)" || hf_rc=$? +# ONLY 8 IS "NO HANDOFF" (Amendment 7(d): a read that failed is no answer). 1 is a register it could not read +# (its own "R22: never 8"), 2 a name refused or a clone that failed, 4 the lock that makes the checkout, and a +# `lanes-edit.sh` predating `handoff-path` says 2 from its unknown-subcommand arm. A 2 is asked a second time +# with only its stderr kept, as `sread` asks, to tell those apart: WITHOUT `--create`, so it is a read and +# clones nothing, and without the fetch. +[[ "$hf_rc" != 2 ]] || hf_said="$(LANES_NO_FETCH=1 "$L" handoff-path "$lane" 2>&1 >/dev/null || :)" +hf_keep="Write the new top block all the same and put it whole in the report: it is then the one copy, and it goes at the head of the handoff by hand once this is answered. The swap goes on (R-A11-11)." if [[ "$hf_rc" == 0 && -n "$handoff_file" && -f "$handoff_file" ]]; then # THE DIGEST OF WHAT IS THERE NOW, by the two tools the helper's own `lane_handoff_digest` tries: # `sha256sum` on Linux, `shasum -a 256` on macOS. @@ -366,8 +378,20 @@ if [[ "$hf_rc" == 0 && -n "$handoff_file" && -f "$handoff_file" ]]; then printf 'handoff_file=%s\nprior_sha256=%s\nprepared_file=%s\n' \ "$handoff_file" "${prior_sha256:-}" "${prepared_file:-}" else - printf 'HANDOFF NOT FOUND: `%s handoff-path %s --create` exited %s and named %s, so there is no handoff to write into and the call under "THE WRITE IS ONE CALL" is NOT made. The swap goes on (R-A11-11); the report carries this line.\n' \ - "$L" "$lane" "$hf_rc" "${handoff_file:-no file}" + case "$hf_rc:$hf_said" in + 0:*|8:*) + printf 'HANDOFF NOT FOUND: `%s handoff-path %s --create` exited %s and named %s: the register has no row for this lane, its row names no handoff, or no file is there, so there is no handoff to write into and the call under "THE WRITE IS ONE CALL" is NOT made. %s\n' \ + "$L" "$lane" "$hf_rc" "${handoff_file:-no file}" "$hf_keep" ;; + 4:*) + printf 'HANDOFF READ FAILED: `%s handoff-path %s --create` exited 4: another write held the lock that makes the lane checkout. The read failed, and that is not "no handoff" (Amendment 7(d)), so the call under "THE WRITE IS ONE CALL" is NOT made. Run step 2 again, from the block under WHERE THE HANDOFF IS. %s\n' \ + "$L" "$lane" "$hf_keep" ;; + 2:*"unknown subcommand"*) + printf 'HANDOFF READ FAILED: `%s handoff-path %s --create` exited 2, and the helper says "unknown subcommand": this lanes-edit.sh predates handoff-path. The read failed, and that is not "no handoff" (Amendment 7(d)), so the call under "THE WRITE IS ONE CALL" is NOT made. Update lanes-edit.sh (openRepoTools --install installs it with this skill), then run step 2 again. %s\n' \ + "$L" "$lane" "$hf_keep" ;; + *) + printf 'HANDOFF READ FAILED: `%s handoff-path %s --create` exited %s (its words are above). The read failed, and that is not "no handoff" (Amendment 7(d)), so the call under "THE WRITE IS ONE CALL" is NOT made. %s\n' \ + "$L" "$lane" "$hf_rc" "$hf_keep" ;; + esac handoff_file="" fi ``` diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index b641d81..19d1d89 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -137,7 +137,8 @@ def _code_lines(blocks: list[str]) -> list[str]: STUB = f"""#!{sys.executable} import json, os, sys argv = sys.argv[1:] -rec = {{"argv": argv, "lane": os.environ.get("LANES_LANE")}} +rec = {{"argv": argv, "lane": os.environ.get("LANES_LANE"), + "no_fetch": os.environ.get("LANES_NO_FETCH")}} if argv[:1] == ["write-handoff"] and "--whole" in argv: try: with open(argv[argv.index("--whole") + 1], "rb") as f: @@ -148,6 +149,8 @@ def _code_lines(blocks: list[str]) -> list[str]: log.write(json.dumps(rec) + "\\n") verb = argv[0] if argv else "" if verb == "handoff-path": + if os.environ.get("STUB_HP_ERR"): + print(os.environ["STUB_HP_ERR"], file=sys.stderr) if os.environ.get("STUB_HP_OUT"): print(os.environ["STUB_HP_OUT"]) sys.exit(int(os.environ.get("STUB_HP_RC", "0"))) @@ -433,23 +436,90 @@ def test_refusal_text_keeps_R_A11_11(): # --- where the handoff is not found, and the file the session never wrote ---- +#: What the session is told to do with the top block it could not write. +KEEP_IN_REPORT = "put it whole in the report" + + @pytest.mark.parametrize("shell", SHELLS) @pytest.mark.parametrize("how, extra", [ ("exit 8: no row, or a row that names no handoff", {"STUB_HP_RC": "8", "STUB_HP_OUT": ""}), - ("exit 1: a register that could not be read", {"STUB_HP_RC": "1", "STUB_HP_OUT": ""}), ("exit 0 and a path that is not a file", {"STUB_HP_OUT": "/nowhere/at/all.md"}), ("exit 0 and no path", {"STUB_HP_OUT": ""}), -], ids=["no-handoff", "unreadable", "not-a-file", "no-path"]) +], ids=["no-handoff", "not-a-file", "no-path"]) def test_a_handoff_that_is_not_found_is_said_and_write_handoff_is_never_called( estate, shell, how, extra): proc = estate.run(shell, estate.step2(), **extra) assert proc.returncode == 0, "the swap goes on" assert "HANDOFF NOT FOUND" in proc.stdout, proc.stdout - assert "HANDOFF NOT WRITTEN" in proc.stdout and "was NOT called" in proc.stdout, proc.stdout + assert "HANDOFF READ FAILED" not in proc.stdout, proc.stdout + assert "HANDOFF NOT WRITTEN" in proc.stdout, proc.stdout + assert "was NOT called" in proc.stdout, proc.stdout + assert KEEP_IN_REPORT in proc.stdout, "the new top block is kept in the report" assert estate.verbs() == ["handoff-path"], estate.calls() assert proc.stdout.endswith("AFTER\n") +#: `lanes-edit.sh`'s unknown-subcommand refusal, as a helper predating +#: `handoff-path` (main at `33ce26d`) prints it on stderr, with exit 2. +OLDER_HELPER = ("lanes-edit.sh: unknown subcommand 'handoff-path' " + "(verify-row|set-row-state|append-line|commit|publish-handoff)") + + +@pytest.mark.parametrize("shell", SHELLS) +@pytest.mark.parametrize("rc, err, must, never", [ + ("1", "the register could not be read (R22: never 8)", + ["exited 1 (its words are above)"], ["Update lanes-edit.sh", "step 2 again"]), + ("2", "the clone of the lane checkout failed", + ["exited 2 (its words are above)"], ["Update lanes-edit.sh", "step 2 again"]), + ("2", OLDER_HELPER, + ["exited 2", "unknown subcommand", "predates handoff-path", "Update lanes-edit.sh", + "step 2 again"], []), + ("4", "the lock that makes the lane checkout is held", + ["exited 4", "Run step 2 again"], ["Update lanes-edit.sh"]), + ("64", "usage: handoff-path [--create]", + ["exited 64 (its words are above)"], ["Update lanes-edit.sh", "step 2 again"]), + ("127", "lanes-edit.sh: not found", + ["exited 127 (its words are above)"], ["Update lanes-edit.sh", "step 2 again"]), +], ids=["unreadable", "clone-failed", "older-helper", "lock", "usage", "no-helper"]) +def test_a_read_that_failed_is_never_said_as_no_handoff(estate, shell, rc, err, must, never): + """rv3's MUST 1 on #311 at `b599ed9`: only `handoff-path`'s 8 is "no + handoff". Any other exit is a read that FAILED (Amendment 7(d); the verb's + own "R22: never 8"), and it says so with the code: an older helper's + unknown-subcommand 2 is told to update `lanes-edit.sh`, and the creation + lock's 4 to run step 2 again. In every one the call is not made, the + session is told to keep its new top block in the report, and the swap goes + on (`R-A11-11`).""" + proc = estate.run(shell, estate.step2(), STUB_HP_RC=rc, STUB_HP_OUT="", STUB_HP_ERR=err) + assert proc.returncode == 0, "the swap goes on" + assert proc.stdout.endswith("AFTER\n"), proc.stdout + assert "HANDOFF READ FAILED" in proc.stdout, proc.stdout + assert "The read failed" in proc.stdout, proc.stdout + assert "HANDOFF NOT FOUND" not in proc.stdout, f"exit {rc} is not 'no handoff'" + for words in must: + assert words in proc.stdout, f"{words!r} missing from\n{proc.stdout}" + for words in never: + assert words not in proc.stdout, f"{words!r} must not be said\n{proc.stdout}" + assert "was NOT called" in proc.stdout, proc.stdout + assert KEEP_IN_REPORT in proc.stdout, "the new top block is kept in the report" + assert "write-handoff" not in estate.verbs(), estate.calls() + + +@pytest.mark.parametrize("shell", SHELLS) +@pytest.mark.parametrize("rc", ["1", "2", "4"]) +def test_only_a_2_is_asked_again_and_never_with_create(estate, shell, rc): + """An older helper's words are read by asking the same read a second time + with only its stderr kept, as step 1's `sread` does, and only for a 2. The + second ask is a READ: no `--create`, so a clone that failed is not tried + again, and the fetch is skipped.""" + estate.run(shell, estate.step2(), STUB_HP_RC=rc, STUB_HP_OUT="", STUB_HP_ERR=OLDER_HELPER) + asked = [(c["argv"], c["no_fetch"]) for c in estate.calls()] + if rc == "2": + assert asked == [(["handoff-path", LANE, "--create"], None), + (["handoff-path", LANE], "1")], asked + else: + assert asked == [(["handoff-path", LANE, "--create"], None)], asked + + @pytest.mark.parametrize("shell", SHELLS) def test_a_prepared_file_the_session_never_wrote_is_never_sent(estate, shell): """`mktemp` makes an empty file; the verb refuses an empty `--whole` file, From c65acbf77a77f007d2096e35f4f4eecea8eed85b Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Fri, 23 Oct 2026 15:41:14 +0000 Subject: [PATCH 03/17] Slice 3, rv3's SHOULD 2: the skill's exit-7 arm says to run the block under WHERE THE HANDOFF IS again, for a new digest and a new prepared file, and then this block once more, and where the verb wrote nothing the new file's temporary path is named for the report (#198) A session that only rebuilt its prepared file kept the old digest and got 7 again: a lane checkout behind origin on its handoff meets that on its first call. rv3's probe of that case is ported (routed and seam, bash and zsh): 7 once, then written, with the peer's file under the new block. The prose also notes lane openRepoTools-3's findings 601 (a target in another lane's clone of the register will be refused, 2) and 603 (a restart fence's refusal will be 7) on opensoft/openRepoTools#312. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit b9ba76da77513c40e1b986efc8d6126aca0fce99) --- skills/handoff/SKILL.md | 15 ++++-- tests/test_handoff_skill_commit_block.py | 58 +++++++++++++++++++++++- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index 33d7f8c..45e4e1c 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -479,7 +479,13 @@ checkout's lock, and `LANES_LANE` names the lane. (This block used to commit in file, the shared workspace checkout among them, with no lock, and to rebase on a pull before its push; finding 282 of the review of opensoft/openRepoTools#307 names it one of the three writers that did.) A STAGED ENTRY THIS HANDOFF DID NOT MAKE and an operation in progress are the verb's refusals now, made before anything is -committed (2); a handoff that moved after its digest was read is exit 7, and nothing is published. +committed (2); once lane openRepoTools-3's finding 601 on opensoft/openRepoTools#312 is fixed, so is a +handoff target in another lane's clone of the register, with nothing written. A handoff that moved after its +digest was read is exit 7, and nothing is published; once that review's finding 603 is in, a restart fence's +refusal is 7 too, where it is 2 today. A 7 is not answered by calling again with the same digest: the block under WHERE THE HANDOFF IS is run +again, for a new digest and a new prepared file, and the call is made once more, once. A lane checkout behind +origin on its handoff meets this on its first call, because the verb brings the checkout to origin before it +compares. `$hf_subject` is the commit's one-line subject: the lane, the workstation (`none` where step 1 found none) and the why in the operator's words, `handoff` where there is none. @@ -495,6 +501,7 @@ if [[ -n "$handoff_file" && -n "$prior_sha256" && -s "${prepared_file:-}" ]]; th # ITS STDOUT IS `` ONCE THE FILE IS WRITTEN, whatever the exit; its own words are on stderr. hf_path="${hf_out%%$'\t'*}" hf_wrote="nothing was written"; [[ -z "$hf_out" ]] || hf_wrote="the file is written at $hf_path" + hf_kept="The new file is kept at $prepared_file: name that path in the report, so that its top block can be put at the head of the handoff by hand once this is answered." case "$hf_rc" in 0) printf 'handoff written: %s (pushed from the lane checkout; a handoff in another repository is written and never committed, and its note above says so)\n' "$hf_path" ;; 2) if [[ -n "$hf_out" ]]; then @@ -504,11 +511,13 @@ if [[ -n "$handoff_file" && -n "$prior_sha256" && -s "${prepared_file:-}" ]]; th fi ;; 3) printf 'HANDOFF PUSHED, NOT CONFIRMED: the push went, or timed out, and origin could not be read back to show the commit on it (exit 3; the note above names it). Do not call it published until it is read back.\n' ;; 6) printf 'HANDOFF NOT PUSHED: its commit is kept on HEAD of the lane checkout and named above (exit 6), and every later write from that checkout refuses until it is published; the note above names the act. Never force.\n' ;; - 7) printf 'HANDOFF NOT WRITTEN: the handoff is no longer the bytes digest %s names, or a restart owns it (exit 7; its words above say which), and nothing was published. If it moved, read it again, make the new file from what is there now, and make the call once more; if a restart owns it, do not retry, because that restart writes this handoff.\n' "$prior_sha256" ;; + 7) printf 'HANDOFF NOT WRITTEN: the handoff is no longer the bytes digest %s names, or a restart owns it (exit 7; its words above say which), and nothing was published. If it moved, run the block under WHERE THE HANDOFF IS again, which reads a new digest and makes a new prepared file, make the new file in it from what is there now, and run this block once more. If a restart owns it, do not retry: that restart writes this handoff.\n' "$prior_sha256" ;; *) printf 'HANDOFF NOT PUBLISHED: write-handoff exited %s (its words are above); %s.\n' "$hf_rc" "$hf_wrote" ;; esac + # WHERE NOTHING WAS WRITTEN, THE NEW FILE IS NOT LOST WITH THE SWAP: its temporary file is named for the report. + [[ -n "$hf_out" ]] || printf '%s\n' "$hf_kept" else - printf 'HANDOFF NOT WRITTEN: there is no handoff located, no digest read or no prepared file written, so write-handoff was NOT called. The swap goes on (R-A11-11); the report carries this line.\n' + printf 'HANDOFF NOT WRITTEN: there is no handoff located, no digest read or no prepared file written, so write-handoff was NOT called. Write the new top block if it is not written yet, and put it whole in the report, so that it can be put at the head of the handoff by hand. The swap goes on (R-A11-11); the report carries this line.\n' fi ``` diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index 19d1d89..a471129 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -391,8 +391,10 @@ def _says(estate: Estate) -> dict: "kept-on-head": (6, True, ["HANDOFF NOT PUSHED", "kept on HEAD", "(exit 6)", "Never force"], []), "moved-or-owned": (7, False, [ - "HANDOFF NOT WRITTEN", f"digest {OLD_DIGEST}", "(exit 7;", "once more", - "if a restart owns it, do not retry"], []), + "HANDOFF NOT WRITTEN", f"digest {OLD_DIGEST}", "(exit 7;", + "run the block under WHERE THE HANDOFF IS again", "reads a new digest", + "makes a new prepared file", "run this block once more", + "a restart owns it, do not retry"], []), "no-handoff": (8, False, ["HANDOFF NOT PUBLISHED", "exited 8", "nothing was written"], []), "environment": (1, False, ["HANDOFF NOT PUBLISHED", "exited 1", "nothing was written"], []), "lock": (4, False, ["HANDOFF NOT PUBLISHED", "exited 4", "nothing was written"], []), @@ -418,6 +420,25 @@ def test_each_exit_of_the_verb_is_said_and_the_swap_goes_on(estate, shell, case) assert estate.verbs().count("write-handoff") == 1, "one call, and it is never repeated" +@pytest.mark.parametrize("shell", SHELLS) +@pytest.mark.parametrize("case", CASES) +def test_the_new_file_is_kept_and_named_wherever_nothing_was_written(estate, shell, case): + """rv3's MUST 1, for the call's own outcomes: where the verb wrote nothing + (no stdout), the session's new file is not lost with the swap. The block + names the temporary file that holds it, for the report; where the verb + wrote the handoff, there is nothing to keep and nothing is said of it.""" + rc, written, _, _ = _says(estate)[case] + out = f"{estate.handoff}\tdeadbeef" if written else "" + proc = estate.run(shell, estate.step2(), STUB_WH_RC=str(rc), STUB_WH_OUT=out) + sent = _write_call(estate)["argv"][3] + kept = f"kept at {sent}" + if written: + assert kept not in proc.stdout, proc.stdout + else: + assert kept in proc.stdout, f"{case}: the new file is not named\n{proc.stdout}" + assert "name that path in the report" in proc.stdout, proc.stdout + + def test_refusal_text_keeps_R_A11_11(): """`R-A11-11`, *a swap is never left unwritten*, is still the text's rule about a refusal, a conflict or a rejected push, and says what the verb does @@ -627,6 +648,39 @@ def test_a_handoff_that_moved_after_its_digest_was_read_is_refused_and_nothing_i assert real.origin_file(HANDOFF_REL) != REAL_NEW +@pytest.mark.parametrize("shell", SHELLS) +@pytest.mark.parametrize("how", ["routed", "seam"]) +def test_a_lane_checkout_behind_origin_is_7_once_and_written_when_step_2_is_run_again( + real, shell, how): + """rv3's SHOULD 2 (question 3 of the body), ported from its probe. A lane + checkout behind origin on its handoff: `handoff-path --create` leaves it + as it is, the digest is of its stale copy, and the verb fast-forwards it + first, so the first call is 7 with nothing published. The 7 arm's words + are the way out: the block under WHERE THE HANDOFF IS run again reads the + digest the checkout has now, and the call made once more writes the new + block over the peer's file, which survives under it.""" + if how == "seam": + real.seam() + else: + made = real.helper("handoff-path", REAL_LANE, "--create") + assert made.returncode == 0, made.stderr + peer_text = HANDOFF + "a peer line, pushed from elsewhere\n" + real.peer_pushes(HANDOFF_REL, peer_text) + careful = (f'[ -z "$prepared_file" ] || {{ printf %s {shlex.quote(BLOCK)} ' + "&& printf -- '---\\n\\n' && cat -- \"$handoff_file\"; } > \"$prepared_file\"") + script = "\n".join([f"L={shlex.quote(str(HELPER))}", f"lane={REAL_LANE}", "ws=Eagle", + f"why={shlex.quote(REAL_WHY)}", _block(B1), careful, _block(B2)]) + first = _real_run(real, shell, script) + assert first.returncode == 0, _said(first) + assert "(exit 7;" in first.stdout, _said(first) + assert "run the block under WHERE THE HANDOFF IS again" in first.stdout, _said(first) + assert real.origin_file(HANDOFF_REL) == peer_text, "nothing is published on a 7" + second = _real_run(real, shell, script) + assert second.returncode == 0, _said(second) + assert "handoff written:" in second.stdout, _said(second) + assert real.origin_file(HANDOFF_REL) == prepended(peer_text, BLOCK), _said(second) + + @pytest.mark.parametrize("shell", SHELLS) def test_a_staged_entry_in_the_lane_checkout_leaves_the_handoff_written_and_uncommitted( real, shell): From 925aec274fd4de92a38417fbcb8ca70df4d4d86d Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Fri, 23 Oct 2026 18:18:01 +0000 Subject: [PATCH 04/17] Slice 3, rv3's SHOULD 3: step 4's register PAUSED line ends with what step 2 did, carried in `$hf_record`, so it says `handoff refreshed` only where `write-handoff` exited 0, `handoff written, its publication NOT confirmed (...)` where the verb wrote the file and exited otherwise, and `handoff NOT refreshed ()` wherever nothing was written or step 2's outcome never reached it (#198) Step 2's first block sets the reason where the handoff was not found or its read failed; the second sets it from the call's exit and stdout, or says the call was not made. Step 4 defaults an unset `$hf_record` (set -u safe) to `handoff NOT refreshed (step 2 left no outcome)`. test_handoff_skill_80_85.py's step-4 preludes bind the 0 outcome, so the skill's line still equals lane-handoff's for a handoff that was written. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit ce10339c7311f92906e8a0dfa65aa628bf86ae54) --- skills/handoff/SKILL.md | 30 +++++++++-- tests/test_handoff_skill_80_85.py | 7 +-- tests/test_handoff_skill_commit_block.py | 64 ++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 7 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index 45e4e1c..39be612 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -352,7 +352,7 @@ unwritten*). ```sh # WHERE THE HANDOFF IS: derived, never spelled. `handoff-path` only reads, and `--create` makes the lane's own # checkout first where this is its first use. -hf_rc=0; handoff_file=""; prior_sha256=""; prepared_file=""; hf_said="" +hf_rc=0; handoff_file=""; prior_sha256=""; prepared_file=""; hf_said=""; hf_record="" handoff_file="$("$L" handoff-path "$lane" --create)" || hf_rc=$? # ONLY 8 IS "NO HANDOFF" (Amendment 7(d): a read that failed is no answer). 1 is a register it could not read # (its own "R22: never 8"), 2 a name refused or a clone that failed, 4 the lock that makes the checkout, and a @@ -392,6 +392,9 @@ else printf 'HANDOFF READ FAILED: `%s handoff-path %s --create` exited %s (its words are above). The read failed, and that is not "no handoff" (Amendment 7(d)), so the call under "THE WRITE IS ONE CALL" is NOT made. %s\n' \ "$L" "$lane" "$hf_rc" "$hf_keep" ;; esac + # STEP 4'S REGISTER LINE SAYS THIS, and never "handoff refreshed" (rv3's SHOULD 3 on #311). + hf_record="handoff NOT refreshed (the handoff read failed, exit $hf_rc)" + [[ "$hf_rc" != 0 && "$hf_rc" != 8 ]] || hf_record="handoff NOT refreshed (no handoff found)" handoff_file="" fi ``` @@ -514,10 +517,17 @@ if [[ -n "$handoff_file" && -n "$prior_sha256" && -s "${prepared_file:-}" ]]; th 7) printf 'HANDOFF NOT WRITTEN: the handoff is no longer the bytes digest %s names, or a restart owns it (exit 7; its words above say which), and nothing was published. If it moved, run the block under WHERE THE HANDOFF IS again, which reads a new digest and makes a new prepared file, make the new file in it from what is there now, and run this block once more. If a restart owns it, do not retry: that restart writes this handoff.\n' "$prior_sha256" ;; *) printf 'HANDOFF NOT PUBLISHED: write-handoff exited %s (its words are above); %s.\n' "$hf_rc" "$hf_wrote" ;; esac + # WHAT THIS CALL DID, CARRIED TO STEP 4'S REGISTER LINE (rv3's SHOULD 3 on #311): refreshed only on a 0, written + # but not confirmed published where the verb wrote the file (its stdout) and exited otherwise, else NOT refreshed. + hf_record="handoff NOT refreshed (write-handoff exited $hf_rc and wrote nothing)" + [[ -z "$hf_out" ]] || hf_record="handoff written, its publication NOT confirmed (write-handoff exited $hf_rc)" + [[ "$hf_rc" != 0 ]] || hf_record="handoff refreshed" # WHERE NOTHING WAS WRITTEN, THE NEW FILE IS NOT LOST WITH THE SWAP: its temporary file is named for the report. [[ -n "$hf_out" ]] || printf '%s\n' "$hf_kept" else printf 'HANDOFF NOT WRITTEN: there is no handoff located, no digest read or no prepared file written, so write-handoff was NOT called. Write the new top block if it is not written yet, and put it whole in the report, so that it can be put at the head of the handoff by hand. The swap goes on (R-A11-11); the report carries this line.\n' + # Block 1's reason where it gave one: a handoff not found, or a read that failed. + hf_record="${hf_record:-handoff NOT refreshed (write-handoff was not called)}" fi ``` @@ -528,6 +538,13 @@ origin's copy of the handoff is still the one this write was made from, and othe and names it (6). The block prints what it did NOT do, and the report carries that line. Never force, never `git add -A`, and never commit staged entries you did not make. +**WHAT THIS STEP DID IS CARRIED TO STEP 4 in `$hf_record`,** so that the register's PAUSED line says it and +never claims it: `handoff refreshed` only where the call exited 0; `handoff written, its publication NOT +confirmed (write-handoff exited )` where the verb wrote the file and exited otherwise; and `handoff NOT +refreshed ()` wherever nothing was written: the handoff was not found, its read failed, the call was not +made, or the verb wrote nothing. Keep it in the shell that runs step 4, as `$uuid_missing` is kept. A step 4 +that it never reached says `handoff NOT refreshed (step 2 left no outcome)`. + ## 3. Subagents and worktrees — the one question, and the sweep that fills WRITERS Run `ListAgents`, then send each running subagent one `SendMessage`: "Handoff imminent: commit and push what @@ -766,8 +783,13 @@ if [[ "$kind" == unknown ]]; then why_text="$why_text kind unknown"; else why_te # template's angle-bracket placeholders instead, for the operator's words and # for what is open, so a block run as printed wrote the placeholders into two # files nothing rewrites. `lane-handoff` hands both writes its computed text, -# and (b) is its line word for word. What is open is for the handoff's state -# line to say, as it is in `lane-handoff`'s record. +# and (b) is its line word for word where step 2's call exited 0. What is open +# is for the handoff's state line to say, as it is in `lane-handoff`'s record. +# (b)'S LAST PHRASE IS WHAT STEP 2 DID, NEVER A CLAIM (rv3's SHOULD 3 on #311): +# `$hf_record`, which step 2's two blocks set. A step 4 that step 2's outcome +# never reached (another shell, or a step 2 stopped before its call) says the +# handoff was NOT refreshed. +hf_record="${hf_record:-handoff NOT refreshed (step 2 left no outcome)}" # (a) the Amendment 7 object-log PAUSED line — the record `swapped` reads. # AMENDMENT 11 CLAUSE (e) — THE SKILL SUPPLIES THE UUID, AND `LANES_SESSION` IS @@ -811,7 +833,7 @@ LANES_LANE="$lane" LANES_SESSION="$uuid" "$L" log PAUSED "lane:$lane" \ # 23 S2): a register write goes through the lane's own checkout, and `append-line` finds # that lane from this variable first, so a line that names no lane with a row is not a # write with no lane. -LANES_LANE="$lane" "$L" append-line "PAUSED — lane $lane, session $uuid@$ws, $(date -u +%Y-%m-%dT%H:%M:%SZ), $why_text; handoff refreshed" +LANES_LANE="$lane" "$L" append-line "PAUSED — lane $lane, session $uuid@$ws, $(date -u +%Y-%m-%dT%H:%M:%SZ), $why_text; $hf_record" fi fi # (c) the row: ONE WRITE, which sets the whole state cell (Amendment 13(a)). diff --git a/tests/test_handoff_skill_80_85.py b/tests/test_handoff_skill_80_85.py index abf66de..5a20952 100644 --- a/tests/test_handoff_skill_80_85.py +++ b/tests/test_handoff_skill_80_85.py @@ -207,9 +207,10 @@ def _why_env(why: Optional[str], kind: Optional[str] = None) -> Dict[str, str]: def _step4(box: Sandbox, shell: str, why: Optional[str], kind: Optional[str] = None) -> subprocess.CompletedProcess: - """Step 4's whole block, with what steps 1 and 2 would have bound.""" + """Step 4's whole block, with what steps 1 and 2 would have bound: step 2's + `$hf_record` is a call that exited 0 (`lane-handoff`'s own line).""" script = (f'L="{box.helper}"\nlane={LANE}\nws={WS}\nuuid={UUID}\n' - 'ws_missing=""\nuuid_missing=""\n' + WHY_PRELUDE + + 'ws_missing=""\nuuid_missing=""\nhf_record="handoff refreshed"\n' + WHY_PRELUDE + '[ -z "${STUB_KIND-}" ] || kind="$STUB_KIND"\n' + _one_block(STEP4).body) proc = box.run(shell, script, _why_env(why, kind)) @@ -402,7 +403,7 @@ def test_an_unset_why_passes_the_door_under_set_u_and_is_written_handoff( run in the same `set -u` shell, writes it `handoff`, as `lane-handoff` does.""" script = (f'set -u\nL="{box.helper}"\nlane={LANE}\nws={WS}\nuuid={UUID}\n' - 'ws_missing=""\nuuid_missing=""\nunset why\n' + + 'ws_missing=""\nuuid_missing=""\nhf_record="handoff refreshed"\nunset why\n' + _one_block(DOOR).body + "printf 'AFTER\\n'\n" + _one_block(STEP4).body) proc = box.run(shell, script) diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index a471129..a7f8581 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -551,6 +551,70 @@ def test_a_prepared_file_the_session_never_wrote_is_never_sent(estate, shell): assert estate.verbs() == ["handoff-path"], estate.calls() +# --- step 4's register line says what step 2 did (rv3's SHOULD 3) ------------ + +#: The register line's last phrase where step 2's one call exited 0. +REFRESHED = "; handoff refreshed" + + +def _register_line(estate: Estate) -> str: + found = [c["argv"] for c in estate.calls() if c["argv"][0] == "append-line"] + assert len(found) == 1, estate.calls() + return found[0][1] + + +@pytest.mark.parametrize("shell", SHELLS) +@pytest.mark.parametrize("extra, written, prepare, ends", [ + ({}, True, True, REFRESHED), + ({"STUB_WH_RC": "7"}, False, True, + "; handoff NOT refreshed (write-handoff exited 7 and wrote nothing)"), + ({"STUB_WH_RC": "2"}, False, True, + "; handoff NOT refreshed (write-handoff exited 2 and wrote nothing)"), + ({"STUB_WH_RC": "2"}, True, True, + "; handoff written, its publication NOT confirmed (write-handoff exited 2)"), + ({"STUB_WH_RC": "3"}, True, True, + "; handoff written, its publication NOT confirmed (write-handoff exited 3)"), + ({"STUB_WH_RC": "6"}, True, True, + "; handoff written, its publication NOT confirmed (write-handoff exited 6)"), + ({"STUB_HP_RC": "8", "STUB_HP_OUT": ""}, False, True, + "; handoff NOT refreshed (no handoff found)"), + ({"STUB_HP_RC": "4", "STUB_HP_OUT": ""}, False, True, + "; handoff NOT refreshed (the handoff read failed, exit 4)"), + ({"STUB_HP_RC": "2", "STUB_HP_OUT": "", "STUB_HP_ERR": OLDER_HELPER}, False, True, + "; handoff NOT refreshed (the handoff read failed, exit 2)"), + ({}, False, False, "; handoff NOT refreshed (write-handoff was not called)"), +], ids=["pushed", "moved-or-owned", "refused-before-writing", "refused-after-writing", + "pushed-not-confirmed", "kept-on-head", "not-found", "read-failed", "older-helper", + "never-prepared"]) +def test_step4_register_line_says_what_step_2_did(estate, shell, extra, written, prepare, ends): + """rv3's SHOULD 3 on #311 at `b599ed9`: the register's PAUSED line ended + `; handoff refreshed` whatever step 2 printed. Step 2 now carries its + outcome to step 4 in `$hf_record`, run here in the same shell as a session + runs them: refreshed only where the call exited 0, written but not + confirmed published where the verb wrote the file and exited otherwise, + and NOT refreshed, with the reason, wherever nothing was written.""" + env = dict(extra) + if written: + env["STUB_WH_OUT"] = f"{estate.handoff}\tdeadbeef" + proc = estate.run(shell, estate.step2(prepare=prepare) + "\n" + estate.step4(), **env) + assert proc.returncode == 0, proc.stderr + line = _register_line(estate) + assert line.endswith(ends), line + assert line.endswith(REFRESHED) == (ends == REFRESHED), line + + +@pytest.mark.parametrize("shell", SHELLS) +@pytest.mark.parametrize("strict", [False, True], ids=["plain", "set-u"]) +def test_a_step_4_that_step_2_never_reached_never_claims_a_refresh(estate, shell, strict): + """A step 4 run where step 2's outcome is not bound (another shell, or + step 2 stopped before its call) says the handoff was NOT refreshed rather + than claiming it, and an unset `$hf_record` passes `set -u`.""" + proc = estate.run(shell, ("set -u\n" if strict else "") + estate.step4()) + assert proc.returncode == 0, proc.stderr + line = _register_line(estate) + assert line.endswith("; handoff NOT refreshed (step 2 left no outcome)"), line + + # --- the real helper: the handoff is pushed from C, and W is untouched -------- REAL_NEW = prepended(HANDOFF, BLOCK) From 31b705f5c42b4e0e5e41c1fdae0a93f36adb27c5 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Fri, 23 Oct 2026 20:29:36 +0000 Subject: [PATCH 05/17] Slice 3, rv3's SHOULD 4: the words that keep the swap safe are pinned (the 3 arm's "Do not call it published until it is read back", the 2 arm's "Do not call it again", and the `*` arm's "the file is written at", now run with stdout), and a 2 from `handoff-path` that is no older helper says the handoff it names may belong to another repository, where nothing is to be written (601) (#198) - `_says()`'s must-lists carry the three phrases; a `*` case with stdout (exit 1 after the write) pins "the file is written at ." and its record line, and the no-stdout `*` cases may never say it. - Block 1's new `2:*` arm, after the unknown-subcommand one: a name refused, a failed clone, or 601's refusal of a handoff in W or in another checkout of the register beside it; where its words name such a file, "the handoff it names belongs to another repository and not to this lane: nothing is to be written there, by this skill or by hand". Pinned with 2a's A54 words (`6d861d4`) on the stub's stderr. - The prose says the same, and block 2's note on 601 says that `handoff-path` shares the refusal, so block 1 meets it first. Refs #198 Co-Authored-By: Claude Opus 5.5 (cherry picked from commit dd956d403226cf543bd93b22a3cde7376de13d10) --- skills/handoff/SKILL.md | 18 +++++++-- tests/test_handoff_skill_commit_block.py | 51 +++++++++++++++++++----- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index 39be612..3ef96dc 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -344,7 +344,11 @@ copy that has moved. Where the block below prints `HANDOFF NOT FOUND` (`handoff- that names no handoff) there is no file to write into. Where it prints `HANDOFF READ FAILED`, the read failed, and that is never "no handoff": every exit but 0 and 8 is one, and the line quotes it. A 2 whose words say "unknown subcommand" is a `lanes-edit.sh` predating `handoff-path`, to be updated; a 4 is the lock that -makes the lane's checkout, and step 2 is run again. In either case the call under "THE WRITE IS ONE CALL" is +makes the lane's checkout, and step 2 is run again. Any other 2 is a name refused, a clone that failed, or a +handoff the row names in W or in another checkout of the register beside it (finding 601 of lane +openRepoTools-3's review of opensoft/openRepoTools#312, refused by the translation `handoff-path` and +`write-handoff` share): that file belongs to another repository, and nothing is written there, by this skill +or by hand. In every one of these cases the call under "THE WRITE IS ONE CALL" is not made, the new top block is still written and kept whole in the report, so that it can be put at the head of the handoff by hand, and the swap goes on with the gap in the report (`R-A11-11`, *a swap is never left unwritten*). @@ -388,6 +392,12 @@ else 2:*"unknown subcommand"*) printf 'HANDOFF READ FAILED: `%s handoff-path %s --create` exited 2, and the helper says "unknown subcommand": this lanes-edit.sh predates handoff-path. The read failed, and that is not "no handoff" (Amendment 7(d)), so the call under "THE WRITE IS ONE CALL" is NOT made. Update lanes-edit.sh (openRepoTools --install installs it with this skill), then run step 2 again. %s\n' \ "$L" "$lane" "$hf_keep" ;; + 2:*) + # 601 (lane openRepoTools-3's review of #312, and its correction 6368669583): a row whose handoff is in W + # or in another checkout of the register beside it is refused by the translation `write-handoff` shares + # with `handoff-path`, 2 with no path printed. That file is not this lane's to write, by hand either. + printf 'HANDOFF READ FAILED: `%s handoff-path %s --create` exited 2 (its words are above): a name refused, a clone of the lane checkout that failed, or a handoff the row names in W or in another checkout of the register beside it (601). Where its words name such a file, the handoff it names belongs to another repository and not to this lane: nothing is to be written there, by this skill or by hand. The read failed, and that is not "no handoff" (Amendment 7(d)), so the call under "THE WRITE IS ONE CALL" is NOT made. %s\n' \ + "$L" "$lane" "$hf_keep" ;; *) printf 'HANDOFF READ FAILED: `%s handoff-path %s --create` exited %s (its words are above). The read failed, and that is not "no handoff" (Amendment 7(d)), so the call under "THE WRITE IS ONE CALL" is NOT made. %s\n' \ "$L" "$lane" "$hf_rc" "$hf_keep" ;; @@ -482,8 +492,10 @@ checkout's lock, and `LANES_LANE` names the lane. (This block used to commit in file, the shared workspace checkout among them, with no lock, and to rebase on a pull before its push; finding 282 of the review of opensoft/openRepoTools#307 names it one of the three writers that did.) A STAGED ENTRY THIS HANDOFF DID NOT MAKE and an operation in progress are the verb's refusals now, made before anything is -committed (2); once lane openRepoTools-3's finding 601 on opensoft/openRepoTools#312 is fixed, so is a -handoff target in another lane's clone of the register, with nothing written. A handoff that moved after its +committed (2); once lane openRepoTools-3's finding 601 on opensoft/openRepoTools#312 is in, so is a +handoff target in W or in another checkout of the register beside it, with nothing written, and since +`handoff-path` shares that refusal, the block under WHERE THE HANDOFF IS meets it first and this call is not +made. A handoff that moved after its digest was read is exit 7, and nothing is published; once that review's finding 603 is in, a restart fence's refusal is 7 too, where it is 2 today. A 7 is not answered by calling again with the same digest: the block under WHERE THE HANDOFF IS is run again, for a new digest and a new prepared file, and the call is made once more, once. A lane checkout behind diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index a7f8581..4460687 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -384,10 +384,12 @@ def _says(estate: Estate) -> dict: "pushed": (0, True, [f"handoff written: {path}"], ["HANDOFF NOT"]), "refused-after-writing": (2, True, [ "HANDOFF NOT COMMITTED", f"written at {path}, uncommitted", "Nothing was pushed", - "refused (7)"], ["HANDOFF NOT WRITTEN"]), + "Do not call it again", "refused (7)"], ["HANDOFF NOT WRITTEN"]), "refused-before-writing": (2, False, [ "HANDOFF NOT WRITTEN", "wrote nothing"], ["HANDOFF NOT COMMITTED"]), - "pushed-not-confirmed": (3, True, ["HANDOFF PUSHED, NOT CONFIRMED", "(exit 3"], []), + "pushed-not-confirmed": (3, True, [ + "HANDOFF PUSHED, NOT CONFIRMED", "(exit 3", + "Do not call it published until it is read back"], []), "kept-on-head": (6, True, ["HANDOFF NOT PUSHED", "kept on HEAD", "(exit 6)", "Never force"], []), "moved-or-owned": (7, False, [ @@ -395,14 +397,24 @@ def _says(estate: Estate) -> dict: "run the block under WHERE THE HANDOFF IS again", "reads a new digest", "makes a new prepared file", "run this block once more", "a restart owns it, do not retry"], []), - "no-handoff": (8, False, ["HANDOFF NOT PUBLISHED", "exited 8", "nothing was written"], []), - "environment": (1, False, ["HANDOFF NOT PUBLISHED", "exited 1", "nothing was written"], []), - "lock": (4, False, ["HANDOFF NOT PUBLISHED", "exited 4", "nothing was written"], []), + "no-handoff": (8, False, ["HANDOFF NOT PUBLISHED", "exited 8", "nothing was written"], + ["the file is written at"]), + "environment": (1, False, ["HANDOFF NOT PUBLISHED", "exited 1", "nothing was written"], + ["the file is written at"]), + "lock": (4, False, ["HANDOFF NOT PUBLISHED", "exited 4", "nothing was written"], + ["the file is written at"]), + # rv3's SHOULD 4: the `*` arm WITH stdout, a verb that wrote the file and then + # failed otherwise (1). The path is followed by the sentence's full stop, so a + # stdout left unsplit (``) is not taken for the path. + "written-then-failed": (1, True, [ + "HANDOFF NOT PUBLISHED", "exited 1", f"the file is written at {path}."], + ["nothing was written"]), } CASES = ["pushed", "refused-after-writing", "refused-before-writing", "pushed-not-confirmed", - "kept-on-head", "moved-or-owned", "no-handoff", "environment", "lock"] + "kept-on-head", "moved-or-owned", "no-handoff", "environment", "lock", + "written-then-failed"] @pytest.mark.parametrize("shell", SHELLS) @@ -485,6 +497,17 @@ def test_a_handoff_that_is_not_found_is_said_and_write_handoff_is_never_called( OLDER_HELPER = ("lanes-edit.sh: unknown subcommand 'handoff-path' " "(verify-row|set-row-state|append-line|commit|publish-handoff)") +#: 601's refusal, as slice 2a's `handoff-path` says it from A54 (`6d861d4`) on: +#: the row names a handoff in another checkout of the register beside W, so the +#: path is not printed, with exit 2 (lane openRepoTools-3's correction 6368669583). +ANOTHER_REPOSITORY = ( + "lanes-edit.sh: lane repoHS-1's handoff handoffs/estate/repoHS-1.md is " + "/w-lanes/repoHS-2/handoffs/estate/repoHS-1.md, in another checkout of the register " + "beside W (/w), not in the checkout /w-lanes/repoHS-1 this lane's writes go through: " + "a lane's handoff is written in its own checkout only (Amendment 23 S1, S2): " + "`write-handoff` refuses it, so no path is printed for a restart to bind or a launch " + "to read (601). Nothing was written.") + @pytest.mark.parametrize("shell", SHELLS) @pytest.mark.parametrize("rc, err, must, never", [ @@ -494,14 +517,20 @@ def test_a_handoff_that_is_not_found_is_said_and_write_handoff_is_never_called( ["exited 2 (its words are above)"], ["Update lanes-edit.sh", "step 2 again"]), ("2", OLDER_HELPER, ["exited 2", "unknown subcommand", "predates handoff-path", "Update lanes-edit.sh", - "step 2 again"], []), + "step 2 again"], ["belongs to another repository"]), + ("2", ANOTHER_REPOSITORY, + ["exited 2 (its words are above)", + "the handoff it names belongs to another repository and not to this lane: " + "nothing is to be written there, by this skill or by hand"], + ["Update lanes-edit.sh", "step 2 again"]), ("4", "the lock that makes the lane checkout is held", ["exited 4", "Run step 2 again"], ["Update lanes-edit.sh"]), ("64", "usage: handoff-path [--create]", ["exited 64 (its words are above)"], ["Update lanes-edit.sh", "step 2 again"]), ("127", "lanes-edit.sh: not found", ["exited 127 (its words are above)"], ["Update lanes-edit.sh", "step 2 again"]), -], ids=["unreadable", "clone-failed", "older-helper", "lock", "usage", "no-helper"]) +], ids=["unreadable", "clone-failed", "older-helper", "another-repository", "lock", "usage", + "no-helper"]) def test_a_read_that_failed_is_never_said_as_no_handoff(estate, shell, rc, err, must, never): """rv3's MUST 1 on #311 at `b599ed9`: only `handoff-path`'s 8 is "no handoff". Any other exit is a read that FAILED (Amendment 7(d); the verb's @@ -576,6 +605,8 @@ def _register_line(estate: Estate) -> str: "; handoff written, its publication NOT confirmed (write-handoff exited 3)"), ({"STUB_WH_RC": "6"}, True, True, "; handoff written, its publication NOT confirmed (write-handoff exited 6)"), + ({"STUB_WH_RC": "1"}, True, True, + "; handoff written, its publication NOT confirmed (write-handoff exited 1)"), ({"STUB_HP_RC": "8", "STUB_HP_OUT": ""}, False, True, "; handoff NOT refreshed (no handoff found)"), ({"STUB_HP_RC": "4", "STUB_HP_OUT": ""}, False, True, @@ -584,8 +615,8 @@ def _register_line(estate: Estate) -> str: "; handoff NOT refreshed (the handoff read failed, exit 2)"), ({}, False, False, "; handoff NOT refreshed (write-handoff was not called)"), ], ids=["pushed", "moved-or-owned", "refused-before-writing", "refused-after-writing", - "pushed-not-confirmed", "kept-on-head", "not-found", "read-failed", "older-helper", - "never-prepared"]) + "pushed-not-confirmed", "kept-on-head", "written-then-failed", "not-found", "read-failed", + "older-helper", "never-prepared"]) def test_step4_register_line_says_what_step_2_did(estate, shell, extra, written, prepare, ends): """rv3's SHOULD 3 on #311 at `b599ed9`: the register's PAUSED line ended `; handoff refreshed` whatever step 2 printed. Step 2 now carries its From d8260c4da05cfa53896fa97ef35bd125df6b6aac Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Fri, 23 Oct 2026 20:42:39 +0000 Subject: [PATCH 06/17] Slice 3, rv3's SHOULD 1, option (b): the session writes only its new top block, to `$block_file`, and the skill builds the complete new file from it, `---` and a blank line, then `cat -- "$handoff_file"`, and proves with `tail -c | cmp` that the old file survives at its end, so a file that lost the history is never sent (#198) - Block 1 makes `$block_file` beside `$prepared_file` and prints both. - Block 2 builds `$prepared_file` in `write-handoff --prepend`'s own layout (`{ cat -- "$block_file" && printf -- '---\n\n' && cat -- "$handoff_file"; }`), then compares its last `wc -c` bytes with the old file. Only a build that proves out is sent; otherwise HANDOFF NOT WRITTEN, no call, the block file named for the report, and step 4's line ends `handoff NOT refreshed (the new file did not keep the old handoff whole)`. - An empty `$block_file` is no block (a whole file typed into `$prepared_file` by hand, the old habit, is not sent either); where the verb wrote nothing, both temporary paths are named. - The 7 arm and its prose say to write the top block to the new block file before the block is run once more. - Tests: the stub's step 2 writes only the block, and the file sent is still `NEW`; a `cat` that writes ten bytes or fails is never sent; the real-helper cases write only `BLOCK` and origin holds it over the old file whole (rv3's probe of the dropped history, now kept). Option (a), `write-handoff --prepend`, is a question for lane openRepoTools-3 in the PR body. Refs #198 Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 8445406184823856a5a4cca19ebafa4d88e2ef29) --- skills/handoff/SKILL.md | 54 ++++++++---- tests/test_handoff_skill_commit_block.py | 103 ++++++++++++++++++----- 2 files changed, 120 insertions(+), 37 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index 3ef96dc..210afed 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -274,8 +274,10 @@ checkout to origin, checks that the file still has the bytes this session read, under the workstation's sidecar lock, commits it by its own pathspec with the Rule 5 `Lane:` trailer, pushes with no pull, reads origin back, and replays the one write on a refused push. It refuses an active restart owned by another operation. So the order below is: say where the handoff is and read its SHA-256, -write the complete new file to a temporary file, then make the one call under "THE WRITE IS ONE CALL". Do -not edit or redirect into the canonical handoff directly, and do not commit it: the verb is the commit. +write the new top block, and only it, to a temporary file, then run the block under "THE WRITE IS ONE CALL", +which builds the complete new file from that block and the old file whole, proves that the old file survives +at its end, and makes the one call. Do not edit or redirect into the canonical handoff directly, do not write +the complete new file by hand, and do not commit it: the verb is the commit. Legacy `rename-lane` is a separate four-file transaction under the workspace's writer mutex; it refuses unfinished/failed restart ownership and uses EXIT rollback. Its stamp now atomically replaces the handoff through a temporary file beside it @@ -356,7 +358,7 @@ unwritten*). ```sh # WHERE THE HANDOFF IS: derived, never spelled. `handoff-path` only reads, and `--create` makes the lane's own # checkout first where this is its first use. -hf_rc=0; handoff_file=""; prior_sha256=""; prepared_file=""; hf_said=""; hf_record="" +hf_rc=0; handoff_file=""; prior_sha256=""; prepared_file=""; block_file=""; hf_said=""; hf_record="" handoff_file="$("$L" handoff-path "$lane" --create)" || hf_rc=$? # ONLY 8 IS "NO HANDOFF" (Amendment 7(d): a read that failed is no answer). 1 is a register it could not read # (its own "R22: never 8"), 2 a name refused or a clone that failed, 4 the lock that makes the checkout, and a @@ -375,12 +377,13 @@ if [[ "$hf_rc" == 0 && -n "$handoff_file" && -f "$handoff_file" ]]; then fi sha_re='^[0-9a-f]{64}$' [[ "$prior_sha256" =~ $sha_re ]] || prior_sha256="" - # THE COMPLETE NEW FILE goes in this one, whole: the top block below, a `---` line and a blank line, then - # every byte of the old file. The verb refuses an empty file, so a file never written is a refusal and not a - # blank handoff. + # THE NEW TOP BLOCK, AND ONLY IT, goes in `$block_file`. The block under THE WRITE IS ONE CALL builds the + # complete new file in `$prepared_file` from it and the old file whole; an empty `$block_file` is no block, + # and nothing is sent. prepared_file="$(mktemp "${TMPDIR:-/tmp}/handoff-prepared.XXXXXX")" || prepared_file="" - printf 'handoff_file=%s\nprior_sha256=%s\nprepared_file=%s\n' \ - "$handoff_file" "${prior_sha256:-}" "${prepared_file:-}" + block_file="$(mktemp "${TMPDIR:-/tmp}/handoff-block.XXXXXX")" || block_file="" + printf 'handoff_file=%s\nprior_sha256=%s\nblock_file=%s\nprepared_file=%s\n' \ + "$handoff_file" "${prior_sha256:-}" "${block_file:-}" "${prepared_file:-}" else case "$hf_rc:$hf_said" in 0:*|8:*) @@ -418,7 +421,8 @@ EVERY WRITER THE LANE HAS RUNNING (its worktree, its branch, its brief, what it **Addendum 1 (i)** reads that section — *"a list to COUNT, not a list to relaunch"*, in force 2026-09-14T20:59:31Z). Write it exactly this shape, and prepend it — a handoff is the one document in this protocol whose purpose is to be read by a later session, so nothing below is rewritten and everything already there stays as history -under a `---` rule: +under a `---` rule. Write the block, and nothing else, to the `$block_file` the block above printed; the block +under "THE WRITE IS ONE CALL" puts it in front of the old file, so the history is never retyped: ```text Lane: (, session ) — single-use resume prompt: stamp RESUMED-by before acting (lane-collision-protocol rule 3) @@ -498,9 +502,17 @@ handoff target in W or in another checkout of the register beside it, with nothi made. A handoff that moved after its digest was read is exit 7, and nothing is published; once that review's finding 603 is in, a restart fence's refusal is 7 too, where it is 2 today. A 7 is not answered by calling again with the same digest: the block under WHERE THE HANDOFF IS is run -again, for a new digest and a new prepared file, and the call is made once more, once. A lane checkout behind +again, for a new digest and a new prepared file, the top block is written to its new block file, and this +block is run once more, once. A lane checkout behind origin on its handoff meets this on its first call, because the verb brings the checkout to origin before it compares. +THE COMPLETE NEW FILE IS BUILT BY THE BLOCK, NEVER BY HAND (rv3's SHOULD 1 on #311): the new top block from +`$block_file`, a `---` line and a blank line, then `cat -- "$handoff_file"`, which is `write-handoff +--prepend`'s own layout. Before the call it proves that the old file survives at the end of the new one, byte +for byte, as `--prepend` proves it: the last bytes of `$prepared_file`, as many as the old file has, are +compared with the old file. Where they differ, or the build failed, the call is not made and nothing is +written. (`--whole` takes any file that is not empty, so a file written whole from what a session remembered +of the old one, the act that lost two lanes' rows on 2026-09-08, would be published as written.) `$hf_subject` is the commit's one-line subject: the lane, the workstation (`none` where step 1 found none) and the why in the operator's words, `handoff` where there is none. @@ -509,14 +521,22 @@ the why in the operator's words, `handoff` where there is none. # and a why with a stray tab must not cost the swap its handoff. hf_subject="handoff($lane@${ws:-none}): PAUSED, ${why:-handoff}" hf_subject="$(printf '%s' "$hf_subject" | LC_ALL=C tr -d '[:cntrl:]')" -hf_rc=0; hf_out="" -if [[ -n "$handoff_file" && -n "$prior_sha256" && -s "${prepared_file:-}" ]]; then +hf_rc=0; hf_out=""; hf_tried=""; hf_built=""; hf_old_size="" +# THE COMPLETE NEW FILE, AND THE PROOF THAT THE OLD ONE SURVIVES AT ITS END (`--prepend`'s layout and its proof). +if [[ -n "$handoff_file" && -n "$prior_sha256" && -n "${prepared_file:-}" && -s "${block_file:-}" ]]; then + hf_tried=1; size_re='^[0-9]+$' + hf_old_size="$(wc -c < "$handoff_file" | tr -d ' ')" + { cat -- "$block_file" && printf -- '---\n\n' && cat -- "$handoff_file"; } > "$prepared_file" && + [[ "$hf_old_size" =~ $size_re ]] && + tail -c "$hf_old_size" -- "$prepared_file" | cmp -s - "$handoff_file" && hf_built=1 +fi +if [[ -n "$hf_built" ]]; then hf_out="$(LANES_LANE="$lane" "$L" write-handoff "$lane" --whole "$prepared_file" \ --expect-digest "$prior_sha256" --subject "$hf_subject")" || hf_rc=$? # ITS STDOUT IS `` ONCE THE FILE IS WRITTEN, whatever the exit; its own words are on stderr. hf_path="${hf_out%%$'\t'*}" hf_wrote="nothing was written"; [[ -z "$hf_out" ]] || hf_wrote="the file is written at $hf_path" - hf_kept="The new file is kept at $prepared_file: name that path in the report, so that its top block can be put at the head of the handoff by hand once this is answered." + hf_kept="The new file is kept at $prepared_file, and its new top block alone at $block_file: name both paths in the report, so that the top block can be put at the head of the handoff by hand once this is answered." case "$hf_rc" in 0) printf 'handoff written: %s (pushed from the lane checkout; a handoff in another repository is written and never committed, and its note above says so)\n' "$hf_path" ;; 2) if [[ -n "$hf_out" ]]; then @@ -526,7 +546,7 @@ if [[ -n "$handoff_file" && -n "$prior_sha256" && -s "${prepared_file:-}" ]]; th fi ;; 3) printf 'HANDOFF PUSHED, NOT CONFIRMED: the push went, or timed out, and origin could not be read back to show the commit on it (exit 3; the note above names it). Do not call it published until it is read back.\n' ;; 6) printf 'HANDOFF NOT PUSHED: its commit is kept on HEAD of the lane checkout and named above (exit 6), and every later write from that checkout refuses until it is published; the note above names the act. Never force.\n' ;; - 7) printf 'HANDOFF NOT WRITTEN: the handoff is no longer the bytes digest %s names, or a restart owns it (exit 7; its words above say which), and nothing was published. If it moved, run the block under WHERE THE HANDOFF IS again, which reads a new digest and makes a new prepared file, make the new file in it from what is there now, and run this block once more. If a restart owns it, do not retry: that restart writes this handoff.\n' "$prior_sha256" ;; + 7) printf 'HANDOFF NOT WRITTEN: the handoff is no longer the bytes digest %s names, or a restart owns it (exit 7; its words above say which), and nothing was published. If it moved, run the block under WHERE THE HANDOFF IS again, which reads a new digest and makes a new prepared file and a new block file, write the new top block to the new block file, and run this block once more: it builds the new file from what is there now. If a restart owns it, do not retry: that restart writes this handoff.\n' "$prior_sha256" ;; *) printf 'HANDOFF NOT PUBLISHED: write-handoff exited %s (its words are above); %s.\n' "$hf_rc" "$hf_wrote" ;; esac # WHAT THIS CALL DID, CARRIED TO STEP 4'S REGISTER LINE (rv3's SHOULD 3 on #311): refreshed only on a 0, written @@ -536,8 +556,12 @@ if [[ -n "$handoff_file" && -n "$prior_sha256" && -s "${prepared_file:-}" ]]; th [[ "$hf_rc" != 0 ]] || hf_record="handoff refreshed" # WHERE NOTHING WAS WRITTEN, THE NEW FILE IS NOT LOST WITH THE SWAP: its temporary file is named for the report. [[ -n "$hf_out" ]] || printf '%s\n' "$hf_kept" +elif [[ -n "$hf_tried" ]]; then + printf 'HANDOFF NOT WRITTEN: the new file built at %s does not end with the old handoff whole (the last %s bytes of it are not %s, or the build failed), so write-handoff was NOT called and nothing was written. The new top block is kept at %s: name that path in the report, so that it can be put at the head of the handoff by hand. The swap goes on (R-A11-11); the report carries this line.\n' \ + "$prepared_file" "${hf_old_size:-}" "$handoff_file" "$block_file" + hf_record="handoff NOT refreshed (the new file did not keep the old handoff whole)" else - printf 'HANDOFF NOT WRITTEN: there is no handoff located, no digest read or no prepared file written, so write-handoff was NOT called. Write the new top block if it is not written yet, and put it whole in the report, so that it can be put at the head of the handoff by hand. The swap goes on (R-A11-11); the report carries this line.\n' + printf 'HANDOFF NOT WRITTEN: there is no handoff located, no digest read or no new top block written to its file, so write-handoff was NOT called. Write the new top block if it is not written yet, and put it whole in the report, so that it can be put at the head of the handoff by hand. The swap goes on (R-A11-11); the report carries this line.\n' # Block 1's reason where it gave one: a handoff not found, or a read that failed. hf_record="${hf_record:-handoff NOT refreshed (write-handoff was not called)}" fi diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index 4460687..1db0708 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -80,8 +80,11 @@ UUID = "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d" OLD = ("Lane: repoHS-1 (p, session s) — single-use resume prompt\n\n" "## RESUME PROMPT — PAUSED, the old block\n\nthe old state\n") -NEW = ("Lane: repoHS-1 (p, session s) — single-use resume prompt\n\n" - "## RESUME PROMPT — PAUSED, the new block\n\n---\n\n" + OLD) +#: The session's new top block, which it writes to `$block_file`, and the +#: complete new file the skill builds from it: `write-handoff --prepend`'s layout. +TOP = ("Lane: repoHS-1 (p, session s) — single-use resume prompt\n\n" + "## RESUME PROMPT — PAUSED, the new block\n\n") +NEW = TOP + "---\n\n" + OLD OLD_DIGEST = hashlib.sha256(OLD.encode("utf-8")).hexdigest() SHELLS = [ @@ -205,12 +208,19 @@ def verbs(self) -> list[str]: return [c["argv"][0] for c in self.calls()] def step2(self, *, ws: str = "Eagle", why: str = "'a usage reset'", - prepare: bool = True) -> str: + prepare: str = "block") -> str: """Step 2 as a session runs it: the first block, the session's own act - of writing the complete new file, then the one call. `ws` and `why` are - shell words, so a case can hand the block an ANSI-C string.""" - write = (f'[ -z "$prepared_file" ] || printf %s {shlex.quote(NEW)} > "$prepared_file"' - if prepare else ":") + of writing its new top block, and only it, to `$block_file`, then the + block that builds the complete new file and makes the one call. `ws` and + `why` are shell words, so a case can hand the block an ANSI-C string. + `prepare` is "block" (as the skill says), "none" (nothing written), or + "by-hand" (the old habit: the whole new file written into + `$prepared_file`, and `$block_file` left empty).""" + write = { + "block": f'[ -z "$block_file" ] || printf %s {shlex.quote(TOP)} > "$block_file"', + "none": ":", + "by-hand": f'[ -z "$prepared_file" ] || printf %s {shlex.quote(NEW)} > "$prepared_file"', + }[prepare] return "\n".join([f"L={shlex.quote(str(self.helper))}", f"lane={LANE}", f"ws={ws}", f"why={why}", _block(B1), write, _block(B2), 'printf "AFTER\\n"']) @@ -250,7 +260,8 @@ def test_block_calls_write_handoff_with_whole_and_expect_digest(estate, shell): assert argv[:3] == ["write-handoff", LANE, "--whole"], argv assert argv[4:] == ["--expect-digest", OLD_DIGEST, "--subject", f"handoff({LANE}@Eagle): PAUSED, a usage reset"], argv - assert call["prepared"] == NEW, "the file sent is the one the session wrote, whole" + assert call["prepared"] == NEW, ( + "the file sent is the session's top block, `---` and a blank line, then the old file whole") assert Path(argv[3]).parent == estate.tmp, "the prepared file is a temporary one" assert call["lane"] == LANE assert f"handoff written: {estate.handoff}" in proc.stdout, proc.stdout @@ -444,11 +455,21 @@ def test_the_new_file_is_kept_and_named_wherever_nothing_was_written(estate, she proc = estate.run(shell, estate.step2(), STUB_WH_RC=str(rc), STUB_WH_OUT=out) sent = _write_call(estate)["argv"][3] kept = f"kept at {sent}" + top = f"its new top block alone at {_block_file(proc)}" if written: assert kept not in proc.stdout, proc.stdout + assert top not in proc.stdout, proc.stdout else: assert kept in proc.stdout, f"{case}: the new file is not named\n{proc.stdout}" - assert "name that path in the report" in proc.stdout, proc.stdout + assert top in proc.stdout, f"{case}: the top block's file is not named\n{proc.stdout}" + assert "name both paths in the report" in proc.stdout, proc.stdout + + +def _block_file(proc: subprocess.CompletedProcess) -> str: + """The `$block_file` block 1 printed for the session to write its top block in.""" + found = re.findall(r"(?m)^block_file=(.+)$", proc.stdout) + assert len(found) == 1, proc.stdout + return found[0] def test_refusal_text_keeps_R_A11_11(): @@ -571,15 +592,51 @@ def test_only_a_2_is_asked_again_and_never_with_create(estate, shell, rc): @pytest.mark.parametrize("shell", SHELLS) -def test_a_prepared_file_the_session_never_wrote_is_never_sent(estate, shell): - """`mktemp` makes an empty file; the verb refuses an empty `--whole` file, - and the block does not even ask.""" - proc = estate.run(shell, estate.step2(prepare=False)) +@pytest.mark.parametrize("prepare", ["none", "by-hand"]) +def test_a_top_block_the_session_never_wrote_is_never_sent(estate, shell, prepare): + """`mktemp` makes an empty file, and an empty `$block_file` is no new top + block: the block does not build a file or ask. A whole file written by hand + into `$prepared_file` (the old habit, where a session that forgot the + history sent only its block, rv3's SHOULD 1) is not what is sent either.""" + proc = estate.run(shell, estate.step2(prepare=prepare)) assert proc.returncode == 0 - assert "HANDOFF NOT WRITTEN" in proc.stdout and "was NOT called" in proc.stdout, proc.stdout + assert "HANDOFF NOT WRITTEN" in proc.stdout, proc.stdout + assert "was NOT called" in proc.stdout, proc.stdout assert estate.verbs() == ["handoff-path"], estate.calls() +#: The register line's last phrase where the new file did not keep the old one. +LOST = "; handoff NOT refreshed (the new file did not keep the old handoff whole)" + + +@pytest.mark.parametrize("shell", SHELLS) +@pytest.mark.parametrize("how", ["short", "failed"]) +def test_a_new_file_that_lost_the_old_one_is_never_sent(estate, shell, how): + """rv3's SHOULD 1 on #311, option (b): the block builds the complete new + file itself, the top block from `$block_file`, `---` and a blank line, then + `cat -- "$handoff_file"`, and proves the old file survives at its end, byte + for byte (`tail -c | cmp`), before the call. A build that lost the + old file's bytes, here a `cat` that wrote only the first ten of them or + failed, is never sent: the call is not made, the block says why and names + the top block's file for the report, and step 4's line says the handoff was + NOT refreshed.""" + cut = 'head -c 10 -- "$last"; exit 0' if how == "short" else "exit 1" + shim = estate.bin / "cat" + shim.write_text(f'#!/bin/sh\nfor last in "$@"; do :; done\n' + f'[ "$last" = "$STUB_CAT_CUT" ] || exec {shutil.which("cat")} "$@"\n{cut}\n', + encoding="utf-8") + shim.chmod(shim.stat().st_mode | stat.S_IXUSR) + proc = estate.run(shell, estate.step2() + "\n" + estate.step4(), + STUB_CAT_CUT=str(estate.handoff)) + assert proc.returncode == 0, proc.stderr + assert "write-handoff" not in estate.verbs(), estate.calls() + assert "HANDOFF NOT WRITTEN" in proc.stdout, proc.stdout + assert "does not end with the old handoff whole" in proc.stdout, proc.stdout + assert "write-handoff was NOT called" in proc.stdout, proc.stdout + assert f"kept at {_block_file(proc)}" in proc.stdout, proc.stdout + assert _register_line(estate).endswith(LOST), _register_line(estate) + + # --- step 4's register line says what step 2 did (rv3's SHOULD 3) ------------ #: The register line's last phrase where step 2's one call exited 0. @@ -627,7 +684,8 @@ def test_step4_register_line_says_what_step_2_did(estate, shell, extra, written, env = dict(extra) if written: env["STUB_WH_OUT"] = f"{estate.handoff}\tdeadbeef" - proc = estate.run(shell, estate.step2(prepare=prepare) + "\n" + estate.step4(), **env) + proc = estate.run(shell, estate.step2(prepare="block" if prepare else "none") + "\n" + + estate.step4(), **env) assert proc.returncode == 0, proc.stderr line = _register_line(estate) assert line.endswith(ends), line @@ -653,11 +711,13 @@ def test_a_step_4_that_step_2_never_reached_never_claims_a_refresh(estate, shell REAL_WHY = "a usage reset" -def _real_script(shell_between: str = "", *, lane: str = REAL_LANE, new: str = REAL_NEW) -> str: +def _real_script(shell_between: str = "", *, lane: str = REAL_LANE, block: str = BLOCK) -> str: + """Step 2 whole: the session writes its new top block alone, and the skill + builds `REAL_NEW` from it and the old file.""" return "\n".join([ f"L={shlex.quote(str(HELPER))}", f"lane={lane}", "ws=Eagle", f"why={shlex.quote(REAL_WHY)}", _block(B1), shell_between, - f'[ -z "$prepared_file" ] || printf %s {shlex.quote(new)} > "$prepared_file"', + f'[ -z "$block_file" ] || printf %s {shlex.quote(block)} > "$block_file"', _block(B2)]) @@ -689,7 +749,9 @@ def test_the_handoff_is_pushed_from_the_lane_checkout_and_W_is_untouched(real, s by its own pathspec with the `Lane:` trailer and pushed from it, and W, the readers' copy, does not move: not its HEAD, its working tree, its reflog, and not the entry another lane left STAGED there, which the old block committed - and published.""" + and published. The session wrote its new top block alone, as rv3's probe of + SHOULD 1 did, and origin's handoff is that block over the old file whole: + the history survives, because the skill builds the file.""" if how == "seam": c = real.seam() else: @@ -761,10 +823,7 @@ def test_a_lane_checkout_behind_origin_is_7_once_and_written_when_step_2_is_run_ assert made.returncode == 0, made.stderr peer_text = HANDOFF + "a peer line, pushed from elsewhere\n" real.peer_pushes(HANDOFF_REL, peer_text) - careful = (f'[ -z "$prepared_file" ] || {{ printf %s {shlex.quote(BLOCK)} ' - "&& printf -- '---\\n\\n' && cat -- \"$handoff_file\"; } > \"$prepared_file\"") - script = "\n".join([f"L={shlex.quote(str(HELPER))}", f"lane={REAL_LANE}", "ws=Eagle", - f"why={shlex.quote(REAL_WHY)}", _block(B1), careful, _block(B2)]) + script = _real_script() first = _real_run(real, shell, script) assert first.returncode == 0, _said(first) assert "(exit 7;" in first.stdout, _said(first) From e5c8df848b539b23045a717d5d4d79f653f258f2 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Fri, 23 Oct 2026 20:51:39 +0000 Subject: [PATCH 07/17] Slice 3, rv3's NITs 1-4: the subject is `lane-handoff`'s (`@${ws:-unknown}`, control characters as spaces), the 0 arm says "handoff written" and leaves what was committed and pushed to the verb's note (604: S2 governs), the three places that called step 2's write a commit "in the LANE'S OWN repository" say it is `write-handoff`'s and not refused for want of a workstation, and four composite asserts are split (#198) - NIT 1: `hf_subject` uses `${ws:-unknown}` and `LC_ALL=C tr '[:cntrl:]' ' '`, as 2b's `lane-handoff:2662` and its `subject_text` (`37b8806`). - NIT 2: `handoff written: (the note above, from write-handoff, says what was committed and where it was pushed)`; it no longer says that a handoff in another repository is never committed, which 604's ruling (an 8(d) handoff is committed and pushed in its own top level) undoes. - NIT 3: step 1's comment, the `$ws_missing` paragraph and step 4's (c) comment; `write-handoff` is not in the dispatcher's no-workstation list (`lanes-edit.sh:16418`). A text test holds the new wording. - NIT 4: four `assert a and b` split in two. Refs #198 Co-Authored-By: Claude Opus 5.5 (cherry picked from commit a9728996dbdf114dd5161aa8a0ae858cd798bd89) --- skills/handoff/SKILL.md | 30 +++++++++-------- tests/test_handoff_skill_commit_block.py | 41 ++++++++++++++++++------ 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index 210afed..b3c542e 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -166,8 +166,9 @@ ws="${ws_pair%% *}" # printed restart command — so a container with no workstation lost the one # thing a swap exists to leave behind. Clause (k) rule (d)'s cost is *"until the # value is exported, every lane write from a container stops"*, and that is the -# register and the logs; step 2's handoff commit is a commit in the LANE'S OWN -# repository and is not one of them. Clause (e)'s shape for a fact a writer +# register and the logs; step 2's handoff is written by `write-handoff`, in the +# lane's own checkout (Amendment 23 S2), and that verb is not one of the writes +# the helper refuses for want of a workstation. Clause (e)'s shape for a fact a writer # cannot carry is *"the gap named in the handoff"*, and that is what happens # here: `$ws_missing` is carried to step 2, which names it, and to step 4, which # skips (a), (b) and (c) alike — every register and log write. @@ -484,8 +485,8 @@ Write `session unknown` where the top block's first line carries ``, as `l (e)'s shape for a fact a writer cannot carry). Say in the state line, in as many words, that this session ran in a container with no `LANES_WORKSTATION`, that step 4's register and object-log writes were therefore NOT made, and that the row still reads as it did — so the next session knows the register is behind its own -handoff rather than believing the lane never paused. This commit is in the LANE'S OWN repository and is not -one of the writes that stop. +handoff rather than believing the lane never paused. The handoff is written by `write-handoff`, in the lane's +own checkout, and that verb is not one of the writes the helper refuses for want of a workstation. **THE WRITE IS ONE CALL, AND IT COMMITS THE HANDOFF'S OWN PATH AND NOTHING ELSE** (Amendment 4(d): *"with explicit pathspecs — never `git add -A`, never a bare `git commit`"*; Amendment 5(c) and (d); Amendment 23 @@ -513,14 +514,16 @@ for byte, as `--prepend` proves it: the last bytes of `$prepared_file`, as many compared with the old file. Where they differ, or the build failed, the call is not made and nothing is written. (`--whole` takes any file that is not empty, so a file written whole from what a session remembered of the old one, the act that lost two lanes' rows on 2026-09-08, would be published as written.) -`$hf_subject` is the commit's one-line subject: the lane, the workstation (`none` where step 1 found none) and -the why in the operator's words, `handoff` where there is none. +`$hf_subject` is the commit's one-line subject: the lane, the workstation (`unknown` where step 1 found none) +and the why in the operator's words, `handoff` where there is none, each control character made a space. It is +`lane-handoff`'s subject for the same act, spelled the same way. ```sh -# A CONTROL CHARACTER IS REMOVED, NOT REFUSED OVER: the verb exits 64 on one in the subject and writes nothing, -# and a why with a stray tab must not cost the swap its handoff. -hf_subject="handoff($lane@${ws:-none}): PAUSED, ${why:-handoff}" -hf_subject="$(printf '%s' "$hf_subject" | LC_ALL=C tr -d '[:cntrl:]')" +# A CONTROL CHARACTER IS MADE A SPACE, NOT REFUSED OVER: the verb exits 64 on one in the subject and writes +# nothing, and a why with a stray tab must not cost the swap its handoff. `lane-handoff` writes the same act's +# subject the same way (its `subject_text`, and `unknown` for a missing workstation; rv3's NIT 1 on #311). +hf_subject="handoff($lane@${ws:-unknown}): PAUSED, ${why:-handoff}" +hf_subject="$(printf '%s' "$hf_subject" | LC_ALL=C tr '[:cntrl:]' ' ')" hf_rc=0; hf_out=""; hf_tried=""; hf_built=""; hf_old_size="" # THE COMPLETE NEW FILE, AND THE PROOF THAT THE OLD ONE SURVIVES AT ITS END (`--prepend`'s layout and its proof). if [[ -n "$handoff_file" && -n "$prior_sha256" && -n "${prepared_file:-}" && -s "${block_file:-}" ]]; then @@ -538,7 +541,7 @@ if [[ -n "$hf_built" ]]; then hf_wrote="nothing was written"; [[ -z "$hf_out" ]] || hf_wrote="the file is written at $hf_path" hf_kept="The new file is kept at $prepared_file, and its new top block alone at $block_file: name both paths in the report, so that the top block can be put at the head of the handoff by hand once this is answered." case "$hf_rc" in - 0) printf 'handoff written: %s (pushed from the lane checkout; a handoff in another repository is written and never committed, and its note above says so)\n' "$hf_path" ;; + 0) printf 'handoff written: %s (the note above, from write-handoff, says what was committed and where it was pushed)\n' "$hf_path" ;; 2) if [[ -n "$hf_out" ]]; then printf 'HANDOFF NOT COMMITTED: write-handoff refused the commit (exit 2; its words are above) and the file is written at %s, uncommitted. Nothing was pushed. Do not call it again: the file no longer has the bytes the digest names, so a second call is refused (7). Answer the refusal, then commit and publish the file as the note above says.\n' "$hf_path" else @@ -903,8 +906,9 @@ fi row_write_refused="" # AND (c) IS A REGISTER WRITE TOO, so a missing workstation stops it as well and # says so ONCE rather than being refused twice by the helper. Ruling 11's -# *"still refreshes the handoff naming the gap"* is step 2's commit, which is in -# the LANE'S OWN repository and is not a register write; this is. `#71`'s copy +# *"still refreshes the handoff naming the gap"* is step 2's `write-handoff`, a +# write in the lane's own checkout that is no register write and is not one of +# the writes the helper refuses for want of a workstation; (c) is one. `#71`'s copy # promised *"(c) below still runs"*, and against this repository's own # dispatcher guard that promise is a write the helper refuses — so the promise # is corrected here rather than repeated. diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index 1db0708..5ef0ab5 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -284,15 +284,17 @@ def test_the_digest_is_of_the_bytes_that_are_there(estate, shell): @pytest.mark.parametrize("shell", SHELLS) @pytest.mark.parametrize("ws, why, subject", [ ("Eagle", "'a usage reset'", f"handoff({LANE}@Eagle): PAUSED, a usage reset"), - ("''", "''", f"handoff({LANE}@none): PAUSED, handoff"), - ("Eagle", "$'a\\tb\\x1bc'", f"handoff({LANE}@Eagle): PAUSED, abc"), + ("''", "''", f"handoff({LANE}@unknown): PAUSED, handoff"), + ("Eagle", "$'a\\tb\\x1bc'", f"handoff({LANE}@Eagle): PAUSED, a b c"), ("Eagle", "'reset — switch'", f"handoff({LANE}@Eagle): PAUSED, reset — switch"), -], ids=["a-why", "no-workstation-no-why", "control-characters-removed", "a-dash-is-kept"]) +], ids=["a-why", "no-workstation-no-why", "control-characters-as-spaces", "a-dash-is-kept"]) def test_the_subject_is_one_line_with_no_control_character(estate, shell, ws, why, subject): """The verb exits 64 on a control character in the subject and writes nothing, so a why with a stray tab would cost the swap its handoff: it is - removed, not refused over. A why the operator did not give is `handoff`, - and a missing workstation is `none`.""" + made a space, not refused over. A why the operator did not give is + `handoff`, and a missing workstation is `unknown`: 2b's `lane-handoff` + writes the same act's subject so (`subject_text`, `${ws:-unknown}`), and + the two writers of one act spell it one way (rv3's NIT 1 on #311).""" estate.run(shell, estate.step2(ws=ws, why=why)) assert _write_call(estate)["argv"][-1] == subject @@ -392,7 +394,10 @@ def test_every_register_write_and_the_handoff_write_names_its_lane_in_the_text() def _says(estate: Estate) -> dict: path = str(estate.handoff) return { - "pushed": (0, True, [f"handoff written: {path}"], ["HANDOFF NOT"]), + # rv3's NIT 2: the 0 arm claims no push of its own; the verb's note says what it + # committed and where (an Amendment 8(d) handoff in its own top level, S2). + "pushed": (0, True, [f"handoff written: {path} (", "says what was committed and where it was pushed"], + ["HANDOFF NOT", "pushed from the lane checkout", "never committed"]), "refused-after-writing": (2, True, [ "HANDOFF NOT COMMITTED", f"written at {path}, uncommitted", "Nothing was pushed", "Do not call it again", "refused (7)"], ["HANDOFF NOT WRITTEN"]), @@ -488,6 +493,18 @@ def test_refusal_text_keeps_R_A11_11(): assert words in paragraph, f"the paragraph no longer says {words!r}:\n{paragraph}" +def test_the_handoff_write_is_said_as_the_verbs_and_not_as_a_commit_of_the_skill(): + """rv3's NIT 3 on #311: step 1's comment, `$ws_missing`'s paragraph and + step 4's (c) comment called step 2's write a commit "in the LANE'S OWN + repository", the wording from before Amendment 23. The write is + `write-handoff`'s, in the lane's own checkout, and it is not among the verbs + the helper's dispatcher refuses for want of a workstation.""" + flat = " ".join(re.sub(r"(?m)^\s*#\s?", "", _text()).split()) + assert "LANE'S OWN repository" not in flat, "the old wording is back" + assert flat.count("not one of the writes the helper refuses for want of a workstation") == 3, ( + "step 1, the `$ws_missing` paragraph and step 4 say why the handoff still goes") + + # --- where the handoff is not found, and the file the session never wrote ---- #: What the session is told to do with the top block it could not write. @@ -771,7 +788,8 @@ def test_the_handoff_is_pushed_from_the_lane_checkout_and_W_is_untouched(real, s assert real.git("--git-dir", str(real.origin), "log", "-1", "--format=%B", "main").strip().splitlines()[-1] == f"Lane: {REAL_LANE}" assert (c / HANDOFF_REL).read_text(encoding="utf-8") == REAL_NEW - assert real.head(c) == real.origin_head() and real.unpublished(c) == [] + assert real.head(c) == real.origin_head() + assert real.unpublished(c) == [] assert real.git("-C", str(c), "status", "--porcelain") == "" assert real.w_state() == w_before, "W moved" assert real.w_reflog() == reflog, "W's reflog moved: something committed or fast-forwarded it" @@ -798,7 +816,8 @@ def test_a_handoff_that_moved_after_its_digest_was_read_is_refused_and_nothing_i f"git -C {peer} push -q origin main") proc = _real_run(real, shell, _real_script(between)) assert proc.returncode == 0, _said(proc) - assert "HANDOFF NOT WRITTEN" in proc.stdout and "once more" in proc.stdout, _said(proc) + assert "HANDOFF NOT WRITTEN" in proc.stdout, _said(proc) + assert "once more" in proc.stdout, _said(proc) assert "nothing was published" in proc.stderr, _said(proc) assert real.subjects()[0] == "a peer edits the handoff" assert "a peer line" in real.origin_file(HANDOFF_REL) @@ -852,7 +871,8 @@ def test_a_staged_entry_in_the_lane_checkout_leaves_the_handoff_written_and_unco assert "HANDOFF NOT COMMITTED" in proc.stdout, _said(proc) assert f"written at {c / HANDOFF_REL}, uncommitted" in proc.stdout, _said(proc) assert (c / HANDOFF_REL).read_text(encoding="utf-8") == REAL_NEW - assert real.origin_head() == tip and real.head(c) == head + assert real.origin_head() == tip + assert real.head(c) == head assert real.git("-C", str(c), "diff", "--cached", "--name-only").split() == [ "handoffs/foreign.md"] assert "handoffs/foreign.md" not in _tree(real) @@ -863,6 +883,7 @@ def test_a_lane_with_no_row_has_nothing_to_write_and_nothing_is_cloned(real, she tip = real.origin_head() proc = _real_run(real, shell, _real_script(lane="repoS4-9")) assert proc.returncode == 0, _said(proc) - assert "HANDOFF NOT FOUND" in proc.stdout and "was NOT called" in proc.stdout, _said(proc) + assert "HANDOFF NOT FOUND" in proc.stdout, _said(proc) + assert "was NOT called" in proc.stdout, _said(proc) assert not real.checkout("repoS4-9").exists() assert real.origin_head() == tip From 9da8a26bf8b5f44d2892f66a74c011b4b880578a Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Fri, 23 Oct 2026 21:36:59 +0000 Subject: [PATCH 08/17] Slice 3, rv3's mutants A, E and I: three stub tests hold what no test held, so a digest of the wrong shape sends nothing, `sha256sum` is asked before `shasum` as the helper's own `lane_handoff_digest` asks, and the digest sent is block 1's even where the file moved between the blocks (#198) rv3's ten planned mutants of skills/handoff/SKILL.md (A-I and P), with one for SHOULD 3 and one for SHOULD 1, were run at a972899 in a copy of the tree. Nine were killed by the module as it stood. Three survived: - A, the digest's shape check dropped: no test gave block 1 a digest of the wrong shape. `test_a_digest_that_is_not_a_sha256_is_no_digest_and_nothing_is_sent` puts a `sha256sum` first on PATH that answers with an error word, upper case or 63 digits: block 1 prints no digest, `write-handoff` is not called, and step 4's line says the handoff was NOT refreshed. - E, the `sha256sum` branch dropped: `shasum` gives the same digest on a host that has both. `test_sha256sum_gives_the_digest_where_shasum_is_there_too` puts a `shasum` first on PATH that answers 64 f's: the digest sent is still the file's, and the shim is never asked. - I, the digest read again in block 2: no test moved the file between the blocks. `test_the_digest_sent_is_block_1s_even_where_the_file_moved_before_the_call` rewrites it there: the new file is built from what is there now, and the call still carries block 1's digest, so the verb refuses (7) rather than take a top block written from bytes that are gone. `Estate.step2` takes `between`, shell run after the session's write and before the second block, and `Estate.tool` puts a shim first on the blocks' PATH. No other test changes. Refs #198 Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 1913db9474f8687febd7f638121e052d0be5e137) --- tests/test_handoff_skill_commit_block.py | 72 +++++++++++++++++++++++- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index 5ef0ab5..90cc920 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -208,23 +208,30 @@ def verbs(self) -> list[str]: return [c["argv"][0] for c in self.calls()] def step2(self, *, ws: str = "Eagle", why: str = "'a usage reset'", - prepare: str = "block") -> str: + prepare: str = "block", between: str = ":") -> str: """Step 2 as a session runs it: the first block, the session's own act of writing its new top block, and only it, to `$block_file`, then the block that builds the complete new file and makes the one call. `ws` and `why` are shell words, so a case can hand the block an ANSI-C string. `prepare` is "block" (as the skill says), "none" (nothing written), or "by-hand" (the old habit: the whole new file written into - `$prepared_file`, and `$block_file` left empty).""" + `$prepared_file`, and `$block_file` left empty). `between` is shell run + after that write and before the second block.""" write = { "block": f'[ -z "$block_file" ] || printf %s {shlex.quote(TOP)} > "$block_file"', "none": ":", "by-hand": f'[ -z "$prepared_file" ] || printf %s {shlex.quote(NEW)} > "$prepared_file"', }[prepare] return "\n".join([f"L={shlex.quote(str(self.helper))}", f"lane={LANE}", f"ws={ws}", - f"why={why}", _block(B1), write, _block(B2), + f"why={why}", _block(B1), write, between, _block(B2), 'printf "AFTER\\n"']) + def tool(self, name: str, script: str) -> None: + """A tool of that name first on the blocks' PATH, ahead of the host's.""" + path = self.bin / name + path.write_text(script, encoding="utf-8") + path.chmod(path.stat().st_mode | stat.S_IXUSR) + def step4(self, *, ws_missing: str = "", uuid_missing: str = "") -> str: return "\n".join([f"L={shlex.quote(str(self.helper))}", f"lane={LANE}", "ws=Eagle", 'why="a usage reset"', f'uuid="{UUID}"', f'ws_missing="{ws_missing}"', @@ -281,6 +288,65 @@ def test_the_digest_is_of_the_bytes_that_are_there(estate, shell): assert argv[argv.index("--expect-digest") + 1] == hashlib.sha256(other.encode()).hexdigest() +def _digest_sent(estate: Estate) -> str: + argv = _write_call(estate)["argv"] + return argv[argv.index("--expect-digest") + 1] + + +@pytest.mark.parametrize("shell", SHELLS) +@pytest.mark.parametrize("answer", [ + "sha256sum:", OLD_DIGEST.upper(), OLD_DIGEST[:63], +], ids=["a-word", "upper-case", "63-digits"]) +def test_a_digest_that_is_not_a_sha256_is_no_digest_and_nothing_is_sent(estate, shell, answer): + """rv3's mutant A on #311: the digest is held to the shape the verb's + `--expect-digest` takes, 64 lower-case hex digits, as it is read. A tool + that answers with anything else, an error word, upper case or a digest cut + short, gives no digest: block 1 prints none, the call is not made, and + step 4's line says the handoff was NOT refreshed.""" + estate.tool("sha256sum", f'#!/bin/sh\nprintf "%s %s\\n" {shlex.quote(answer)} "$2"\n') + proc = estate.run(shell, estate.step2() + "\n" + estate.step4()) + assert proc.returncode == 0, proc.stderr + assert "prior_sha256=" in proc.stdout, proc.stdout + assert "write-handoff" not in estate.verbs(), estate.calls() + assert "HANDOFF NOT WRITTEN" in proc.stdout, proc.stdout + assert "no digest read" in proc.stdout, proc.stdout + assert _register_line(estate).endswith( + "; handoff NOT refreshed (write-handoff was not called)"), _register_line(estate) + + +@pytest.mark.skipif(shutil.which("sha256sum") is None, + reason="the order of the two tools counts only where both are there") +@pytest.mark.parametrize("shell", SHELLS) +def test_sha256sum_gives_the_digest_where_shasum_is_there_too(estate, shell): + """rv3's mutant E on #311: the digest is read by the two tools the + helper's own `lane_handoff_digest` tries, in its order, `sha256sum` and + then `shasum -a 256`. Where both are there, `sha256sum`'s is the one: a + `shasum` that answers otherwise is never asked.""" + estate.tool("shasum", '#!/bin/sh\necho "w3 shim shasum: $*" >&2\n' + f'printf "%s %s\\n" {"f" * 64} "$4"\n') + proc = estate.run(shell, estate.step2()) + assert proc.returncode == 0, proc.stderr + assert _digest_sent(estate) == OLD_DIGEST, proc.stderr + assert "w3 shim shasum" not in proc.stderr, proc.stderr + + +@pytest.mark.parametrize("shell", SHELLS) +def test_the_digest_sent_is_block_1s_even_where_the_file_moved_before_the_call(estate, shell): + """rv3's mutant I on #311: the digest is of the bytes the session read in + block 1, which its new top block was written from, and is never read + again at the call. Here the file moves between the blocks: the new file is + built from what is there now, and the call still carries block 1's digest, + so the verb finds other bytes and refuses (7) rather than taking a top + block written from a file that is no longer there.""" + moved = OLD + "a peer line, written between the blocks\n" + between = f"printf %s {shlex.quote(moved)} > {shlex.quote(str(estate.handoff))}" + proc = estate.run(shell, estate.step2(between=between)) + assert proc.returncode == 0, proc.stderr + assert _digest_sent(estate) == OLD_DIGEST, proc.stdout + assert _write_call(estate)["prepared"] == TOP + "---\n\n" + moved, ( + "the new file is built from the file as it is at the call") + + @pytest.mark.parametrize("shell", SHELLS) @pytest.mark.parametrize("ws, why, subject", [ ("Eagle", "'a usage reset'", f"handoff({LANE}@Eagle): PAUSED, a usage reset"), From 1052cd5eb1f18261f4ad1c648e5211249c278e03 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Sat, 24 Oct 2026 14:40:50 +0000 Subject: [PATCH 09/17] Slice 3, 981 (MUST) of lane openRepoTools-3's review of #311, by option (a) on lane 3's word: the skill writes its top block with `write-handoff --prepend "$block_file"`, so the verb builds and proves the new file under its lock and refuses a block with no final newline (64), where the build glued `---` onto its last line; new arms for that 64 and for a restart's 7 (#198) Block 1 reads no digest and makes no prepared file; block 2 builds nothing. A peer's edit between the blocks, and a lane checkout behind origin, are now written at the first call with the peer's edit kept under the new block. Real-helper cases: the 64 and its fix, and an active restart's 7. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 18c5b3c7c959aca29ed04a96804221ce66daeae4) --- skills/handoff/SKILL.md | 121 ++++--- tests/test_handoff_skill_commit_block.py | 384 +++++++++++------------ 2 files changed, 231 insertions(+), 274 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index b3c542e..0b4add4 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -269,16 +269,16 @@ Only the second branch leaves anything blank, and it leaves it blank because not ## 2. Refresh the handoff -The handoff is written by ONE call, `lanes-edit.sh write-handoff --whole` (Amendment 23, S2–S4), and this +The handoff is written by ONE call, `lanes-edit.sh write-handoff --prepend` (Amendment 23, S2–S4), and this skill holds no lock and runs no `git` of its own. Under the lane checkout's lock the verb brings that -checkout to origin, checks that the file still has the bytes this session read, replaces it atomically +checkout to origin, reads the handoff there, puts the new top block, a `---` line and a blank line in front +of it, proves byte for byte that the block and the old file both survive, replaces the file atomically under the workstation's sidecar lock, commits it by its own pathspec with the Rule 5 `Lane:` trailer, pushes with no pull, reads origin back, and replays the one write on a refused push. It refuses an active -restart owned by another operation. So the order below is: say where the handoff is and read its SHA-256, -write the new top block, and only it, to a temporary file, then run the block under "THE WRITE IS ONE CALL", -which builds the complete new file from that block and the old file whole, proves that the old file survives -at its end, and makes the one call. Do not edit or redirect into the canonical handoff directly, do not write -the complete new file by hand, and do not commit it: the verb is the commit. +restart owned by another operation. So the order below is: say where the handoff is, write the new top +block, and only it, to a temporary file, then run the block under "THE WRITE IS ONE CALL", which makes the +one call. Do not edit or redirect into the canonical handoff directly, do not write the complete new file +by hand, and do not commit it: the verb is the commit. Legacy `rename-lane` is a separate four-file transaction under the workspace's writer mutex; it refuses unfinished/failed restart ownership and uses EXIT rollback. Its stamp now atomically replaces the handoff through a temporary file beside it @@ -340,10 +340,11 @@ check above is all this skill asks of it. The file `write-handoff` writes is `ha translated into the lane's OWN checkout (Amendment 23 S2), derived here and never spelled. No checkout path is written anywhere in this step, and none is taken from the environment or from the helper's workspace root as a fallback. A read that failed is no answer (Amendment 7(d)), so nothing is guessed in its place. `--create` -makes that checkout first where this is its first use, so that there is a file to read. The SHA-256 of the -file as it stands is the digest the verb holds this write to: it replaces the file only while the file still -has those bytes (exit 7 where it does not), so the new file is made from what was read here and never from a -copy that has moved. Where the block below prints `HANDOFF NOT FOUND` (`handoff-path`'s 8: no row, or a row +makes that checkout first where this is its first use, so that there is a file to read: the session reads +it there for what its new top block carries forward. No digest is taken and no new file is built here: the +verb reads the handoff again under the lock, from what S3 has just brought from origin, and puts the block +in front of it as it is then, so a peer's block that landed meanwhile is kept under the new one, newest on +top. Where the block below prints `HANDOFF NOT FOUND` (`handoff-path`'s 8: no row, or a row that names no handoff) there is no file to write into. Where it prints `HANDOFF READ FAILED`, the read failed, and that is never "no handoff": every exit but 0 and 8 is one, and the line quotes it. A 2 whose words say "unknown subcommand" is a `lanes-edit.sh` predating `handoff-path`, to be updated; a 4 is the lock that @@ -359,7 +360,7 @@ unwritten*). ```sh # WHERE THE HANDOFF IS: derived, never spelled. `handoff-path` only reads, and `--create` makes the lane's own # checkout first where this is its first use. -hf_rc=0; handoff_file=""; prior_sha256=""; prepared_file=""; block_file=""; hf_said=""; hf_record="" +hf_rc=0; handoff_file=""; block_file=""; hf_said=""; hf_record="" handoff_file="$("$L" handoff-path "$lane" --create)" || hf_rc=$? # ONLY 8 IS "NO HANDOFF" (Amendment 7(d): a read that failed is no answer). 1 is a register it could not read # (its own "R22: never 8"), 2 a name refused or a clone that failed, 4 the lock that makes the checkout, and a @@ -369,22 +370,11 @@ handoff_file="$("$L" handoff-path "$lane" --create)" || hf_rc=$? [[ "$hf_rc" != 2 ]] || hf_said="$(LANES_NO_FETCH=1 "$L" handoff-path "$lane" 2>&1 >/dev/null || :)" hf_keep="Write the new top block all the same and put it whole in the report: it is then the one copy, and it goes at the head of the handoff by hand once this is answered. The swap goes on (R-A11-11)." if [[ "$hf_rc" == 0 && -n "$handoff_file" && -f "$handoff_file" ]]; then - # THE DIGEST OF WHAT IS THERE NOW, by the two tools the helper's own `lane_handoff_digest` tries: - # `sha256sum` on Linux, `shasum -a 256` on macOS. - if command -v sha256sum >/dev/null 2>&1; then - prior_sha256="$(sha256sum -- "$handoff_file" | awk '{print $1; exit}')" - elif command -v shasum >/dev/null 2>&1; then - prior_sha256="$(shasum -a 256 -- "$handoff_file" | awk '{print $1; exit}')" - fi - sha_re='^[0-9a-f]{64}$' - [[ "$prior_sha256" =~ $sha_re ]] || prior_sha256="" - # THE NEW TOP BLOCK, AND ONLY IT, goes in `$block_file`. The block under THE WRITE IS ONE CALL builds the - # complete new file in `$prepared_file` from it and the old file whole; an empty `$block_file` is no block, - # and nothing is sent. - prepared_file="$(mktemp "${TMPDIR:-/tmp}/handoff-prepared.XXXXXX")" || prepared_file="" + # THE NEW TOP BLOCK, AND ONLY IT, goes in `$block_file`, ending with a newline. `write-handoff --prepend` + # puts it in front of the handoff as the verb reads it under the lock (lane openRepoTools-3's finding 981 + # on #311, option (a)); an empty `$block_file` is no block, and nothing is sent. block_file="$(mktemp "${TMPDIR:-/tmp}/handoff-block.XXXXXX")" || block_file="" - printf 'handoff_file=%s\nprior_sha256=%s\nblock_file=%s\nprepared_file=%s\n' \ - "$handoff_file" "${prior_sha256:-}" "${block_file:-}" "${prepared_file:-}" + printf 'handoff_file=%s\nblock_file=%s\n' "$handoff_file" "${block_file:-}" else case "$hf_rc:$hf_said" in 0:*|8:*) @@ -422,8 +412,9 @@ EVERY WRITER THE LANE HAS RUNNING (its worktree, its branch, its brief, what it **Addendum 1 (i)** reads that section — *"a list to COUNT, not a list to relaunch"*, in force 2026-09-14T20:59:31Z). Write it exactly this shape, and prepend it — a handoff is the one document in this protocol whose purpose is to be read by a later session, so nothing below is rewritten and everything already there stays as history -under a `---` rule. Write the block, and nothing else, to the `$block_file` the block above printed; the block -under "THE WRITE IS ONE CALL" puts it in front of the old file, so the history is never retyped: +under a `---` rule. Write the block, and nothing else, to the `$block_file` the block above printed, and end +its last line with a newline; `write-handoff --prepend` puts it in front of the old file, so the history is +never retyped: ```text Lane: (, session ) — single-use resume prompt: stamp RESUMED-by before acting (lane-collision-protocol rule 3) @@ -500,20 +491,20 @@ THIS HANDOFF DID NOT MAKE and an operation in progress are the verb's refusals n committed (2); once lane openRepoTools-3's finding 601 on opensoft/openRepoTools#312 is in, so is a handoff target in W or in another checkout of the register beside it, with nothing written, and since `handoff-path` shares that refusal, the block under WHERE THE HANDOFF IS meets it first and this call is not -made. A handoff that moved after its -digest was read is exit 7, and nothing is published; once that review's finding 603 is in, a restart fence's -refusal is 7 too, where it is 2 today. A 7 is not answered by calling again with the same digest: the block under WHERE THE HANDOFF IS is run -again, for a new digest and a new prepared file, the top block is written to its new block file, and this -block is run once more, once. A lane checkout behind -origin on its handoff meets this on its first call, because the verb brings the checkout to origin before it -compares. -THE COMPLETE NEW FILE IS BUILT BY THE BLOCK, NEVER BY HAND (rv3's SHOULD 1 on #311): the new top block from -`$block_file`, a `---` line and a blank line, then `cat -- "$handoff_file"`, which is `write-handoff ---prepend`'s own layout. Before the call it proves that the old file survives at the end of the new one, byte -for byte, as `--prepend` proves it: the last bytes of `$prepared_file`, as many as the old file has, are -compared with the old file. Where they differ, or the build failed, the call is not made and nothing is -written. (`--whole` takes any file that is not empty, so a file written whole from what a session remembered -of the old one, the act that lost two lanes' rows on 2026-09-08, would be published as written.) +made. +THE NEW FILE IS MADE BY THE VERB, NEVER BY HAND (rv3's SHOULD 1 and lane openRepoTools-3's finding 981 on +#311, option (a)): `--prepend "$block_file"` reads the handoff under the lane checkout's lock, from the bytes +S3 has just brought to origin's, puts the new top block, a `---` line and a blank line in front of it, and +proves byte for byte that the block, the rule and the old file whole are all there before it replaces the +file. A file written whole from what a session remembered of the old one, the act that lost two lanes' rows on +2026-09-08, is never sent, and neither is a digest: a peer's block pushed after this session read the handoff +is not a refusal, it is kept under the new block, newest on top, which is the shape every handoff has. It is +`lane-handoff`'s own write of the same act (Amendment 17(a)). The verb refuses a block whose last byte is not +a newline (64) and writes nothing, because the `---` under it would join its last line and `lane-start` reads +the top block only to the first line that opens with `---`, so the next session's first prompt would run on +into the block before it: end the block file with a newline and run this block once more, once. A 7 with +`--prepend` is a restart's: an active restart owns the handoff, and that restart writes it, so the call is +not made again. `$hf_subject` is the commit's one-line subject: the lane, the workstation (`unknown` where step 1 found none) and the why in the operator's words, `handoff` where there is none, each control character made a space. It is `lane-handoff`'s subject for the same act, spelled the same way. @@ -524,32 +515,31 @@ and the why in the operator's words, `handoff` where there is none, each control # subject the same way (its `subject_text`, and `unknown` for a missing workstation; rv3's NIT 1 on #311). hf_subject="handoff($lane@${ws:-unknown}): PAUSED, ${why:-handoff}" hf_subject="$(printf '%s' "$hf_subject" | LC_ALL=C tr '[:cntrl:]' ' ')" -hf_rc=0; hf_out=""; hf_tried=""; hf_built=""; hf_old_size="" -# THE COMPLETE NEW FILE, AND THE PROOF THAT THE OLD ONE SURVIVES AT ITS END (`--prepend`'s layout and its proof). -if [[ -n "$handoff_file" && -n "$prior_sha256" && -n "${prepared_file:-}" && -s "${block_file:-}" ]]; then - hf_tried=1; size_re='^[0-9]+$' - hf_old_size="$(wc -c < "$handoff_file" | tr -d ' ')" - { cat -- "$block_file" && printf -- '---\n\n' && cat -- "$handoff_file"; } > "$prepared_file" && - [[ "$hf_old_size" =~ $size_re ]] && - tail -c "$hf_old_size" -- "$prepared_file" | cmp -s - "$handoff_file" && hf_built=1 -fi -if [[ -n "$hf_built" ]]; then - hf_out="$(LANES_LANE="$lane" "$L" write-handoff "$lane" --whole "$prepared_file" \ - --expect-digest "$prior_sha256" --subject "$hf_subject")" || hf_rc=$? +hf_rc=0; hf_out="" +# THE ONE CALL: the verb reads the handoff under the lock and puts the block in front of it (`--prepend`). +if [[ -n "$handoff_file" && -s "${block_file:-}" ]]; then + hf_out="$(LANES_LANE="$lane" "$L" write-handoff "$lane" --prepend "$block_file" \ + --subject "$hf_subject")" || hf_rc=$? # ITS STDOUT IS `` ONCE THE FILE IS WRITTEN, whatever the exit; its own words are on stderr. hf_path="${hf_out%%$'\t'*}" hf_wrote="nothing was written"; [[ -z "$hf_out" ]] || hf_wrote="the file is written at $hf_path" - hf_kept="The new file is kept at $prepared_file, and its new top block alone at $block_file: name both paths in the report, so that the top block can be put at the head of the handoff by hand once this is answered." + hf_kept="The new top block is kept at $block_file: name that path in the report, so that the top block can be put at the head of the handoff by hand once this is answered." case "$hf_rc" in 0) printf 'handoff written: %s (the note above, from write-handoff, says what was committed and where it was pushed)\n' "$hf_path" ;; 2) if [[ -n "$hf_out" ]]; then - printf 'HANDOFF NOT COMMITTED: write-handoff refused the commit (exit 2; its words are above) and the file is written at %s, uncommitted. Nothing was pushed. Do not call it again: the file no longer has the bytes the digest names, so a second call is refused (7). Answer the refusal, then commit and publish the file as the note above says.\n' "$hf_path" + printf 'HANDOFF NOT COMMITTED: write-handoff refused the commit (exit 2; its words are above) and the file is written at %s, uncommitted. Nothing was pushed. Do not call it again: the new top block is at the head of the file now, and a second call would put a second copy of it on top. Answer the refusal, then commit and publish the file as the note above says.\n' "$hf_path" else printf 'HANDOFF NOT WRITTEN: write-handoff refused (exit 2; its words are above) and wrote nothing.\n' fi ;; 3) printf 'HANDOFF PUSHED, NOT CONFIRMED: the push went, or timed out, and origin could not be read back to show the commit on it (exit 3; the note above names it). Do not call it published until it is read back.\n' ;; 6) printf 'HANDOFF NOT PUSHED: its commit is kept on HEAD of the lane checkout and named above (exit 6), and every later write from that checkout refuses until it is published; the note above names the act. Never force.\n' ;; - 7) printf 'HANDOFF NOT WRITTEN: the handoff is no longer the bytes digest %s names, or a restart owns it (exit 7; its words above say which), and nothing was published. If it moved, run the block under WHERE THE HANDOFF IS again, which reads a new digest and makes a new prepared file and a new block file, write the new top block to the new block file, and run this block once more: it builds the new file from what is there now. If a restart owns it, do not retry: that restart writes this handoff.\n' "$prior_sha256" ;; + # A 7 WITH `--prepend` IS A RESTART'S (lane openRepoTools-3's finding 981 on #311): no digest is sent, so + # it is never "moved since it was read". The verb's own "changed before publication" is another act's + # write in the instant between its read and its replace, under the lock, and the same call reads it again. + 7) printf 'HANDOFF NOT WRITTEN: an active restart owns this handoff (exit 7; its words are above), and nothing was published. Do not retry: that restart writes this handoff. (Where its words say instead that the handoff changed before publication, another act wrote it in the instant of this write: run this block once more, once.)\n' ;; + # 64 IS A CALL THE VERB REFUSED AS GIVEN, and the one this block can make is a block whose last byte is no + # newline (981): the `---` under it would join its last line. The verb never alters a caller's bytes. + 64) printf 'HANDOFF NOT WRITTEN: write-handoff refused the call (exit 64; its words are above) and wrote nothing. Where they say the block does not end with a newline, end it with one (echo >> %s) and run this block once more, once.\n' "$block_file" ;; *) printf 'HANDOFF NOT PUBLISHED: write-handoff exited %s (its words are above); %s.\n' "$hf_rc" "$hf_wrote" ;; esac # WHAT THIS CALL DID, CARRIED TO STEP 4'S REGISTER LINE (rv3's SHOULD 3 on #311): refreshed only on a 0, written @@ -557,14 +547,10 @@ if [[ -n "$hf_built" ]]; then hf_record="handoff NOT refreshed (write-handoff exited $hf_rc and wrote nothing)" [[ -z "$hf_out" ]] || hf_record="handoff written, its publication NOT confirmed (write-handoff exited $hf_rc)" [[ "$hf_rc" != 0 ]] || hf_record="handoff refreshed" - # WHERE NOTHING WAS WRITTEN, THE NEW FILE IS NOT LOST WITH THE SWAP: its temporary file is named for the report. + # WHERE NOTHING WAS WRITTEN, THE NEW TOP BLOCK IS NOT LOST WITH THE SWAP: its temporary file is named for the report. [[ -n "$hf_out" ]] || printf '%s\n' "$hf_kept" -elif [[ -n "$hf_tried" ]]; then - printf 'HANDOFF NOT WRITTEN: the new file built at %s does not end with the old handoff whole (the last %s bytes of it are not %s, or the build failed), so write-handoff was NOT called and nothing was written. The new top block is kept at %s: name that path in the report, so that it can be put at the head of the handoff by hand. The swap goes on (R-A11-11); the report carries this line.\n' \ - "$prepared_file" "${hf_old_size:-}" "$handoff_file" "$block_file" - hf_record="handoff NOT refreshed (the new file did not keep the old handoff whole)" else - printf 'HANDOFF NOT WRITTEN: there is no handoff located, no digest read or no new top block written to its file, so write-handoff was NOT called. Write the new top block if it is not written yet, and put it whole in the report, so that it can be put at the head of the handoff by hand. The swap goes on (R-A11-11); the report carries this line.\n' + printf 'HANDOFF NOT WRITTEN: there is no handoff located, or no new top block written to its file, so write-handoff was NOT called. Write the new top block if it is not written yet, and put it whole in the report, so that it can be put at the head of the handoff by hand. The swap goes on (R-A11-11); the report carries this line.\n' # Block 1's reason where it gave one: a handoff not found, or a read that failed. hf_record="${hf_record:-handoff NOT refreshed (write-handoff was not called)}" fi @@ -572,9 +558,10 @@ fi A refusal, a conflict or a rejected push does not stop the swap (`R-A11-11`, *a swap is never left unwritten*): where the lane's checkout refuses the commit (2), the verb has still written the handoff, commits -nothing and prints git's own words; where its push is refused it replays the write on origin's new tip if -origin's copy of the handoff is still the one this write was made from, and otherwise keeps its commit on HEAD -and names it (6). The block prints what it did NOT do, and the report carries that line. Never force, never +nothing and prints git's own words; where its push is refused it replays the write on origin's new tip, the +block put in front of origin's copy of the handoff, and where origin no longer has that handoff, or its row +names a file the replay cannot write, it keeps its commit on HEAD and names it (6). The block prints what it +did NOT do, and the report carries that line. Never force, never `git add -A`, and never commit staged entries you did not make. **WHAT THIS STEP DID IS CARRIED TO STEP 4 in `$hf_record`,** so that the register's PAUSED line says it and diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index 90cc920..0c98e44 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -19,14 +19,18 @@ copy (Amendment 23 S5). Slice 2a gave the one act that replaces all of it: handoff-path --create where the file is - write-handoff --whole --expect-digest --subject - -which takes the lane checkout's lock, brings the checkout to origin, checks the -digest, replaces the file atomically, commits it by its own pathspec with the -`Lane:` trailer, pushes, reads origin back and replays a refused push. So this -file holds the skill's two blocks to what is left of them: derive the path and -the digest, send the call with exactly those arguments, say what each exit -means and never stop the swap (`R-A11-11`), and spell no checkout of its own. + write-handoff --prepend --subject + +which takes the lane checkout's lock, brings the checkout to origin, reads the +handoff there, puts the new top block, `---` and a blank line in front of it, +proves the old file survives, replaces the file atomically, commits it by its +own pathspec with the `Lane:` trailer, pushes, reads origin back and replays a +refused push (lane openRepoTools-3's finding 981 on #311, option (a): the +`--whole` call this file held before took a file the session had glued +together, and a block with no final newline merged into the old one). So this +file holds the skill's two blocks to what is left of them: derive the path, +send the call with exactly those arguments, say what each exit means and never +stop the swap (`R-A11-11`), and spell no checkout of its own. THE BLOCKS ARE RUN, NOT READ, under `bash` and under `zsh` where one is installed, because the Bash tool runs a skill's block under the user's own @@ -48,7 +52,6 @@ from __future__ import annotations -import hashlib import json import os import re @@ -73,7 +76,7 @@ #: Each carried by exactly one shell block of the skill: how the blocks are found. B1 = 'handoff_file="$("$L" handoff-path "$lane" --create)"' -B2 = '--whole "$prepared_file"' +B2 = '--prepend "$block_file"' STEP4 = "# (c) the row: ONE WRITE" LANE = "repoHS-1" @@ -81,11 +84,9 @@ OLD = ("Lane: repoHS-1 (p, session s) — single-use resume prompt\n\n" "## RESUME PROMPT — PAUSED, the old block\n\nthe old state\n") #: The session's new top block, which it writes to `$block_file`, and the -#: complete new file the skill builds from it: `write-handoff --prepend`'s layout. +#: complete new file `write-handoff --prepend` makes of it under its lock. TOP = ("Lane: repoHS-1 (p, session s) — single-use resume prompt\n\n" "## RESUME PROMPT — PAUSED, the new block\n\n") -NEW = TOP + "---\n\n" + OLD -OLD_DIGEST = hashlib.sha256(OLD.encode("utf-8")).hexdigest() SHELLS = [ pytest.param("bash", id="bash"), @@ -142,12 +143,12 @@ def _code_lines(blocks: list[str]) -> list[str]: argv = sys.argv[1:] rec = {{"argv": argv, "lane": os.environ.get("LANES_LANE"), "no_fetch": os.environ.get("LANES_NO_FETCH")}} -if argv[:1] == ["write-handoff"] and "--whole" in argv: +if argv[:1] == ["write-handoff"] and "--prepend" in argv: try: - with open(argv[argv.index("--whole") + 1], "rb") as f: - rec["prepared"] = f.read().decode("utf-8") + with open(argv[argv.index("--prepend") + 1], "rb") as f: + rec["block"] = f.read().decode("utf-8") except (OSError, IndexError): - rec["prepared"] = None + rec["block"] = None with open(os.environ["STUB_LOG"], "a", encoding="utf-8") as log: log.write(json.dumps(rec) + "\\n") verb = argv[0] if argv else "" @@ -208,22 +209,18 @@ def verbs(self) -> list[str]: return [c["argv"][0] for c in self.calls()] def step2(self, *, ws: str = "Eagle", why: str = "'a usage reset'", - prepare: str = "block", between: str = ":") -> str: + prepare: str = "block") -> str: """Step 2 as a session runs it: the first block, the session's own act of writing its new top block, and only it, to `$block_file`, then the - block that builds the complete new file and makes the one call. `ws` and - `why` are shell words, so a case can hand the block an ANSI-C string. - `prepare` is "block" (as the skill says), "none" (nothing written), or - "by-hand" (the old habit: the whole new file written into - `$prepared_file`, and `$block_file` left empty). `between` is shell run - after that write and before the second block.""" + block that makes the one call. `ws` and `why` are shell words, so a + case can hand the block an ANSI-C string. `prepare` is "block" (as the + skill says) or "none" (nothing written).""" write = { "block": f'[ -z "$block_file" ] || printf %s {shlex.quote(TOP)} > "$block_file"', "none": ":", - "by-hand": f'[ -z "$prepared_file" ] || printf %s {shlex.quote(NEW)} > "$prepared_file"', }[prepare] return "\n".join([f"L={shlex.quote(str(self.helper))}", f"lane={LANE}", f"ws={ws}", - f"why={why}", _block(B1), write, between, _block(B2), + f"why={why}", _block(B1), write, _block(B2), 'printf "AFTER\\n"']) def tool(self, name: str, script: str) -> None: @@ -253,23 +250,22 @@ def _write_call(estate: Estate) -> dict: # --- the call: the arguments, and nothing else ------------------------------- @pytest.mark.parametrize("shell", SHELLS) -def test_block_calls_write_handoff_with_whole_and_expect_digest(estate, shell): - """The handoff's place is asked of `handoff-path --create`, its digest is - read from the bytes there, and `write-handoff` is called ONCE with the - prepared file the session wrote, that digest, and the subject: the lane, the - workstation and the why. `LANES_LANE` is the lane.""" +def test_block_calls_write_handoff_with_prepend_and_the_block_file(estate, shell): + """The handoff's place is asked of `handoff-path --create`, and + `write-handoff` is called ONCE with `--prepend` and the block file block 1 + printed, which holds the session's new top block and only it, and the + subject: the lane, the workstation and the why. No digest is sent: the verb + reads the handoff itself, under its lock (lane openRepoTools-3's finding 981 + on #311, option (a)). `LANES_LANE` is the lane.""" proc = estate.run(shell, estate.step2(), STUB_WH_OUT=f"{estate.handoff}\tdeadbeef") assert proc.returncode == 0, proc.stderr assert estate.verbs() == ["handoff-path", "write-handoff"], estate.calls() assert estate.calls()[0]["argv"] == ["handoff-path", LANE, "--create"] call = _write_call(estate) - argv = call["argv"] - assert argv[:3] == ["write-handoff", LANE, "--whole"], argv - assert argv[4:] == ["--expect-digest", OLD_DIGEST, "--subject", - f"handoff({LANE}@Eagle): PAUSED, a usage reset"], argv - assert call["prepared"] == NEW, ( - "the file sent is the session's top block, `---` and a blank line, then the old file whole") - assert Path(argv[3]).parent == estate.tmp, "the prepared file is a temporary one" + assert call["argv"] == ["write-handoff", LANE, "--prepend", _block_file(proc), "--subject", + f"handoff({LANE}@Eagle): PAUSED, a usage reset"], call["argv"] + assert call["block"] == TOP, "the file sent is the session's new top block, and only it" + assert Path(_block_file(proc)).parent == estate.tmp, "the block file is a temporary one" assert call["lane"] == LANE assert f"handoff written: {estate.handoff}" in proc.stdout, proc.stdout assert proc.stdout.endswith("AFTER\n") @@ -278,73 +274,22 @@ def test_block_calls_write_handoff_with_whole_and_expect_digest(estate, shell): @pytest.mark.parametrize("shell", SHELLS) -def test_the_digest_is_of_the_bytes_that_are_there(estate, shell): - """Not of a copy and not of the row's spelling: the file `handoff-path` - named, read as it stands.""" - other = "a different handoff\nwith other bytes\n" - estate.handoff.write_text(other, encoding="utf-8") - estate.run(shell, estate.step2()) - argv = _write_call(estate)["argv"] - assert argv[argv.index("--expect-digest") + 1] == hashlib.sha256(other.encode()).hexdigest() - - -def _digest_sent(estate: Estate) -> str: - argv = _write_call(estate)["argv"] - return argv[argv.index("--expect-digest") + 1] - - -@pytest.mark.parametrize("shell", SHELLS) -@pytest.mark.parametrize("answer", [ - "sha256sum:", OLD_DIGEST.upper(), OLD_DIGEST[:63], -], ids=["a-word", "upper-case", "63-digits"]) -def test_a_digest_that_is_not_a_sha256_is_no_digest_and_nothing_is_sent(estate, shell, answer): - """rv3's mutant A on #311: the digest is held to the shape the verb's - `--expect-digest` takes, 64 lower-case hex digits, as it is read. A tool - that answers with anything else, an error word, upper case or a digest cut - short, gives no digest: block 1 prints none, the call is not made, and - step 4's line says the handoff was NOT refreshed.""" - estate.tool("sha256sum", f'#!/bin/sh\nprintf "%s %s\\n" {shlex.quote(answer)} "$2"\n') - proc = estate.run(shell, estate.step2() + "\n" + estate.step4()) - assert proc.returncode == 0, proc.stderr - assert "prior_sha256=" in proc.stdout, proc.stdout - assert "write-handoff" not in estate.verbs(), estate.calls() - assert "HANDOFF NOT WRITTEN" in proc.stdout, proc.stdout - assert "no digest read" in proc.stdout, proc.stdout - assert _register_line(estate).endswith( - "; handoff NOT refreshed (write-handoff was not called)"), _register_line(estate) - - -@pytest.mark.skipif(shutil.which("sha256sum") is None, - reason="the order of the two tools counts only where both are there") -@pytest.mark.parametrize("shell", SHELLS) -def test_sha256sum_gives_the_digest_where_shasum_is_there_too(estate, shell): - """rv3's mutant E on #311: the digest is read by the two tools the - helper's own `lane_handoff_digest` tries, in its order, `sha256sum` and - then `shasum -a 256`. Where both are there, `sha256sum`'s is the one: a - `shasum` that answers otherwise is never asked.""" - estate.tool("shasum", '#!/bin/sh\necho "w3 shim shasum: $*" >&2\n' - f'printf "%s %s\\n" {"f" * 64} "$4"\n') - proc = estate.run(shell, estate.step2()) - assert proc.returncode == 0, proc.stderr - assert _digest_sent(estate) == OLD_DIGEST, proc.stderr - assert "w3 shim shasum" not in proc.stderr, proc.stderr - - -@pytest.mark.parametrize("shell", SHELLS) -def test_the_digest_sent_is_block_1s_even_where_the_file_moved_before_the_call(estate, shell): - """rv3's mutant I on #311: the digest is of the bytes the session read in - block 1, which its new top block was written from, and is never read - again at the call. Here the file moves between the blocks: the new file is - built from what is there now, and the call still carries block 1's digest, - so the verb finds other bytes and refuses (7) rather than taking a top - block written from a file that is no longer there.""" - moved = OLD + "a peer line, written between the blocks\n" - between = f"printf %s {shlex.quote(moved)} > {shlex.quote(str(estate.handoff))}" - proc = estate.run(shell, estate.step2(between=between)) +def test_no_digest_is_read_and_no_new_file_is_built(estate, shell): + """981, option (a): the skill reads no digest and builds no complete new + file, so a `sha256sum` or `shasum` it would ask is never asked, the one + temporary file it makes is the block file, and none of `--whole`, + `--expect-digest`, `prior_sha256` or `prepared_file` is left in either + block for a later edit to send.""" + for tool in ("sha256sum", "shasum"): + estate.tool(tool, f'#!/bin/sh\necho "w3 shim {tool} asked" >&2\nexit 1\n') + proc = estate.run(shell, estate.step2(), STUB_WH_OUT=f"{estate.handoff}\tdeadbeef") assert proc.returncode == 0, proc.stderr - assert _digest_sent(estate) == OLD_DIGEST, proc.stdout - assert _write_call(estate)["prepared"] == TOP + "---\n\n" + moved, ( - "the new file is built from the file as it is at the call") + assert "w3 shim" not in proc.stderr, proc.stderr + assert [p.name.split(".")[0] for p in estate.tmp.iterdir()] == ["handoff-block"], ( + list(estate.tmp.iterdir())) + code = "\n".join(_code_lines([_block(B1), _block(B2)])) + for gone in ("--whole", "--expect-digest", "prior_sha256", "prepared_file", "sha256sum", "shasum"): + assert gone not in code, f"step 2's blocks still say {gone!r}" @pytest.mark.parametrize("shell", SHELLS) @@ -387,7 +332,7 @@ def shape() -> list[dict]: for call in estate.calls(): argv = list(call["argv"]) if argv[0] == "write-handoff": - argv[3] = "" + argv[3] = "" out.append(dict(call, argv=argv)) return out @@ -400,11 +345,12 @@ def shape() -> list[dict]: assert shape() == plain, "an ambient LANES_REPO changed the call" -def test_the_three_variables_the_old_call_named_are_defined_in_a_block(): +def test_the_variables_the_call_names_are_defined_in_block_1(): """`publish-handoff "$lane" "$handoff_file" "$prepared_file" --expect-digest - "$prior_sha256"` used its three variables without defining any of them.""" + "$prior_sha256"` used its three variables without defining any of them. + The call `--prepend` makes names two, and block 1 defines both.""" code = "\n".join(_code_lines([_block(B1)])) - for name in ("handoff_file", "prior_sha256", "prepared_file"): + for name in ("handoff_file", "block_file"): assert re.search(rf"(?m)^\s*{name}=", code), f"no block defines ${name}" assert "publish-handoff" not in "\n".join(_blocks(_text())), ( "the old call is gone: write-handoff is the one act") @@ -466,7 +412,7 @@ def _says(estate: Estate) -> dict: ["HANDOFF NOT", "pushed from the lane checkout", "never committed"]), "refused-after-writing": (2, True, [ "HANDOFF NOT COMMITTED", f"written at {path}, uncommitted", "Nothing was pushed", - "Do not call it again", "refused (7)"], ["HANDOFF NOT WRITTEN"]), + "Do not call it again", "a second copy of it on top"], ["HANDOFF NOT WRITTEN"]), "refused-before-writing": (2, False, [ "HANDOFF NOT WRITTEN", "wrote nothing"], ["HANDOFF NOT COMMITTED"]), "pushed-not-confirmed": (3, True, [ @@ -474,11 +420,16 @@ def _says(estate: Estate) -> dict: "Do not call it published until it is read back"], []), "kept-on-head": (6, True, ["HANDOFF NOT PUSHED", "kept on HEAD", "(exit 6)", "Never force"], []), - "moved-or-owned": (7, False, [ - "HANDOFF NOT WRITTEN", f"digest {OLD_DIGEST}", "(exit 7;", - "run the block under WHERE THE HANDOFF IS again", "reads a new digest", - "makes a new prepared file", "run this block once more", - "a restart owns it, do not retry"], []), + # 981, option (a): with `--prepend` no digest is sent, so a 7 is a restart's. + "owned-by-a-restart": (7, False, [ + "HANDOFF NOT WRITTEN", "an active restart owns this handoff", "(exit 7;", + "nothing was published", "Do not retry: that restart writes this handoff"], + ["digest", "WHERE THE HANDOFF IS", "prepared file"]), + # 981: the verb refuses a block whose last byte is no newline (64), and writes nothing. + "block-with-no-final-newline": (64, False, [ + "HANDOFF NOT WRITTEN", "(exit 64;", "wrote nothing", + "Where they say the block does not end with a newline, end it with one (echo >> ", + "run this block once more, once"], ["the file is written at"]), "no-handoff": (8, False, ["HANDOFF NOT PUBLISHED", "exited 8", "nothing was written"], ["the file is written at"]), "environment": (1, False, ["HANDOFF NOT PUBLISHED", "exited 1", "nothing was written"], @@ -495,8 +446,8 @@ def _says(estate: Estate) -> dict: CASES = ["pushed", "refused-after-writing", "refused-before-writing", "pushed-not-confirmed", - "kept-on-head", "moved-or-owned", "no-handoff", "environment", "lock", - "written-then-failed"] + "kept-on-head", "owned-by-a-restart", "block-with-no-final-newline", "no-handoff", + "environment", "lock", "written-then-failed"] @pytest.mark.parametrize("shell", SHELLS) @@ -516,24 +467,23 @@ def test_each_exit_of_the_verb_is_said_and_the_swap_goes_on(estate, shell, case) @pytest.mark.parametrize("shell", SHELLS) @pytest.mark.parametrize("case", CASES) -def test_the_new_file_is_kept_and_named_wherever_nothing_was_written(estate, shell, case): +def test_the_new_top_block_is_kept_and_named_wherever_nothing_was_written(estate, shell, case): """rv3's MUST 1, for the call's own outcomes: where the verb wrote nothing - (no stdout), the session's new file is not lost with the swap. The block - names the temporary file that holds it, for the report; where the verb - wrote the handoff, there is nothing to keep and nothing is said of it.""" + (no stdout), the session's new top block is not lost with the swap. The + block names the temporary file that holds it, the one sent, for the + report; where the verb wrote the handoff, there is nothing to keep and + nothing is said of it.""" rc, written, _, _ = _says(estate)[case] out = f"{estate.handoff}\tdeadbeef" if written else "" proc = estate.run(shell, estate.step2(), STUB_WH_RC=str(rc), STUB_WH_OUT=out) sent = _write_call(estate)["argv"][3] - kept = f"kept at {sent}" - top = f"its new top block alone at {_block_file(proc)}" + assert sent == _block_file(proc) + kept = f"The new top block is kept at {sent}" if written: assert kept not in proc.stdout, proc.stdout - assert top not in proc.stdout, proc.stdout else: - assert kept in proc.stdout, f"{case}: the new file is not named\n{proc.stdout}" - assert top in proc.stdout, f"{case}: the top block's file is not named\n{proc.stdout}" - assert "name both paths in the report" in proc.stdout, proc.stdout + assert kept in proc.stdout, f"{case}: the top block's file is not named\n{proc.stdout}" + assert "name that path in the report" in proc.stdout, proc.stdout def _block_file(proc: subprocess.CompletedProcess) -> str: @@ -675,12 +625,10 @@ def test_only_a_2_is_asked_again_and_never_with_create(estate, shell, rc): @pytest.mark.parametrize("shell", SHELLS) -@pytest.mark.parametrize("prepare", ["none", "by-hand"]) +@pytest.mark.parametrize("prepare", ["none"]) def test_a_top_block_the_session_never_wrote_is_never_sent(estate, shell, prepare): """`mktemp` makes an empty file, and an empty `$block_file` is no new top - block: the block does not build a file or ask. A whole file written by hand - into `$prepared_file` (the old habit, where a session that forgot the - history sent only its block, rv3's SHOULD 1) is not what is sent either.""" + block: the block does not ask (the verb would refuse it, 1).""" proc = estate.run(shell, estate.step2(prepare=prepare)) assert proc.returncode == 0 assert "HANDOFF NOT WRITTEN" in proc.stdout, proc.stdout @@ -688,38 +636,6 @@ def test_a_top_block_the_session_never_wrote_is_never_sent(estate, shell, prepar assert estate.verbs() == ["handoff-path"], estate.calls() -#: The register line's last phrase where the new file did not keep the old one. -LOST = "; handoff NOT refreshed (the new file did not keep the old handoff whole)" - - -@pytest.mark.parametrize("shell", SHELLS) -@pytest.mark.parametrize("how", ["short", "failed"]) -def test_a_new_file_that_lost_the_old_one_is_never_sent(estate, shell, how): - """rv3's SHOULD 1 on #311, option (b): the block builds the complete new - file itself, the top block from `$block_file`, `---` and a blank line, then - `cat -- "$handoff_file"`, and proves the old file survives at its end, byte - for byte (`tail -c | cmp`), before the call. A build that lost the - old file's bytes, here a `cat` that wrote only the first ten of them or - failed, is never sent: the call is not made, the block says why and names - the top block's file for the report, and step 4's line says the handoff was - NOT refreshed.""" - cut = 'head -c 10 -- "$last"; exit 0' if how == "short" else "exit 1" - shim = estate.bin / "cat" - shim.write_text(f'#!/bin/sh\nfor last in "$@"; do :; done\n' - f'[ "$last" = "$STUB_CAT_CUT" ] || exec {shutil.which("cat")} "$@"\n{cut}\n', - encoding="utf-8") - shim.chmod(shim.stat().st_mode | stat.S_IXUSR) - proc = estate.run(shell, estate.step2() + "\n" + estate.step4(), - STUB_CAT_CUT=str(estate.handoff)) - assert proc.returncode == 0, proc.stderr - assert "write-handoff" not in estate.verbs(), estate.calls() - assert "HANDOFF NOT WRITTEN" in proc.stdout, proc.stdout - assert "does not end with the old handoff whole" in proc.stdout, proc.stdout - assert "write-handoff was NOT called" in proc.stdout, proc.stdout - assert f"kept at {_block_file(proc)}" in proc.stdout, proc.stdout - assert _register_line(estate).endswith(LOST), _register_line(estate) - - # --- step 4's register line says what step 2 did (rv3's SHOULD 3) ------------ #: The register line's last phrase where step 2's one call exited 0. @@ -737,6 +653,8 @@ def _register_line(estate: Estate) -> str: ({}, True, True, REFRESHED), ({"STUB_WH_RC": "7"}, False, True, "; handoff NOT refreshed (write-handoff exited 7 and wrote nothing)"), + ({"STUB_WH_RC": "64"}, False, True, + "; handoff NOT refreshed (write-handoff exited 64 and wrote nothing)"), ({"STUB_WH_RC": "2"}, False, True, "; handoff NOT refreshed (write-handoff exited 2 and wrote nothing)"), ({"STUB_WH_RC": "2"}, True, True, @@ -754,7 +672,8 @@ def _register_line(estate: Estate) -> str: ({"STUB_HP_RC": "2", "STUB_HP_OUT": "", "STUB_HP_ERR": OLDER_HELPER}, False, True, "; handoff NOT refreshed (the handoff read failed, exit 2)"), ({}, False, False, "; handoff NOT refreshed (write-handoff was not called)"), -], ids=["pushed", "moved-or-owned", "refused-before-writing", "refused-after-writing", +], ids=["pushed", "owned-by-a-restart", "block-with-no-final-newline", "refused-before-writing", + "refused-after-writing", "pushed-not-confirmed", "kept-on-head", "written-then-failed", "not-found", "read-failed", "older-helper", "never-prepared"]) def test_step4_register_line_says_what_step_2_did(estate, shell, extra, written, prepare, ends): @@ -794,14 +713,16 @@ def test_a_step_4_that_step_2_never_reached_never_claims_a_refresh(estate, shell REAL_WHY = "a usage reset" -def _real_script(shell_between: str = "", *, lane: str = REAL_LANE, block: str = BLOCK) -> str: - """Step 2 whole: the session writes its new top block alone, and the skill - builds `REAL_NEW` from it and the old file.""" +def _real_script(shell_between: str = "", *, lane: str = REAL_LANE, block: str = BLOCK, + after: str = "") -> str: + """Step 2 whole: the session writes its new top block alone, and the verb + puts it in front of the old file under its lock (`REAL_NEW`). `after` is + shell run once block 2 is done, in the same shell.""" return "\n".join([ f"L={shlex.quote(str(HELPER))}", f"lane={lane}", "ws=Eagle", f"why={shlex.quote(REAL_WHY)}", _block(B1), shell_between, f'[ -z "$block_file" ] || printf %s {shlex.quote(block)} > "$block_file"', - _block(B2)]) + _block(B2), after]) def _real_run(box: RealBox, shell: str, script: str) -> subprocess.CompletedProcess: @@ -834,7 +755,7 @@ def test_the_handoff_is_pushed_from_the_lane_checkout_and_W_is_untouched(real, s not the entry another lane left STAGED there, which the old block committed and published. The session wrote its new top block alone, as rv3's probe of SHOULD 1 did, and origin's handoff is that block over the old file whole: - the history survives, because the skill builds the file.""" + the history survives, because the verb makes the file (`--prepend`).""" if how == "seam": c = real.seam() else: @@ -866,40 +787,42 @@ def test_the_handoff_is_pushed_from_the_lane_checkout_and_W_is_untouched(real, s assert not real.lock_of(c).exists(), "the checkout's lock is released" -@pytest.mark.parametrize("shell", SHELLS) -def test_a_handoff_that_moved_after_its_digest_was_read_is_refused_and_nothing_is_published( - real, shell): - """The digest is of the bytes the new file was made from. A peer's edit that - lands between the read and the call is exit 7: the verb fetches the lane's - checkout, finds other bytes, and publishes nothing; the block says so and - tells the session to read it again.""" - peer = shlex.quote(str(real.seed)) +def _peer_edit(box: RealBox) -> str: + """Shell that a peer runs between the blocks: it appends a line to the + lane's handoff in another clone and pushes it.""" + peer = shlex.quote(str(box.seed)) rel = shlex.quote(HANDOFF_REL) - between = (f"git -C {peer} pull -q --ff-only origin main && " - f"printf '%s\\n' 'a peer line' >> {peer}/{rel} && " - f"git -C {peer} add -- {rel} && " - f"git -C {peer} commit -q -m 'a peer edits the handoff' && " - f"git -C {peer} push -q origin main") - proc = _real_run(real, shell, _real_script(between)) + return (f"git -C {peer} pull -q --ff-only origin main && " + f"printf '%s\\n' 'a peer line' >> {peer}/{rel} && " + f"git -C {peer} add -- {rel} && " + f"git -C {peer} commit -q -m 'a peer edits the handoff' && " + f"git -C {peer} push -q origin main") + + +@pytest.mark.parametrize("shell", SHELLS) +def test_a_peers_edit_pushed_between_the_blocks_is_kept_under_the_new_block(real, shell): + """981, option (a), as lane openRepoTools-3 answered question 1: with + `--whole` and a digest, a peer's edit that landed between the read and + the call was 7 and nothing was published. With `--prepend` the verb + reads the handoff under its lock, from what S3 has just brought from + origin, so the new block goes on top of the peer's file and both are + kept, newest on top: the handoff's normal shape, at the first call.""" + proc = _real_run(real, shell, _real_script(_peer_edit(real))) assert proc.returncode == 0, _said(proc) - assert "HANDOFF NOT WRITTEN" in proc.stdout, _said(proc) - assert "once more" in proc.stdout, _said(proc) - assert "nothing was published" in proc.stderr, _said(proc) - assert real.subjects()[0] == "a peer edits the handoff" - assert "a peer line" in real.origin_file(HANDOFF_REL) - assert real.origin_file(HANDOFF_REL) != REAL_NEW + assert "handoff written:" in proc.stdout, _said(proc) + assert real.subjects()[:2] == [f"handoff({REAL_LANE}@Eagle): PAUSED, {REAL_WHY}", + "a peer edits the handoff"] + assert real.origin_file(HANDOFF_REL) == prepended(HANDOFF + "a peer line\n", BLOCK), _said(proc) @pytest.mark.parametrize("shell", SHELLS) @pytest.mark.parametrize("how", ["routed", "seam"]) -def test_a_lane_checkout_behind_origin_is_7_once_and_written_when_step_2_is_run_again( - real, shell, how): - """rv3's SHOULD 2 (question 3 of the body), ported from its probe. A lane - checkout behind origin on its handoff: `handoff-path --create` leaves it - as it is, the digest is of its stale copy, and the verb fast-forwards it - first, so the first call is 7 with nothing published. The 7 arm's words - are the way out: the block under WHERE THE HANDOFF IS run again reads the - digest the checkout has now, and the call made once more writes the new +def test_a_lane_checkout_behind_origin_is_written_on_the_first_call(real, shell, how): + """rv3's SHOULD 2 (question 3 of the body), ported from its probe, and + lane openRepoTools-3's answer to question 4: a lane checkout behind origin + on its handoff was 7 once with `--whole`, because the digest was of its + stale copy. With `--prepend` the case goes away: the verb fast-forwards + the checkout and reads the file there, so the first call writes the new block over the peer's file, which survives under it.""" if how == "seam": real.seam() @@ -908,24 +831,70 @@ def test_a_lane_checkout_behind_origin_is_7_once_and_written_when_step_2_is_run_ assert made.returncode == 0, made.stderr peer_text = HANDOFF + "a peer line, pushed from elsewhere\n" real.peer_pushes(HANDOFF_REL, peer_text) - script = _real_script() - first = _real_run(real, shell, script) + proc = _real_run(real, shell, _real_script()) + assert proc.returncode == 0, _said(proc) + assert "handoff written:" in proc.stdout, _said(proc) + assert real.origin_file(HANDOFF_REL) == prepended(peer_text, BLOCK), _said(proc) + + +@pytest.mark.parametrize("shell", SHELLS) +def test_a_block_with_no_final_newline_is_refused_and_written_once_it_has_one(real, shell): + """981 (MUST) of lane openRepoTools-3's review of #311 at `1913db9`: a + top block with no final newline (a Write-tool body, a `printf %s`) was + glued to the `---` under it, so origin's handoff read `…the newer + block---`, and `lane-start`'s `handoff_top_block`, which stops at the + first line opening with `---`, gave the next session the new block and the + old one as one prompt. The verb refuses that block (64) and writes nothing; + the block says so, and what its 64 arm says to do, an `echo` onto the + block file and block 2 once more, writes the handoff whole.""" + c = real.seam() + tip = real.origin_head() + unterminated = BLOCK.rstrip("\n") + assert not unterminated.endswith("\n") + first = _real_run(real, shell, _real_script( + block=unterminated, after='printf "FIRST_RECORD=%s\\n" "$hf_record"')) assert first.returncode == 0, _said(first) - assert "(exit 7;" in first.stdout, _said(first) - assert "run the block under WHERE THE HANDOFF IS again" in first.stdout, _said(first) - assert real.origin_file(HANDOFF_REL) == peer_text, "nothing is published on a 7" - second = _real_run(real, shell, script) - assert second.returncode == 0, _said(second) - assert "handoff written:" in second.stdout, _said(second) - assert real.origin_file(HANDOFF_REL) == prepended(peer_text, BLOCK), _said(second) + assert "(exit 64;" in first.stdout, _said(first) + assert "does not end with a newline" in first.stderr, _said(first) + assert "HANDOFF NOT WRITTEN" in first.stdout, _said(first) + assert real.origin_head() == tip, "nothing is published on a 64" + assert (c / HANDOFF_REL).read_text(encoding="utf-8") == HANDOFF, "nothing is written on a 64" + assert ("FIRST_RECORD=handoff NOT refreshed (write-handoff exited 64 and wrote nothing)" + in first.stdout), _said(first) + fixed = _real_run(real, shell, _real_script( + block=unterminated, after='echo >> "$block_file"\n' + _block(B2))) + assert fixed.returncode == 0, _said(fixed) + assert "handoff written:" in fixed.stdout, _said(fixed) + assert real.origin_file(HANDOFF_REL) == prepended(HANDOFF, unterminated + "\n"), _said(fixed) + top = real.origin_file(HANDOFF_REL).split("\n---\n")[0] + assert top == unterminated, "the rule is a line of its own: no line of the block runs into it" + + +@pytest.mark.parametrize("shell", SHELLS) +def test_an_active_restart_is_a_7_and_the_block_says_not_to_retry(real, shell): + """981, option (a): with `--prepend` a 7 is a restart's (`an active + restart owns this handoff`), never "moved since it was read", so the arm + says that restart writes the handoff and does not send the session back + for a new digest. Nothing is written or published.""" + c = real.seam() + real.restart(c / HANDOFF_REL) + tip = real.origin_head() + proc = _real_run(real, shell, _real_script()) + assert proc.returncode == 0, _said(proc) + assert "an active restart owns this handoff" in proc.stderr, _said(proc) + assert "(exit 7;" in proc.stdout, _said(proc) + assert "Do not retry: that restart writes this handoff" in proc.stdout, _said(proc) + assert "WHERE THE HANDOFF IS" not in proc.stdout, _said(proc) + assert (c / HANDOFF_REL).read_text(encoding="utf-8") == HANDOFF + assert real.origin_head() == tip @pytest.mark.parametrize("shell", SHELLS) def test_a_staged_entry_in_the_lane_checkout_leaves_the_handoff_written_and_uncommitted( real, shell): - """S3's refusal (2) with `--whole`: the file is written (`R-A11-11`), nothing + """S3's refusal (2) with `--prepend`: the file is written (`R-A11-11`), nothing is committed or pushed, the entry that is not the handoff's stays staged and - unpublished, and the block says exactly that.""" + unpublished, and the block says exactly that, and not to call it again.""" c = real.seam() real.write(c, "handoffs/foreign.md", "someone else's entry\n") real.git("-C", str(c), "add", "--", "handoffs/foreign.md") @@ -936,6 +905,7 @@ def test_a_staged_entry_in_the_lane_checkout_leaves_the_handoff_written_and_unco assert proc.returncode == 0, _said(proc) assert "HANDOFF NOT COMMITTED" in proc.stdout, _said(proc) assert f"written at {c / HANDOFF_REL}, uncommitted" in proc.stdout, _said(proc) + assert "Do not call it again" in proc.stdout, _said(proc) assert (c / HANDOFF_REL).read_text(encoding="utf-8") == REAL_NEW assert real.origin_head() == tip assert real.head(c) == head From f6b029f962dfe85f2860081b3c06941c8a360b25 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Sat, 24 Oct 2026 14:48:06 +0000 Subject: [PATCH 10/17] Slice 3, 982 of lane openRepoTools-3's review of #311: the 6 arm says, in the verb's words after 604, where the commit is kept or that there is none (on HEAD of the lane checkout, in an Amendment 8(d) handoff's own repository, or nowhere where the add or the commit failed), with a real 8(d) case whose push fails there (#198) Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 2dc07d17f33eccaa91a70c5dd2713937eb78bcce) --- skills/handoff/SKILL.md | 11 +++++-- tests/test_handoff_skill_commit_block.py | 39 ++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index 0b4add4..bb41055 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -532,7 +532,10 @@ if [[ -n "$handoff_file" && -s "${block_file:-}" ]]; then printf 'HANDOFF NOT WRITTEN: write-handoff refused (exit 2; its words are above) and wrote nothing.\n' fi ;; 3) printf 'HANDOFF PUSHED, NOT CONFIRMED: the push went, or timed out, and origin could not be read back to show the commit on it (exit 3; the note above names it). Do not call it published until it is read back.\n' ;; - 6) printf 'HANDOFF NOT PUSHED: its commit is kept on HEAD of the lane checkout and named above (exit 6), and every later write from that checkout refuses until it is published; the note above names the act. Never force.\n' ;; + # A 6 IS SAID IN THE VERB'S WORDS (lane openRepoTools-3's finding 982 on #311): since 604 it is also an + # Amendment 8(d) handoff, written, whose commit or push in its own repository failed, and `commit_publish`'s + # failed add or commit is 6 with no commit at all. The verb's note says which, and where. + 6) printf 'HANDOFF NOT PUSHED: write-handoff exited 6, and the file is written at %s. The note above says where its commit is kept, or that there is none: on HEAD of the lane checkout, named, where every later write from that checkout refuses until it is published; for an Amendment 8(d) handoff, in its own repository, where its commit or push there failed; or nowhere, where its add or its commit failed. Do the act the note names. Never force.\n' "$hf_path" ;; # A 7 WITH `--prepend` IS A RESTART'S (lane openRepoTools-3's finding 981 on #311): no digest is sent, so # it is never "moved since it was read". The verb's own "changed before publication" is another act's # write in the instant between its read and its replace, under the lock, and the same call reads it again. @@ -560,8 +563,10 @@ A refusal, a conflict or a rejected push does not stop the swap (`R-A11-11`, *a unwritten*): where the lane's checkout refuses the commit (2), the verb has still written the handoff, commits nothing and prints git's own words; where its push is refused it replays the write on origin's new tip, the block put in front of origin's copy of the handoff, and where origin no longer has that handoff, or its row -names a file the replay cannot write, it keeps its commit on HEAD and names it (6). The block prints what it -did NOT do, and the report carries that line. Never force, never +names a file the replay cannot write, it keeps its commit on HEAD and names it (6). An Amendment 8(d) +handoff, in another repository, is committed and pushed there (604), and a commit or push there that failed +is 6 too, with the commit kept there or not made, as the verb's note says. The block prints what it did NOT +do, and the report carries that line. Never force, never `git add -A`, and never commit staged entries you did not make. **WHAT THIS STEP DID IS CARRIED TO STEP 4 in `$hf_record`,** so that the register's PAUSED line says it and diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index 0c98e44..0e8d57d 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -65,7 +65,8 @@ import pytest from conftest import REPO, WINDOWS_SKIP -from test_a23_write_handoff import BLOCK, HANDOFF, HANDOFF_REL, Box as RealBox, prepended +from test_a23_write_handoff import (BLOCK, HANDOFF, HANDOFF_REL, Box as RealBox, _other_repository, + prepended) pytestmark = [WINDOWS_SKIP, pytest.mark.skipif( shutil.which("bash") is None or shutil.which("git") is None, @@ -418,8 +419,12 @@ def _says(estate: Estate) -> dict: "pushed-not-confirmed": (3, True, [ "HANDOFF PUSHED, NOT CONFIRMED", "(exit 3", "Do not call it published until it is read back"], []), - "kept-on-head": (6, True, ["HANDOFF NOT PUSHED", "kept on HEAD", "(exit 6)", - "Never force"], []), + # 982: the 6 arm in the verb's words after 604, where the commit is kept, or that there is none. + "kept-on-head": (6, True, [ + "HANDOFF NOT PUSHED", "write-handoff exited 6", f"the file is written at {path}.", + "says where its commit is kept, or that there is none", "on HEAD of the lane checkout", + "for an Amendment 8(d) handoff, in its own repository", "where its add or its commit failed", + "Never force"], ["its commit is kept on HEAD of the lane checkout and named above (exit 6)"]), # 981, option (a): with `--prepend` no digest is sent, so a 7 is a restart's. "owned-by-a-restart": (7, False, [ "HANDOFF NOT WRITTEN", "an active restart owns this handoff", "(exit 7;", @@ -914,6 +919,34 @@ def test_a_staged_entry_in_the_lane_checkout_leaves_the_handoff_written_and_unco assert "handoffs/foreign.md" not in _tree(real) +@pytest.mark.parametrize("shell", SHELLS) +def test_an_8d_handoff_whose_push_fails_there_is_6_and_its_commit_is_said_to_be_there(real, shell): + """982 of lane openRepoTools-3's review of #311: since 604 (2a's A57), a 6 + is also an Amendment 8(d) handoff, written, whose commit or push in its + own repository failed. Here that repository has no origin: the handoff is + written and committed there, the push cannot be made (6), the lane + checkout's HEAD and the register's origin do not move, and the block's 6 + arm says where the commit is kept, or that there is none, from the verb's + note, never that it is on HEAD of the lane checkout.""" + other = _other_repository(real, origin=False) + real.cell_is(str(other / "handoff.md")) + c = real.seam() + before = real.git("-C", str(other), "rev-parse", "HEAD").strip() + head, tip = real.head(c), real.origin_head() + proc = _real_run(real, shell, _real_script()) + assert proc.returncode == 0, _said(proc) + assert "HANDOFF NOT PUSHED" in proc.stdout, _said(proc) + assert "write-handoff exited 6" in proc.stdout, _said(proc) + assert "for an Amendment 8(d) handoff, in its own repository" in proc.stdout, _said(proc) + assert "and committed there, on main" in proc.stderr, _said(proc) + assert (other / "handoff.md").read_text(encoding="utf-8") == REAL_NEW + assert real.git("-C", str(other), "rev-parse", "HEAD~1").strip() == before + assert real.git("-C", str(other), "log", "-1", "--format=%B").strip().splitlines() == [ + f"handoff({REAL_LANE}@Eagle): PAUSED, {REAL_WHY}", "", f"Lane: {REAL_LANE}"] + assert real.head(c) == head, "the lane checkout's HEAD did not move" + assert real.origin_head() == tip, "the register's origin did not move" + + @pytest.mark.parametrize("shell", SHELLS) def test_a_lane_with_no_row_has_nothing_to_write_and_nothing_is_cloned(real, shell): tip = real.origin_head() From 8797d119f5284749d0d4ae6e8ca855e5e6569ef2 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Sun, 8 Nov 2026 14:26:03 +0000 Subject: [PATCH 11/17] Slice 3, 983 of lane openRepoTools-3's review of #311: a kept top block goes into the handoff by the one call, `LANES_LANE= lanes-edit.sh write-handoff --prepend --subject ""`, which block 2 prints filled in, never by a hand edit; in the 601 arm it goes to the lane's own handoff once the row's cell is corrected (#198) Every message that kept the new top block said it went "at the head of the handoff by hand", against step 2's own rule ("Do not edit or redirect into the canonical handoff directly") and lane 3's ruling on #303's 653(b). In the 601 arm one line said nothing was to be written there, by this skill or by hand, and the next that it goes at the head of the handoff by hand. - Block 1 makes `$block_file` whatever `handoff-path` answered, so a block kept after a failed read is on disk, and each failure arm prints `block_file=`. Its keep message names the call in lane 3's words, and says block 2 prints it filled in. - The 601 arm says the file it names is not this lane's and the new top block goes to the lane's own handoff once the row's cell is corrected to name it. - Block 2 builds the call (`hf_call`) with each word single-quoted, a quote in it escaped by sed, so it runs as printed under bash and zsh, and prints it on a line of its own after the kept message and in the not-called arm. - The prose under WHERE THE HANDOFF IS names the call and "never by a hand edit". Tests: the printed line, run as printed, makes exactly the call the block makes, for the verb's 7, 64 and 2 with nothing written, a handoff not found, a read that failed and a block never written, under bash and zsh; a why holding a quote, a double quote, a `$` and a backtick reaches the subject unchanged; block 1's not-found and read-failed lines name the call; the 601 row says where the block goes; no text says "head of the handoff by hand". Refs #198 Co-Authored-By: Claude Opus 5.5 --- skills/handoff/SKILL.md | 41 +++++++---- tests/test_handoff_skill_commit_block.py | 92 +++++++++++++++++++++++- 2 files changed, 118 insertions(+), 15 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index bb41055..4dd762b 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -352,10 +352,13 @@ makes the lane's checkout, and step 2 is run again. Any other 2 is a name refuse handoff the row names in W or in another checkout of the register beside it (finding 601 of lane openRepoTools-3's review of opensoft/openRepoTools#312, refused by the translation `handoff-path` and `write-handoff` share): that file belongs to another repository, and nothing is written there, by this skill -or by hand. In every one of these cases the call under "THE WRITE IS ONE CALL" is -not made, the new top block is still written and kept whole in the report, so that it can be put at the head -of the handoff by hand, and the swap goes on with the gap in the report (`R-A11-11`, *a swap is never left -unwritten*). +or by hand; the new top block goes to the lane's own handoff once the row's cell is corrected to name it. In +every one of these cases the call under "THE WRITE IS ONE CALL" is not made, the new top block is still +written, to the `$block_file` the block prints, and kept whole in the report, and the swap goes on with the +gap in the report (`R-A11-11`, *a swap is never left unwritten*). Once the gap is answered, the block goes into +the handoff by the one call, `LANES_LANE= lanes-edit.sh write-handoff --prepend +--subject ""`, which the block under "THE WRITE IS ONE CALL" prints filled in, and never by a hand +edit: the handoff is the verb's to write (lane openRepoTools-3's finding 983 on #311). ```sh # WHERE THE HANDOFF IS: derived, never spelled. `handoff-path` only reads, and `--create` makes the lane's own @@ -368,12 +371,14 @@ handoff_file="$("$L" handoff-path "$lane" --create)" || hf_rc=$? # with only its stderr kept, as `sread` asks, to tell those apart: WITHOUT `--create`, so it is a read and # clones nothing, and without the fetch. [[ "$hf_rc" != 2 ]] || hf_said="$(LANES_NO_FETCH=1 "$L" handoff-path "$lane" 2>&1 >/dev/null || :)" -hf_keep="Write the new top block all the same and put it whole in the report: it is then the one copy, and it goes at the head of the handoff by hand once this is answered. The swap goes on (R-A11-11)." +# THE NEW TOP BLOCK, AND ONLY IT, goes in `$block_file`, ending with a newline, WHATEVER THE READ ANSWERED. +# `write-handoff --prepend` puts it in front of the handoff as the verb reads it under the lock (lane +# openRepoTools-3's finding 981 on #311, option (a)); an empty `$block_file` is no block, and nothing is sent. +# Where the read failed, the file is still made: it is where the block is kept until the one call below can +# put it in the handoff, never a hand edit of the handoff (finding 983). +block_file="$(mktemp "${TMPDIR:-/tmp}/handoff-block.XXXXXX")" || block_file="" +hf_keep='Write the new top block all the same, to the block_file this block prints, and put it whole in the report: it is then the one copy, and once this is answered it goes into the handoff only by the one call LANES_LANE= lanes-edit.sh write-handoff --prepend --subject "", which the block under THE WRITE IS ONE CALL prints filled in, never by a hand edit. The swap goes on (R-A11-11).' if [[ "$hf_rc" == 0 && -n "$handoff_file" && -f "$handoff_file" ]]; then - # THE NEW TOP BLOCK, AND ONLY IT, goes in `$block_file`, ending with a newline. `write-handoff --prepend` - # puts it in front of the handoff as the verb reads it under the lock (lane openRepoTools-3's finding 981 - # on #311, option (a)); an empty `$block_file` is no block, and nothing is sent. - block_file="$(mktemp "${TMPDIR:-/tmp}/handoff-block.XXXXXX")" || block_file="" printf 'handoff_file=%s\nblock_file=%s\n' "$handoff_file" "${block_file:-}" else case "$hf_rc:$hf_said" in @@ -389,8 +394,9 @@ else 2:*) # 601 (lane openRepoTools-3's review of #312, and its correction 6368669583): a row whose handoff is in W # or in another checkout of the register beside it is refused by the translation `write-handoff` shares - # with `handoff-path`, 2 with no path printed. That file is not this lane's to write, by hand either. - printf 'HANDOFF READ FAILED: `%s handoff-path %s --create` exited 2 (its words are above): a name refused, a clone of the lane checkout that failed, or a handoff the row names in W or in another checkout of the register beside it (601). Where its words name such a file, the handoff it names belongs to another repository and not to this lane: nothing is to be written there, by this skill or by hand. The read failed, and that is not "no handoff" (Amendment 7(d)), so the call under "THE WRITE IS ONE CALL" is NOT made. %s\n' \ + # with `handoff-path`, 2 with no path printed. That file is not this lane's to write, by hand either; the + # kept block goes to the lane's own handoff once the row's cell names it (983). + printf 'HANDOFF READ FAILED: `%s handoff-path %s --create` exited 2 (its words are above): a name refused, a clone of the lane checkout that failed, or a handoff the row names in W or in another checkout of the register beside it (601). Where its words name such a file, the handoff it names belongs to another repository and not to this lane: nothing is to be written there, by this skill or by hand, and the new top block goes to the handoff of this lane, in its own checkout, once the handoff cell of its row is corrected to name it. The read failed, and that is not "no handoff" (Amendment 7(d)), so the call under "THE WRITE IS ONE CALL" is NOT made. %s\n' \ "$L" "$lane" "$hf_keep" ;; *) printf 'HANDOFF READ FAILED: `%s handoff-path %s --create` exited %s (its words are above). The read failed, and that is not "no handoff" (Amendment 7(d)), so the call under "THE WRITE IS ONE CALL" is NOT made. %s\n' \ @@ -400,6 +406,7 @@ else hf_record="handoff NOT refreshed (the handoff read failed, exit $hf_rc)" [[ "$hf_rc" != 0 && "$hf_rc" != 8 ]] || hf_record="handoff NOT refreshed (no handoff found)" handoff_file="" + printf 'block_file=%s\n' "${block_file:-}" fi ``` @@ -515,6 +522,11 @@ and the why in the operator's words, `handoff` where there is none, each control # subject the same way (its `subject_text`, and `unknown` for a missing workstation; rv3's NIT 1 on #311). hf_subject="handoff($lane@${ws:-unknown}): PAUSED, ${why:-handoff}" hf_subject="$(printf '%s' "$hf_subject" | LC_ALL=C tr '[:cntrl:]' ' ')" +# THE ONE CALL, PRINTED FILLED IN FOR A BLOCK THAT IS KEPT (lane openRepoTools-3's finding 983 on #311): a kept +# block goes into the handoff by this call once the gap is answered, never by a hand edit of the handoff. Each +# word is single-quoted, a quote in it spelled '\'', so the line runs as printed under bash and zsh alike. +hf_quote() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"; } +hf_call="LANES_LANE=$(hf_quote "$lane") $(hf_quote "$L") write-handoff $(hf_quote "$lane") --prepend $(hf_quote "${block_file:-}") --subject $(hf_quote "$hf_subject")" hf_rc=0; hf_out="" # THE ONE CALL: the verb reads the handoff under the lock and puts the block in front of it (`--prepend`). if [[ -n "$handoff_file" && -s "${block_file:-}" ]]; then @@ -523,7 +535,7 @@ if [[ -n "$handoff_file" && -s "${block_file:-}" ]]; then # ITS STDOUT IS `` ONCE THE FILE IS WRITTEN, whatever the exit; its own words are on stderr. hf_path="${hf_out%%$'\t'*}" hf_wrote="nothing was written"; [[ -z "$hf_out" ]] || hf_wrote="the file is written at $hf_path" - hf_kept="The new top block is kept at $block_file: name that path in the report, so that the top block can be put at the head of the handoff by hand once this is answered." + hf_kept="The new top block is kept at $block_file: name that path in the report. Where it is still to go into the handoff once this is answered, it goes by this one call, never by a hand edit:" case "$hf_rc" in 0) printf 'handoff written: %s (the note above, from write-handoff, says what was committed and where it was pushed)\n' "$hf_path" ;; 2) if [[ -n "$hf_out" ]]; then @@ -551,9 +563,10 @@ if [[ -n "$handoff_file" && -s "${block_file:-}" ]]; then [[ -z "$hf_out" ]] || hf_record="handoff written, its publication NOT confirmed (write-handoff exited $hf_rc)" [[ "$hf_rc" != 0 ]] || hf_record="handoff refreshed" # WHERE NOTHING WAS WRITTEN, THE NEW TOP BLOCK IS NOT LOST WITH THE SWAP: its temporary file is named for the report. - [[ -n "$hf_out" ]] || printf '%s\n' "$hf_kept" + [[ -n "$hf_out" ]] || printf '%s\n%s\n' "$hf_kept" "$hf_call" else - printf 'HANDOFF NOT WRITTEN: there is no handoff located, or no new top block written to its file, so write-handoff was NOT called. Write the new top block if it is not written yet, and put it whole in the report, so that it can be put at the head of the handoff by hand. The swap goes on (R-A11-11); the report carries this line.\n' + printf 'HANDOFF NOT WRITTEN: there is no handoff located, or no new top block written to its file, so write-handoff was NOT called. Write the new top block to %s if it is not written yet, and put it whole in the report. Once that is answered, it goes into the handoff by this one call, never by a hand edit:\n%s\nThe swap goes on (R-A11-11); the report carries this line.\n' \ + "${block_file:-a file of its own}" "$hf_call" # Block 1's reason where it gave one: a handoff not found, or a read that failed. hf_record="${hf_record:-handoff NOT refreshed (write-handoff was not called)}" fi diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index 0e8d57d..4abe5a2 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -206,6 +206,17 @@ def calls(self) -> list[dict]: return [json.loads(line) for line in self.log.read_text(encoding="utf-8").splitlines()] + def replay(self, shell: str, proc: subprocess.CompletedProcess) -> dict: + """Run the one call block 2 printed, as printed, and return the call + it made (983: a kept block goes into the handoff by that call).""" + line = _printed_call(proc) + before = len(self.calls()) + again = self.run(shell, line) + assert again.returncode == 0, again.stderr + made = self.calls()[before:] + assert len(made) == 1, made + return made[0] + def verbs(self) -> list[str]: return [c["argv"][0] for c in self.calls()] @@ -498,6 +509,74 @@ def _block_file(proc: subprocess.CompletedProcess) -> str: return found[0] +#: The one call a kept top block goes into the handoff by, in lane +#: openRepoTools-3's words (finding 983 on #311): never a hand edit of the +#: handoff, which step 2 forbids ("Do not edit or redirect into the canonical +#: handoff directly"). Block 1 names it so; block 2 prints it filled in. +KEPT_CALL = ('LANES_LANE= lanes-edit.sh write-handoff --prepend ' + '--subject ""') + + +def _printed_call(proc: subprocess.CompletedProcess) -> str: + """The one call block 2 printed, filled in, on a line of its own.""" + found = re.findall(r"(?m)^LANES_LANE=.*$", proc.stdout) + assert len(found) == 1, proc.stdout + return found[0] + + +@pytest.mark.parametrize("shell", SHELLS) +@pytest.mark.parametrize("extra, prepare", [ + ({"STUB_WH_RC": "7"}, "block"), + ({"STUB_WH_RC": "64"}, "block"), + ({"STUB_WH_RC": "2"}, "block"), + ({"STUB_HP_RC": "8", "STUB_HP_OUT": ""}, "block"), + ({"STUB_HP_RC": "2", "STUB_HP_OUT": "", "STUB_HP_ERR": "a name refused"}, "block"), + ({}, "none"), +], ids=["owned-by-a-restart", "block-with-no-final-newline", "refused-before-writing", "not-found", + "read-failed", "never-prepared"]) +def test_a_kept_block_goes_into_the_handoff_by_the_one_call_block_2_prints( + estate, shell, extra, prepare): + """983 of lane openRepoTools-3's review of #311: every message that keeps + the new top block said it goes "at the head of the handoff by hand", which + step 2 forbids. Block 2 now prints the one call that puts it there, filled + in, on a line of its own, and that line, run as printed, makes exactly the + call the block makes: the lane, `--prepend` and the block file, and the + subject.""" + proc = estate.run(shell, estate.step2(prepare=prepare), **extra) + assert proc.returncode == 0, proc.stderr + assert "by hand" not in proc.stdout.replace("by this skill or by hand", ""), proc.stdout + expected = ["write-handoff", LANE, "--prepend", _block_file(proc), "--subject", + f"handoff({LANE}@Eagle): PAUSED, a usage reset"] + made = estate.replay(shell, proc) + assert made["argv"] == expected, made + assert made["lane"] == LANE, made + + +@pytest.mark.parametrize("shell", SHELLS) +def test_the_printed_call_carries_a_why_with_quotes_and_dollars_unchanged(estate, shell): + """The printed call is quoted for the shell that runs it: a why that holds + a quote, a double quote, a `$` and a backtick reaches the verb's subject as + the block's own call carries it.""" + why = "it's \"$HOME\" `id` a\\b" + proc = estate.run(shell, estate.step2(why=shlex.quote(why)), STUB_WH_RC="7") + assert proc.returncode == 0, proc.stderr + sent = _write_call(estate)["argv"] + assert sent[-1] == f"handoff({LANE}@Eagle): PAUSED, {why}", sent + assert estate.replay(shell, proc)["argv"] == sent + + +def test_a_kept_block_is_never_said_to_go_into_the_handoff_by_hand(): + """983: no line of the skill, prose, comment or message, says the kept + block is put "at the head of the handoff by hand"; the paragraph under + WHERE THE HANDOFF IS names the one call instead.""" + flat = " ".join(re.sub(r"(?m)^\s*#\s?", "", _text()).split()) + assert not re.search(r"head of the handoff by hand", flat), "the hand edit is back" + held = [p for p in _prose("## 2. Refresh the handoff") if "In every one of these cases" in p] + assert len(held) == 1, held + assert KEPT_CALL in held[0], held[0] + assert "never by a hand edit" in held[0], held[0] + + def test_refusal_text_keeps_R_A11_11(): """`R-A11-11`, *a swap is never left unwritten*, is still the text's rule about a refusal, a conflict or a rejected push, and says what the verb does @@ -532,6 +611,13 @@ def test_the_handoff_write_is_said_as_the_verbs_and_not_as_a_commit_of_the_skill KEEP_IN_REPORT = "put it whole in the report" +def _block_1_line(proc: subprocess.CompletedProcess, opening: str) -> str: + """Block 1's one line that opens with `opening`.""" + found = [line for line in proc.stdout.splitlines() if line.startswith(opening)] + assert len(found) == 1, proc.stdout + return found[0] + + @pytest.mark.parametrize("shell", SHELLS) @pytest.mark.parametrize("how, extra", [ ("exit 8: no row, or a row that names no handoff", {"STUB_HP_RC": "8", "STUB_HP_OUT": ""}), @@ -547,6 +633,7 @@ def test_a_handoff_that_is_not_found_is_said_and_write_handoff_is_never_called( assert "HANDOFF NOT WRITTEN" in proc.stdout, proc.stdout assert "was NOT called" in proc.stdout, proc.stdout assert KEEP_IN_REPORT in proc.stdout, "the new top block is kept in the report" + assert KEPT_CALL in _block_1_line(proc, "HANDOFF NOT FOUND"), "983: the kept block's one call" assert estate.verbs() == ["handoff-path"], estate.calls() assert proc.stdout.endswith("AFTER\n") @@ -580,7 +667,9 @@ def test_a_handoff_that_is_not_found_is_said_and_write_handoff_is_never_called( ("2", ANOTHER_REPOSITORY, ["exited 2 (its words are above)", "the handoff it names belongs to another repository and not to this lane: " - "nothing is to be written there, by this skill or by hand"], + "nothing is to be written there, by this skill or by hand", + "the new top block goes to the handoff of this lane, in its own checkout, once the handoff " + "cell of its row is corrected to name it"], ["Update lanes-edit.sh", "step 2 again"]), ("4", "the lock that makes the lane checkout is held", ["exited 4", "Run step 2 again"], ["Update lanes-edit.sh"]), @@ -610,6 +699,7 @@ def test_a_read_that_failed_is_never_said_as_no_handoff(estate, shell, rc, err, assert words not in proc.stdout, f"{words!r} must not be said\n{proc.stdout}" assert "was NOT called" in proc.stdout, proc.stdout assert KEEP_IN_REPORT in proc.stdout, "the new top block is kept in the report" + assert KEPT_CALL in _block_1_line(proc, "HANDOFF READ FAILED"), "983: the kept block's one call" assert "write-handoff" not in estate.verbs(), estate.calls() From d61b02f3ad7162dd0a8169faef36c4d719afed47 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Sun, 8 Nov 2026 14:32:30 +0000 Subject: [PATCH 12/17] Slice 3, 984 of lane openRepoTools-3's review of #311: block 2 ends by printing `hf_record=`, block 1's failure arms print it with `block_file=`, and the prose says every Bash call is a new shell and what to set before block 2 and before step 4, so a step 4 in its own call no longer writes "step 2 left no outcome" after a 0 (#198) Every Bash call is a new shell, and step 2 takes at least two calls with step 3 between it and step 4. Step 4 learnt step 2's outcome only from `$hf_record` in the same shell, so a step 4 in its own call appended "; handoff NOT refreshed (step 2 left no outcome)" after a 0, a false line in append-only LANES.md, and block 2 in a new shell said "there is no handoff located". - Block 2's last line is `printf 'hf_record=%s\n' "$hf_record"`. - Block 1's failure arms print `hf_record=` beside `block_file=`, so a block 2 in another shell keeps block 1's reason ("no handoff found", "the handoff read failed, exit ") rather than "write-handoff was not called". - The paragraph that carries the outcome to step 4 says: EVERY BASH CALL IS A NEW SHELL; before block 2, set `L`, `lane`, `ws` and `why` as step 1 has them and each `name=value` line block 1 printed, quoted; before step 4, set `hf_record` from block 2's last line, quoted. Tests: block 1, block 2 and step 4 each run in its own shell, carrying only the printed values, for a 0, a 7, a 3, a handoff not found and a read that failed, under bash and zsh: block 2's last line and the register line's tail are what step 2 did, never "step 2 left no outcome"; the prose is pinned. Refs #198 Co-Authored-By: Claude Opus 5.5 --- skills/handoff/SKILL.md | 15 ++++- tests/test_handoff_skill_commit_block.py | 78 +++++++++++++++++++++++- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index 4dd762b..87825af 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -406,7 +406,9 @@ else hf_record="handoff NOT refreshed (the handoff read failed, exit $hf_rc)" [[ "$hf_rc" != 0 && "$hf_rc" != 8 ]] || hf_record="handoff NOT refreshed (no handoff found)" handoff_file="" - printf 'block_file=%s\n' "${block_file:-}" + # EVERY BASH CALL IS A NEW SHELL (lane openRepoTools-3's finding 984 on #311): what block 2 needs from here is + # printed, to be set, quoted, in the shell that runs it. + printf 'block_file=%s\nhf_record=%s\n' "${block_file:-}" "$hf_record" fi ``` @@ -570,6 +572,9 @@ else # Block 1's reason where it gave one: a handoff not found, or a read that failed. hf_record="${hf_record:-handoff NOT refreshed (write-handoff was not called)}" fi +# WHAT THIS STEP DID, PRINTED LAST, because step 4 runs in another Bash call, a new shell (984): set +# `hf_record` there, quoted, from this line. +printf 'hf_record=%s\n' "$hf_record" ``` A refusal, a conflict or a rejected push does not stop the swap (`R-A11-11`, *a swap is never left @@ -586,8 +591,12 @@ do, and the report carries that line. Never force, never never claims it: `handoff refreshed` only where the call exited 0; `handoff written, its publication NOT confirmed (write-handoff exited )` where the verb wrote the file and exited otherwise; and `handoff NOT refreshed ()` wherever nothing was written: the handoff was not found, its read failed, the call was not -made, or the verb wrote nothing. Keep it in the shell that runs step 4, as `$uuid_missing` is kept. A step 4 -that it never reached says `handoff NOT refreshed (step 2 left no outcome)`. +made, or the verb wrote nothing. EVERY BASH CALL IS A NEW SHELL, so nothing one block sets reaches the next +by itself, and step 3 comes between step 2 and step 4 (lane openRepoTools-3's finding 984 on #311). Before +block 2, set `L`, `lane`, `ws` and `why` as step 1 has them, and each `name=value` line block 1 printed +(`handoff_file`, `block_file` and `hf_record`, those it printed), quoted. Before step 4, set `hf_record` from +block 2's last line, `hf_record=`, quoted, beside step 4's other values. A step 4 that it +never reached says `handoff NOT refreshed (step 2 left no outcome)`. ## 3. Subagents and worktrees — the one question, and the sweep that fills WRITERS diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index 4abe5a2..4b02e60 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -241,10 +241,14 @@ def tool(self, name: str, script: str) -> None: path.write_text(script, encoding="utf-8") path.chmod(path.stat().st_mode | stat.S_IXUSR) - def step4(self, *, ws_missing: str = "", uuid_missing: str = "") -> str: + def step4(self, *, ws_missing: str = "", uuid_missing: str = "", + hf_record: str | None = None) -> str: + """Step 4's block. `hf_record`, where given, is set as a session sets + block 2's last printed line in the shell that runs step 4 (984).""" + carried = [] if hf_record is None else [f"hf_record={shlex.quote(hf_record)}"] return "\n".join([f"L={shlex.quote(str(self.helper))}", f"lane={LANE}", "ws=Eagle", 'why="a usage reset"', f'uuid="{UUID}"', f'ws_missing="{ws_missing}"', - f'uuid_missing="{uuid_missing}"', _block(STEP4), + f'uuid_missing="{uuid_missing}"', *carried, _block(STEP4), 'printf "row_write_refused=[%s]\\n" "$row_write_refused"']) @@ -801,6 +805,76 @@ def test_a_step_4_that_step_2_never_reached_never_claims_a_refresh(estate, shell assert line.endswith("; handoff NOT refreshed (step 2 left no outcome)"), line +#: The names block 1 prints for a session to carry to block 2, and block 2 to step 4. +CARRIED = ("handoff_file", "block_file", "hf_record") + + +def _printed(proc: subprocess.CompletedProcess) -> dict: + """Every `name=value` line a block printed, of the names that cross from + one Bash call to the next; the last of each name wins.""" + return {m.group(1): m.group(2) for m in + re.finditer(r"(?m)^(" + "|".join(CARRIED) + r")=(.*)$", proc.stdout)} + + +@pytest.mark.parametrize("shell", SHELLS) +@pytest.mark.parametrize("extra, written, ends", [ + ({}, True, "handoff refreshed"), + ({"STUB_WH_RC": "7"}, False, "handoff NOT refreshed (write-handoff exited 7 and wrote nothing)"), + ({"STUB_WH_RC": "3"}, True, "handoff written, its publication NOT confirmed (write-handoff exited 3)"), + ({"STUB_HP_RC": "8", "STUB_HP_OUT": ""}, False, "handoff NOT refreshed (no handoff found)"), + ({"STUB_HP_RC": "4", "STUB_HP_OUT": ""}, False, + "handoff NOT refreshed (the handoff read failed, exit 4)"), +], ids=["pushed", "owned-by-a-restart", "pushed-not-confirmed", "not-found", "read-failed"]) +def test_block_1_block_2_and_step_4_each_in_its_own_shell_carry_what_step_2_did( + estate, shell, extra, written, ends): + """984 of lane openRepoTools-3's review of #311: every Bash call is a new + shell, so step 4 learnt step 2's outcome only from `$hf_record` in the + same shell, and a step 4 in its own call wrote "; handoff NOT refreshed + (step 2 left no outcome)" after a 0, a false line in append-only + LANES.md; block 2 in a new shell said "there is no handoff located". + Here each runs in its own shell as a session runs them: block 1, the + session's write of its block, block 2 with step 1's values and every + `name=value` line block 1 printed set, and step 4 with the `hf_record=` + line block 2 ends by printing.""" + env = dict(extra) + if written: + env["STUB_WH_OUT"] = f"{estate.handoff}\tdeadbeef" + step1 = [f"L={shlex.quote(str(estate.helper))}", f"lane={LANE}", "ws=Eagle", "why='a usage reset'"] + first = estate.run(shell, "\n".join(step1[:2] + [_block(B1)]), **env) + assert first.returncode == 0, first.stderr + carried = _printed(first) + block_file = carried.get("block_file", "") + assert block_file and block_file != "", first.stdout + Path(block_file).write_text(TOP, encoding="utf-8") + second = estate.run(shell, "\n".join(step1 + [f"{k}={shlex.quote(v)}" for k, v in carried.items()] + + [_block(B2)]), **env) + assert second.returncode == 0, second.stderr + if "handoff_file" in carried: + assert "there is no handoff located" not in second.stdout, second.stdout + last = second.stdout.splitlines()[-1] + assert last == f"hf_record={ends}", second.stdout + third = estate.run(shell, estate.step4(hf_record=_printed(second)["hf_record"])) + assert third.returncode == 0, third.stderr + line = _register_line(estate) + assert line.endswith(f"; {ends}"), line + assert "step 2 left no outcome" not in line, line + + +def test_the_prose_says_what_to_set_before_block_2_and_before_step_4(): + """984: the paragraph that carries step 2's outcome to step 4 says every + Bash call is a new shell, what to set before block 2 (step 1's values + and block 1's printed lines) and that `hf_record` from block 2's last line + is set before step 4.""" + held = [p for p in _prose("## 2. Refresh the handoff") + if p.startswith("**WHAT THIS STEP DID IS CARRIED TO STEP 4")] + assert len(held) == 1, held + paragraph = held[0] + for words in ("EVERY BASH CALL IS A NEW SHELL", "Before block 2", "`L`, `lane`, `ws` and `why`", + "`handoff_file`, `block_file` and `hf_record`", "Before step 4", + "block 2's last line, `hf_record=", "quoted"): + assert words in paragraph, f"the paragraph no longer says {words!r}:\n{paragraph}" + + # --- the real helper: the handoff is pushed from C, and W is untouched -------- REAL_NEW = prepended(HANDOFF, BLOCK) From 054e84ddacb249d666d32a1a3c750c8c040266e2 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Sun, 8 Nov 2026 14:39:06 +0000 Subject: [PATCH 13/17] Slice 3, NIT 985 of lane openRepoTools-3's review of #311: the 2 arm and its paragraph call a handoff in W or in a sibling clone "another checkout of the register", the helper's term, not 8(d)'s "another repository", and name 602 (a relative cell that climbs out) and 607 (a glob character, `handoff-path`'s refusal since 2a's A60, 991) beside 601 (#198) - The 2 arm: "a handoff cell that handoff-path and write-handoff both refuse: a handoff the row names in W or in another checkout of the register beside it (601), a relative cell whose . or .. components climb out of the lane checkout (602), or a path there that holds a glob character (607)", and the file such a cell names "is not the handoff of this lane". - The paragraph under WHERE THE HANDOFF IS says the same, and its file "is not this lane's handoff". - The arm's comment and block 1's head comment name the three refusals. Tests: the 601 row (now `ANOTHER_CHECKOUT`), and new rows for the helper's 602 and 607 words, each say all three and never "another repository", under bash and zsh; a text test that "belongs to another repository" is gone and the paragraph names 602 and 607. Refs #198 Co-Authored-By: Claude Opus 5.5 --- skills/handoff/SKILL.md | 29 ++++++----- tests/test_handoff_skill_commit_block.py | 61 ++++++++++++++++++++---- 2 files changed, 68 insertions(+), 22 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index 87825af..92beb9c 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -349,10 +349,12 @@ that names no handoff) there is no file to write into. Where it prints `HANDOFF failed, and that is never "no handoff": every exit but 0 and 8 is one, and the line quotes it. A 2 whose words say "unknown subcommand" is a `lanes-edit.sh` predating `handoff-path`, to be updated; a 4 is the lock that makes the lane's checkout, and step 2 is run again. Any other 2 is a name refused, a clone that failed, or a -handoff the row names in W or in another checkout of the register beside it (finding 601 of lane -openRepoTools-3's review of opensoft/openRepoTools#312, refused by the translation `handoff-path` and -`write-handoff` share): that file belongs to another repository, and nothing is written there, by this skill -or by hand; the new top block goes to the lane's own handoff once the row's cell is corrected to name it. In +handoff cell the translation `handoff-path` and `write-handoff` share refuses: a handoff the row names in W +or in another checkout of the register beside it (finding 601 of lane openRepoTools-3's review of +opensoft/openRepoTools#312), a relative cell whose `.` or `..` components climb out of the lane's checkout +(602), or a path there that holds a glob character (607, which `handoff-path` refuses since 991). The file +such a cell names is not this lane's handoff, and nothing is written there, by this skill or by hand; the new +top block goes to the lane's own handoff once the row's cell is corrected to name it. In every one of these cases the call under "THE WRITE IS ONE CALL" is not made, the new top block is still written, to the `$block_file` the block prints, and kept whole in the report, and the swap goes on with the gap in the report (`R-A11-11`, *a swap is never left unwritten*). Once the gap is answered, the block goes into @@ -366,10 +368,10 @@ edit: the handoff is the verb's to write (lane openRepoTools-3's finding 983 on hf_rc=0; handoff_file=""; block_file=""; hf_said=""; hf_record="" handoff_file="$("$L" handoff-path "$lane" --create)" || hf_rc=$? # ONLY 8 IS "NO HANDOFF" (Amendment 7(d): a read that failed is no answer). 1 is a register it could not read -# (its own "R22: never 8"), 2 a name refused or a clone that failed, 4 the lock that makes the checkout, and a -# `lanes-edit.sh` predating `handoff-path` says 2 from its unknown-subcommand arm. A 2 is asked a second time -# with only its stderr kept, as `sread` asks, to tell those apart: WITHOUT `--create`, so it is a read and -# clones nothing, and without the fetch. +# (its own "R22: never 8"), 2 a name refused, a cell refused (601, 602, 607) or a clone that failed, 4 the lock +# that makes the checkout, and a `lanes-edit.sh` predating `handoff-path` says 2 from its unknown-subcommand +# arm. A 2 is asked a second time with only its stderr kept, as `sread` asks, to tell those apart: WITHOUT +# `--create`, so it is a read and clones nothing, and without the fetch. [[ "$hf_rc" != 2 ]] || hf_said="$(LANES_NO_FETCH=1 "$L" handoff-path "$lane" 2>&1 >/dev/null || :)" # THE NEW TOP BLOCK, AND ONLY IT, goes in `$block_file`, ending with a newline, WHATEVER THE READ ANSWERED. # `write-handoff --prepend` puts it in front of the handoff as the verb reads it under the lock (lane @@ -393,10 +395,13 @@ else "$L" "$lane" "$hf_keep" ;; 2:*) # 601 (lane openRepoTools-3's review of #312, and its correction 6368669583): a row whose handoff is in W - # or in another checkout of the register beside it is refused by the translation `write-handoff` shares - # with `handoff-path`, 2 with no path printed. That file is not this lane's to write, by hand either; the - # kept block goes to the lane's own handoff once the row's cell names it (983). - printf 'HANDOFF READ FAILED: `%s handoff-path %s --create` exited 2 (its words are above): a name refused, a clone of the lane checkout that failed, or a handoff the row names in W or in another checkout of the register beside it (601). Where its words name such a file, the handoff it names belongs to another repository and not to this lane: nothing is to be written there, by this skill or by hand, and the new top block goes to the handoff of this lane, in its own checkout, once the handoff cell of its row is corrected to name it. The read failed, and that is not "no handoff" (Amendment 7(d)), so the call under "THE WRITE IS ONE CALL" is NOT made. %s\n' \ + # or in another checkout of the register beside it, a relative cell that climbs out of the lane checkout + # (602), and a path there with a glob character (607, `handoff-path`'s since 991) are refused by the + # translation `write-handoff` shares with `handoff-path`, 2 with no path printed. The helper calls the + # first "another checkout of the register" (985: never 8(d)'s "another repository"). That file is not this + # lane's to write, by hand either; the kept block goes to the lane's own handoff once the row's cell + # names it (983). + printf 'HANDOFF READ FAILED: `%s handoff-path %s --create` exited 2 (its words are above): a name refused, a clone of the lane checkout that failed, or a handoff cell that handoff-path and write-handoff both refuse: a handoff the row names in W or in another checkout of the register beside it (601), a relative cell whose . or .. components climb out of the lane checkout (602), or a path there that holds a glob character (607). Where its words name such a cell, the file it names is not the handoff of this lane: nothing is to be written there, by this skill or by hand, and the new top block goes to the handoff of this lane, in its own checkout, once the handoff cell of its row is corrected to name it. The read failed, and that is not "no handoff" (Amendment 7(d)), so the call under "THE WRITE IS ONE CALL" is NOT made. %s\n' \ "$L" "$lane" "$hf_keep" ;; *) printf 'HANDOFF READ FAILED: `%s handoff-path %s --create` exited %s (its words are above). The read failed, and that is not "no handoff" (Amendment 7(d)), so the call under "THE WRITE IS ONE CALL" is NOT made. %s\n' \ diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index 4b02e60..6e2eb3f 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -650,7 +650,7 @@ def test_a_handoff_that_is_not_found_is_said_and_write_handoff_is_never_called( #: 601's refusal, as slice 2a's `handoff-path` says it from A54 (`6d861d4`) on: #: the row names a handoff in another checkout of the register beside W, so the #: path is not printed, with exit 2 (lane openRepoTools-3's correction 6368669583). -ANOTHER_REPOSITORY = ( +ANOTHER_CHECKOUT = ( "lanes-edit.sh: lane repoHS-1's handoff handoffs/estate/repoHS-1.md is " "/w-lanes/repoHS-2/handoffs/estate/repoHS-1.md, in another checkout of the register " "beside W (/w), not in the checkout /w-lanes/repoHS-1 this lane's writes go through: " @@ -658,6 +658,35 @@ def test_a_handoff_that_is_not_found_is_said_and_write_handoff_is_never_called( "`write-handoff` refuses it, so no path is printed for a restart to bind or a launch " "to read (601). Nothing was written.") +#: 602's refusal (A55): a relative cell whose `.` or `..` components climb out +#: of the lane's checkout, 2 with nothing printed. +CLIMBS_OUT = ( + "lanes-edit.sh: lane repoHS-1's handoff cell ../repoHS-2/handoffs/estate/repoHS-1.md 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.") + +#: 607's refusal, which `handoff-path` makes since 2a's A60 (991): a glob +#: character in the handoff's path in the lane's checkout, 2 with no path. +GLOBBED = ( + "lanes-edit.sh: lane repoHS-1's handoff handoffs/estate/repo*.md is handoffs/estate/repo*.md " + "in the checkout /w-lanes/repoHS-1, 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.") + +#: What the 2 arm says of a cell `handoff-path` and `write-handoff` both refuse, +#: in the helper's terms (985): another checkout of the register, never 8(d)'s +#: "another repository"; 601, 602 and 607 named; and where the kept block goes. +CELL_REFUSED = [ + "exited 2 (its words are above)", + "in W or in another checkout of the register beside it (601)", + "climb out of the lane checkout (602)", + "holds a glob character (607)", + "the file it names is not the handoff of this lane: " + "nothing is to be written there, by this skill or by hand", + "the new top block goes to the handoff of this lane, in its own checkout, once the handoff " + "cell of its row is corrected to name it"] + @pytest.mark.parametrize("shell", SHELLS) @pytest.mark.parametrize("rc, err, must, never", [ @@ -668,21 +697,17 @@ def test_a_handoff_that_is_not_found_is_said_and_write_handoff_is_never_called( ("2", OLDER_HELPER, ["exited 2", "unknown subcommand", "predates handoff-path", "Update lanes-edit.sh", "step 2 again"], ["belongs to another repository"]), - ("2", ANOTHER_REPOSITORY, - ["exited 2 (its words are above)", - "the handoff it names belongs to another repository and not to this lane: " - "nothing is to be written there, by this skill or by hand", - "the new top block goes to the handoff of this lane, in its own checkout, once the handoff " - "cell of its row is corrected to name it"], - ["Update lanes-edit.sh", "step 2 again"]), + ("2", ANOTHER_CHECKOUT, CELL_REFUSED, ["Update lanes-edit.sh", "step 2 again", "another repository"]), + ("2", CLIMBS_OUT, CELL_REFUSED, ["Update lanes-edit.sh", "step 2 again", "another repository"]), + ("2", GLOBBED, CELL_REFUSED, ["Update lanes-edit.sh", "step 2 again", "another repository"]), ("4", "the lock that makes the lane checkout is held", ["exited 4", "Run step 2 again"], ["Update lanes-edit.sh"]), ("64", "usage: handoff-path [--create]", ["exited 64 (its words are above)"], ["Update lanes-edit.sh", "step 2 again"]), ("127", "lanes-edit.sh: not found", ["exited 127 (its words are above)"], ["Update lanes-edit.sh", "step 2 again"]), -], ids=["unreadable", "clone-failed", "older-helper", "another-repository", "lock", "usage", - "no-helper"]) +], ids=["unreadable", "clone-failed", "older-helper", "another-checkout", "climbs-out", "globbed", + "lock", "usage", "no-helper"]) def test_a_read_that_failed_is_never_said_as_no_handoff(estate, shell, rc, err, must, never): """rv3's MUST 1 on #311 at `b599ed9`: only `handoff-path`'s 8 is "no handoff". Any other exit is a read that FAILED (Amendment 7(d); the verb's @@ -707,6 +732,22 @@ def test_a_read_that_failed_is_never_said_as_no_handoff(estate, shell, rc, err, assert "write-handoff" not in estate.verbs(), estate.calls() +def test_a_cell_the_translation_refuses_is_said_in_the_helpers_terms(): + """985 of lane openRepoTools-3's review of #311: the 2 arm and the + paragraph above it called a file in W or in a sibling clone "another + repository", which is Amendment 8(d)'s term; the helper calls it "another + checkout of the register". Both now say so, and name 602 (a relative cell + that climbs out) and 607 (a glob character, refused by `handoff-path` since + 991) beside 601.""" + flat = " ".join(re.sub(r"(?m)^\s*#\s?", "", _text()).split()) + assert "belongs to another repository" not in flat, "8(d)'s term for a sibling checkout is back" + held = [p for p in _prose("## 2. Refresh the handoff") if "In every one of these cases" in p] + assert len(held) == 1, held + for words in ("in another checkout of the register beside it", "(602)", "glob character (607", + "is not this lane's handoff"): + assert words in held[0], f"the paragraph no longer says {words!r}:\n{held[0]}" + + @pytest.mark.parametrize("shell", SHELLS) @pytest.mark.parametrize("rc", ["1", "2", "4"]) def test_only_a_2_is_asked_again_and_never_with_create(estate, shell, rc): From 11c854fe546885bad69d28f7eb1736caaffbe530 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Sun, 8 Nov 2026 14:44:24 +0000 Subject: [PATCH 14/17] Slice 3, NIT 986 of lane openRepoTools-3's review of #311: with 2a's round merged, the one-call paragraph no longer says "once ... finding 601 ... is in": the verb's 601, 602 and 607 refusals are each a 2 with nothing written, and the block under WHERE THE HANDOFF IS meets them first (#198) The paragraph under THE WRITE IS ONE CALL said "once lane openRepoTools-3's finding 601 on opensoft/openRepoTools#312 is in, so is a handoff target in W ...", false since the merge of 2a's head. It now says the verb's refusals as they are: a handoff target in W or in another checkout of the register beside it (601), a relative cell that climbs out of the lane's checkout (602) and a path there with a glob character (607), each a 2 with nothing written, and `handoff-path` shares them. The other phrase 986 names ("a restart fence's refusal is 7 too, where it is 2 today") went with 981's rewrite of the 7 arm (1052cd5). Tests: a text test that neither phrase is back and the paragraph names 601, 602 and 607. Refs #198 Co-Authored-By: Claude Opus 5.5 --- skills/handoff/SKILL.md | 9 +++++---- tests/test_handoff_skill_commit_block.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index 92beb9c..85ff4ea 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -502,10 +502,11 @@ checkout's lock, and `LANES_LANE` names the lane. (This block used to commit in file, the shared workspace checkout among them, with no lock, and to rebase on a pull before its push; finding 282 of the review of opensoft/openRepoTools#307 names it one of the three writers that did.) A STAGED ENTRY THIS HANDOFF DID NOT MAKE and an operation in progress are the verb's refusals now, made before anything is -committed (2); once lane openRepoTools-3's finding 601 on opensoft/openRepoTools#312 is in, so is a -handoff target in W or in another checkout of the register beside it, with nothing written, and since -`handoff-path` shares that refusal, the block under WHERE THE HANDOFF IS meets it first and this call is not -made. +committed (2). So are, each a 2 with nothing written, a handoff target in W or in another checkout of the +register beside it (601, lane openRepoTools-3's finding on opensoft/openRepoTools#312), a relative cell that +climbs out of the lane's checkout (602) and a path there with a glob character (607), and since +`handoff-path` shares those refusals, the block under WHERE THE HANDOFF IS meets them first and this call is +not made. THE NEW FILE IS MADE BY THE VERB, NEVER BY HAND (rv3's SHOULD 1 and lane openRepoTools-3's finding 981 on #311, option (a)): `--prepend "$block_file"` reads the handoff under the lane checkout's lock, from the bytes S3 has just brought to origin's, puts the new top block, a `---` line and a blank line in front of it, and diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index 6e2eb3f..c1849ab 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -597,6 +597,23 @@ def test_refusal_text_keeps_R_A11_11(): assert words in paragraph, f"the paragraph no longer says {words!r}:\n{paragraph}" +def test_the_one_call_paragraph_says_what_2a_has_merged_and_not_what_it_once_would(): + """NIT 986 of lane openRepoTools-3's review of #311: "once ... finding 601 + ... is in" and "a restart fence's refusal is 7 too, where it is 2 today" + were false once 2a's round was merged here. The paragraph says the verb's + refusals as they are: 601, 602 and 607, each a 2 with nothing written, met + first by the block under WHERE THE HANDOFF IS.""" + flat = " ".join(re.sub(r"(?m)^\s*#\s?", "", _text()).split()) + for gone in ("is in, so is", "where it is 2 today"): + assert gone not in flat, f"{gone!r} is back" + held = [p for p in _prose("## 2. Refresh the handoff") + if p.startswith("**THE WRITE IS ONE CALL, AND IT COMMITS")] + assert len(held) == 1, held + for words in ("(601, ", "(602)", "(607)", "with nothing written", + "the block under WHERE THE HANDOFF IS meets them first and this call is not made"): + assert words in held[0], f"the paragraph no longer says {words!r}:\n{held[0]}" + + def test_the_handoff_write_is_said_as_the_verbs_and_not_as_a_commit_of_the_skill(): """rv3's NIT 3 on #311: step 1's comment, `$ws_missing`'s paragraph and step 4's (c) comment called step 2's write a commit "in the LANE'S OWN From 3e828ee8bdb7959a82d3894adb4f809ae71cf284 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Sun, 8 Nov 2026 14:51:33 +0000 Subject: [PATCH 15/17] Slice 3, NIT 987 of lane openRepoTools-3's review of #311: two rows kill the two mutants that survived the module, a `handoff-path` 0 that names no file (step 4 says "no handoff found", never "the read failed, exit 0") and a step 4 run after block 1 alone (an empty `$hf_record` is no outcome) (#198) Lane 3 found two mutants of the skill that every case survived: - `[[ "$hf_rc" != 0 && "$hf_rc" != 8 ]]` changed to `[[ "$hf_rc" != 8 ]]` in block 1 records "(the handoff read failed, exit 0)" for a 0 with no file, where F12 promises "(no handoff found)". The SHOULD 3 table gains a `zero-with-no-file` row (`handoff-path` exits 0 and names /nowhere/at/all.md). - `${hf_record:-...}` changed to `${hf_record-...}` in step 4 writes a line ending "; " after block 1 alone, because block 1 sets `hf_record` empty. `test_a_step_4_after_block_1_alone_never_claims_a_refresh` runs block 1, then step 4 in the same shell: "(step 2 left no outcome)". No change to the skill. Refs #198 Co-Authored-By: Claude Opus 5.5 --- tests/test_handoff_skill_commit_block.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index c1849ab..68d64bd 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -824,6 +824,9 @@ def _register_line(estate: Estate) -> str: "; handoff written, its publication NOT confirmed (write-handoff exited 1)"), ({"STUB_HP_RC": "8", "STUB_HP_OUT": ""}, False, True, "; handoff NOT refreshed (no handoff found)"), + # NIT 987: a 0 that names no file is "no handoff found" too, never "the read failed, exit 0". + ({"STUB_HP_OUT": "/nowhere/at/all.md"}, False, True, + "; handoff NOT refreshed (no handoff found)"), ({"STUB_HP_RC": "4", "STUB_HP_OUT": ""}, False, True, "; handoff NOT refreshed (the handoff read failed, exit 4)"), ({"STUB_HP_RC": "2", "STUB_HP_OUT": "", "STUB_HP_ERR": OLDER_HELPER}, False, True, @@ -831,8 +834,8 @@ def _register_line(estate: Estate) -> str: ({}, False, False, "; handoff NOT refreshed (write-handoff was not called)"), ], ids=["pushed", "owned-by-a-restart", "block-with-no-final-newline", "refused-before-writing", "refused-after-writing", - "pushed-not-confirmed", "kept-on-head", "written-then-failed", "not-found", "read-failed", - "older-helper", "never-prepared"]) + "pushed-not-confirmed", "kept-on-head", "written-then-failed", "not-found", "zero-with-no-file", + "read-failed", "older-helper", "never-prepared"]) def test_step4_register_line_says_what_step_2_did(estate, shell, extra, written, prepare, ends): """rv3's SHOULD 3 on #311 at `b599ed9`: the register's PAUSED line ended `; handoff refreshed` whatever step 2 printed. Step 2 now carries its @@ -933,6 +936,20 @@ def test_the_prose_says_what_to_set_before_block_2_and_before_step_4(): assert words in paragraph, f"the paragraph no longer says {words!r}:\n{paragraph}" +@pytest.mark.parametrize("shell", SHELLS) +def test_a_step_4_after_block_1_alone_never_claims_a_refresh(estate, shell): + """NIT 987: block 1 found the handoff and set `hf_record` empty, and block + 2 never ran. Step 4 in the same shell says the handoff was NOT refreshed: + an empty `$hf_record` is no outcome, as an unset one is.""" + script = "\n".join([f"L={shlex.quote(str(estate.helper))}", f"lane={LANE}", _block(B1), + estate.step4()]) + proc = estate.run(shell, script) + assert proc.returncode == 0, proc.stderr + assert "write-handoff" not in estate.verbs(), estate.calls() + line = _register_line(estate) + assert line.endswith("; handoff NOT refreshed (step 2 left no outcome)"), line + + # --- the real helper: the handoff is pushed from C, and W is untouched -------- REAL_NEW = prepended(HANDOFF, BLOCK) From 2a420e025378fe11584e9850ad868a0c978d01c2 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Sun, 8 Nov 2026 15:00:09 +0000 Subject: [PATCH 16/17] Slice 3, lane openRepoTools-3's answer 6 and INFO 990 on #311: step 4's record takes the refined phrases the skill and 2b's `lane-handoff` share ("handoff written, NOT published" for a 2 or a 6 that wrote the file, "its publication NOT confirmed" for a 3 or any other), and the 0 arm says the verb's note may say nothing was committed (#198) Answer 6: the two writers of one act record its outcome in one set of phrases, "refined on both sides", and 2b's fit (c) takes them: - `handoff refreshed` only on a 0; - `handoff written, NOT published (write-handoff exited )` where the verb wrote the file and exited 2 (the commit refused) or 6 (kept unpushed, or not made); - `handoff written, its publication NOT confirmed (write-handoff exited )` where it wrote the file and exited 3, or any other exit, where the verb said nothing of what it published; - `handoff NOT refreshed ()` where nothing was written. INFO 990, against 2b's 721 and 728: a 0 may commit nothing (LANES_NO_GIT=1, an Amendment 8(d) handoff in no git work tree, a clone of the register not beside W), and since 604 the verb's note says "NOT committed" in each. The 0 arm now reads "(the note above, from write-handoff, says what was committed and where it was pushed, or that nothing was committed)". The record of a 0 stays `handoff refreshed`, which claims no commit, and is `lane-handoff`'s phrase for the same act. Tests: the step-4 table's 2 and 6 rows that wrote the file, the 0 arm's pin, and the paragraph's phrase list. Refs #198 Co-Authored-By: Claude Opus 5.5 --- skills/handoff/SKILL.md | 22 ++++++++++++++------ tests/test_handoff_skill_commit_block.py | 26 +++++++++++++++++++++--- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/skills/handoff/SKILL.md b/skills/handoff/SKILL.md index 85ff4ea..f5cf9f4 100644 --- a/skills/handoff/SKILL.md +++ b/skills/handoff/SKILL.md @@ -545,7 +545,10 @@ if [[ -n "$handoff_file" && -s "${block_file:-}" ]]; then hf_wrote="nothing was written"; [[ -z "$hf_out" ]] || hf_wrote="the file is written at $hf_path" hf_kept="The new top block is kept at $block_file: name that path in the report. Where it is still to go into the handoff once this is answered, it goes by this one call, never by a hand edit:" case "$hf_rc" in - 0) printf 'handoff written: %s (the note above, from write-handoff, says what was committed and where it was pushed)\n' "$hf_path" ;; + # A 0 MAY COMMIT NOTHING (lane openRepoTools-3's INFO 990 on #311; 2b's 721 and 728): LANES_NO_GIT=1, an + # Amendment 8(d) handoff in no git work tree, or one in a clone of the register not beside W is written and + # NOT committed, and the verb's note says so. So the words defer to that note and claim no commit. + 0) printf 'handoff written: %s (the note above, from write-handoff, says what was committed and where it was pushed, or that nothing was committed)\n' "$hf_path" ;; 2) if [[ -n "$hf_out" ]]; then printf 'HANDOFF NOT COMMITTED: write-handoff refused the commit (exit 2; its words are above) and the file is written at %s, uncommitted. Nothing was pushed. Do not call it again: the new top block is at the head of the file now, and a second call would put a second copy of it on top. Answer the refusal, then commit and publish the file as the note above says.\n' "$hf_path" else @@ -565,10 +568,15 @@ if [[ -n "$handoff_file" && -s "${block_file:-}" ]]; then 64) printf 'HANDOFF NOT WRITTEN: write-handoff refused the call (exit 64; its words are above) and wrote nothing. Where they say the block does not end with a newline, end it with one (echo >> %s) and run this block once more, once.\n' "$block_file" ;; *) printf 'HANDOFF NOT PUBLISHED: write-handoff exited %s (its words are above); %s.\n' "$hf_rc" "$hf_wrote" ;; esac - # WHAT THIS CALL DID, CARRIED TO STEP 4'S REGISTER LINE (rv3's SHOULD 3 on #311): refreshed only on a 0, written - # but not confirmed published where the verb wrote the file (its stdout) and exited otherwise, else NOT refreshed. + # WHAT THIS CALL DID, CARRIED TO STEP 4'S REGISTER LINE (rv3's SHOULD 3 on #311), in the phrases lane + # openRepoTools-3's answer 6 has the skill and 2b's `lane-handoff` share: refreshed only on a 0; written, NOT + # published where the verb wrote the file (its stdout) and refused the commit (2) or kept it unpushed (6); + # written, its publication NOT confirmed where it wrote the file and exited 3, or otherwise; else NOT refreshed. hf_record="handoff NOT refreshed (write-handoff exited $hf_rc and wrote nothing)" [[ -z "$hf_out" ]] || hf_record="handoff written, its publication NOT confirmed (write-handoff exited $hf_rc)" + case "$hf_rc:${hf_out:+written}" in + 2:written|6:written) hf_record="handoff written, NOT published (write-handoff exited $hf_rc)" ;; + esac [[ "$hf_rc" != 0 ]] || hf_record="handoff refreshed" # WHERE NOTHING WAS WRITTEN, THE NEW TOP BLOCK IS NOT LOST WITH THE SWAP: its temporary file is named for the report. [[ -n "$hf_out" ]] || printf '%s\n%s\n' "$hf_kept" "$hf_call" @@ -594,10 +602,12 @@ do, and the report carries that line. Never force, never `git add -A`, and never commit staged entries you did not make. **WHAT THIS STEP DID IS CARRIED TO STEP 4 in `$hf_record`,** so that the register's PAUSED line says it and -never claims it: `handoff refreshed` only where the call exited 0; `handoff written, its publication NOT -confirmed (write-handoff exited )` where the verb wrote the file and exited otherwise; and `handoff NOT +never claims it: `handoff refreshed` only where the call exited 0; `handoff written, NOT published +(write-handoff exited )` where the verb wrote the file and exited 2 or 6; `handoff written, its publication +NOT confirmed (write-handoff exited )` where it wrote the file and exited 3, or otherwise; and `handoff NOT refreshed ()` wherever nothing was written: the handoff was not found, its read failed, the call was not -made, or the verb wrote nothing. EVERY BASH CALL IS A NEW SHELL, so nothing one block sets reaches the next +made, or the verb wrote nothing. These are the phrases lane openRepoTools-3's answer 6 on #311 has 2b's +`lane-handoff` take too, through its fit (c), so the two writers of one act record its outcome alike. EVERY BASH CALL IS A NEW SHELL, so nothing one block sets reaches the next by itself, and step 3 comes between step 2 and step 4 (lane openRepoTools-3's finding 984 on #311). Before block 2, set `L`, `lane`, `ws` and `why` as step 1 has them, and each `name=value` line block 1 printed (`handoff_file`, `block_file` and `hf_record`, those it printed), quoted. Before step 4, set `hf_record` from diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index 68d64bd..2c31ed9 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -424,7 +424,10 @@ def _says(estate: Estate) -> dict: return { # rv3's NIT 2: the 0 arm claims no push of its own; the verb's note says what it # committed and where (an Amendment 8(d) handoff in its own top level, S2). - "pushed": (0, True, [f"handoff written: {path} (", "says what was committed and where it was pushed"], + # INFO 990 of lane openRepoTools-3's review of #311: a 0 may commit nothing (LANES_NO_GIT=1, an + # 8(d) handoff in no work tree, a clone of the register not beside W), and the verb's note says so. + "pushed": (0, True, [f"handoff written: {path} (", "says what was committed and where it was pushed", + "or that nothing was committed"], ["HANDOFF NOT", "pushed from the lane checkout", "never committed"]), "refused-after-writing": (2, True, [ "HANDOFF NOT COMMITTED", f"written at {path}, uncommitted", "Nothing was pushed", @@ -815,11 +818,11 @@ def _register_line(estate: Estate) -> str: ({"STUB_WH_RC": "2"}, False, True, "; handoff NOT refreshed (write-handoff exited 2 and wrote nothing)"), ({"STUB_WH_RC": "2"}, True, True, - "; handoff written, its publication NOT confirmed (write-handoff exited 2)"), + "; handoff written, NOT published (write-handoff exited 2)"), ({"STUB_WH_RC": "3"}, True, True, "; handoff written, its publication NOT confirmed (write-handoff exited 3)"), ({"STUB_WH_RC": "6"}, True, True, - "; handoff written, its publication NOT confirmed (write-handoff exited 6)"), + "; handoff written, NOT published (write-handoff exited 6)"), ({"STUB_WH_RC": "1"}, True, True, "; handoff written, its publication NOT confirmed (write-handoff exited 1)"), ({"STUB_HP_RC": "8", "STUB_HP_OUT": ""}, False, True, @@ -921,6 +924,23 @@ def test_block_1_block_2_and_step_4_each_in_its_own_shell_carry_what_step_2_did( assert "step 2 left no outcome" not in line, line +def test_the_record_phrases_are_lane_3s_refined_set(): + """Lane openRepoTools-3's answer 6 on #311: the skill and 2b's + `lane-handoff` record one act's outcome in one set of phrases, "refined on + both sides": "written, NOT published (exited n)" for a 2 or a 6 that wrote + the file, and "publication NOT confirmed" for a 3, and for any other exit + that wrote the file, where the verb said nothing of what it published.""" + held = [p for p in _prose("## 2. Refresh the handoff") + if p.startswith("**WHAT THIS STEP DID IS CARRIED TO STEP 4")] + assert len(held) == 1, held + for words in ("`handoff refreshed` only where the call exited 0", + "`handoff written, NOT published (write-handoff exited )` where the verb wrote the file " + "and exited 2 or 6", + "`handoff written, its publication NOT confirmed (write-handoff exited )` where it wrote " + "the file and exited 3, or otherwise"): + assert words in held[0], f"the paragraph no longer says {words!r}:\n{held[0]}" + + def test_the_prose_says_what_to_set_before_block_2_and_before_step_4(): """984: the paragraph that carries step 2's outcome to step 4 says every Bash call is a new shell, what to set before block 2 (step 1's values From 7690a1fcbb6a2965ca0d2066f8be6d7101af3be0 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Sun, 8 Nov 2026 18:00:42 +0000 Subject: [PATCH 17/17] Slice 3, lane openRepoTools-3's answer 3: the handoff skill joins `_NO_PULL_FILES`, whose comment now says why, and the module's stand-in for that entry is retired, because the generic test holds the same rule over the same file (opensoft/openRepoTools#198) Lane 3's answer 3 on opensoft/openRepoTools#311 left the entry `"skills/handoff/SKILL.md"` and 2b's hygiene comment ("the skill, which still pulls until its slice, joins it then") to one edit, made with whichever of 2b and this slice lands second. The one sitting that lane 3 ruled (opensoft/openRepoTools#198, comment 6724194619) lands 2b first and this slice second, so the edit is this slice's. tests/test_repo_hygiene.py: `_NO_PULL_FILES` takes "skills/handoff/SKILL.md" as its fourth name, and the comment above it says in the past tense that the handoff skill is asserted in slice 3, whose top block is `write-handoff --prepend`'s now. tests/test_handoff_skill_commit_block.py: the module's test_the_skill_never_pulls_with_rebase_outside_a_comment is retired. It was the stand-in for the entry "until the third name goes in" (F7), and its own docstring says the file list takes the skill at the merge. It asserted nothing the generic test does not: the same pattern (`\bpull\b[^\n]*?(?:--rebase\b|\s-r\b)`), the same filter (a line that is not a comment), the same file (`skills/handoff/SKILL.md` under the code root, read as UTF-8). The generic test also has its own cases of what the pattern reads. Red first: with `git pull --rebase origin main` appended to the skill, outside a comment, in a scratch worktree, the generic test passed on the three names and only the stand-in failed (the hole the entry closes); with the entry, and the stand-in gone, the generic test fails on `test_no_lane_writer_pulls_with_rebase[skills/handoff/SKILL.md]` at line 1118. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 --- tests/test_handoff_skill_commit_block.py | 10 ---------- tests/test_repo_hygiene.py | 9 +++++---- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/tests/test_handoff_skill_commit_block.py b/tests/test_handoff_skill_commit_block.py index 2c31ed9..565da72 100644 --- a/tests/test_handoff_skill_commit_block.py +++ b/tests/test_handoff_skill_commit_block.py @@ -372,16 +372,6 @@ def test_the_variables_the_call_names_are_defined_in_block_1(): "the old call is gone: write-handoff is the one act") -def test_the_skill_never_pulls_with_rebase_outside_a_comment(): - """Amendment 23 S3-S4 (`tests/test_repo_hygiene.py`'s no-pull rule, whose - file list takes this file at the merge): no line of the skill that is not a - comment runs, or tells a person to run, a rebasing pull.""" - pull = re.compile(r"\bpull\b[^\n]*?(?:--rebase\b|\s-r\b)") - found = [n for n, line in enumerate(_text().splitlines(), 1) - if not line.lstrip().startswith("#") and pull.search(line)] - assert not found, f"{SKILL.name} line(s) {found} run or print a rebasing pull" - - def test_the_blocks_parse_under_bash(tmp_path): for name, marker in (("first", B1), ("second", B2)): script = tmp_path / f"{name}.sh" diff --git a/tests/test_repo_hygiene.py b/tests/test_repo_hygiene.py index d5e39e6..9f7bd16 100644 --- a/tests/test_repo_hygiene.py +++ b/tests/test_repo_hygiene.py @@ -3289,11 +3289,12 @@ def test_the_exit_3_rule_is_green_on_a_rewording_that_keeps_the_meaning( #: AMENDMENT 23 S3-S4: NO LANE WRITER PULLS (#198). A `pull --rebase` in front #: of a push is what stranded a write on a stale base eight times in two days; #: every writer now fetches and fast-forwards before the edit, and a refused -#: push replays the one edit. Asserted for `lanes-edit.sh` in slice 1b, and for +#: push replays the one edit. Asserted for `lanes-edit.sh` in slice 1b, for #: `lane-start` and `lane-handoff` in slice 2b, whose Rule 3 stamp and PAUSED -#: top block are `write-handoff`'s now; the skill, which still pulls until its -#: slice, joins it then. -_NO_PULL_FILES = ("lanes-edit.sh", "lane-start", "lane-handoff") +#: top block are `write-handoff`'s now, and for the handoff skill in slice 3, +#: whose top block is `write-handoff --prepend`'s now. +_NO_PULL_FILES = ("lanes-edit.sh", "lane-start", "lane-handoff", + "skills/handoff/SKILL.md") #: A `pull` that rebases: `--rebase`, `-r` or `--rebase=…`, on a line that is #: not a comment. _PULL_REBASE = re.compile(r"\bpull\b[^\n]*?(?:--rebase\b|\s-r\b)")