Repository navigation
Amendment 23 S6.2: lane-end --sweep-checkouts removes the register checkouts CLOSED lanes left under <W>-lanes/, and the clones beside W, only where nothing holds them, each decided under its lock after a fetch (opensoft/openRepoTools#198, slice 4) - #21
Draft
brettheap wants to merge 57 commits into
Draft
brettheap wants to merge 57 commits into
brettheap wants to merge 57 commits into
Conversation
…eckouts CLOSED lanes left under `<W>-lanes/`, and every clone beside W, in a dry run that removes nothing (#198, slice 4 stage 1) Stage 1 of slice 4: the flag, its refusals, the candidate list and the dry run. - `--sweep-checkouts` is its own act, answered before the lane derivation like `--retire-dormant`. It refuses a lane, every other act's flag, `--yes` with no `LANES_LANE`, `--yes` under `LANES_NO_FETCH=1` (S3 refuses a writer whose fetch did not run, rv1b's MUST 3), and a W that stands in a `*-lanes` directory (as `lanes_home_check` does: from a lane window every other lane's checkout would stand "beside W"). - A candidate is a clone of the register directly under `<W>-lanes/`, at its own real path with a `.git` directory of its own, whose lane `lanes --closed --all` classes `closed`, or a former name's checkout beside its new one (S1). Never W, a LIVE lane's checkout, the runner's own, the checkout a caller's `LANES_REPO` names, a link, a non-clone or a hidden entry. - The clones beside W are listed and never candidates: this build removes nothing outside `<W>-lanes/` (the brief's rule, kept against S6.2's wider one until lane openRepoTools-3 rules). - The dry run exits 3 when there is a candidate and 0 when there is none. `--yes` is refused in this commit; the holds and the removal are stage 2. Tests: tests/test_a23_sweep_checkouts.py, 16 cases on `tests/a23_box.py`'s RegisterBox, in #202's `ended` style. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit faac2e7b90cec371780ec785cb513e24f2b7b0b6)
…D lane's register checkout only when nothing holds it, deciding each one again under its own writers' lock after a fetch (#198, slice 4 stage 2)
Stage 2 of slice 4: the holds and the removal.
- The holds (S6.2 (a) to (c)), each reported with what it holds:
- (a) a `+` under `git cherry` on any local branch or a detached HEAD
(refs spelled in full), a merge commit origin lacks (which `git cherry`
skips), any other local ref reaching what origin lacks, a stash, staged
or modified files, index entries marked assume-unchanged or
skip-worktree, untracked files anywhere, an operation in progress,
unmerged entries, an `index.lock`, the checkout's own writers' lock, and
no `origin/<branch>` to compare with. Ignored files hold nothing and are
named.
- (b) another repository (W, one beside it, or one under `<W>-lanes/`)
whose alternates file names its objects, and linked worktrees.
- (c) a live process with its working directory inside (`/proc/*/cwd`,
or `lsof`; with neither, "cannot tell" holds it).
- `--yes`, one candidate at a time (rv1b's MUSTs 1-2): take its
`lanes/.lanes-edit.lock` with `lanes-edit.sh`'s pid line, fetch it
(`LANES_GIT`, `LANES_GIT_TIMEOUT`, an explicit refspec), ask the lane's
state again (`lanes --fetch --closed --lane`), read every hold, what leans
on it and the working directories again, and only then move it aside to
`<W>-lanes/.<name>.removing-<UTC>-<pid>` and remove that. A removal that
does not finish says so by path and exits 1.
- The dry run tags each candidate `remove` or `hold`, and lists what holds
each clone beside W. Nothing outside `<W>-lanes/` is removed.
The NOTED line and the leaves-first order are stage 3.
Tests: tests/test_a23_sweep_checkouts.py, 37 cases (16 of stage 1, now
reading `remove`, and 21 new: the act, 14 holds, a dry-run-only hold, a
live cwd, the re-check under the lock, a lane resumed after the listing, a
failed fetch, and nothing beside W removed).
Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 991d8c6c97ee443897b4e0fc4787f4275440ae4c)
… of closed lanes' checkouts leaves first, keeps every clone a held one borrows from, and records what it removed in one NOTED line through the runner's own checkout (#198, slice 4 stage 3) Stage 3 of slice 4: the dependents order and the NOTED line. - Leaves first (S6.2 (b), D21): the candidates are taken in dependency order, every clone before the clones it borrows objects from, by passes that each place every candidate no unplaced candidate leans on, in listed order. A cycle places nothing more, and what is left goes last, held by what leans on it. The order is a plan: `--yes` still reads what leans on each candidate again just before its removal, so a held dependent keeps its whole chain. - What leans on a clone (D18): a repository whose alternates file names an object directory AT OR UNDER the clone, not only its own `.git/objects`, because removing the clone removes any store kept inside it. - The NOTED line (D9, D15, D20): after the removals, and only when something was removed, ONE `lanes-edit.sh log NOTED lane:<runner>` line in the runner's own log, written through the runner's own checkout under S3 and S4, in a subshell with `LANES_NO_FETCH` unset. Its text is built only from names held to letters, digits, `.`, `_` and `-`, and names no host path. A line the writer refuses exits 1 with the exact command that writes it by hand. - The session field (D19): `--yes` takes `LANES_SESSION`, or the lane's last recorded session, and refuses (2) before anything is removed when that is not a transcript uuid. Tests: tests/test_a23_sweep_checkouts.py, 47 cases (37 of stages 1-2, one of them now checking the runner's NOTED commit, and 10 new: leaves first in one run, a held link of the chain three ways, the NOTED line through the runner's checkout, nothing removed writes nothing, a refused NOTED line exits 1 with a working by-hand command, a non-uuid session refused, a store kept inside a clone held, and a directory sharing W's git directory kept as W). The CHAIN lanes repoS4-5, 6 and 7 are ENDED rows and logs in the fixture; their checkouts are made by the cases that need them. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 2140189e2c7850475a1135a9ac7b07bc2219fcca)
…LDs and NITs) into slice 4: git found no conflict, since slice 4 changes only `lane-end` and its own test module (#198) Brings slice 1b's six commits after `6ecfc52`, this slice's base: `c454247` (MUST 4: a refused push whose commit is no longer HEAD is not replayed), `10169f3` (MUSTs 1 and 2: `claim` and `add-row` run again on origin's new tip), `92fbe46` (SHOULD 1 and NIT 1: the manual and the carry and no-pull rules), `3c6b240` (rv1b's second report), `6471fd1` (MUST-N and SHOULD B) and `d9ff4e6` (SHOULD A and NITs 1-2). They change `lanes-edit.sh`, `docs/README-lanes.md`, `tests/test_repo_hygiene.py` and four `tests/test_a23_*`/`test_commit_push_*` modules. Slice 4 changes none of them, and `git merge-tree` predicted this tree before the merge. The meeting point was weighed, and nothing in slice 4 changes. The sweep's NOTED line is a `lanes-edit.sh log` write, and slice 1b's new code is on that write's path (`log_sync`, `checkout_sync`, `cp_replay`). The sweep relies on none of the replay's details: it passes the write the runner's lane and transcript uuid, leaves `LANES_NO_FETCH` unset so S3's fetch runs, and treats any refusal as exit 1 with the command that writes the line by hand. The new carry and no-pull rules read only `lanes-edit.sh` and the manual, and `lane-end` holds none of their words. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 210f4cfdbad2e427fe41b3fd6025b5b03971a2bb)
…) into slice 4: git found no conflict, since slice 4 changes only `lane-end` and its own test module (#198) Brings slice 1b's two commits after `d9ff4e6`, the head slice 4 last merged: `c71621f` (SHOULD C: the replay's drop is a compare-and-swap of the branch from this write's own commit, so a commit that lands in its window is never dropped) and `e6ea8f8` (the shell suite's stage 4: every case that pinned the pull, the capture, the carry or a race now pins what a write does instead). They change `lanes-edit.sh`, `docs/README-lanes.md`, `tests/test_a23_publish_replay.py` and `tests/test_lane_helpers.sh`. Slice 4 changes none of them, and `git merge-tree` predicted this tree before the merge. The meeting point was weighed again, and nothing in slice 4 changes. The sweep's NOTED line is a `lanes-edit.sh log` write, and `c71621f` is on that write's replay path. The sweep relies on none of the replay's details: it passes the write the runner's lane and transcript uuid, leaves `LANES_NO_FETCH` unset so S3's fetch runs, and treats any refusal as exit 1 with the command that writes the line by hand. The shell suite names no `--sweep-checkouts` case. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit cede05468222b3929d9d18fdf74e34f5670ab173)
…es on a register whose fetch of W failed, at the listing or at the re-ask under each checkout's lock (rv4's MUST 1, #198, slice 4) `lanes --fetch` exits 0 on `log_sync`'s `fell-back` (ls-remote answered, the fetch of W then failed or timed out) and reads the stale ref; it says so only in its notes, with the phrase "reading the logs as they stand locally". The sweep matched only "NO FETCH WAS MADE" at the listing and sent the re-ask's stderr to /dev/null, so `--yes` took a stale register for "fetched just now" and removed the checkout of a lane that had RESUMED on origin. - The listing (`--yes`): the refusal matches the shared phrase, so a failed fetch of W refuses with 1 and "Nothing was removed", as a fetch never made does. - `sc_still`: the re-ask's stderr is captured (into `LE_TMP`, which `cleanup` removes on every exit) and the checkout is held, with a reason that says the state was not read fresh, when it says "reading the logs as they stand locally", "session records could not be read" or "could not be read, so its rows are not in this listing". A capture that cannot be made or read back holds too. - Tests (`tests/test_a23_sweep_checkouts.py`), each red on `cede054` and green here: a W fetch that fails at the listing refuses `--yes` and changes nothing (rv4's P5); a lane that RESUMES at its checkout's fetch while W's fetch fails from then on is kept (P1); session records that become unreadable at the re-check keep the checkout. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit b896230ddb1aab37138889d048c33582bc4ec713)
…tes cannot be read back holds that checkout by itself, with its own reason, and a register unreadable at the re-check gets a test of its own (rv4-r2's SHOULD 1, #198, slice 4)
`sc_still` captured the re-ask's notes in `LE_TMP` and, where `cat` could
not read the file back, put a stand-in sentence in their place that was
written to match the "could not be read, so its rows are not in this
listing" clause. So the checkout was held only through that clause, the
reason it printed was false ("the register could not be read"), and a
reword of the clause would have let the path fail open with no test to
notice.
- `sc_still`: a failed `cat` of the capture sets its own reason ("its
capture could not be read back") and returns 1 at once, as the `mktemp`
branch above it does, and as the listing refuses on both.
- Tests (`tests/test_a23_sweep_checkouts.py`):
- the capture goes mode 000 during the re-ask's own fetch of W (rv4-r2's
probe): held, with the capture's reason and not the register's. Red at
`b896230` (it named the register), green here;
- the register cannot be read at the re-check: origin takes a commit that
changes it, the re-ask's fetch of W brings it in, and its blob is then
moved out of W's objects, so the tree names a register `git show`
cannot read. Held, "the register could not be read". It passes at
`b896230` too, since the clause was there; it is the test that now
reaches the clause, which the capture's stand-in reached before. With
the clause deleted, both the CLOSED lane's checkout and the former
name's are removed.
- `_push_from_peer`, a hook command in which the peer that seeded origin
appends a line to a file and pushes it.
Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 2e58431d2483147ebd9e74ea712892f95f8a380c)
…gin at its checkout's fetch share one hook command, `_resumed_on_origin`, instead of each spelling the peer's pull, append, commit and push (rv4-r2's NIT 2, #198, slice 4) The re-check case from rv1b's MUSTs 1-2 and the one from rv4's MUST 1 (P1) each carried the same five-step shell body, which Sonar's duplication check may flag. `_resumed_on_origin(box, lane)` builds it on `_push_from_peer`, and both cases call it. No case changes what it asserts. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit e152a89daa1ab9824646165c23bb42083a99e2d9)
…NO FETCH IS MADE is pinned at the listing and at the re-ask under a checkout's lock, as the fetch that fails after `ls-remote` already was (rv4-r2's NIT 1, #198, slice 4) The module tested only `log_sync`'s `fell-back` (the fetch fails after `ls-remote` answers). `lanes --fetch` says the other way, an `ls-remote` that fails, as "NO FETCH WAS MADE ... reading the logs as they stand locally", and nothing here made it. - The listing's refusal case is parametrized over the failing operation: the fetch (`fell-back`), and `ls-remote` (no fetch). Each refuses `--yes` with 1 and changes nothing. - A new case: W's `ls-remote` fails from the candidate's own fetch on, so the re-ask makes no fetch, and the checkout is held as "not read fresh ... W's fetch did not complete". - With the listing's refusal disabled both listing cases fail, and with `sc_still`'s fetch clause disabled the new case fails (and so does P1). Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit dfec3c5c03f2a170e2aa2e45f42a736826de8be5)
… or 6 (its commit kept) is no longer reported as "NOT written" with a by-hand command that would record the sweep twice; both are handed the publish act first (rv4's SHOULD 2, #198, slice 4)
`lanes-edit.sh log`'s 3 means the line was committed and pushed and the
read-back could not confirm it: it may be on origin. Its 6 means, in most of
its causes, that the commit is kept in the checkout the write went through
and only origin lacks it (a push refused with origin unmoved, the replay's
bound), and in the rest that `git add` or `commit` failed with nothing
committed. The sweep said "NOT written" for every non-zero exit and handed
out `log NOTED ...` by hand, which then wrote a second line, or met S3's
refusal of a checkout holding a commit origin lacks.
- 3: the sweep says the line may be on origin already and is not to be
written again, and prints the publish act (`LANES_LANE=<runner>
lanes-edit.sh commit "..."` with nothing staged), which sends the commit
only where origin lacks it by content and says so where it does not.
- 6: the publish act first; the by-hand line only where the publish act
finds nothing to send, which is the `git add`/`commit` failure.
- Any other refusal: as before, "NOT written" and the line by hand.
- Each remedy is printed on a line of its own. The exit is still 1.
- Tests (`tests/test_a23_sweep_checkouts.py`), both red at `dfec3c5` ("was
NOT written") and green here:
- rv4's P2: the real `log` runs and its wrapper exits 3; origin holds the
line once, the sweep hands out the publish act alone, and running it
leaves origin holding the line once;
- every push is refused with origin unmoved, so `log` exits 6 with its
commit kept on HEAD; the sweep hands out the publish act and then the
by-hand line; with pushes allowed again the publish act sends the kept
commit, and origin holds the line once.
- The refusing-log case now uses the shared `_wrapped_log` and `_by_hand`
helpers; it asserts what it did.
Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit a4cbf1a0d08d16cdee213c0b4a5b05477d5222ca)
…r each checkout's lock, and the exit for a removal that did not finish, each get a case, so none of rv4's four one-line mutants survives (rv4's SHOULD 1, #198, slice 4) rv4 found four one-line mutants of `lane-end` that no case killed. Each is a part of D14 (the decision made again under the lock) or an exit: - R1, the cwd scan not repeated under the lock: a process starts working in the CLOSED lane's checkout at that checkout's own fetch (rv4's P4), and the checkout is held, naming its pid. Under R1 it is removed. - R2, the listing's refusal on a W fetch that did not complete: killed since `dfec3c5` by both cases of the parametrized listing test (the fetch fails after `ls-remote`, and `ls-remote` fails). - R3, the former-name re-ask never holding: the listing finds repoS4-8's checkout beside its new name's, and at its own fetch the new name's checkout goes. The former name's checkout is then that lane's own again (S1), and is held as "no longer a former name". Under R3 it is removed. - R4, no exit 1 for a removal that did not finish: the CLOSED lane's checkout holds an ignored, read-only directory, so its aside cannot all be removed. It is named PARTLY, the run exits 1 naming the aside, and the aside stays. Under R4 the run exits 0. Skipped as root. Each was run once against its case in a separate clone (`c7-mut.py`): all three KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 9a7e8c88b544a0e3a0cdab17fc29f8444524de13)
… assert per condition, so a failure names the condition that failed (rv4's SHOULD 3, Sonar S9073, #198, slice 4) The defect slice 1b split out in `3c6b240`. `assert a and b` reports only that the whole was false; each condition is now its own assert, with the message the composite carried where it had one. The refusal cases (nothing under `<W>-lanes/` changed, origin unmoved), the beside-W listing, every hold, the fetch that fails, the NOTED line's fetch-before-push order, nothing removed writing nothing, the store kept inside a clone, and the directory sharing W's git directory. No case changes what it asserts. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 1345897d7bb9dcc64475ebc8616e3390607fb7cc)
… LIVE, at the listing and in the re-ask under its lock, since a window started before the rename may still write through it (rv4's SHOULD 4, D27, #198, slice 4)
Since 10-19 the launcher exports `LANES_REPO=<W>-lanes/<lane>`, and a window
of a lane started before its rename still names the old path. The sweep
took a former name's checkout beside its new one as a candidate whatever
the lane's state, and only its own caller's `LANES_REPO` was kept, so a
LIVE window's writes failed after the sweep until it was relaunched.
Nothing was lost (the holds pass), but the window broke.
- `sc_judge`: a former name's checkout beside its new one is kept while
that lane is LIVE ("a former name of lane X, which is LIVE: a window of
it started before the rename may still write through this path, which
its LANES_REPO names").
- `sc_still`: the former-name re-ask holds it when the lane reads LIVE now.
- D27: LIVE, and not `/proc/<pid>/environ`. On this workstation a window of
the lane is a live session record naming it, which is what LIVE is, and
the listing and the sweep already share that read. `environ` is the
environment at exec only, cannot be read for another user's processes,
and does not exist on macOS. A renamed lane's old checkout now stays
while the lane is LIVE; the next sweep after that removes it.
- Tests, both red at `1345897` (the former name's checkout is `remove`, and
removed) and green here: repoS4-9 LIVE (a live session record names its
transcript) keeps repoS4-8's checkout in the dry run and under `--yes`;
repoS4-9 coming LIVE at repoS4-8's own fetch holds it as "LIVE now".
Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 65594885b445802224064c5e2dcba54bca4425a9)
… it, the act names ignored files and counts a partial removal apart, and under each lock the sweep keeps its lock fresh, leaves one taken over, and asks again what made the checkout a candidate (rv4's NITs 1-4, #198, slice 4)
- NIT 1: `sc_dep_scan` reads the `.git` FILES in the directories it reads
alternates files in (W, the clones beside it, `<W>-lanes/`), and a clone
whose git directory, or one inside it, such a file names is held: "its
git directory is named by the .git file of X, which works in it" (rv4's
P3). `sc_dependents` takes the list to read as an argument.
- NIT 2: the act's `removed` and `PARTLY` lines name the ignored files, as
the dry run's `remove` line does, so an ignored nested repository that
goes with a checkout is on the record.
- NIT 3: a removal that did not finish is counted apart: "N removed, M moved
aside and not all removed, K held", and the NOTED line's "removed N"
counts only finished removals. A run whose only act is a partial removal
still writes its NOTED line.
- NIT 4, under each checkout's lock:
- the lock's age is refreshed (`touch -c`, which never makes a lock)
before the fetch and before the re-ask, as `git_net`'s heartbeat does;
- `sc_is_register_clone` is asked again after the re-ask, so a checkout
that stopped being a clone of the register's origin is held;
- the lock's pid line is read again just before the move, and a line
naming another holder (a takeover) holds the checkout and leaves that
lock to its holder; `sc_lock_drop` and `cleanup` leave such a lock, as
`mutex_release` does.
- `LANES_GIT_TIMEOUT=0` still means no bound, as it does for
`lanes-edit.sh`'s own network calls (D16): left on purpose.
- EXIT 1's text names a removal that did not finish and a NOTED line not
confirmed.
- Tests, each red at `6559488` and green here: a `.git` file beside W
naming the CLOSED lane's `.git` (held, dry run and `--yes`); an ignored
file named on the `removed` line; the partial-removal case now also pins
"1 removed, 1 moved aside and not all removed" and the NOTED line's
counts; a checkout whose origin is re-pointed at its own fetch (held); a
lock whose pid line another writer replaced at the fetch (held, and that
pid line left as it is).
Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 10468ac67729a573ec0712eb7ca0cd6628b6abfb)
…ved are one parametrized case, which adds D19's refusal when `LANES_SESSION` is unset and `last-session` names no session (rv4's NIT 5, #198, slice 4) Three near-identical cases (no `LANES_LANE`, `LANES_NO_FETCH=1`, a `LANES_SESSION` that is no uuid) each asserted rc 2, their sentence, and nothing under `<W>-lanes/` or on origin changed. They are one case, `test_yes_is_refused_before_anything_is_removed`, which also asserts "Nothing was removed" for all of them. A fourth parameter is D19's other branch, untested until now: `LANES_SESSION` unset and a `last-session` that names none (a wrapper exits 8), refused naming both. With D19's uuid check disabled (`p-uuid`), both session cases fail. The wrapper helper takes the verb it stands in for (`_wrapped`). Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit a376e5b0bb67242a6212822b5e05408c57d435be)
…eason, and the runner's own checkout and the one a caller's `LANES_REPO` names are kept even where their lane is CLOSED, so the battery's three mutants the module missed are killed (#198, slice 4) The 31-mutant battery at `cede054` killed `n-runner` and `n-xrepo` by their keep reason alone (the fixture's runner is IDLE, and its `LANES_REPO` names the PAUSED lane's checkout, so the class check kept both), and `n-live` survived. - The runner is the CLOSED lane: its own checkout is kept as the runner's, and only the former name's checkout is swept, by the dry run and by `--yes`. Under `n-runner` that checkout is a candidate. - `LANES_REPO` names the CLOSED lane's checkout: kept as `LANES_REPO`'s. Under `n-xrepo` it is a candidate. - A LIVE lane, and an ENDED lane whose transcript a live session record here names: each kept with "lane X is LIVE: a live session record here names it", dry run and `--yes`. No case can kill `n-live` by behaviour: `lanes` gives a LIVE lane no class at all (`lanes-edit.sh`: `ENDED|RETIRED) lr_class=closed`, `LIVE) :`), so no listing has a lane that is LIVE and classed closed, and the class check keeps every LIVE lane too. The LIVE rule is defence in depth, and the reason is pinned. Each of the three mutants was run once against these cases in a separate clone: all KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 459c1f6b2a696bc20885558db04c4a00378e1a9b)
…sc_lock_drop`, when something else holds the checkout first, and by `cleanup`, when a signal ends the sweep (rv4's NIT 4, #198, slice 4) `10468ac` made `sc_lock_drop` and `cleanup` leave a lock whose pid line names another holder, as `mutex_release` does, but its case reached neither: the check before the move clears the sweep's hold on the lock first. Two cases now reach them: - the takeover case is parametrized: the pid line is replaced at the checkout's fetch and that fetch then fails, so the checkout is held for the fetch and `sc_lock_drop` runs; the other writer's pid line is left; - a new case: the pid line is replaced at the fetch and the sweep is sent TERM; it exits 143, and `cleanup` on the way out leaves that pid line and the checkout. With either ownership check made unconditional, its case fails. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit cd060f09a9644397be53e8c3e6169e97f45b72b0)
… read and the manual's wording) into slice 4: git found no conflict, since slice 4 changes only `lane-end` and its own test module (#198) Brings slice 1b's twelve commits after `e6ea8f8`, the head slice 4 last merged, among them: `9cdf8ab` and `4b94f87` (A37: `CP_KEEP_IF_GONE` names the one file a write edits in place, so a refused push whose origin no longer has it keeps the commit on HEAD instead of replaying into nothing, and the replay's HEAD check has a case only it can answer), `638b13a` and `f9b1de9` (the refusal with origin unmoved, and the unconfirmed push, read HEAD before they say "kept on HEAD"), `3a4bc4a` (a drop's compare-and-swap refused for a held `refs/heads/<b>.lock` shows git's own words), `514b0c4`, `251e900` and `9683fd1` (`migrate-state-cells`' lines for a case-only rename), `24efc8a` and `0c822f3` (cases for the A37 check and the carry form's tree step), and `5588307` (the manual's words for `add-row`'s refusal). They change `lanes-edit.sh`, `docs/README-lanes.md`, `tests/test_a23_publish_replay.py` and `tests/test_a23_guard_refusals.py`. Slice 4 changes none of them, and `git merge-tree` predicted this tree (cb4640e) before the merge. The meeting point was weighed again, and nothing in slice 4 changes. The sweep's NOTED line is a `lanes-edit.sh log` write, and the refusals this round reworded are on that write's publish path. The sweep relies on none of their wording: it passes the write the runner's lane and transcript uuid, leaves `LANES_NO_FETCH` unset so S3's fetch runs, and treats any refusal as exit 1 with the publish act or the command that writes the line by hand. `lane-end` and `tests/test_a23_sweep_checkouts.py` are byte-identical to `cd060f0`'s, so the mutant battery run at `cd060f0` still holds. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit 4f3108449919dd4ac83822b41a72d858aa742a62)
…its lane is CLOSED, as every other checkout is, at the listing and in the re-ask under its lock, since a process started before the rename may still write through it (rv4-c3's SHOULD c3-1, D29, #198, slice 4)
D27 kept a former name's checkout beside its new one only while its lane
was LIVE. LIVE is a pid test in this container, so it missed two things
that still carry the old `LANES_REPO=<W>-lanes/<old>`: a process started
from a window before the rename (a landing pilot) that outlives that
window's session, and a pre-rename window in another container that
shares `<W>-lanes/`. Either way the lane reads PAUSED or IDLE, the old
checkout was removed, and that process's next register write (a HOLD's
release, say) was refused by S3 as "not a git checkout", so an open HOLD
blocked every landing. Nothing was lost, since the holds passed.
- D29 takes rv4-c3's first option and supersedes D27's LIVE test:
`sc_judge` judges a former name's checkout beside its new one through
`sc_judge_lane`, as the lane's own checkout is (the runner's lane kept,
a lane the register does not know kept, LIVE kept, anything but CLOSED
kept, CLOSED a candidate). The keep line says first that it is "a
former name of lane X, beside that lane's own checkout, kept until that
lane is CLOSED: a process started before the rename may still write
through this path"; the candidate line names the new checkout and
"lane X is CLOSED (<state>)".
- `sc_still`: the former-name re-ask under the lock no longer returns
after its rename checks; it falls through to the LIVE and CLOSED checks
every candidate gets, so a lane that RESUMES or comes LIVE after the
listing holds the checkout.
- The cost: a renamed lane's old checkout stays until the lane closes.
- Tests, red at `4f31084` (17 of 19 cases; the former name is `remove` or
`removed`) and green here (19 passed):
- new: rv4-c3's P1 (a `sleep` carrying `LANES_REPO=<W>-lanes/repoS4-8`,
lane repoS4-9 IDLE: kept by the dry run and `--yes`); a former name
of a CLOSED lane is removed, with its lane's own checkout; a former
name whose lane RESUMES at its fetch is held "IDLE now, not CLOSED";
- the D27 cases read D29's reason, and the one that comes LIVE after
the listing starts from a CLOSED lane;
- the cases that counted repoS4-8's checkout as removable by default
now count repoS4-2's alone, or a CHAIN lane's checkout where they
need a second candidate (`_closed_checkout`); `_new_name_ended` ends
repoS4-9 on origin and in W.
Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 67e3433a30f07283d1090a07e8d3edf423ee2699)
…writers' lock it just took lets that lock go and holds the checkout, so it no longer leaves a lock that names no holder, with a false "taken over" reason (rv4-c3's NIT c3-2, #198, slice 4) `sc_lock_take` wrote its pid line with `|| :`. When the line was never written (a full disk, a directory that cannot be written in), the check before the move read an empty line, held the checkout as "a writer took it over", and cleared `SC_LOCK`, so the empty lock directory the sweep had made stayed. Every later sweep then held that CLOSED lane's checkout for it, and its writers waited out `mutex_stale`'s ten minutes. - `sc_lock_take`: a pid line that cannot be written empties and removes the lock just made, and holds the checkout: "its writers' lock was taken, and this sweep's pid line could not be written into it, so the lock was let go again" (or, where `rmdir` fails too, that the lock is left). No writer can have taken a lock from a live holder in under ten minutes, so the lock is still the sweep's to remove. An empty line at the check before the move is then only ever another writer's. - Test (red at the parent: the lock is left; green here): `test_a_lock_whose_pid_line_cannot_be_written_is_let_go`. A `mkdir` first on PATH leaves the new lock directory read-only (mode 500), so the sweep's own `printf` fails, as rv4-c3's P2 has it. P2 itself took the pid file away at the fetch, after the line was written; under this fix that lock reads as another writer's and is left to it, as the takeover case already pins, so the module makes the write itself fail. Skipped as root. - Not changed: `sc_lock_drop` and `cleanup` still release a lock whose pid line cannot be read, as `lanes-edit.sh`'s `mutex_release` does. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 95f62f1d830e1770a6beed1ebce80e423fa93cc8)
… read as git reads it, so a directory working in a clone's git directory through one still holds that clone (rv4-c3's NIT c3-3, #198, slice 4) `sc_dep_scan` took the `gitdir:` line with `sed`, which kept the line's trailing CR, so the path it named did not resolve and the directory was not seen. git strips the CR and works in the clone's git directory, so the dry run listed that clone as `remove`, and `--yes` would have removed the git directory another checkout works in. - `sc_dep_scan`: `tr -d '\r'` strips the CR before `sed` reads the line. The `\r` is `tr`'s, not `sed`'s, because BSD `sed` reads no `\r` in its script. - Test: `test_a_git_file_naming_a_clones_git_directory_holds_it` is now parametrized over how the `.git` file spells the path. The new `CRLF` case (rv4-c3's P3) is red at the parent (`remove` for `hold`) and green here, as is the `absolute` case. `git rev-parse --absolute-git-dir` proves the file works in the clone's git directory first. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 5b029c471f8854879a0ded51bec6106fb26197b5)
…t, and a `.git` file whose `gitdir:` is relative to the directory it is in, each get a module case, so neither branch can be undone unseen (rv4-c3's NIT c3-4, #198, slice 4)
rv4-c3 found two branches of the last fix round with no module case and
no mutant: `sc_lock_beat`, which refreshes the lock's age before the
candidate's fetch and before the re-ask, and `sc_dep_scan`'s reading of a
relative `gitdir:` against the directory its `.git` file is in. Both
behave at the head (rv4-c3's P5 and P4 pass there); nothing pinned them.
- `test_the_lock_is_refreshed_before_the_reask` (P5): the candidate's own
fetch ages its lock twenty minutes (`TZ=UTC0 touch -t`, POSIX, where
the probe had GNU's `touch -d`), and a `lanes` wrapper finds it under
ten minutes old (`find -mmin -10`, as `mutex_stale` ages it) at the
re-ask. The case also checks that the hook did age it.
- `test_a_git_file_naming_a_clones_git_directory_holds_it[relative to the
directory it is in]` (P4): the third spelling of the `.git` file case.
- Each is shown to bite by the mutant that undoes its branch, run once in
the mutant clone: `sc_lock_beat`'s body emptied (`sc_lock_beat() { :; }`)
and the relative branch dropped. Both KILLED.
Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e6cb1e8a7926c554c004d2a645ba17da2534312b)
… kept for its own true reason, not "until that lane is CLOSED" (rv4-c4's NIT c4-1, #198, slice 4) rv4-c4 found one thing wrong in the last round's reasons. Where lane repoS4-8 is renamed repoS4-9 and repoS4-9 runs the sweep, the sweep kept repoS4-8's checkout with the former-name prefix of D29 in front of the runner's reason: "... kept until that lane is CLOSED: ...; lane repoS4-9 runs this sweep: its checkout is the one its NOTED line goes through". Both halves are wrong for that checkout. The lane may be CLOSED already, and the NOTED line goes through repoS4-9's own checkout, not this one. `sc_judge_lane`'s runner branch now says, for a former name (its third argument, the new name's checkout beside it): "a former name of lane X, which runs this sweep: a process it started before the rename may still write through this path". That is what the checkout is kept for: a process started before the rename, whose LANES_REPO names the old path (D29). The branch for the runner's own checkout is unchanged. Nothing is kept or removed differently, only the reason said. `test_a_former_name_of_the_runners_own_lane_is_kept_for_its_own_reason` (rv4-c4's P6, as a module case, the dry run and `--yes`): repoS4-9 ENDS and runs the sweep; the former name's row and repoS4-9's own are asserted whole, repoS4-2's is the only checkout swept, and both stay on disk. Red at e6cb1e8 (the "kept until that lane is CLOSED" text), green with the fix. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit 78caf60bedc19501f6af8d7d72a201b8ec17f9dc)
…rite the way a full disk does, so the `rm -f` of the pid file is pinned (rv4-c4's NIT c4-2, #198, slice 4; tests only) rv4-c4 found `sc_lock_take`'s `rm -f` of the pid file unpinned. The module's case for a pid line that cannot be written made the lock directory mode 500, so the open of the pid file failed and no pid file was ever made: nothing for the `rm -f` to remove. On a full disk the open succeeds and leaves an EMPTY pid file, and then the `rmdir` of the lock fails unless the sweep has removed that file, so the lock is left (c3-2's harm). rv4-c4's probes showed it: the module's case passes against a lane-end without the `rm -f` (P8m), and a case with a link to /dev/full as the pid file fails against it, the lock left holding ['pid'] (P7m). `test_a_lock_whose_pid_line_cannot_be_written_is_let_go` takes a second parameter, rv4-c4's P7 (`ln -s /dev/full <lock>/pid` after the `mkdir` shim's own `mkdir`): the pid file's write fails with ENOSPC. The first parameter is the case as it was. The case's assertions are the same for both: the checkout is held with the lock-was-let-go reason, the lock is gone, and the next sweep removes the checkout. The new parameter skips where there is no /dev/full; the first skips as root, as it did. The code is right at e6cb1e8, so the new parameter passes there. It bites against the mutant that drops the `rm -f` (`c4-2-rm`): KILLED, the lock left holding the pid file. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit 53b7bf3349dfa94fc01dd52e830a0f94f90364bb)
…ept in `.git/lanes-edit/` holds the checkout, so the sweep no longer deletes a person's only copy of it (lane openRepoTools-3's MUST 351, #198, slice 4) S3 step 4's carry saves a hand edit of the register to `.git/lanes-edit/hand-edit-<UTC>-<pid>.diff`. Where the edit no longer applies to origin's version, `commit` refuses, KEEPS the diff there and leaves the register at origin's version, so `git status` and the untracked list see nothing. `sc_holds` read nothing in the git directory but the operation markers, `index.lock` and `worktrees/`, so lane 3's reproduction ended with the CLOSED checkout and the kept diff both gone, and a NOTED line saying it "held nothing unpublished". - `sc_holds`: anything in `.git/lanes-edit/` holds, but the writer's own scratch: git's words from a write (`apply.err`, `fetch.err`, `ff.err`, `reset.err`) and the lines a carry removed (`removed`). It says "lanes-edit.sh keeps N (<names>) in .git/lanes-edit/: a hand edit of the register its commit could not carry, or the copy of one it carried (read it; remove it once origin has the edit)". A `.git/lanes-edit/` that cannot be listed holds as well, with its own reason. - This is lane 3's tested fix, with `reset.err` added to the scratch: the push replay's drop writes it there (`lanes-edit.sh:3033` here, `:3161` at fc6f34d), so every checkout whose write once lost a push race would otherwise be held for good. - Tests (red at 53b7bf3: `remove` for `hold`; green here): `test_a_hand_edit_lanes_edit_kept_holds`, end to end: the row is edited by hand in the CLOSED lane's checkout, a peer changes the same row on origin, and the real `lanes-edit.sh commit` exits 2 and keeps the diff; the dry run and `--yes` both hold the checkout and name the file, and both stay. Its second parameter makes the directory mode 000 (skipped as root). `test_lanes_edit_scratch_alone_holds_nothing` is the control: the five scratch names alone hold nothing, and the checkout is removed. - Not changed: the copy a successful carry leaves is held too, until a person removes it. Lane 3 gives that companion to #307 (delete or rename the copy once the commit is confirmed on origin). Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit e78e9fd81213ff161f40a79b589390e4454f3b2c)
…or under it is held, and the removal never crosses a mount, so `rm -rf` no longer deletes what a mount shows from outside `<W>-lanes/` (lane openRepoTools-3's SHOULD 371 and 357, #198, slice 4) A mount point at an ignored path, or inside `.git/`, held nothing: ignored files hold nothing, and nothing reads `.git/` for mounts. The `mv` then carried the mount along with its directory, and `rm -rf --` of the aside deleted what the mount showed, which lives outside `<W>-lanes/` (lane 3 showed it with the sweep's own two commands in a privileged container). - `sc_mount_scan` reads the mount table: `/proc/self/mountinfo`'s field 5, or, where there is no such file, `mount`'s output (`X on DIR [type T] (opts)`, Linux's and BSD's). A table that cannot be read, or that names no `/`, is no mount table: the sweep cannot tell, and holds every candidate with "whether a file system is mounted in it cannot be told". - `sc_mounts_inside` spells the checkout's path with mountinfo's octal escapes (backslash, space, TAB) and matches it, through ENVIRON, as the mount point itself or a prefix with `/`. `sc_judge_holds` holds with "a file system is mounted at or under it: N (...), and removing it would remove what that mount shows, which is not this checkout's". - It is read at the listing, with the working directories, and again under each candidate's lock with the dependents and the working directories, just before the move. - A second layer only: `rm -rf --one-file-system` (GNU) or `-x` (BSD), each asked once of a path that is not there; an `rm` that takes neither removes as before. Alone it is not enough: a bind mount of the same file system is one `rm` cannot see. - `LANE_END_MOUNTINFO` names another table: the test seam, in the ENVIRONMENT block. - Tests (red at e78e9fd: `remove`/`removed` for `hold`; green here): `test_a_mount_inside_a_checkout_holds` (at an ignored path, inside `.git`, at a path with a space, which mountinfo spells `\040`), the dry run and `--yes`; `test_a_mount_made_after_the_listing_holds` (the table names the mount only from the checkout's own fetch on: the re-read under the lock holds it); `test_a_mount_table_that_cannot_be_read_holds` (no table and a failing `mount`; a table naming no `/`). - Not run: a real bind mount through `lane-end` end to end. This host has no user namespaces (`unshare -rm`: "Operation not permitted"), so the tests read a table through the seam. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 50a37b7c584ce6576feac02b3f15d94666629308)
…atched against the mount table as `/proc/self/mountinfo` spells it, so 371's escaping is pinned (lane openRepoTools-3's SHOULD 371, #198, slice 4; tests only) The escape mutant (the checkout's path matched unescaped) survived 371's cases: in each, the space was in the MOUNT POINT under the checkout, and the checkout's own path, the prefix that is matched, held none. - `test_a_mount_in_a_checkout_whose_own_path_has_a_space_holds`: a box whose W stands in `with space/`, and a mount table naming a mount point at an ignored path in the CLOSED lane's checkout, as mountinfo spells it (`with\040space`). The dry run holds the checkout and names the mount point. With the escaping undone it is `remove`. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 596aeeeb32732fb274d5fdd855d8c95061c7e4a7)
…n hand finish and prints its NOTED line by hand, and every run names an unfinished removal's aside with the command that removes it (lane openRepoTools-3's SHOULD 372, #198, slice 4) - INT and TERM wait while a checkout is moved aside, removed and counted: the `mv` and the `rm` run in subshells that ignore both (`exec` keeps an ignored signal ignored), so a Ctrl-C to the process group no longer stops `rm` halfway and leaves a whole checkout under a hidden name. - A signal then ends the act. After any removal it prints the NOTED line by hand for what was done, ending "the sweep was stopped by a signal". One that comes once the NOTED write has started says the line may be on origin already and hands out the publish act first. - Every run lists each `.<name>.removing-*` under `<W>-lanes/` as a `leftover` line, apart from the hidden count, with `chmod -R u+rwX -- <p> && rm -rf -- <p>`. No sweep removes one. - A removal that did not finish is said before the NOTED write, so a refused write no longer exits before saying it. Tests: five new cases. All five fail at 596aeee and pass with the fix, with their neighbours (11 passed). Hygiene: 253 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit ff12b0154850bf0b0bf823e29f1100fa813b945d)
…t its own cause needs (lane openRepoTools-3's SHOULD 373)
Exit 6 of `lanes-edit.sh log` has three causes, and the two remedies the
sweep printed for every 6 (the publish act, then the line by hand) both
refused for two of them. The write's words are now kept (captured inside
the write's own subshell, so a signal's trap still speaks on the sweep's
stderr) as well as shown, and choose the remedy:
- a push refused with origin unmoved: the line is committed and only
origin lacks it, so the publish act alone;
- `git add failed` / `git commit failed`: the line is staged or in the
tree, so `git -C <checkout> restore --staged --worktree --
lanes/log/<runner>.md`, then the line by hand;
- the bound ("the commit is local and kept on HEAD", or no commit to drop
to): S6.3's archive, `git -C <checkout> push origin
HEAD:refs/heads/archive/<runner>-stranded-<UTC>`, then `reset --keep
refs/remotes/origin/<branch>`, then the line by hand;
- words the sweep does not know: the two remedies as before.
The checkout is the one `lanes-edit.sh` writes through (S2): the caller's
LANES_REPO, the runner's own under LANES_ROUTE_WRITES=1, else W.
Tests: a pre-commit hook in the runner's checkout, and a peer commit landing
before each of the six pushes; each case runs the printed commands in order
and origin holds the line once (the archive holds the stranded commit). The
origin-unmoved case now expects the publish act alone. Red at ff12b01: 3
failed; green: 12 passed with the NOTED, signal and partial neighbours.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 3373d5be2b3599e6cec93a8d1a3d054ee1e98c8a)
…o its five exact names and to its `-L` guard, and a drift guard reads the names `lanes-edit.sh` writes there from its own text (lane openRepoTools-3's SHOULD 391, #198, slice 4; tests only) Two mutants of MUST 351's hold passed the whole module: a prefix match (`/^removed/` would remove a person's `removed.bak`), and the guard's `|| [ -L ... ]` dropped (a dangling `.git/lanes-edit` link then holds nothing). Lane openRepoTools-3's 19 proposed cases, copied into the module in its style: - `test_a_name_that_only_looks_like_scratch_holds`: 12 look-alike names (a backup, a suffix, another case, a hidden file, a space, a leading dash) each hold, in the dry run and under `--yes`, and the file stays; - a subdirectory holds; a dangling link holds; an empty folder holds nothing, and the checkout is removed; - the drift guard: one case per name `lanes-edit.sh` writes into the folder (`$cs_tmp/<name>`, `.../lanes-edit/<name>`), read from the script of the tree under test, each removable alone, so a sixth scratch name fails until `lane-end` knows it. One more case asserts the guard finds `fetch.err`, so a renamed variable cannot leave it with no case. Both mutants are KILLED by these cases. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 6b3c308cb702a7b53ef98afa1717fb2c39f8ddc2)
… are pinned, the move asked again whether its path is directly under `<W>-lanes/`, the lock let go when the move fails, and a run whose only removal did not finish recorded (lane openRepoTools-3's SHOULD 374, #198, slice 4; tests only) Three one-line mutants of the act passed the whole module. A case for each: - `test_a_lanes_folder_swapped_for_a_link_under_the_lock_holds`: a `git` first on PATH, after the candidate's last hold read under its lock, copies `<W>-lanes/` (its lock and pid line included) to a decoy and puts a link to the decoy in its place. Only `sc_under_home`, asked again at the move, stops it: the checkout is held, and the decoy's copy stays. So its false branch is reachable. - `test_a_checkout_that_cannot_be_moved_aside_lets_its_lock_go`: an `mv` first on PATH refuses the move to the aside; the checkout is held, its lock is gone, and the next candidate is removed (its own lock would otherwise leave the first one's for good). - `test_a_removal_that_does_not_finish_exits_1` is parametrized: alone, with no removal that finished, the PARTLY run still writes its NOTED line, naming the aside. Each of the three mutants is KILLED by its case. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 75c711859fb8cbfe571ed96c678096211333cbb3)
…mes no transcript uuid, so a register or a log that could not be read refuses `--yes` as a read that failed, with its reason (lane openRepoTools-3's finding 545, #198, slice 4)
`--yes` with no `LANES_SESSION` reads the runner's uuid with
`lanes-edit.sh last-session <runner>`, and took ANY nonzero exit as "names
none" (`|| sc_uuid=""`), refused with 2. `last-session` exits 8 only where
no uuid is knowable, and 1 where the register or the log could not be
read; lane openRepoTools-4's #303 makes the 1 come for an unreadable log
too. A read that failed is not an answer.
Now 0 is the uuid, 8 is "names none" (2, as before), and any other exit
refuses with 1 ("Nothing was removed"), naming the exit and the writer's
last line of stderr, which is captured for that (a capture that cannot
be made names the exit alone).
`test_a_last_session_that_cannot_be_read_is_never_none`: a `last-session`
stub that exits 1 refuses with 1 and its words (red before: 2, "names
none"), and one that exits 8 still refuses with 2.
Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 24e3c6d6bb15c51d997df855d1fdd2f5726fcc3b)
…ere clones are made, under W's parent to depth 6, hidden ones too, and under `$TMPDIR` and `$HOME` to depth 4, and a scan that cannot finish holds every candidate (lane openRepoTools-3's SHOULD 352, with NITs 378 and 356, #198, slice 4) `sc_dep_scan` read alternates files in three places only: W, the entries directly in W's parent, and `<W>-lanes/`. A `--reference` clone of a CLOSED lane's checkout in `.lane-worktrees/`, in a project's `review/`, or in `$TMPDIR` held nothing, and `--yes` removed what it borrows from. Now `find` lists every object directory (pruned there) and every `.git` file under W's parent to depth 6 and under `$TMPDIR` and `$HOME` to depth 4, by their real paths; the lines are deduplicated, since the roots can nest. The dry run names the scope, and the scan is read again under each lock, as before. A repository at or under a candidate, or under a path the run removes, goes with it. - A scope that cannot be entered, a `find` that fails for anything but a directory it cannot read, or an alternates or `.git` file that cannot be read (NIT 356; a mode-000 `.git` file was skipped) holds every candidate: "what leans on it cannot be told: ...". - A directory `find` cannot read (another user's, or gone while read) is counted once and said, and holds nothing, by question 2's rule. - The lines are written TARGET FIRST (NIT 378), so a TAB in the dependent's own path no longer hides it. Tests: `test_a_dependent_anywhere_in_the_scope_holds` (5 places), `test_the_dry_run_names_the_dependents_scope`, `test_a_dependents_scan_that_fails_holds_every_candidate`, `test_a_directory_the_dependents_scan_cannot_read_is_counted_and_holds_nothing`, `test_a_dependents_file_that_cannot_be_read_holds_every_candidate` (2). Red before: 10 failed. Green: 10, plus 17 neighbours (leaves first, the held-parent chain, the store inside a clone, the `.git` file cases). Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit e04d26396dd20e9abbd60c7ca8dadfa289d40d02)
…o, under every hold a lane's checkout has and one more, a live process whose `LANES_REPO` names the clone (Brett Heap's ruling on #308's question 1, D37, with lane openRepoTools-3's conditions, #198, slice 4) Brett Heap ruled on #308's question 1 (2026-10-22T12:46:57Z, relayed by lane openRepoTools-3): "yes, have the sweep delete the 139 clones". The per-profile clones beside W, which this build only listed, are now candidates, in one leaves-first order with the checkouts under `<W>-lanes/`, under lane 3's conditions: - 352's wider dependents scan comes first (e04d263), since 47 of the 139 borrow objects through alternates. - Every hold applies: (a)-(c), `.git/lanes-edit/` (351) and the mounts (371). Unpublished content stays until its owner acts. - A NEW HOLD: a live process whose `LANES_REPO` (read from `/proc/<pid>/environ` by the shell itself, NUL by NUL) names the clone, or a path in it, as spelled or by its real path. A process whose environment cannot be read is counted and said, and holds nothing. With no `/proc`, a clone beside W is held as "cannot tell". `LANE_END_PROC` is the test seam. - The clone the caller's `LANES_REPO` names is kept. A clone beside W is no lane's, so no lane state is asked of it: under its lock, after its fetch, whether it is still a register clone is asked again. It is moved aside beside W, never into `<W>-lanes/`, and an aside left there is named by every later run. `sc_under_home` now takes a path directly in W's parent too, never W or `<W>-lanes/`. The NOTED line names the clones beside W it removed apart from the lane checkouts, each character outside a lane name's written as `?`, and counts those left. The dry run and the act's summaries count both places. The module's box keeps its three clones beside W held by default (an untracked note in the leaf), so every other case is as it was. 13 new cases (leaves first with a name holding a space, a held middle clone, four holds, a mount and a working directory, `LANES_REPO` by path and through a link, no `/proc`, the caller's own `LANES_REPO`, an unfinished removal beside W) and 4 changed ones: red at e04d263, 17 failed; green, 17 passed. With them, the 87 cases of the act, the holds, the dry runs, the locks, the dependents and the NOTED line pass, and hygiene's 253. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit dba02941780592ff253f28151292a1074c57cd1a)
…wrapped, and a case holds every comment line of the sweep's section to 80 (lane openRepoTools-3's NIT 394, #198, slice 4) The holds' comment line that ends "No `origin/<branch>` to compare" stood at 93 columns, in a block that is otherwise 80 or less. It is wrapped, and the rest of its paragraph with it; no word changes. `test_the_sweeps_comments_are_80_columns_or_less` reads `lane-end` from the section's header, `# ---- AMENDMENT 23 S6.2`, to the `fi` that ends it, and fails on any comment line over 80 characters (characters, not bytes: a dash is one column). Red before the wrap (line 1381, 93), green after; its mutant, one comment line lengthened again, is KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 433f759beb91dbdf44ece97c410fc64183e5d8bc)
…listed, or whose real path cannot be read, refuses the run with 1 instead of reading as "nothing to remove" (lane openRepoTools-3's NIT 377, #198, slice 4) With `<W>-lanes/` at mode 000, `sc_real` could not enter it, so its real path read as empty, and the sweep said it "resolves through a link to , which this sweep never follows" and exited 0, in the dry run and under `--yes`: a read that failed, reported as nothing there. It is now asked first whether it can be entered and listed, and whether its real path can be read; when it cannot, the run refuses with 1 (D10: a read that failed) and "Nothing was removed", before anything beside W is looked at either. `test_a_lanes_folder_that_cannot_be_entered_or_listed_is_refused` (skipped as root): the dry run and `--yes` both exit 1 with that reason, and every checkout and clone stays. Red before the fix (the dry run read on and exited 3, the clones beside W being free); green, with the dry runs and the lane-checkout W case, and hygiene. Its mutant, the new test dropped, is KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 9b19e124e7b183024bf11ddc081c936f78350612)
…` that is W refuses with 2 before anything is removed, in S5's words (lane openRepoTools-3's NIT 376, #198, slice 4) Under `LANES_ROUTE_WRITES=1`, `lanes-edit.sh` refuses any write through a `LANES_REPO` that is W, the readers' copy (Amendment 23 S5), by its real toplevel or its git common directory. The sweep removed a checkout first, then its NOTED line was refused, and the by-hand line it printed was refused again in that window. The preflight now asks the same question of the caller's `LANES_REPO` before the first removal and refuses with 2, naming the lane checkout the line would go through. Without routing, `LANES_REPO=W` is the default write path, and is left as it is; the dry run writes nothing and is not refused. `test_yes_with_LANES_REPO_naming_W_is_refused_before_anything_is_removed`, W named by its own path and by a linked worktree of it (one git common directory): `--yes` exits 2 with S5's words, the CLOSED lane's checkout stays, and nothing reaches origin; the dry run still exits 3. Red before the fix (the act ran, exit 1); green after, with the other `LANES_REPO` cases, the preflight refusals and hygiene. Its mutant, the new test dropped, is KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 0f564e84ad17149df57854e7330eaae3f4dbb49b)
…is cleaned as `lanes-edit.sh`'s `lock_ident` cleans its own, `:` kept and a bad container `unknown` (lane openRepoTools-3's NIT 359, #198, slice 4) A writer of a checkout compares the pid line of the lock it meets with its own `lock_ident` to know a holder in its own container, whose pid it can ask; any other line is aged by the directory's mtime alone. The sweep kept only `[A-Za-z0-9._-]` and mapped anything else, and an empty value, to `none` (container) or `unknown` (host), while `lock_ident` keeps `:` too (`lanes_binding_value_ok`), and maps an empty container to `none` and a bad one to `unknown`. So a container named with a `:` made the sweep's line read as another container's, and a killed sweep's lock was waited out for ten minutes instead of taken back at once. The sweep's line now follows `lock_ident` character for character. `test_the_locks_pid_line_is_lanes_edits_own`: a hook at the CLOSED lane's fetch copies the pid line the sweep wrote; with `LANES_CONTAINER=bench:a` it ends ` bench:a hosta`, with `bench a` it ends ` unknown hosta`, and with `LANES_HOST=host:1` it ends ` bench-a host:1`. Red before the fix (`none`, `none`, `unknown`); green after. Its mutant, `:` dropped from the container's set, is KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 909dce4c46f481ed5220a21866eb611a8e707d41)
…cters of the checkout's, so a long name is moved aside instead of held in every act (lane openRepoTools-3's NIT 379, #198, slice 4) The aside was `.<name>.removing-<UTC>-<pid>`, which grows with the name: a 240-character name, with its 35-character suffix, is past NAME_MAX, so its `mv` always failed, and the checkout was `remove` in every dry run and held "could not be moved aside" in every `--yes`. The aside is now `.<the name's first 48 characters>.removing-<UTC>-<pid>`, at most 84 characters, still named for a person and still matched by `.?*.removing-*`, so every later run names one that is left. `test_a_long_name_is_moved_aside_under_a_name_that_does_not_grow`: a 240-character clone beside W is `remove` in the dry run and `removed` under `--yes`, through an aside (seen by an `mv` first on PATH) whose name is the first 48 characters and the suffix, and no aside is left. Red before the fix (held, could not be moved aside); green after. `l3-d37-aside` is re-pointed at the new line, and the new mutant, the whole name kept, is KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 1bd64905f14f7859a5e43272d0a98f0a0b91346a)
…son the report prints is written as `?`, so a name with a newline writes no line of its own (lane openRepoTools-3's NIT 358, #198, slice 4)
The report printed names raw: a newline in a name under `<W>-lanes/`
wrote a line of its own into it, one that could read as another
entry's (`remove FORGED ...`), and a TAB in a dependent's path was
printed inside a hold's reason. The decision was always right; only the
report could be forged. Every entry line (`keep`, `hold`, `remove`,
`removed`, `PARTLY`, `leftover`) now writes each control character of
its path and its reason as `?`, by the shell's own `${x//[[:cntrl:]]/?}`
(no process per name). The commands the `leftover` line hands out keep
`%q`, which a shell reads back exactly.
`test_a_name_with_a_control_character_writes_no_line_of_its_own`: a
directory under `<W>-lanes/` named `odd`, a newline, and a line that
reads as the removal of `FORGED`: in the dry run and under `--yes`
there is one line naming it, a `keep` whose path ends `odd? remove
...`, and no `FORGED` entry. Red before the fix; green after. The TAB
dependent case of 352 now expects `tab?here`. Its mutant, the `keep`
line's mapping dropped, is KILLED.
Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ed8576fc3c8b0dc28c2fb8faf0e4a3f4684fe542)
…are named beside it, in the dry run and the act (lane openRepoTools-3's NIT 354, #198, slice 4) A commit taken off its branch (`reset --keep HEAD~1`) is on no ref, so nothing held the checkout, and the removal took the reflog that could undo a mistaken reset, unsaid. Such commits are now named, as ignored files are: "commit(s) only its reflog reaches, which go with it: N (<short shas>)", from `git rev-list --abbrev-commit --reflog --not --all`, on the `remove` line of the dry run and the `removed` line of the act. They do not hold: a write replayed after a lost push race leaves one whose patch origin has, so a hold would keep such a checkout for good. Lane openRepoTools-3 offered "hold, or name"; this names. `test_commits_only_the_reflog_reaches_are_named`: a commit in the CLOSED lane's checkout, then `reset --keep HEAD~1`; the dry run's `remove` and the act's `removed` name it by its short sha. Red before the fix; green after. Its mutant, the read dropped, is KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 307b9ca518591cfa477be7deddbf093a9450f0f0)
…nored editor's swap file hold the checkout, annotated tags are named, and the comment that called ignored files the register's own rules is put right (lane openRepoTools-3's NIT 355, #198, slice 4) Ignored files hold nothing, and are named. But `--exclude-standard` reads the checkout's `.gitignore` files, its `.git/info/exclude` and the user's `core.excludesFile`, not the register's rules alone, as the holds' comment said. Two things such rules can hide are not disposable: - a repository of its own at an ignored path, which may hold commits nothing else has (at any other path it holds, as untracked): lane 3's probe, one in `.git/info/exclude`'s `vendor/` with a commit of its own, gave `remove`; - an editor's swap file (`*.swp`), which says a person has a file of the checkout open. Each ignored directory is looked in (`find`, `.git` pruned) and each of these holds; an ignored directory that cannot be looked in holds too. An annotated tag on a published commit reaches nothing origin lacks and holds nothing, but its tag object goes with the checkout, so it is named beside it, as ignored files are. `test_what_ignored_rules_hide_that_may_be_work_holds` (a repository at an ignored path, an ignored swap file): each holds, in the dry run and under `--yes`, and the checkout stays as it was. `test_annotated_tags_are_named`: an annotated and a lightweight tag on the published HEAD; the dry run's `remove` and the act's `removed` name the annotated one alone. Red before the fix (3 failed); green after. Its mutants, the repository hold and the swap hold dropped, are KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 9e0cbd1588f36459659688f071df3f6cc7ee4942)
…` with 2, and a candidate gone at its turn is reported as gone, not held (lane openRepoTools-3's NIT 375, #198, slice 4) Two `--yes` runs at once removed nothing twice, but each one's NOTED line said the other's removals were "held and left", and one candidate was left by both. `--yes` now takes a lock of its own before it reads or removes anything: `<W's git directory>/lanes-sweep-checkouts.lock`, made by `mkdir`, with `lanes-edit.sh`'s pid line, and let go on every way out by the exit's `cleanup` unless its line names another holder. A second `--yes` is refused with 2, naming the lock and its pid line, and how to remove one a killed sweep left. The lock is in W's git directory, not `<W>-lanes/` as lane 3 sketched, because since D37 the clones beside W are swept when there is no `<W>-lanes/` at all. A candidate that is no longer there at its turn (a person, or another workstation's sweep, removed or moved it) is reported `gone`, not held for want of a `lanes/` directory, and is neither named removed nor held and left in the NOTED line. `test_a_second_sweep_is_refused_while_one_runs`: a hook at the CLOSED lane's fetch, inside the first sweep's act, runs a second `--yes`; it exits 2 with the lock named and prints nothing, and the first removes the checkout, writes one NOTED line and lets its lock go. `test_a_candidate_gone_at_its_turn_is_said_as_gone`: a hook at the first candidate's fetch moves the second away; it is `gone`. Red before the fix; green after. Its mutants, the lock taken by `mkdir -p` and the gone test dropped, are KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 51ee5ff243fe36261fd5560c94d6dc3918084303)
…utex_steal`'s rename is: the lock's pid line, the working directories and what leans on the checkout are asked again of what was moved, and any of them moves it back (lane openRepoTools-3's NIT 353, #198, slice 4) Under the lock, the working directories and the dependents were read before about ten git reads, and the pid line just before the `mv`, so three things could come between a read and the move unseen: a process that came to work in the checkout during the git reads, a `--reference` clone of it made then, and a writer that took the lock over between the pid-line read and the move. Lane 3's probes removed the checkout in all three. The window is short, but a removal is the one act nothing undoes. After the move, before the removal, the sweep now asks of what was moved: the pid line of the lock that went with it, read again; the working directories, read again against the aside's path (a process's working directory follows the rename); and what leans on it, read again, with an alternates or `.git` file whose target is gone kept as written, so a clone that names the old path is matched. Any of them moves the checkout back to its path and holds it, saying so; its lock is let go unless another writer holds it now. The lock is refreshed just before the pid-line read. Three cases, red before the fix (each `removed`), green after: a `git` first on PATH, right after the last read under the lock, starts a process in the checkout, or makes a `--reference` clone of it; an `mv` first on PATH writes another holder's pid line into the lock just before the move. Each is held "... as it was moved aside ...; it was moved back", and no aside is left. Their mutants are KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 811c10fc0fc57affb27a1dfa7fed982ff2d82622)
…lds the clone it names, the case a kept CR fails, since NIT 353 keeps a target that does not resolve as written (the one survivor of the battery at 811c10f, #198, slice 4; tests only) The mutant `c3-3-crlf` (`tr -d '\r'` replaced by `cat` in the dependents scan's `.git` file read) was KILLED at e04d263 and at every commit after it up to 51ee5ff, and SURVIVED at 811c10f (NIT 353). Its one case wrote `gitdir: <clone>/.git` and a CRLF. Since 353 a target that does not resolve is kept as written, so that the clone a moved checkout leaves behind is still matched; with the CR kept, `<clone>/.git` and the CR after it is still under `<clone>/`, and the clone was held anyway. The CR handling is not redundant: a relative path, as git writes a submodule's `.git` file, resolves only once the CR is gone. With the CR kept, `<dir>/../<W>-lanes/<lane>/.git` and its CR does not resolve, is kept as written, is under no candidate, and the clone is removed. `test_a_git_file_naming_a_clones_git_directory_holds_it` takes a fourth parameter, `CRLF and relative, whose path resolves once the CR is gone`; git itself reads that file as the clone's git directory, as the case asserts first. The mutant is re-pointed at it, and is KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 80bd3922ed3cd01b36da8004f0befa9693f55868)
…NES_WORKSPACE_ROOT` names another directory than workspace.yaml's `path:`, and that directory, or a clone beside W with a `<name>-lanes/` of its own, is never a candidate (rv4b's MUST A on 811c10f, D45, #198, slice 4) rv4b's pre-review of 811c10f found that `LANES_WORKSPACE_ROOT` set to a per-profile clone beside the real W made that clone "W", and the real W, the readers' copy that workspace.yaml's `path:` names, "a clone of the register beside W": with nothing borrowing from it, `--yes` removed it, and the second-`--yes` lock moved into the substitute's git directory. - `path:` is read from workspace.yaml as every helper reads it, by `lanes_ws_field`, which no `LANES_WORKSPACE_ROOT` reaches. Where the variable is set and its real path is not `path:`'s, `--yes` exits 2 before the lock and before any listing, naming both and saying to unset the variable or point it at `path:`. Nothing is removed. - The dry run still runs, and its header says that `--yes` refuses. - `sc_is_a_w` keeps the directory `path:` names, and a clone beside W with a `<name>-lanes/` beside it (a W with lane checkouts of its own), at the listing and again under the clone's lock before the act. - The seam stays (D45): the shared resolver is byte-identical in the four lane helpers, and the module and the shell suite point helpers at a sandbox through it. A value whose real path is `path:`'s, as spelled or through a link, is no refusal. The header's text says lane windows export it too, and names the refusal. Seven new cases in `tests/test_a23_sweep_checkouts.py`: 5 failed and 2 passed at 80bd392; the module's MUST A and neighbour selection with `tests/test_repo_hygiene.py`, 291 passed. Six new mutants (`rv4b-A-refuse`, `-real`, `-path`, `-lanes`, `-relock`, `-dry`), all KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit c73a9afc6ebfa9569a3d44947fa5be89ffddee65)
…side, the windows' `LANES_REPO` and the mount table are read again, and a window that came to name the old path, a mount that came to be in the aside, or no table or `/proc` to tell by, moves it back (rv4b's SHOULD D on 811c10f, #198, slice 4) rv4b's pre-review of 811c10f found that NIT 353's re-check after the move asked the lock's pid line, the working directories and what leans on the checkout again, but not D37's `LANES_REPO` hold or the mounts: both were read before the git reads under the lock, so a window opened on the CLOSED lane's checkout during them (probe P4) was missed, and the checkout removed under it. - After the move, `sc_repo_scan` runs again, and a live process whose `LANES_REPO` names the old path, as spelled, holds it; with no `/proc` then, a clone beside W is held as "cannot be told", as D37 holds it at the listing and under the lock. - `sc_mount_scan` runs again, and a mount point at or under the aside (the move carried it) holds it; no mount table that names `/` holds it as "cannot be told". - Each moves the checkout back, its lock let go, as the other three do. Four new cases in `tests/test_a23_sweep_checkouts.py` (a window opened after the last read, a mount made as it is moved aside, no table then, `/proc` gone then beside W): 4 failed at c73a9af (`removed` for `hold`). With the fix, the module's post-move, mount, window and `/proc` selection, 27 passed; `tests/test_repo_hygiene.py`, 253 passed. Seven new mutants (`rv4b-D-repo`, `-repo-scan`, `-reponone`, `-mount`, `-mount-scan`, `-mountnone`, `-aside`), all KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 2bc9b667617dd51cb942fa3a1b11ad6a334bedab)
… scan cannot read holds every `--sweep-checkouts` candidate, in words that name it and `chmod`; another user's is still counted and holds nothing (rv4b's SHOULD B on 811c10f, #198, slice 4) rv4b's pre-review of 811c10f found that every directory the scan could not read was counted and held nothing, this user's own too: a `--reference` clone with a commit of its own, kept in a mode-000 folder beside W (probe P2), was not seen, `--yes` removed the CLOSED lane's checkout, and the clone's `fsck` then exited 10. - Each "Permission denied" line of `find` names the directory: GNU `find` quotes it in the C locale (`'P'`, a quote as `\'`, a backslash, TAB, newline and non-ASCII bytes as escapes), BSD `find` does not; `sc_find_err_path` undoes the quoting. - Where that directory is this user's own (`[ -O ]`), the scan cannot finish (`sc_dep_fail`), which holds every candidate: the words name the directory and `chmod u+rx -- <it>`. - Another user's directory, or one gone while read, is counted and said, and holds nothing, as decided under Q2. The module's case for a mode-000 directory beside W is changed to match: it now holds both CLOSED lanes' checkouts, with a dependent inside it, a plain name and one with a quote, a blank and a backslash. A new case keeps another user's directory counted (a `find` first on PATH says it could not read one, since a test cannot make one without root). Red at 2bc9b66: 2 failed, 1 passed (the own-directory cases, rc 3 for 0). Green: the module's dependents, scope and unreadable-file selection 26 passed, tests/test_repo_hygiene.py 253 passed; the five new mutants (rv4b-B-own, -any, -quotes, -unquote, -escapes) KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit e12d077630b5017ef7cea9b69bb020e669cabd2b)
…epth 8, where a Claude Code scratchpad keeps its clones, and the scope line names each root's depth (rv4b's SHOULD C on 811c10f, #198, slice 4)
rv4b's pre-review of 811c10f found that `$TMPDIR` was scanned to depth
4 only. A Claude Code scratchpad is
`/tmp/claude-<uid>/<project>/<session>/scratchpad`, so a clone made in
one stands at depth 5 and its object directory at 7 (probe P3): it was
not seen, `--yes` removed the checkout it borrowed from, and its `fsck`
exited 10.
- `sc_dep_root 8 "${TMPDIR:-/tmp}"`: repositories to depth 8, so a
clone in a folder of a scratchpad's own (three levels further) is
seen too.
- The dry run's scope line takes each root's depth from the scan's own
list, and says why `$TMPDIR` goes deeper.
`DEPENDENTS_AFAR` takes a clone eight levels under `$TMPDIR` in a
scratchpad's shape, and one in `$HOME` (rv4b's unpinned `:1675`).
Red at e12d077: 2 failed, 6 passed (the scratchpad clone and the scope
line). Green: the module's dependents and scope selection 18 passed,
tests/test_repo_hygiene.py 253 passed (the comment names the
scratchpad's shape relative to `$TMPDIR`, not a host path); the
mutants rv4b-C-depth, rv4b-C-scope, r4b-home-depth (rv4b's) and
l3-352-tmpdir (re-pointed at depth 8) KILLED.
Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7d194593726d9ccb3e645cc132f10f7209bce383)
…e is spelled from the real path of the deepest directory on it that does, and the read after the move matches the `.git` files too, so a `.git` file or a relative `../` path written after the last read still names the moved checkout, and the checkout is moved back (the 353 gap, D44's note, D46, opensoft/openRepoTools#198, slice 4) NIT 353 kept a target that does not resolve as written, so that a clone made to lean on a checkout's old path, after the move, is still matched. A relative target with `..`, as git writes a submodule's `.git` file, or a relative alternates line, was kept with its `..`, named no candidate, and the checkout was removed under it (D44's note, read from the code then; now a case). - `sc_real_or_text`: the real path where it resolves; otherwise the real path of the deepest directory on it that stands, the rest appended as written. An absolute target reads as before. - The read after the move (NIT 353's commit point) matched only the alternates lines: the `.git` files were read under the lock alone, so a `.git` file written between the last read and the move, absolute or relative, was never matched, and the checkout was removed under the directory that works in it (the one case still red on 10-24). It now matches the `.git` files too, by the alternates' rule (what went with the aside is not matched), and says "its git directory is named by the .git file of <dir>, which works in it and came as it was moved aside". New case: an absolute `.git` file written after the last read. - A CRLF line whose CR is kept now reads under the clone too, whether absolute or relative, so `c3-3-crlf`'s kill moves to a new `GIT_FILES` case: a CRLF line whose last name is a link to the clone's git directory, which resolves only once the CR is gone (D46). Red, before this commit: with `sc_real_or_text` alone (10-24), the selection below ended 1 failed, 31 passed: `test_a_relative_path_written_after_the_last_read_moves_it_back[.git file]`, `removed` for `hold`. With this commit, the module's 353 selection (`relative_path_written or git_file or after_the_last_read or moves_it_back or dependent or comments_are_80`): 33 passed. `tests/test_repo_hygiene.py`: 234 passed, 18 skipped, 1 failed, `test_the_root_carries_the_line_ending_rule`, which reads a `.gitattributes` that the code leg's main has and this slice's base does not yet (slice 1b' brings it). Seven mutants, all KILLED: `rv4b-353-text`, `rv4b-353-gitfile`, `rv4b-353-alt`, `r4b-rel-alt`, `c3-3-crlf` (re-pointed), and two new: `w4r-353-post-gitfile` (the read after the move without the `.git` files, as before this commit: the new case gives `removed` for `hold`) and `w4r-353-post-lines` (that read given the alternates lines again). Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ck, it asks where the checkout went, so a directory that came to stand at its path meanwhile, and took it inside, is said with the checkout's place, not as "moved back" (rv4b's NIT G on 811c10f, opensoft/openRepoTools#198, slice 4) rv4b's pre-review of 811c10f: between the test that the old path is free and the `mv` that moves the aside back, a directory made at the old path takes the aside inside it, as `mv` moves into a directory, and the row said "it was moved back" over a checkout hidden in that one. BSD `mv` has no `-T`, so the move is not changed; where the aside went is asked after it: - `<old path>/<the aside's name>` stands: the row says the checkout could not be moved back, that a directory came to stand at its path as it was, and where the checkout is now, which no later run names (move it out by hand); its lock is not let go. - Otherwise "it was moved back", and the lock let go, as before. `test_a_directory_made_at_its_path_as_it_is_moved_back_is_said`: an `mv` first on PATH writes another holder's pid line into the lock just before the move aside, so the checkout is held and moved back, and makes a directory at the old path just before the move back. Red, the new case alone before the fix: 1 failed (the row said "it was moved back" over the checkout inside the new directory). With the fix, the module's selection `directory_made_at_its_path or moves_it_back or moved_back or comments_are_80`: 12 passed. `tests/test_repo_hygiene.py`: 234 passed, 18 skipped, 1 failed, `test_the_root_carries_the_line_ending_rule` (the `.gitattributes` slice 1b' brings; as at the parent). The new mutant `rv4b-G-nested` (the check after the move back off): KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is split in two, so a failure names the half that failed (rv4b's NIT H on 811c10f, Sonar S9073, opensoft/openRepoTools#198, slice 4) rv4b's pre-review of 811c10f named four asserts that join two checks with `and` (Sonar S9073); a fifth of the same shape is split with them: - `test_a_former_name_of_the_runners_own_lane_is_kept_for_its_own_reason`: `old.is_dir()`, then `new.is_dir()`; - `test_a_lanes_folder_swapped_for_a_link_under_the_lock_holds`: the shim's marker ("the shim ran"), then `<W>-lanes/` a link; - `test_a_dependents_scan_that_fails_holds_every_candidate`: each checkout a directory; - `test_clones_beside_W_are_removed_leaves_first`: W, then `<W>-lanes/`; - `test_a_held_clone_beside_W_keeps_what_it_borrows_from`: each clone. Text only: no case changes what it checks, and lane-end is unchanged. The five cases' selection: 6 passed (one case has two parameters). `tests/test_repo_hygiene.py`: 234 passed, 18 skipped, 1 failed, `test_the_root_carries_the_line_ending_rule` (the `.gitattributes` slice 1b' brings; as at the parent). No mutant: no behaviour changes. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efusal of a lane say that the sweep looks at the register clones beside W too, as it has since D37 (rv4b's NIT I on 811c10f, opensoft/openRepoTools#198, slice 4) rv4b's pre-review of 811c10f found text that D37 (Q1, answered by Brett Heap: the per-profile clones beside W are swept too) left stale: - the usage line named only the checkouts under `<W>-lanes/` that CLOSED lanes left; it names the register clones beside W too; - exit 3 said "something under `<W>-lanes/` is removable"; it says "or a register clone beside W"; - the refusal of a lane said the sweep "looks at every register checkout under <W>-lanes/"; it adds "and every register clone beside W". The header's "test seam" text was the third item; MUST A (D45) already rewrote it. `test_the_sweep_takes_no_lane_and_no_other_act[a lane]` now reads the refusal's words. Red, the case before the fix: 1 failed (the refusal named `<W>-lanes/` only). With the fix, the module's selection `takes_no_lane_and_no_other_act or dry_run_with_nothing_to_remove or comments_are_80`: 10 passed. `tests/test_repo_hygiene.py`: 234 passed, 18 skipped, 1 failed, `test_the_root_carries_the_line_ending_rule` (the `.gitattributes` slice 1b' brings; as at the parent). The new mutant `rv4b-I-refusal` (the refusal's old words): KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hose processes were read, and says that another container's processes, their working directories and their windows' `LANES_REPO` are not seen (rv4b's NIT J on 811c10f, from Q2's answer, opensoft/openRepoTools#198, slice 4) rv4b's pre-review of 811c10f: Q2's answer asked the header to say whose processes the sweep reads. `/proc` (or `lsof`) shows the processes of the container it runs in, so beside W an old window in another container on the same host, writing through a per-profile clone, is not seen, and the header did not say so. - The header's new `processes` line: `those container <c> on host <h> shows; another container's processes, their working directories and their windows' LANES_REPO, are not seen`. - The container and host are read from `lanes-edit.sh workstation` in the dry run too (before, only under `--yes`, for the lock's pid line), cleaned as before. - `/proc/<pid>/fd` is not read: by decision this round (D48), not code. `test_the_dry_run_names_the_dependents_scope` reads the line, with the box's `bench-a` and `hosta`. Red, the case before the fix: 1 failed (no `processes` line). With the fix, the module's selection `dependents_scope or pid_line or dry_run or header or workstation or comments_are_80`: 10 passed. `tests/test_repo_hygiene.py`: 234 passed, 18 skipped, 1 failed, `test_the_root_carries_the_line_ending_rule` (the `.gitattributes` slice 1b' brings; as at the parent). Two new mutants, `rv4b-J-line` (no `processes` line) and `rv4b-J-dry` (the dry run does not read the workstation), and `l3-359-colon` re-pointed at the block, now two columns left: all three KILLED. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…code leg's main 577110e) into slice 4 with --no-ff: git found no conflict, since the one file both change, lane-end, changes in places apart (opensoft/openRepoTools#198, slice 4) Slice 1b's code-leg branch, feat/198-a23-slice-1b-guard-publish at 34799b7, as the coordinator named it. It brings slice 1a' and, through it, the code leg's main 577110e: the front door (with the root's .gitattributes), T017's five lanes-edit.sh fixes, and the bash 3.2 `ps` subshell in lane-end's dh_settle_gone (opensoft/openRepoTools#292), outside the sweep's section. The merge's tree is the one `git merge-tree` gave before the merge was made. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es-edit.sh keeps in `.git/lanes-edit/claim-reads-<UTC>-<pid>.txt` for a claim's re-run, are the writer's scratch and hold no checkout; the drift guard found the name at the merge of slice 1b (D49, opensoft/openRepoTools#198, slice 4) The merge of slice 1b's code-leg branch (34799b7) brought 8a88f50 (lane openRepoTools-3's 706 on opensoft/openRepoTools#307): where a claim's Rule 1 reads are over the cap that crosses its re-run's `exec`, attempt 1 keeps them whole in `.git/lanes-edit/claim-reads-<UTC>-<pid>.txt`, and `cleanup` removes the file when the claim's last run ends; only a killed claim leaves one. The sweep holds a checkout for anything in `.git/lanes-edit/` but the writer's own scratch, so a leftover one held its checkout for good. SHOULD 391's drift guard, which reads its cases from lanes-edit.sh's text, failed on it at the merge. - `.git/lanes-edit/`'s filter: a plain file whose name starts `claim-reads-` is scratch too, by find's own test, as the five exact names are. It is a copy of what GitHub keeps. A prefix, since the name carries a UTC and a pid; a folder of that name, or a name with anything before the prefix, still holds (NIT 392's rule). - New case `test_a_claims_whole_reads_alone_hold_nothing` (the name as lanes-edit.sh writes it, removed under `--yes`); `xclaim-reads-1.txt` joins the look-alikes that hold, and a folder named `claim-reads-1.txt` the kinds that are not scratch. Red at the merge e402a23, the whole module: 1 failed, 194 passed (`test_every_other_name_lanes_edit_writes_there_holds_nothing[claim-reads-]`, `hold` for `remove`). With the fix, the module's selection `lanes_edit or scratch or claims_whole_reads or comments_are_80`: 39 passed. `tests/test_repo_hygiene.py`: 235 passed, 18 skipped. Two new mutants, `w4r-D49-claim-reads` (the exemption taken away: the new case and the guard's fail) and `w4r-D49-prefix` (`*claim-reads-*`: the look-alike fails): both KILLED. The four mutants of the filter's find line (`l3-351-scratch`, `l3-391-prefix`, `l3-392-kind`, `l3-392-error`) are re-pointed at the new line for the battery. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Contributor
Author
Mutants at
|
| Mutant | One line changed | First failing case | The kill rests on |
|---|---|---|---|
h-plus |
a + under git cherry does not hold | test_holds[a + on main] |
behaviour |
h-merge |
a merge commit origin lacks does not hold | test_holds[a merge only] |
behaviour |
h-ref |
another local ref reaching what origin lacks does not hold | test_holds[a tag] |
behaviour |
h-stash |
a stash does not hold | test_holds[a stash] |
behaviour |
h-staged |
staged or modified files do not hold | test_holds[staged] |
behaviour |
h-assume |
assume-unchanged entries do not hold | test_holds[assume-unchanged] |
behaviour |
h-untracked |
untracked files do not hold | test_holds[untracked under handoffs] |
behaviour |
h-op |
an operation in progress does not hold | test_holds[a merge in progress] |
behaviour |
h-lock |
its writers' lock does not hold the dry run | test_holds[its writers' lock] |
behaviour |
h-noorigin |
no origin/ does not hold | test_no_origin_main_holds_the_dry_run |
behaviour |
h-worktree |
a linked worktree does not hold | test_holds[a linked worktree] |
behaviour |
h-deps |
a dependent does not hold | test_held_parent_keeps_its_dependents_chain[the leaf held] |
behaviour |
h-cwd |
a live cwd does not hold | test_a_live_cwd_holds |
reason only: 353's read after the move holds it, and moves it back (below) |
a-fetch |
a failed fetch does not hold | test_a_fetch_that_fails_holds |
behaviour |
a-still |
the lane's state is not asked again under the lock | test_a_lane_resumed_after_the_listing_is_kept |
behaviour |
a-recheck |
the holds are not read again under the lock | test_recheck_before_removal |
behaviour |
n-live |
a LIVE lane is not kept by its state | test_a_live_lanes_checkout_is_kept_by_the_live_rule[a LIVE lane] |
reason only: the class check keeps a LIVE lane too (below) |
n-closed |
a lane that is not CLOSED is a candidate | test_a_paused_lane_is_kept_without_LANES_REPO |
a PAUSED lane's checkout goes from keep to remove |
n-runner |
the runner's own checkout is not kept as the runner's | test_W_and_live_lane_never_candidates |
reason in the first case; run alone, test_a_closed_runner_keeps_its_own_checkout is killed by the checkout becoming a candidate |
n-xrepo |
the caller's LANES_REPO is not kept | test_W_and_live_lane_never_candidates |
reason in the first case; run alone, test_a_closed_lanes_checkout_named_by_LANES_REPO_is_kept is killed by the checkout becoming a candidate |
n-link |
a symbolic link is not refused as one | test_W_and_live_lane_never_candidates |
reason only: a second keep still holds it (below) |
n-w |
W is not refused as W | test_a_directory_sharing_Ws_git_directory_is_kept_as_W |
reason only: a second keep still holds it (below) |
n-wlanes |
a W that is a lane checkout is not refused | test_a_W_that_is_a_lane_checkout_is_refused |
exit code, 0 for 2: the sweep goes on beside a lane checkout |
p-lane |
--yes without LANES_LANE is not refused | test_yes_is_refused_before_anything_is_removed[no LANES_LANE] |
exit code and message only, 1 for 2: set -u stops it, nothing removed (below) |
p-nofetch |
--yes under LANES_NO_FETCH=1 is not refused | test_yes_is_refused_before_anything_is_removed[LANES_NO_FETCH=1] |
exit code and message only, 1 for 2: the listing's own refusal stops it, nothing removed (below) |
p-uuid |
D19: a LANES_SESSION that is no uuid is not refused | test_yes_is_refused_before_anything_is_removed[a LANES_SESSION that is no uuid] |
exit code, 1 for 2: the act ran and removed |
o-leaves |
D21: candidates are taken in listed order, not leaves first | test_leaves_first_in_one_run |
the order of the removals |
o-under |
D18: only a clone's own .git/objects is leaned on | test_a_store_kept_inside_a_clone_is_leaned_on |
behaviour |
w-nofetch |
the NOTED line is written under LANES_NO_FETCH=1 | test_noted_line_written_through_runner_checkout |
exit code, 1 for 0: the NOTED line is refused |
w-rc |
a NOTED line not written is a quiet success | test_noted_line_not_written_exits_1_with_the_command |
exit code, 0 for 1: an unwritten line is a quiet success |
d-dry |
the dry run acts | test_dry_run_lists_and_removes_nothing |
exit code, 0 for 3: the dry run took the act's path and removed a checkout |
m1b-listing-off |
MUST 1: the listing's --yes refusal on a W fetch that did not complete is off | test_a_W_fetch_that_fails_at_the_listing_refuses_yes[the fetch fails after ls-remote (fell-back)] |
exit code, 0 for 1: --yes goes on to the act on a register it could not fetch |
m2-sc-fetch |
MUST 1: the re-ask ignores a W fetch that did not complete | test_a_lane_resumed_while_W_cannot_fetch_is_kept |
behaviour |
m3-sc-sessions |
MUST 1: the re-ask ignores unreadable session records | test_session_records_unreadable_at_the_recheck_keep_the_checkout |
behaviour |
m4-sc-register |
MUST 1: the re-ask ignores an unreadable register | test_a_register_unreadable_at_the_recheck_keeps_the_checkout |
behaviour |
m5-sc-capture |
rv4-r2 SHOULD 1: a capture not read back does not hold by itself | test_a_capture_that_cannot_be_read_back_holds_by_itself |
behaviour |
R1-no-cwd-rescan |
SHOULD 1 R1: the working directories are not read again under the lock | test_a_cwd_that_appears_after_the_listing_holds |
reason only: 353's read after the move holds it, and moves it back (below) |
R3-no-former-name-reask |
rv4 R3: the former-name re-ask never holds | test_a_former_name_whose_new_checkout_goes_after_the_listing_is_kept |
behaviour |
R4-no-partial-exit |
rv4 R4: no exit 1 for a removal that did not finish | test_a_removal_that_does_not_finish_exits_1[beside a removal that finished] |
exit code, 0 for 1: a removal that did not finish |
s2-exit3 |
SHOULD 2: log's exit 3 is reported as NOT written | test_noted_line_unconfirmed_is_never_written_again |
wording: "was NOT written" for a line that landed, after the exit code held |
s2-exit6 |
SHOULD 2: exit 6 gets the generic refusal (the arm dropped) | test_noted_line_kept_unpublished_is_published_not_written_again |
wording: "was NOT written" for a line kept unpublished, after the exit code held |
d29-listing |
D29: a former name beside its new checkout is a candidate whatever its lane (D27 and D29 undone) | test_a_former_name_of_a_lane_not_closed_is_kept |
reason: an IDLE lane's former-name checkout goes from keep to hold, by D37's LANES_REPO hold (below) |
d29-still |
D29: the re-ask passes a former name without asking its lane LIVE or CLOSED | test_a_former_name_whose_lane_resumes_after_the_listing_is_kept |
behaviour |
n1-gitfile |
NIT 1: a .git file naming a clone's git directory does not hold | test_a_git_file_naming_a_clones_git_directory_holds_it[absolute] |
behaviour |
n2-ign |
NIT 2: the act's removed line does not name ignored files | test_ignored_files_are_named_at_the_act |
wording: the removed line's note of ignored files |
n3-count |
NIT 3: a partial removal is counted as removed | test_a_removal_that_does_not_finish_exits_1[beside a removal that finished] |
wording: the summary's counts |
n4-clone |
NIT 4: what made it a register checkout is not asked again under the lock | test_a_checkout_whose_origin_changes_after_the_listing_is_held |
behaviour |
n4-owner |
NIT 4: a lock taken over before the move does not hold | test_a_lock_taken_over_under_the_sweep_holds_and_is_left[then the move is asked] |
reason only: 353's read after the move holds it, and moves it back (below) |
n4-drop |
NIT 4: sc_lock_drop removes a lock taken over | test_a_lock_taken_over_under_the_sweep_holds_and_is_left[then the fetch fails] |
the other holder's lock is removed |
n4-cleanup |
NIT 4: cleanup removes a lock taken over | test_a_lock_taken_over_is_left_by_a_sweep_ended_by_a_signal |
the other holder's lock is removed |
c3-2-pid |
NIT c3-2: a pid line that cannot be written is ignored, and the lock is kept | test_a_lock_whose_pid_line_cannot_be_written_is_let_go[the lock directory cannot be written in] |
the sweep's own pid-less lock is left in the checkout |
c3-3-crlf |
NIT c3-3: a .git file's trailing CR is kept, so its gitdir does not resolve | test_a_git_file_naming_a_clones_git_directory_holds_it[CRLF, through a link that resolves once the CR is gone] |
behaviour |
c3-4-beat |
NIT c3-4: the lock is never refreshed (sc_lock_beat() { :; }) | test_the_lock_is_refreshed_before_the_reask |
the lock reads over ten minutes old at the re-ask, where a writer in another container may take it over |
c3-4-relative |
NIT c3-4: a relative gitdir: is not read against the directory its .git file is in | test_a_git_file_naming_a_clones_git_directory_holds_it[relative to the directory it is in] |
behaviour |
c4-1-runner |
NIT c4-1: the runner's former-name checkout is kept with the reason it had before the fix (the former-name prefix, then the runner's own checkout's reason) | test_a_former_name_of_the_runners_own_lane_is_kept_for_its_own_reason[dry run] |
reason only: nothing is kept or removed differently, the case asserts the whole row (below) |
c4-2-rm |
NIT c4-2: the pid file a failed write leaves is not removed, so the rmdir of the lock fails and the lock is left | test_a_lock_whose_pid_line_cannot_be_written_is_let_go[the pid file's write fails, as on a full disk] |
behaviour: the lock is left holding the empty pid file (['pid']); the case's other parameter alone does not kill it (below) |
l3-351-kept |
MUST 351: a hand edit lanes-edit.sh kept in .git/lanes-edit/ does not hold | test_a_hand_edit_lanes_edit_kept_holds[the kept edit is named] |
behaviour |
l3-351-scratch |
MUST 351: the writer's own scratch in .git/lanes-edit/ holds too (the filter dropped) | test_lanes_edit_scratch_alone_holds_nothing |
behaviour: the writer's own scratch alone holds the checkout |
l3-371-mount |
SHOULD 371: a file system mounted in a checkout does not hold | test_a_mount_inside_a_checkout_holds[at an ignored path] |
behaviour |
l3-371-none |
SHOULD 371: no mount table that can be read holds nothing | test_a_mount_table_that_cannot_be_read_holds[no table] |
behaviour |
l3-371-rescan |
SHOULD 371: the mount table is not read again under the lock | test_a_mount_made_after_the_listing_holds |
behaviour |
l3-371-escape |
SHOULD 371: the path is not spelled with mountinfo's escapes | test_a_mount_in_a_checkout_whose_own_path_has_a_space_holds |
behaviour |
l3-372-exec-rm |
SHOULD 372: the aside's rm is a plain rm again, so a Ctrl-C to the group stops it halfway | test_a_signal_during_a_removal_lets_it_finish_and_prints_the_record[INT to its process group, as a Ctrl-C] |
behaviour: a Ctrl-C to the group leaves the other removal PARTLY done |
l3-372-byhand |
SHOULD 372: a signal after removals prints no NOTED line by hand | test_a_signal_during_a_removal_lets_it_finish_and_prints_the_record[TERM to the sweep alone] |
the remedies: no NOTED line to write by hand after a signal |
l3-372-leftover |
SHOULD 372: a leftover aside is counted with the hidden entries again | test_a_removal_left_unfinished_is_named_by_every_run |
behaviour: the aside a removal left is not listed |
l3-372-noting |
SHOULD 372: a signal during the NOTED write is not known as one | test_a_signal_while_the_noted_line_is_written_never_writes_it_twice |
wording: a signal during the write does not say the line may be on origin already |
l3-372-partial-first |
SHOULD 372: the unfinished removal is not said before the NOTED write | test_a_removal_that_does_not_finish_is_said_when_the_noted_write_fails |
wording: the unfinished removal is not said when the NOTED write fails |
l3-373-unmoved |
SHOULD 373: a push refused with origin unmoved is not told apart | test_noted_line_kept_unpublished_is_published_not_written_again |
the remedies: three handed out where the publish act alone is right |
l3-373-restore |
SHOULD 373: a failed git add or commit is not told apart | test_noted_line_whose_commit_fails_is_put_back_then_written_by_hand |
wording and remedies: the failed commit is not said, nor its restore handed out |
l3-373-archive |
SHOULD 373: a write that lost the race to its bound is not told apart | test_noted_line_that_loses_six_push_races_is_archived_then_written_by_hand |
the remedies: two handed out, with no archive push |
l3-373-capture |
SHOULD 373: the write's words are not kept, so no cause is known | test_noted_line_whose_commit_fails_is_put_back_then_written_by_hand |
wording and remedies: with the words not kept, the failed commit is not said |
l3-391-prefix |
SHOULD 391: the scratch names match as prefixes (lane 3's mutant c) | test_a_name_that_only_looks_like_scratch_holds[removed.bak] |
behaviour |
l3-391-link |
SHOULD 391: a dangling .git/lanes-edit link is not looked at (lane 3's mutant f) | test_a_dangling_link_for_lanes_edit_holds |
behaviour |
l3-392-kind |
NIT 392: a folder or other non-file with a scratch name is scratch (-type f dropped) | test_only_a_plain_file_with_a_scratch_name_is_scratch[a folder named removed] |
behaviour |
l3-392-error |
NIT 392: a find that cannot list the folder holds nothing | test_a_dangling_link_for_lanes_edit_holds |
behaviour |
l3-374-under-home |
SHOULD 374: the move is not asked again whether the path is directly under -lanes/ | test_a_lanes_folder_swapped_for_a_link_under_the_lock_holds |
behaviour |
l3-374-mv-lock |
SHOULD 374: a move that fails leaves the checkout's lock | test_a_checkout_that_cannot_be_moved_aside_lets_its_lock_go |
behaviour: the held checkout's lock is left |
l3-374-partly-noted |
SHOULD 374: a run whose only removal did not finish writes no NOTED line | test_a_removal_that_does_not_finish_exits_1[alone] |
behaviour: no NOTED line for a run whose only removal did not finish |
l3-545-unread |
finding 545: a last-session that could not read is taken as one that names none | test_a_last_session_that_cannot_be_read_is_never_none[exit 1, a log that cannot be read] |
exit code, 2 for 1: a log that could not be read is taken as naming no session |
l3-352-tmpdir |
SHOULD 352: $TMPDIR is not in the dependents scope | test_a_dependent_anywhere_in_the_scope_holds[in TMPDIR] |
behaviour |
l3-352-depth |
SHOULD 352: W's parent is scanned to depth 3, not 6 | test_a_dependent_anywhere_in_the_scope_holds[five levels under W's parent] |
behaviour |
l3-352-fail |
SHOULD 352: a dependents scan that cannot finish holds nothing | test_a_dependents_scan_that_fails_holds_every_candidate |
behaviour: the dry run exits 3, the candidate removable |
l3-378-order |
NIT 378: the lines are read back dependent first | test_a_dependent_anywhere_in_the_scope_holds[whose own path holds a TAB] |
reason: the target is named as the dependent (still held) |
l3-356-gitfile |
NIT 356: a .git file that cannot be read is skipped | test_a_dependents_file_that_cannot_be_read_holds_every_candidate[.git file] |
behaviour: the dry run exits 3, the candidate removable |
l3-d37-beside |
D37: a clone beside W is no candidate | test_clones_beside_W_are_removed_leaves_first |
behaviour: the clones beside W are no candidates |
l3-d37-xrepo |
D37: the clone beside W the caller's LANES_REPO names is not kept | test_the_clone_beside_W_the_callers_LANES_REPO_names_is_kept |
the keep is gone (another hold stops the removal) |
l3-d37-still |
D37: the lane's state is asked of a clone beside W, which is no lane's | test_clones_beside_W_are_removed_leaves_first |
behaviour: the clones beside W are held under the lock, none removed |
l3-d37-repo |
D37: a window whose LANES_REPO names a clone does not hold it | test_a_window_whose_LANES_REPO_names_a_clone_beside_W_holds_it[its own path] |
behaviour |
l3-d37-real |
D37: a LANES_REPO is matched as spelled only, not by its real path | test_a_window_whose_LANES_REPO_names_a_clone_beside_W_holds_it[a link to it] |
behaviour |
l3-d37-noproc |
D37: with no /proc a clone beside W is not held as cannot tell | test_with_no_proc_whether_a_window_names_a_clone_beside_W_cannot_be_told |
behaviour |
l3-d37-aside |
D37: a clone beside W is moved aside into -lanes/ | test_a_removal_beside_W_that_does_not_finish_leaves_its_aside_beside_W |
behaviour: the aside is made in <W>-lanes/, not beside W |
l3-d37-leftover |
D37: an aside beside W is not named by later runs | test_a_removal_beside_W_that_does_not_finish_leaves_its_aside_beside_W |
behaviour: the aside left beside W is not listed |
l3-d37-noted |
D37: the NOTED line does not name the clones beside W it removed | test_clones_beside_W_are_removed_leaves_first |
wording: the NOTED line lacks the clones beside W |
l3-d37-names |
D37: a clone's name is written into the NOTED line as it is | test_clones_beside_W_are_removed_leaves_first |
wording: a name with a blank is written as it is |
l3-394-wide |
NIT 394: a comment line of the sweep's section over 80 columns | test_the_sweeps_comments_are_80_columns_or_less |
the width case: a comment line at 88 columns |
l3-377-enter |
NIT 377: a -lanes/ that cannot be entered or listed is not refused | test_a_lanes_folder_that_cannot_be_entered_or_listed_is_refused |
exit code: the run reads on past a <W>-lanes/ it cannot enter |
l3-376-w |
NIT 376: --yes under routing with a LANES_REPO that is W is not refused | test_yes_with_LANES_REPO_naming_W_is_refused_before_anything_is_removed[W] |
exit code: the act ran and removed |
l3-359-colon |
NIT 359: the pid line's container drops lock_ident's : |
test_the_locks_pid_line_is_lanes_edits_own[a container with a colon] |
wording: the pid line's container |
l3-379-long |
NIT 379: the aside's name keeps the whole name, past NAME_MAX for a long one | test_a_long_name_is_moved_aside_under_a_name_that_does_not_grow |
behaviour: the long name is held, as it cannot be moved aside |
l3-358-keep |
NIT 358: a keep line prints its path raw | test_a_name_with_a_control_character_writes_no_line_of_its_own |
behaviour: a forged row is printed |
l3-354-reflog |
NIT 354: commits only the reflog reaches are not named | test_commits_only_the_reflog_reaches_are_named |
wording: the reflog's commits are not named |
l3-355-repo |
NIT 355: a repository at an ignored path does not hold | test_what_ignored_rules_hide_that_may_be_work_holds[a repository at an ignored path] |
behaviour |
l3-355-swap |
NIT 355: an ignored editor's swap file does not hold | test_what_ignored_rules_hide_that_may_be_work_holds[an ignored swap file] |
behaviour |
l3-355-tag |
NIT 355: annotated tags are not named | test_annotated_tags_are_named |
wording: the annotated tag is not named |
l3-375-lock |
NIT 375: a second --yes takes the sweep-wide lock anyway (mkdir -p) | test_a_second_sweep_is_refused_while_one_runs |
exit code, 0 for 2: a second sweep runs |
l3-375-gone |
NIT 375: a candidate gone at its turn is held, not said as gone | test_a_candidate_gone_at_its_turn_is_said_as_gone |
reason: a candidate gone at its turn is held, not said as gone |
l3-353-cwd |
NIT 353: a process that came in after the last read is not asked of the aside | test_a_process_that_comes_in_after_the_last_read_moves_it_back |
behaviour |
l3-353-deps |
NIT 353: a clone made after the last read is not asked after the move | test_a_clone_made_after_the_last_read_moves_it_back |
behaviour |
l3-353-text |
NIT 353: a .git or alternates target that is gone is dropped, not kept | test_a_clone_made_after_the_last_read_moves_it_back |
behaviour |
l3-353-pid |
NIT 353: the pid line is not read again from the aside | test_a_lock_taken_over_at_the_move_moves_it_back |
behaviour |
l3-353-back |
NIT 353: a checkout held after its move is not moved back | test_a_lock_taken_over_at_the_move_moves_it_back |
behaviour: the held checkout is left at its aside |
rv4b-A-refuse |
MUST A: --yes with a LANES_WORKSPACE_ROOT that is not path:'s W is not refused | test_a_W_override_naming_a_clone_beside_W_never_sweeps_the_real_W[--yes] |
behaviour: --yes is not refused, and the run sweeps from the wrong W |
rv4b-A-real |
MUST A: the override is compared with path: as spelled, not by its real path | test_a_W_override_naming_W_itself_is_no_refusal[a link to W] |
exit code, 2 for 0: an override through a link to W itself is refused |
rv4b-A-path |
MUST A: the directory workspace.yaml's path: names may be a candidate | test_the_directory_workspace_yaml_names_is_never_a_candidate |
behaviour |
rv4b-A-lanes |
MUST A: a clone beside W with a -lanes/ beside it may be a candidate | test_a_clone_beside_W_with_lane_checkouts_of_its_own_is_never_a_candidate[at the listing] |
behaviour |
rv4b-A-relock |
MUST A: whether it is a W is not asked again under the lock | test_a_clone_beside_W_with_lane_checkouts_of_its_own_is_never_a_candidate[after the listing] |
behaviour |
rv4b-A-dry |
MUST A: the dry run does not say what --yes refuses | test_the_directory_workspace_yaml_names_is_never_a_candidate |
wording: the dry run's header does not say that --yes refuses |
rv4b-D-repo |
SHOULD D: a window that came to name the old path does not move it back | test_a_window_that_names_it_after_the_last_read_moves_it_back |
behaviour |
rv4b-D-repo-scan |
SHOULD D: the windows' environments are not read again after the move | test_a_window_that_names_it_after_the_last_read_moves_it_back |
behaviour |
rv4b-D-reponone |
SHOULD D: with no /proc after the move a clone beside W is not moved back | test_a_proc_gone_as_a_clone_beside_W_is_moved_aside_moves_it_back |
behaviour |
rv4b-D-mount |
SHOULD D: a mount that came to be in the aside does not move it back | test_a_mount_made_as_it_is_moved_aside_moves_it_back[a mount in it] |
behaviour |
rv4b-D-mount-scan |
SHOULD D: the mount table is not read again after the move | test_a_mount_made_as_it_is_moved_aside_moves_it_back[a mount in it] |
behaviour |
rv4b-D-mountnone |
SHOULD D: no mount table after the move does not move it back | test_a_mount_made_as_it_is_moved_aside_moves_it_back[no table] |
behaviour |
rv4b-D-aside |
SHOULD D: the mounts are matched against the old path, not the aside that carried them | test_a_mount_made_as_it_is_moved_aside_moves_it_back[a mount in it] |
behaviour |
rv4b-B-own |
SHOULD B: a directory of this user's own the scan cannot read holds nothing | test_a_directory_of_this_users_own_the_dependents_scan_cannot_read_holds_every_candidate[plain] |
behaviour |
rv4b-B-any |
SHOULD B: every directory the scan cannot read holds, another user's too | test_a_directory_of_another_user_the_dependents_scan_cannot_read_is_counted_and_holds_nothing |
behaviour: another user's unreadable directory holds every candidate |
rv4b-B-quotes |
SHOULD B: find's quotes are kept on the path, so no directory is told to be this user's | test_a_directory_of_this_users_own_the_dependents_scan_cannot_read_holds_every_candidate[plain] |
behaviour |
rv4b-B-unquote |
SHOULD B: find's escaped quote is kept, so a name with a quote is not found | test_a_directory_of_this_users_own_the_dependents_scan_cannot_read_holds_every_candidate[with a quote, a blank and a backslash] |
behaviour |
rv4b-B-escapes |
SHOULD B: find's backslash escapes are kept, so a name with a backslash is not found | test_a_directory_of_this_users_own_the_dependents_scan_cannot_read_holds_every_candidate[with a quote, a blank and a backslash] |
behaviour |
rv4b-C-depth |
SHOULD C: TMPDIR is scanned to depth 7, short of a clone eight levels down a scratchpad | test_a_dependent_anywhere_in_the_scope_holds[in a Claude Code scratchpad, eight levels under TMPDIR] |
behaviour |
rv4b-C-scope |
SHOULD C: the scope line names a depth that is not the scan's | test_the_dry_run_names_the_dependents_scope |
wording: the scope line names a depth the scan does not use |
r4b-home-depth |
rv4b's mutant: $HOME is scanned to depth 0 | test_a_dependent_anywhere_in_the_scope_holds[in HOME] |
behaviour |
rv4b-353-text |
the 353 gap: a target that does not resolve is kept as written, its .. and all | test_a_relative_path_written_after_the_last_read_moves_it_back[.git file] |
behaviour |
rv4b-353-gitfile |
the 353 gap: a .git file's target that does not resolve is dropped | test_a_relative_path_written_after_the_last_read_moves_it_back[.git file] |
behaviour |
rv4b-353-alt |
the 353 gap: an alternates target that does not resolve is dropped | test_a_relative_path_written_after_the_last_read_moves_it_back[alternates file] |
behaviour |
r4b-rel-alt |
rv4b's mutant: a relative alternates line is not read against its objects directory | test_a_relative_path_written_after_the_last_read_moves_it_back[alternates file] |
behaviour |
w4r-353-post-gitfile |
D46: a .git file written after the last read is not read after the move | test_a_git_file_written_after_the_last_read_moves_it_back |
behaviour |
w4r-353-post-lines |
D46: the read after the move matches the alternates lines twice, the .git files' never | test_a_git_file_written_after_the_last_read_moves_it_back |
behaviour |
rv4b-G-nested |
NIT G: a move back into a directory that came is said as moved back | test_a_directory_made_at_its_path_as_it_is_moved_back_is_said |
reason: "it was moved back" over a checkout hidden in the new directory |
rv4b-I-refusal |
NIT I: the refusal of a lane says the sweep looks under -lanes/ only | test_the_sweep_takes_no_lane_and_no_other_act[a lane] |
wording: the refusal of a lane |
rv4b-J-line |
NIT J: the header does not name the container whose processes were read | test_the_dry_run_names_the_dependents_scope |
wording: the header's processes line |
rv4b-J-dry |
NIT J: the dry run does not read the container and host | test_the_dry_run_names_the_dependents_scope |
wording: the header's processes line names no container |
w4r-D49-claim-reads |
D49: a claim's whole Rule 1 reads in .git/lanes-edit/ hold the checkout | test_a_claims_whole_reads_alone_hold_nothing |
behaviour: a leftover claim-reads file holds the checkout for good |
w4r-D49-prefix |
D49: anything with claim-reads- in its name is scratch, not only a name starting so | test_a_name_that_only_looks_like_scratch_holds[xclaim-reads-1.txt] |
behaviour |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Lane: openreposhape-2 (openRepoShape-2)
Claim: opensoft/openRepoTools#198 (comment)
Re-homed from opensoft/openRepoTools#308
This is slice 4 of act 2 (Amendment 23), re-homed to the code leg, opensoft/openRepoTools-code, by cherry-pick and not by merge, in the stack order (Brett Heap's Q28 answer, on opensoft/openRepoTools#198, comment 6722244541: slices 2a, 2b, 3 and 4 open here as draft PRs by 2026-11-12). The old PR, opensoft/openRepoTools#308, stays as the archived original; nothing more is pushed to its branch. Below the receipt is the original body, brought up to this round.
feat/198-a23-slice-4-lane-end-sweepfeat/198-a23-slice-4-lane-end-sweep7d194593726d9ccb3e645cc132f10f7209bce383e5063e0db7f1dd1c90c4397401525ff03d7e9009558830702f2d0834799b7(slice 1b's code-leg branch, with slice 1a' and the code leg'smain577110e), merged ase402a231f16bbcaac7b855988f600765ca26c975013f1f6Method.
f18b818to code0cefa1f: same subject, author and author date; filter-repo only rewrote the short shas in its message.git commit-tree. Its old tree is cut to the code leg's 26 owned top-level paths (the cutover plan'sleg: codeentries), and its parents are mapped. Author, author date and committer date are kept; the committer is Brett Heap. The message is kept byte for byte, plus a(cherry picked from commit <old sha>)line placed asgit cherry-pick -xplaces it.lane-endandtests/test_a23_sweep_checkouts.py, both on the code leg, so no hunk was dropped and no commit is empty. Its spec patch is empty.Range-diff summary (
git range-diff 5588307..7d19459 02f2d08..e5063e0 --creation-factor=60): all 47 plain commits pair, and each pair differs by the added cherry-pick line alone. None is unmatched. The 3 merges are not shown by this git (2.43); the pair-by-pair tree check covers them.This round, on the code leg (rv4b's pre-review of old
811c10f, here4a356ac): the last old head,7d19459, carried MUST A and SHOULDs B, C and D. On the code leg,2d7f80dcloses the 353 gap (decision 27), andf6de965,e572b49,001bd6dand28fb2adare NITs G, H, I and J. Thene402a23merges slice 1b's code-leg head34799b7, as the coordinator named it, with--no-ff: no conflict, since the one file both change,lane-end, changes in places apart (1b' brings the code leg'smain, with opensoft/openRepoTools#292'spssubshell indh_settle_gone, outside the sweep). The merge's module run found one name slice 1b'slanes-edit.shnow writes in.git/lanes-edit/that the sweep did not know, and1f16bbcis that fix (decision 30).Reading the original body below. Its shas are old shas; the receipt maps each to its new one. The ones it names most:
210f4cfisab1109d,53b7bf3is7541a21,e6cb1e8isc4c8e9b,cd060f0is7cf3147,ff12b01isd22503d,811c10fis4a356ac,80bd392isb6a517c, and the mergescede054and4f31084are98595bbandb1c3855. Slice 1b's5588307is02f2d08,d9ff4e6isc4393e8,e6ea8f8ise594547,6ecfc52is9714694and3905009is2ed9e62; slice 1a's15c8e3fiscb05cfc. "Pushed" there means pushed to the old branch. Ten-digit numbers in parentheses are comment ids in the old repository.Receipt: the 50 rebuilt commits, old sha to new sha (subjects cut to 56 characters)
faac2e7b9a93dfd882Amendment 23 S6.2: 'lane-end --sweep-checkouts' lists ...991d8c6c98fa703bd2Amendment 23 S6.2: 'lane-end --sweep-checkouts --yes' ...2140189e2b1eac0d6bAmendment 23 S6.2: 'lane-end --sweep-checkouts --yes' ...210f4cfdbab1109d23Merge slice 1b's head d9ff4e6 (rv1b's MUST 4, MUSTs ...cede0546898595bb08Merge slice 1b's head e6ea8f8 (SHOULD C and the shell ...b896230dd3f18f87d3Amendment 23 S6.2: 'lane-end --sweep-checkouts --yes' ...2e58431d2b923cbaccAmendment 23 S6.2: a re-ask under a checkout's lock ...e152a89daea6a07d35Amendment 23 S6.2 tests: the two cases in which a lane ...dfec3c5c036f08a1d2Amendment 23 S6.2 tests: the path on which W's ...a4cbf1a0defc6ff7eeAmendment 23 S6.2: a NOTED write that exits 3 (pushed ...9a7e8c88bd1dc971f4Amendment 23 S6.2 tests: the cwd scan and the ...1345897d7f07aa026cAmendment 23 S6.2 tests: the ten composite asserts are ...65594885b5b285a290Amendment 23 S6.2: a former name's checkout is kept ...10468ac67f233cc665Amendment 23 S6.2: a '.git' file naming a clone's git ...a376e5b0b483a72f46Amendment 23 S6.2 tests: the '--yes' refusals before ...459c1f6b2a0d058ae6Amendment 23 S6.2 tests: a LIVE lane is kept by the ...cd060f09a7cf314729Amendment 23 S6.2 tests: a lock taken over is left to ...4f3108449b1c385585Merge slice 1b's head 5588307 (A37's keep-if-gone, the ...67e3433a3ee326d2d4Amendment 23 S6.2: a former name's checkout is a ...95f62f1d8513364dc7Amendment 23 S6.2: a sweep whose pid line cannot be ...5b029c4717f45bf8d4Amendment 23 S6.2: a '.git' file whose 'gitdir:' line ...e6cb1e8a7c4c8e9b4eAmendment 23 S6.2 tests: the lock's refresh before the ...78caf60be8ca153ee5lane-end --sweep-checkouts: a former name of the ...53b7bf3347541a2110Amendment 23 S6.2 tests: D30's case also fails the ...e78e9fd812f5613cd2lane-end --sweep-checkouts: a hand edit that ...50a37b7c5d6521bc76lane-end --sweep-checkouts: a checkout with a file ...596aeeeb3b097a0a8aAmendment 23 S6.2 tests: a checkout whose own path ...ff12b0154d22503da8lane-end --sweep-checkouts: an interrupted '--yes' lets ...3373d5be2b2fee5e4blane-end --sweep-checkouts: a NOTED write's exit 6 is ...6b3c308cbcb512871aAmendment 23 S6.2 tests: the '.git/lanes-edit/' ...4c45223e04c73d6d5elane-end --sweep-checkouts: 'find' decides what in ...75c7118596ff075ac6Amendment 23 S6.2 tests: three guards of the act that ...24e3c6d6b1b3de12c4lane-end --sweep-checkouts: only 'last-session''s 8 is ...e04d2639663d64a344lane-end --sweep-checkouts: what leans on a checkout is ...dba0294176018b914elane-end --sweep-checkouts: the register clones beside ...433f759be4511512c0lane-end --sweep-checkouts: the holds' comment line at ...9b19e124ea06b2cbaalane-end --sweep-checkouts: a '<W>-lanes/' that cannot ...0f564e84a1ac76163elane-end --sweep-checkouts: under routing, '--yes' with ...909dce4c4a90f7ba39lane-end --sweep-checkouts: the pid line of the lock ...1bd64905fdd23c572elane-end --sweep-checkouts: the aside's name keeps the ...ed8576fc391b4c2b3blane-end --sweep-checkouts: each control character in a ...307b9ca51859e86601lane-end --sweep-checkouts: commits only a checkout's ...9e0cbd158e43c08442lane-end --sweep-checkouts: a repository at an ignored ...51ee5ff248b28e3c6dlane-end --sweep-checkouts: a sweep-wide lock refuses a ...811c10fc04a356ac90lane-end --sweep-checkouts: the move aside is the ...80bd3922eb6a517c92Amendment 23 S6.2 tests: a CRLF '.git' file whose path ...c73a9afc68025e3851Amendment 23 S6.2: '--sweep-checkouts --yes' refuses ...2bc9b667692186da6fAmendment 23 S6.2: after '--sweep-checkouts --yes' ...e12d07763a90260a4bAmendment 23 S6.2: a directory of this user's own that ...7d1945937e5063e0dbAmendment 23 S6.2: the dependents scan reads '$TMPDIR' ...Stacked on
Act 2's slice 1b,
feat/198-a23-slice-1b-guard-publish, at5588307(it carries slice 1a, opensoft/openRepoTools#282, at15c8e3f). This branch began at 1b's3905009and reached6ecfc52by a fast-forward before its first commit. After its three stages it merged 1b'sd9ff4e6with--no-ffas210f4cf, then 1b'se6ea8f8ascede054, and then 1b's5588307as4f31084. git found no conflict any time, because slice 4 changes no file slice 1b changes. On the code leg, slice 1b's code-leg head34799b7, which carries slice 1a' and the code leg'smain577110e, is merged with--no-ffase402a23, again with no conflict: the one file both change,lane-end, changes in places apart.Please review the commits after
5588307, here02f2d08:git log --no-merges 02f2d08..HEAD. They are the three stages, then the fix round for the pre-review (rv4) and its re-check (rv4-r2), then the round for the re-review of that fix round (rv4-c3), one commit per finding, then the round for its re-check (rv4-c4), then the round for lane openRepoTools-3's review at53b7bf3, one commit per finding:git log --no-merges 7541a21..80bd392here (old53b7bf3..80bd392). Its re-check of 351 (391, 392, 394), finding 545, Brett Heap's answer to question 1 (D37) and lane 3's NITs are in it too, and its last commit, tests only, pins again the one mutant the round's battery found alive (c3-3-crlf, decision 25). Then this round, for rv4b's pre-review of811c10f, one commit per finding: MUST A, SHOULDs D, B and C, the 353 gap (decision 27), NITs G, H, I and J, then the merge of slice 1b's code-leg branch and decision 30:git log --no-merges b6a517c..HEADhere (old80bd392). SHOULD E is a measurement and a question (decision 28, question 4), and NIT F is decision 25. The slice's whole diff isgit diff 02f2d08 HEADhere:lane-endand the newtests/test_a23_sweep_checkouts.py, nothing else.Decisions
lane-end --sweep-checkouts [--yes] [--dry-run]is its own act, like--retire-dormant, answered before the lane derivation (which refuses a line naming no lane). It refuses (2) a lane,--retire,--retire-dormant,--force,--inventory-only,--state-was,--homeand--reason.--dry-runwith--yesis a dry run. The usage, OPTIONS and EXIT text name it.<W>-lanes/is removed (D2). Amendment 23 S6.2 sweeps every register clone BESIDE W too (the per-profile clones). The brief this slice was written under says twice that nothing outside<W>-lanes/is ever a candidate. A removal cannot be undone, and the plan says slice 4 is not needed for the install, so this build takes the stricter rule: it LISTS each clone beside W with what holds it, and removes none. Question 1 below. Superseded by decision 23: Brett Heap answered question 1, and the clones beside W are swept too.<W>-lanes/that is a clone of the register (a directory at its own real path, not a link, a.gitdirectory of its own, the root of its checkout, origin the same repository as W's, git common directory not W's) whose lanelanes --closed --allclassesclosed; or a former name's checkout beside its new one (S1), once that lane is CLOSED too (decision 15). A former name with no new one beside it is that lane's own checkout, and is judged as the lane's. Hidden entries (creation locks,.partialclones, the sidecar lock) are never looked at, and are counted.LANES_REPOnames (Eagle's lane windows export one); a symbolic link, or anything a link leads to; a<W>-lanes/that is itself a link. A W that stands in a*-lanesdirectory is refused (2), aslanes_home_checkrefuses it: from a lane window, "beside W" would be every other lane's checkout.+undergit cherry refs/remotes/origin/<branch>for every local branch and a detached HEAD, refs spelled in full; a merge commit origin lacks, whichgit cherryskips; any other local ref (a tag, a note) reaching what origin lacks; a stash; staged or modified files; index entries marked assume-unchanged or skip-worktree, whichgit statusdoes not look at; untracked files ANYWHERE, since no tool can tell a person's work from scratch; an operation in progress, unmerged entries, a heldindex.lock; the checkout's own writers' lock; and noorigin/<branch>to compare with. Ignored files hold nothing, and are named..gitFILE names the clone's git directory, or one inside it (rv4's NIT 1), read as git reads it: a CRLF line (rv4-c3's NIT c3-3) or a path relative to the directory the file is in; and linked worktrees./proc/*/cwd, orlsofon macOS; with neither, "cannot tell" holds it. A working directory that cannot be read (another user's process, or one that ended) is counted and said, and does not hold. Question 2 below.--yesreads what leans on each candidate again just before its removal, so a held dependent keeps the whole chain it borrows from.origin/<branch>as last fetched, and says so. 3 means something is removable, 0 nothing.--yesdecides each checkout again, under its own lock, after a fetch (D8, D14; rv1b's MUSTs 1-2). Per candidate, in D6's order:mkdiritslanes/.lanes-edit.lockwithlanes-edit.sh's pid line, so its writers wait; fetch it with an explicit refspec, bounded (a failed fetch holds it); ask the lane's state again (lanes --fetch --closed --lane), and for a former namecanon-laneagain; read every hold, what leans on it and the working directories again; only thenmvit aside to<W>-lanes/.<name>.removing-<UTC>-<pid>(its S1 path is absent at once, so the next write clones afresh) and remove that. A removal that does not finish says so by path and exits 1. The pre-scan is a plan, never a decision.touch -c) before the fetch and the re-ask, asgit_net's heartbeat does. A pid line that cannot be written into a lock just taken lets that lock go at once and holds the checkout (rv4-c3's NIT c3-2), so the sweep never leaves a lock that names no holder, and a line that is not the sweep's is only ever another writer's. Its test fails the write two ways, since each needs its own check: a lock directory that cannot be written in (no pid file is made), and a pid file whose write fails, as on a full disk, which leaves an EMPTY file that the sweep'srm -fmust remove before thermdircan take the lock away (rv4-c4's NIT c4-2). Its pid line is read again just before themv: a line naming another holder means the lock was taken over, so the checkout is held and that lock is left to its holder.sc_lock_dropand the exit'scleanupleave such a lock, asmutex_releasedoes. The act'sremovedandPARTLYlines name the ignored files that went with the checkout (NIT 2).lanes --fetchexits 0 when W's fetch fails afterls-remoteanswered (fell-back), and says so only in its notes. So a W fetch that does not complete, either way, refuses--yesat the listing (1, "Nothing was removed"). Under each lock the re-ask's notes are captured and read, and the checkout is held when they say the logs were read as they stand locally, the session records could not be read, or the register could not be read. A capture that cannot be made or read back holds the checkout too, with that as its reason.LANES_LANEset (2 without), the lane known by a row or a log (2), noLANES_NO_FETCH=1(2: S3 refuses a writer whose fetch did not run, ratified choice 2, rv1b's MUST 3), and a transcript uuid for the NOTED line's session field, fromLANES_SESSIONorlast-session(2).lanes-edit.sh's own checks when the line is written are still the decision.lanes-edit.sh log NOTED lane:<runner>line in the runner's own log, written through the runner's own checkout under S3 and S4, in a subshell withLANES_NO_FETCHunset. Its text names what was removed, what was moved aside and not all removed, what was held, and how many clones beside W were left, from names held to[A-Za-z0-9._-], and no host path. Its count, and the summary's, count a removal that did not finish apart from the ones that did (NIT 3), and a run whose only act is a partial removal still writes the line. A line the writer refuses exits 1 with the exact command that writes it by hand. Nothing was lost then: each clone removed held nothing unpublished. A write that did not confirm is not one that did not happen (rv4's SHOULD 2):log's 3 (pushed, not confirmed) may be on origin already, and its 6 is mostly a commit kept in the checkout and not on origin. Writing the line again after either would record the sweep twice, or meet S3's refusal of a checkout holding a commit origin lacks. So both are handed the publish act first (LANES_LANE=<runner> lanes-edit.sh commit "..."with nothing staged, Amendment 23 S4), which sends what origin lacks by content and says so where it lacks nothing. Only a 6 whose publish act finds nothing to send (git addorcommitfailed) is written by hand. The exit is 1 in every case.LANES_GITandLANES_GIT_TIMEOUT(D16), the seam and the budgetlanes-edit.shuses, throughtimeout -k 5where it exists.--yesdone and recorded; 3 the dry run, something removable; 2 refused; 1 W or the writer not found, a read that failed, a removal that did not finish, or a removal not recorded.${x,,},${a[@]+"${a[@]}"}for every possibly empty array, nocommand <utility>butcommand -v(Under macOS's bash 3.2, a failingcommand <utility>in a list or an if condition exits a set -e script: lanes --worktrees dies silently (lanes:317), and wip's .gitignore step can stop (openRepoTools:4014) openRepoTools#292).lane-worktrees' classification) is not in this slice (D12). Slice 1a already lists<W>-lanes/*as register checkouts. What is left, classing W and the per-profile clones, would widen this slice into a second file with its own tests; lane openRepoTools-3 answered question 3 that it goes in its own PR.LANES_REPO=<W>-lanes/<lane>, and whatever was started before the rename still names the old path. D27 kept the old checkout only while the lane was LIVE, and LIVE is a pid test in this container: it misses a process started from a pre-rename window that outlives that window's session (a landing pilot, with the lane then PAUSED), and a pre-rename window in another container that shares<W>-lanes/. Removing the path then refuses that process's next register write (S3: not a git checkout), a HOLD's release among them, and an open HOLD blocks every landing. So a former name's checkout beside its new one is judged as the lane's own checkout is: kept while the lane is anything but CLOSED, with the reason "a former name of lane X, beside that lane's own checkout, kept until that lane is CLOSED", and a candidate once it is CLOSED. The re-ask under the lock asks both that it is still a former name beside the new checkout and that the lane is still CLOSED. The cost is the old checkout's tree, kept until the lane is CLOSED. The other fix rv4 offered, holding any checkout a live process'sLANES_REPOnames through/proc/<pid>/environ, was not taken:environis the environment at exec only, cannot be read for another user's processes or another container's, and does not exist on macOS. (Decision 23 reads it after all, as one more hold on every candidate, where Brett's answer to question 1 makes the clones beside W removable: nothing else names the window that writes through one.).git/lanes-edit/holds, but the writer's own scratch (D33; lane openRepoTools-3's MUST 351, SHOULD 391, NIT 392). S3 step 4's carry keeps a hand edit of the register it could not carry in.git/lanes-edit/hand-edit-<UTC>-<pid>.diff, with the register at origin's version, sogit statussees nothing and that file may be a person's only copy. Anything there holds, but a PLAIN FILE named exactlyapply.err,fetch.err,ff.err,reset.err(git's words from a write; the push replay's drop writes the last) orremoved(the lines a carry removed).finddecides the kinds, so a folder, a link or a name with a newline that only carries one of those names holds (392). A folder that cannot be listed, or afindthat fails, holds as "cannot be listed". The module pins the five exact names against 12 look-alikes, the-Lguard, and, as a drift guard, each namelanes-edit.shwrites there, read from its own text (391). The copy a successful carry leaves (hand-edit-*.diff) holds too until a person removes it; Amendment 23 slice 1b: every writer fetches and fast-forwards under its lock or refuses with nothing written, pushes with no pull and reads origin back, a refused push replays the one edit, andcommitwith nothing staged is the publish act (#198) openRepoTools#307's companion fix (393) decides its name./proc/self/mountinfo's field 5, elsemount's output) is read at the listing and again under each lock, just before the move; the candidate's path is spelled with mountinfo's escapes to match it. A table that cannot be read, or names no/, holds every candidate as "cannot tell".rm -rf --one-file-system(GNU) or-x(BSD) is a second layer only, probed once.LANE_END_MOUNTINFOis the test seam. A real bind mount throughlane-endwas not run: this host has no user namespaces.--yes, INT and TERM wait while a checkout is moved aside, removed and counted: themvand thermrun as( trap '' INT TERM; exec ... ), so a Ctrl-C to the process group cannot stop them halfway. A signal then ends the act; after any removal it prints the NOTED line to write by hand; once the line's write has begun, it says the line may be on origin already and hands out the publish act first. An aside a removal left (.<name>.removing-*) is listed by name in every run asleftover, withchmod -R u+rwX -- <p> && rm -rf -- <p>; no sweep removes one. A removal that did not finish is said before the NOTED write, and the run still exits 1.LE_TMP, captured inside the write's own subshell, so a signal's trap still speaks on the sweep's stderr). "With origin/ unmoved": the publish act alone. "git add failed" or "git commit failed":git -C <checkout> restore --staged --worktree -- lanes/log/<runner>.md, then the line by hand. The commit kept on HEAD after the push lost its races (or git could not name the commit to drop to): S6.3's archive,git -C <checkout> push origin HEAD:refs/heads/archive/<runner>-stranded-<UTC>, thengit -C <checkout> reset --keep refs/remotes/origin/<branch>, then the line by hand. Any other words: the two remedies as before.<checkout>is the caller'sLANES_REPO, else<W>-lanes/<runner>under routing, else W. The "no longer on HEAD" refusals keep the generic remedies on purpose: HEAD there is not only this write's commit, so an archive andreset --keepcould take another writer's commit off the checkout.last-session's 8 alone is "names none" (D40; lane openRepoTools-3's finding 545). Any other nonzero exit is a register or a log that could not be read, refused with 1 and "Nothing was removed", naming the exit and the writer's last line of stderr.findover three roots, by real path: W's parent to depth 6 (a repository'sobjectsat depth 8),$TMPDIR(or /tmp) and$HOMEto depth 4 ($TMPDIRto depth 8 since rv4b's SHOULD C, below), hidden directories included,objectspruned,.gitfiles listed; lines deduplicated, since the roots can nest. Each line is<target> TAB <dependent>, the target first, so a TAB in a dependent's own path stays in its own field (378). A repository at or under the candidate, or under a path gone in this run, goes with it. A scan that cannot finish holds every candidate: a root that cannot be entered, afinderror other than a directory it cannot read, an alternates or.gitfile that cannot be read (356). A directoryfindcannot read is counted once and said, and holds nothing (question 2's rule); since rv4b's SHOULD B, one of this user's own holds every candidate, below. The dry run's header names the scope. Measured since: decision 28.<W>-lanes/'s in the listing, in ONE leaves-first order with them, judged by every hold a lane's checkout has and one more: a live process whoseLANES_REPOnames the clone (read from/proc/<pid>/environby the shell itself, NUL by NUL; as spelled, relative ones against the process's working directory, and by real path). A process whose environment cannot be read is counted and said, and holds nothing; with no/proc, a clone beside W is held as "cannot tell" (LANE_END_PROCis the test seam). The clone the caller'sLANES_REPOnames is kept. Under its lock a clone is asked again whether it is still a register clone (no lane state is asked of it). It is moved aside beside W, never into<W>-lanes/, and an aside left there is listed by every run. The NOTED line names the clones beside W it removed apart from the lane checkouts, each character outside a lane name's written as?, and counts those left; the dry run's and the act's summaries count both places. This supersedes decision 2.test_the_sweeps_comments_are_80_columns_or_lessholds every comment line of the sweep's section to 80.<W>-lanes/that cannot be entered or listed, or whose real path cannot be read, refuses the run with 1 ("Nothing was removed") before anything beside W is looked at. It read as a link to nothing, and exited 0.LANES_ROUTE_WRITES=1,--yeswith aLANES_REPOwhose real toplevel or git common directory is W's is refused with 2 before anything is removed, in S5's words, naming<W>-lanes/<runner>/. Without routing that is the default write path, and is left. The dry run is not refused.lanes-edit.sh'slock_identcleans its own:[A-Za-z0-9._:-]kept, an empty containernoneand an empty hostunknown, and either oneunknownwhere it holds anything else..<the name's first 48 characters>.removing-<UTC>-<pid>, so a long name is moved aside instead of held in every act.keep,hold,remove,removed,PARTLY,leftover,gone) prints each control character of its path and reason as?, so a name with a newline writes no line of its own. The commands keep%q.*.swp, and an ignored directoryfindcannot look in each hold. Annotated tags are named. The comment that called ignored files the register's own rules says what--exclude-standardreads.--yestakes<W's git common directory>/lanes-sweep-checkouts.lock(mkdir, pid line<pid> <ident>) before the listing, and the exit lets it go. A second--yesis refused with 2, naming the lock, its pid line, and therm -rffor one a killed sweep left. It is in W's git directory, not<W>-lanes/, because the clones beside W are swept where there is no<W>-lanes/at all. A candidate that is not there at its turn is printedgone: neither removed nor held, and not in the NOTED line.mutex_steal's rename is. After themvand before therm, the aside's lock pid line, the working directories and what leans on it are read again; an alternates or.gittarget that is gone is kept as written, and what is inside the aside is skipped. Any of them moves the checkout back and holds it ("...; it was moved back"), or, where the move back fails, "...; it could not be moved back, and is at ". The lock is refreshed just before the pid read before the move. The cost is a second dependents scan per removal.c3-3-crlf, the one survivor of the battery at811c10f: the CR handling is not redundant, so the case was strengthened and the mutant kept (D44). NIT 353 kept a.gitor alternates target that does not resolve as written. A CR kept after an absolutegitdir: <c>/.gitgives<c>/.git<CR>, still under<c>/, so the clone was held either way. A relative path, as git writes a submodule's.gitfile, resolves only once the CR is gone. So the case took a CRLF line with a relative path, and the mutant was pointed at it. The hold's reason was not changed to name the gitdir: that would changelane-end's output for a test's sake, and a kill on behaviour (removeforhold) is stronger. Decision 27 moves the kill again.path:names;LANES_WORKSPACE_ROOTstays the seam, and--yesrefuses where the two differ (D45; rv4b's MUST A on811c10f).--sweep-checkouts --yesexits 2 whenLANES_WORKSPACE_ROOTis set and its real path is not the real path ofpath:. This comes before the sweep-wide lock and before any listing.path:is read from the file bylanes_ws_field, which noLANES_WORKSPACE_ROOTreaches. No workspace.yaml, or apath:that cannot be entered, counts as different. The words name the variable, W as it resolved andpath:, and say to unset it or point it atpath:. The dry run still runs, and its header says that--yesrefuses there.sc_is_a_w, asked at the listing and again under the clone's lock): the directorypath:names, wherever it stands; and a clone beside W with a<name>-lanes/(or a link of that name) beside it, which is a W with lane checkouts of its own, not a per-profile clone. So even a dry run with a wrong override keeps the real W, by its reason.tests/test_repo_hygiene.pyholds it there, solane-endcannot drop the override alone.tests/test_lane_helpers.shpoints every helper at one sandbox through it (it never runs--sweep-checkouts), and the module's cases use it. A value whose real path ispath:'s, as spelled or through a link, is no refusal.path:(a git checkout whose origin isrepository:)..gitfiles too (D46; the 353 gap, decision 25's note)..gitor alternates target through..that named the moved checkout was kept as written, with its.., and named no candidate, so a clone that came during the last reads with such a path did not move the checkout back.sc_real_or_textnow gives the real path where the target resolves, and otherwise the real path of the deepest directory on it that stands, with the rest appended as written. So it names the old path again. An absolute target reads as before...taken away). git itself refuses an alternates path through a directory that is not there ("unable to normalize alternate object path", tried on this host), so no working dependent has one, and keeping the..can only hold more, never less..gitfiles. It matched only the alternates lines:sc_gitfile_lineswas read under the lock alone. So a.gitfile written between the last read and the move, absolute or relative, did not move the checkout back, and it was removed under the directory that works in it. That was the one case still red on 10-24. The read after the move now matches the.gitfiles by the alternates' rule (nothing that went with the aside counts), and says "its git directory is named by the .git file of , which works in it and came as it was moved aside". A new case writes an absolute.gitfile after the last read.c3-3-crlfmutant), a relative CRLF line now reads<clone>/.git<CR>, under the clone, so decision 25's case no longer kills it. A CRLF line whose last name is a link to the clone's git directory resolves only once the CR is gone. That case is added toGIT_FILES, andc3-3-crlfis pointed at it. Decision 25's case stays as a behaviour case.811c10f): the dependents scan was measured, not redesigned; a--yessweep of the 139 clones beside W is question 4 below (D47).find, read only, at the depths the sweep uses since SHOULD C, 2026-10-23T20:33-21:30Z, with Eagle at load 26-50 from other lanes throughout, so a high reading, not an idle one): W's parent/workspace/projectsto depth 8, 813.7 s (58,825 matches: 45,674 object directories, 988 alternates files, 13,151.gitfiles);/tmpto depth 10, 1,754.6 s (273,072 matches);$HOMEto depth 6, 53.7 s (46 matches). One full scan: about 2,622 s, 44 minutes.--yesruns: one scan at the listing, one under each candidate's lock, and one after each move aside. For the 139 clones beside W that is up to 1 + 2 x 139 = 279 scans, about 203 hours at these numbers./proc/<pid>/fdnot read, by decision (D48).mvhas no-T; where the aside went is asked after it.<old path>/<aside name>standing means a directory came to stand at the old path and took the checkout inside it. The row says so and names where the checkout is (move it out by hand), and the lock is not let go. Otherwise "it was moved back", as before.old.is_dir() and new.is_dir()); text only.processesline names the container and host whose processes were read (lanes-edit.sh workstation, now read in the dry run too), and says that another container's processes, their working directories and their windows'LANES_REPOare not seen./proc/<pid>/fdis not read, by decision this round. A process that holds a file of a checkout open, with its working directory elsewhere, is not seen. Reading every process's open files is another scan of every pid, it sees only this container's processes, as the working-directory scan does, and its cost was not measured. The lock, the working directories, the windows'LANES_REPOand the dependents are the holds. Question 2 names it as a limit..git/lanes-edit/are the writer's scratch (D49; found by SHOULD 391's drift guard when slice 1b's code-leg branch was merged). Slice 1b's 706 (on Amendment 23 slice 1b: every writer fetches and fast-forwards under its lock or refuses with nothing written, pushes with no pull and reads origin back, a refused push replays the one edit, andcommitwith nothing staged is the publish act (#198) openRepoTools#307) keeps a claim's Rule 1 reads whole in.git/lanes-edit/claim-reads-<UTC>-<pid>.txtfor the claim's re-run, and the claim's last run removes the file; only a killed claim leaves one. The guard, which reads its cases fromlanes-edit.sh's text, failed on that name at the merge (holdforremove): a checkout with a leftover one would have been held for good. A plain file whose name startsclaim-reads-is now scratch too. It is a copy of what GitHub keeps, so removing it loses nothing; a folder of that name, or a name with anything beforeclaim-reads-, still holds (NIT 392's rule). New case: such a file alone holds nothing; one more look-alike and one more folder case.What changed
lane-end:--sweep-checkoutsflag, its refusals, and the usage, OPTIONS and EXIT text;AMENDMENT 23 S6.2: --sweep-checkouts, answered before the lane derivation: the W and<W>-lanes/reads (sc_real,sc_git_common,sc_is_register_clone); the lanes' state fromlanes --closed --alland its notes (sc_lookup); the runner and its preflight; the candidates and the keep lines (sc_judge,sc_judge_lane); the clones beside W; the holds (sc_holds,sc_dep_scanwith its.gitfiles,sc_dependents,sc_cwd_scan,sc_cwd_inside,sc_judge_holds); the guard on every path (sc_under_home); the lock (sc_lock_take,sc_lock_beat,sc_lock_holder,sc_lock_drop, released by the exit trap unless it was taken over); the fetch (sc_fetch) and the re-ask (sc_still); the order (sc_leaned_on_by_unplaced); the report; the act; and the NOTED line;--yesalso belongs to--sweep-checkouts;.git/lanes-edit/hold; the mount table (sc_mount_scan); the signals held off around a removal (sc_sig_defer,sc_sig_arm,sc_on_signal); the NOTED write's kept words and its exit 6 by cause (sc_log_noted,sc_wco); the dependents scan's three roots (sc_dep_root,sc_in_gone); the clones beside W and theLANES_REPOhold (sc_repo_scan,sc_repo_inside); the commits and tags named beside a checkout (sc_named); the sweep-wide lock; and the reads after the move. The usage text says what the sweep removes beside W, andcleanuplets the sweep-wide lock go;path:names it, and--yesrefused whereLANES_WORKSPACE_ROOTnames another directory (sc_is_a_w, MUST A); a directory of this user's own the dependents scan cannot read holds every candidate (SHOULD B);$TMPDIRscanned to depth 8, and the scope line names each root's depth (SHOULD C); the windows'LANES_REPOand the mounts read again after the move (SHOULD D);sc_real_or_text, and the.gitfiles matched after the move (decision 27); the move back asked where it went (NIT G); the usage line, exit 3 and a refusal naming the clones beside W (NIT I); and the header'sprocessesline (NIT J); andclaim-reads-*as the writer's scratch in.git/lanes-edit/(decision 30).tests/test_a23_sweep_checkouts.py(new), ontests/a23_box.py'sRegisterBox, in Make lane-end's synopsis say what a DUPLICATE retire does, read a state cell that breaks the phrase's grammar whole instead of narrowing it, and correct the TAKEOVER grammar table and two tests.yml header comments openRepoTools#202'sendedstyle. No fixture is copied.Tests
6ecfc52tests/test_a23_sweep_checkouts.py(new)1f16bbc, on the code leg; 193 passed at28fb2ad, before the merge of slice 1b; at the mergee402a23, 194 passed and 1 failed, the drift guard decision 30 answers (83 at53b7bf3; 80 ate6cb1e8; 73 atcd060f0; 47 at210f4cf)tests/test_repo_hygiene.py1f16bbc, on the code leg: 235 passed, 18 skipped (cases for files of the spec leg or of theupstream/openRepoShapesubmodule, which a standalone code-leg checkout lacks); 253 passed at53b7bf3(slice 1b'sd9ff4e6adds 6 cases)tests/test_guard_launch_mode.py53b7bf3tests/test_test_roots.py53b7bf3tests/test_precision_90a_96.py53b7bf3testsjob (the whole suite with the shell suite, Ubuntu 26.04 image)ff12b01(run 39805790284): 3215 passed, 1 failed; ate6cb1e8(run 39750439670): 3159 passed, 75 skipped, 1 failedtests-no-submodulejobe6cb1e8(run 39750439670): 2362 passed, 872 skipped, 1 failedtests/test_lane_worktrees_report.py210f4cftests/test_register_scan_hardening_138.py210f4cftests/test_lane_start_hardening_138.py210f4cftests/test_lane_start_claude_current.py210f4cftests/test_wrapper_hardening_138.py210f4cftests/test_skill_shell_blocks.py210f4cftests/test_install_skill_and_hook.py210f4cftests/test_openrepotools_command.py210f4cftests/test_triad_lane_tooling.py210f4cftests/test_triad_lane_tooling_handoff.py210f4cftests/test_a23_lane_start_read.py210f4cftests/test_lane_worktrees.py210f4cfEach run was one pytest process per file, with every
LANES_*andTMUX*unset,GH_TOKEN=bogus, a stubghfirst on PATH andGIT_SSH_COMMAND=false. The "before" column is slice 1b's6ecfc52, this branch's base, before any slice-4 commit. The lane modules andtest_guard_launch_mode.pyran on agit archiveof it.test_repo_hygiene.pyandtest_test_roots.pyran in a git worktree at it, because they read git's index and an archive has none. The "after" column is this branch's head210f4cf, which also carries slice 1b'sd9ff4e6; the new module, hygiene,test_guard_launch_mode.py,test_test_roots.pyandtest_precision_90a_96.pyran again at the fix round's headcd060f0and again at the head of rv4-c3's round,e6cb1e8, and at the head of rv4-c4's round,53b7bf3; the new module and hygiene ran again at this round's head,1f16bbc, on the code leg, one pytest process each. The other rows were not run again locally after210f4cf, because the rounds change only the sweep's own section oflane-end, its usage text and itscleanup, which acts only when the sweep holds a lock; CI'stestsjob runs every one of them. The merges changed neitherlane-endnor the new module. The table holds every Python module undertests/that nameslane-endat the head (git grep -l), plustest_guard_launch_mode.py. The shell suitetests/test_lane_helpers.shnames it too, and is not run locally (below).Not run locally, by the lane's rules: the shell suite (
tests/test_lane_helpers_suite.py) andtests/test_supervised_restart_regressions.py. CI'stestsjob runs them. CI's one failure in both jobs istests/test_claude_current.py::test_a_command_that_ignores_term_is_killed_after_the_grace[host]("KILL never came: 30.0s"), the 26.04 image'stimeout, which fails on every branch of 1b's line;packagefails on the image's missing py3.9.53b7bf3adds only D31's reason text and D32's test parameter overe6cb1e8.Hygiene before the merge of slice 1b'. At each commit of this round before
e402a23, and at the re-homed heade5063e0,test_the_root_carries_the_line_ending_rulefailed (234 passed, 18 skipped, 1 failed): it reads the root's.gitattributes, which the code leg'smainhas and which came here with slice 1b's code-leg branch. At1f16bbcit passes. CI on the code leg is asked for with this description.Mutants
Each mutant changes ONE line of
lane-end's sweep (a check turned off, or a branch or message swapped) and runs the case or cases written to catch it:pytest -q -xin a git worktree, whoselane-endand test module were compared byte for byte with the branch's before every mutant. KILLED means pytest ended with 1 and a failing test; a collection error or any other end is not a kill. 144 mutants: 144 KILLED, 0 SURVIVED, 0 ERROR.1f16bbc, on the code leg, all 144 in one battery, after the whole module passed there, each mutated line shown first to occur exactly once inlane-end. They are the 56 of the earlier rounds; the 55 of lane openRepoTools-3's round at53b7bf3(351, 352, 356, 371-374, 378, its re-check's 391 and 392, finding 545, D37 and the NITs; 111 at811c10f, wherec3-3-crlfalone survived, decision 25); and rv4b's round: MUST A's six, SHOULD D's seven, SHOULD B's five, SHOULD C's two and one port, the 353 gap's four and two more (decision 27), NITs G, I and J's four, and decision 30's two.c3-3-crlf(decision 27),l3-359-colon(NIT J moved its block),l3-353-text(353's "a target that is gone is dropped", now insidesc_real_or_text), and the four of the.git/lanes-edit/filter'sfindline (l3-351-scratch,l3-391-prefix,l3-392-kind,l3-392-error), to which decision 30 added a name./tmp; each one's lines and cases are as last run on the old branch.The table, one row per mutant (the line changed, the first failing case, and what the kill rests on), is in this PR's first comment: with 144 rows it does not fit in the description's 65,536 characters beside the rest.
In the table, "First failing case" is where pytest stopped, and the last column says "behaviour" where the hold is gone and the checkout becomes removable (removed under
--yes). Where a kill rests on something short of a behaviour, it says so in the last column, and here:n-live:lanesgives a LIVE lane no class (ENDED|RETIRED) lr_class=closed,LIVE) :inlanes-edit.sh), so the class check keeps every LIVE lane with the LIVE rule off. The case pins the reason exactly (lane X is LIVE: a live session record here names it, notis LIVE, not CLOSED (Amendment 19)), for a LIVE lane and for an ENDED lane a live session record names.n-link: a link falls to the "a directory whose real path is ..., not the path it is listed at" keep. The case pinssymbolic link.n-w: a directory sharing W's git directory falls to the "git directory is ..., not its own .git" keep. The case pinsW itself, the readers' copy.p-lane: withoutLANES_LANE,set -ustops the run (1,LANES_LANE: unbound variable) before anything is removed. The case pins 2 and the message.p-nofetch: the listing's own refusal of a W fetch that did not run stops the run (1, "Nothing was removed"). The case pins 2 and the message.c4-1-runner: the mutant is the text before rv4-c4's fix. The same branch keeps the runner's former name either way, and nothing is removed differently, so only the reason says what changed. The case pins the whole row, the former name's and the runner's own checkout's.h-cwd,R1-no-cwd-rescan,n4-owner), that second read still holds the checkout, so no input shows a difference but the reason: the cases pin the first read's words (a live process works in it,is no longer this sweep's), not "as it was moved aside; it was moved back".d29-listing, reason since D37. Every candidate is now asked whether a live process'sLANES_REPOnames it, and the case's window started before the rename does: with D29 undone, the IDLE lane's former name goes from keep to hold, not to remove. The case pins the keep row.c4-2-rm(norm -fof the pid file) is killed by the pid file whose write fails, as on a full disk: the lock is left holding the emptypid. The case's other parameter, a lock directory that cannot be written in, passes against it (c4-2-rm-control,runs/mut4-control.log: SURVIVED, 1 passed), because no pid file is made for therm -fto remove, which is what rv4-c4 found.n-runnerandn-xrepo,-xstops attest_W_and_live_lane_never_candidates, which fails on the reason text (the class check keeps the checkout under another reason). The case written for each, run alone against the same mutant, fails because the CLOSED checkout becomes a candidate:test_a_closed_runner_keeps_its_own_checkoutandtest_a_closed_lanes_checkout_named_by_LANES_REPO_is_kept.d-dry(the mutated dry run removed a checkout),p-uuidandm1b-listing-off(the run went on to the act),w-nofetch(the NOTED line was refused),n-wlanes. Forw-rcandR4-no-partial-exitthe exit code is the behaviour.s2-exit3ands2-exit6keep the exit code (1) and fail on the message that says the line was "NOT written" and hands out "Write the line by hand" for a line that landed or is kept in the checkout;n2-ignandn3-counttest the report's note of ignored files and the summary's counts, which are what they change.Not mutable to a failing test:
sc_under_home(candidates come only from<W>-lanes/*and the clones beside W, so no input reaches its false branch). It was not run as a mutant.c3-3-crlf, the battery's survivor at811c10f(tr -d '\r'replaced bycatin the dependents scan's.gitfile read). NIT 353 kept a target that does not resolve as written, so a CR kept after an absolute<clone>/.gitstill named a path under the clone. Decision 25 pointed it at a relative CRLF line; decision 27 spells such a line from the clone, so it now kills only through a line whose last name is a link to the clone's git directory, which resolves only once the CR is gone.Questions, answered, and one for lane openRepoTools-3
LANES_REPOnames; every hold a lane's checkout has, 351's and 371's among them; and the unpublished content kept until its owner acts. The cases mirror the<W>-lanes/ones: a profile chain removed leaves first, a held link keeping what it borrows from, each hold, a dry run that lists each clone with its hold orremove, the caller's own clone kept, and a repository of another origin never listed. Decision 2 is superseded. Nothing is removed on Eagle until the sweep ships, with 2b, 3 and the flip./proc/<pid>/fdscan is not, by decision 29, and is a limit: a process that holds a file of a checkout open, with its working directory elsewhere, is not seen.--yes(rv4b's SHOULD E, decision 28). One full scan took about 44 minutes on Eagle (W's parent 814 s,/tmp1,755 s,$HOME54 s, under load), and--yesruns one at the listing, one under each candidate's lock and one after each move: up to 279 scans, about 203 hours, for the 139 clones beside W. A scan under a lock also outlasts the lock's 10-minute stale time; the pid line read after the move then holds the checkout, so nothing is removed wrongly, but the sweep holds where it should remove. Should the sweep scan once under the sweep-wide lock, and then only re-check what can change cheaply (each candidate's ownobjectsand.gitmtimes, or the first scan's matches read again without a newfind)? And does SHOULD C's depth 8 for$TMPDIRstand, at/tmp's 1,755 s?Not changed
docs/README-lanes.md: the manual's sweep section is slice 5's. After the cutover a docs edit is an openRepoTools-spec change.lane-worktrees: see question 3, its own PR.lanes-edit.sh: the sweep calls it (lanes,canon-lane,last-session,workstation,log) and changes nothing in it.LANES_GIT_TIMEOUT=0still means a fetch with no bound (rv4's NIT 4). It means that forlanes-edit.sh's own network calls too (D16), and the sweep keeps the one budget the helper has./proc/<pid>/environscan; decision 15 says why it was not taken.mkdirfirst on PATH that leaves the lock directory read-only).53b7bf3: MUST 351, SHOULDs 352 and 371-374, finding 545, and the NITs 353-356, 358, 359, 375-379; its re-check of 351: SHOULD 391 and NITs 392 and 394. All are done (decisions 17-22 and 24, and the round's tests). 393 is Amendment 23 slice 1b: every writer fetches and fast-forwards under its lock or refuses with nothing written, pushes with no pull and reads origin back, a refused push replays the one edit, andcommitwith nothing staged is the publish act (#198) openRepoTools#307's.811c10f: MUST A (decision 26), SHOULDs B, C and D, the 353 gap (decision 27) and NITs F to J (decisions 25 and 29) are done, one commit each. SHOULD E is measured and asked as question 4, not redesigned (decision 28). Of rv4b's list of lines no case pinned, the.gitfile naming a path gone after the move is decision 27's fix, and$HOME's root and relative alternates lines are pinned byr4b-home-depthandr4b-rel-alt.sc_lock_dropand the exit'scleanupstill release a lock whose pid line cannot be read, aslanes-edit.sh'smutex_releasedoes. Since NIT c3-2 that state follows only a steal caught between itsmkdirand itsprintf, after a ten-minute age-out the sweep's refreshes prevent.Refs opensoft/openRepoTools#198
🤖 Generated with Claude Code