#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""lane-worktrees - one act that archives, rescues and removes a lane's
abandoned worktrees, branches, scratch and killed-suite sandboxes
(opensoft/openRepoTools#162).

    lane-worktrees sweep <lane> [--dry-run | --yes] [--porcelain]
                   [--bundle] [--branches] [--links] [--push-untracked]
                   [--include-scratch] [--include-caches] [--include-sandboxes]
                   [--include-foreign --word "<verbatim>"]
                   [--live <path> ... | --live none] [--fetch] [--estate <dir>]
    lane-worktrees sweep --expire [--dry-run | --yes] [--porcelain]
    lane-worktrees sweep --all --dry-run --report [--post <path|owner/repo#n>]
                   [--estate <dir>]
    lane-worktrees add <lane> <slice> [--branch <name>] [--from <ref>]
                   [--checkout <dir>] [--dry-run]
    lane-worktrees --help | --version

A SWAP THAT DOES NOT FINISH GRACEFULLY LEAVES ITS WRITERS' TREES WHERE THEY
STOOD, and the protocol's worktree-safety rule (a swap never commits, pushes,
stashes, resets or cleans) is right to leave them. This is the act that
retires them afterwards, in one place, with one rule: NOTHING IS DELETED THAT
IS NOT FIRST ON ORIGIN OR IN A BUNDLE under the sweeps directory, and a tree a
live writer owns is never touched.

THE DRY RUN IS THE DEFAULT and changes nothing - no fetch, no ref, no file,
no register line (`--fetch` asks origin first; `lanes-edit.sh`'s own reads
fetch the register as they always do). `--yes` performs the table: it fetches
the register itself first and refuses if that fails (an inherited
LANES_NO_FETCH does not skip it), then fetches origin in every repository it
acts in, with a pinned refspec that never prunes a local branch or tag.

BEFORE EACH REMOVAL git is asked which commits it would take - a worktree's
HEAD and reflog, a clone's every ref (an annotated tag's object included)
and reflog, a pruned registration's, a `--branches` delete's tip and reflog -
and any that no origin ref, surviving branch, tag or stash keeps go to a
bundle first (ONLY-COPY bundles are rows of rescues.tsv, and `--expire`
never removes their archive). The same question is the SELF-CHECK right
before the removal: anything neither on origin nor in a verified bundle is
refused, exit 2, and nothing after it is acted on; so is everything after a
register line that could not be written. Untracked files are never pushed
(the bundle only) unless `--push-untracked`, and untracked git-lfs content,
which a bundle would carry as a pointer only, keeps its tree; a branch an open
pull request or another lane owns is never pushed onto (a rescue branch
instead, and gh's OPEN wins over a LANDED line); where git-lfs is configured
the push runs its hooks, and a clone whose local git-lfs store a bundle alone
would have to stand for is kept. A cache that holds a repository (a bare one
included) is never removed, nor is any `tmp.*` sandbox (it is listed: nothing
proves a suite made it), nor a tree or registration whose git directory keeps
a submodule repository (a deinitialized one's included) with work no remote
of it holds.

WHICH TREES ARE THE LANE'S is #97's inventory (`lanes-edit.sh lane-trees`)
and the disk - never the derived index (Amendment 14 clause (b)). A tree the
inventory does not name is FOREIGN: reported, and left, unless
`--include-foreign` and a `--word`. A tree another lane's inventory names is
that lane's and is never acted on from here.

WHO MAY ACT is #97's reconciliation (`lanes-edit.sh lane-reconcile`): a lane
bound on another host or container is UNKNOWN, never dead (Amendment 18(b)),
so every act is refused for it; a lane held by another live session is
refused; a lane whose holder is THIS session, or one with a tree THIS session
recorded, acts only on the writer count the coordinator states with `--live`
(Amendment 17 Addendum 1 (i)) - each path naming a tree of the table, or
`--live none` alone (anything else is usage, 64).

"MERGED" is the register's LANDED line for the branch's pull request, or
`gh`'s MERGED state for it - never `git branch --merged` alone, because a
squash merge leaves no ancestry.

The sweeps directory: ${XDG_STATE_HOME:-$HOME/.local/state}/openRepoTools/
sweeps/<lane>/<UTC>/ with MANIFEST.sha256, DISPOSITION.md and rescues.tsv.
`sweep --expire` lists (and with `--yes` removes) archives older than the
retention (90 days; `sweep.conf`) whose every rescue branch is still on
origin at the rescued SHA; an archive whose rescue branch is gone or moved,
that holds an only-copy bundle (a `bundle:` row), or that is not whole (its
MANIFEST.sha256 missing or not matching, its rescues.tsv unreadable), is
never expired.

THE ESTATE REPORT (`--all --dry-run --report`) is the net under every actor
that never runs `lane-end`: FOREIGN clones, trees of ENDED lanes, unmerged
branches, root-`main` divergence, rescue branches and dirty trees awaiting
disposition, caches and sandboxes, large ignored directories, evidence-shaped
paths, archives past retention, the workspace's bytecode hygiene and
`status --all`'s findings - one markdown document, printed or `--post`ed to a
file or an issue. It changes nothing. `lane-start` runs it once a day per
workstation, detached (`LANE_WORKTREES_REPORT=off` is the switch). It exits 0,
or 1 when `--post` could not deliver it (the report is then printed).

PORCELAIN: one row per line, fields separated by one TAB. In every field a
backslash is written `\\`, a TAB `\t`, a newline `\n` and a carriage
return `\r`, so a path holding any of them is still one field of one row.
Undecodable bytes in a path pass through as the bytes they are.

ADD (`add <lane> <slice>`, #163) IS HOW A LANE MAKES A WORKTREE, AND IT
RECORDS THE TREE IN THE LANE'S #97 INVENTORY IN THE SAME ACT: a `git worktree
add` of the lane's checkout (or `--checkout <dir>`) at `<parent of the lane's
dir>/.lane-worktrees/<lane>/<slice>`, on branch `--branch` (default: the
slice) - checked out where it exists, else made from `--from` (default HEAD)
with `--no-track`, so a bare `git push` never aims a writer's commits at the
branch it started from - then `lanes-edit.sh set-lane-tree`. A record that
fails, or a `git worktree add` that fails after making the tree (a failing
post-checkout hook), takes the fresh tree and its new branch back out. The
path is printed on
stdout; `--dry-run` makes nothing. A tree made any other way is FOREIGN until
`lanes-edit.sh set-lane-tree <lane> <path>` records it. Exit 0 made and
recorded, 2 refused (nothing made), 1 made and neither recorded nor taken back
out (the commands are printed), 64 usage.

A TRIAD (opensoft/openRepoTools#186, T018). Where the lane's checkout is an
ASSEMBLY - its `project.yaml` declares a leg other than itself, read by
`lanes-edit.sh project-legs` - the sweep also reads each leg and each
dependency nested in one as a `leg` row (never acted on; counted only where
its HEAD's own `Lane:` trailer is this lane's and it holds work, or where it
could not be read), lists the worktrees git registers in every leg, and takes
`<assembly>/worktrees` - the paired `worktrees/<feature>/{spec,code}` - as a
root whose trees are the lane's by that same trailer rule. The estate report
counts each leg of every canonical assembly as a checkout of its own - its
branches, root `main` and rescue branches, each row naming its assembly - and
adds a section of the legs holding uncommitted or unpublished work, only where
an assembly's leg is checked out. `add` records a tree made from any checkout
of a leg's repository - the leg itself or a worktree of it - with the LEG as
its checkout, and says, of a tree made from the assembly itself, that its legs
are empty there. A checkout with no `project.yaml` is asked nothing, and its
table, its report and its `add` are what they always were.

Exit codes:
  0  dry run: nothing to retire  ·  --yes: every act completed
  3  dry run: something to retire (the `lane-end` gate's answer, #163)
  2  refused: the lane is bound elsewhere, held by another live session,
     managed-owned, has trees but no #97 snapshot, or a read failed (a lane
     root, a registration list, another lane's claim on a lane with a tree,
     the register's fetch under --yes) - or an error no read caught -
     nothing was changed; or, under --yes, the self-check refused a removal
     (what was done before it is recorded, nothing after it was acted on)
  1  --yes: an act failed part-way; what was done is recorded and the
     rest is left exactly as it was
 64  usage
"""

from __future__ import annotations

import argparse
import datetime as _dt
import getpass
import hashlib
import json
import os
import re
import shlex
import shutil
import socket
import stat
import subprocess
import sys
import tarfile
import tempfile
import time

PROG = "lane-worktrees"
VERSION = "1"
US = "\x1f"

#: Dependency caches and bytecode: they hold no work and are rebuilt, so they
#: are removed WITHOUT archiving (`--include-caches`) and never counted as a
#: tree's ignored work.
CACHE_NAMES = ("__pycache__", ".pytest_cache", ".mypy_cache", ".ruff_cache",
               "node_modules")
VENV_NAMES = ("venv", ".venv")

#: Ignored BUILD OUTPUT: generated, rebuilt, and often gigabytes. A tree's
#: ignored files are archived before it goes - except caches and these.
BUILD_NAMES = ("target", "build", "dist", ".tox", ".nox", ".gradle", ".next",
               ".turbo", ".cache", "coverage", "htmlcov", ".eggs")

#: A branch with one of these names is never pushed "as is" by a sweep: its
#: unpublished commits go to a rescue branch instead. Pushing a lane's stray
#: commits onto a default branch is not a rescue.
PROTECTED_BRANCHES = ("main", "master")

#: The register words a legacy writer may never write (ruling 2026-10-04).
RESERVED = ("managed owner", "managed binding", "mode=managed", "managed:")

GIT_NET_TIMEOUT = 180
GIT_TIMEOUT = 120

DEFAULT_CONF = {
    "retention_days": 90,
    "aging_days": 14,
    "sandbox_min_age_minutes": 60,
    "foreign_quiet_hours": 24,
    "ignored_report_mb": 50,
    "ignored_archive_mb": 200,
}

UUID_RE = re.compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-"
                     r"[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
STAMP_RE = re.compile(r"^(\d{8}T\d{6}Z)")
SLUG_RE = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$")
LANDED_OBJ_RE = re.compile(
    r"^LANDED — lane [^,]+, session [^,]+, [^,]+, "
    r"([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)#(\d+)\b")
LANDED_ROW_RE = re.compile(r"^LANDED — .*PR #(\d+) into "
                           r"([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+) main")


class Usage(Exception):
    """A usage error: exit 64."""


class Refusal(Exception):
    """A refusal before anything was changed: exit 2."""


# ================================================================ utilities

def utc_now() -> str:
    return _dt.datetime.now(_dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def utc_stamp() -> str:
    return _dt.datetime.now(_dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ")


def say(text: str = "") -> None:
    sys.stdout.write(text + "\n")


def pesc(value) -> str:
    """One porcelain field: a backslash, TAB, newline or carriage return in
    it is written `\\`, `\\t`, `\\n`, `\\r`, so a row is always one line of
    exactly its columns - a path with a TAB in it is still one field (B3)."""
    return (str(value).replace("\\", "\\\\").replace("\t", "\\t")
            .replace("\n", "\\n").replace("\r", "\\r"))


def prow(*fields) -> None:
    say("\t".join(pesc(f) for f in fields))


def note(text: str) -> None:
    sys.stderr.write(f"{PROG}: {text}\n")


def run(argv, cwd=None, env=None, timeout=GIT_TIMEOUT, input_text=None):
    """Run `argv`; NEVER raises for a non-zero exit or a missing program.

    A program that could not be started is exit 127 with the reason on
    stderr, and a timeout is 124 - the two shapes every caller here already
    reads as "the read failed", which is never an answer (Amendment 7(d))."""
    try:
        # SURROGATEESCAPE, NOT STRICT UTF-8: a path is bytes, and a worktree
        # named in Latin-1 is a tree to report, never a traceback (B2).
        return subprocess.run(
            [str(a) for a in argv], cwd=cwd, env=env, capture_output=True,
            encoding="utf-8", errors="surrogateescape", timeout=timeout, input=input_text,
            stdin=None if input_text is not None else subprocess.DEVNULL)
    except subprocess.TimeoutExpired:
        return subprocess.CompletedProcess(argv, 124, "", f"timed out after {timeout}s")
    except OSError as exc:
        return subprocess.CompletedProcess(argv, 127, "", str(exc))


#: Repositories read as a git directory ALONE (#174, Copilot round 2): a
#: submodule repository under `<gitdir>/modules/` names its working tree in
#: `core.worktree`, and where that directory is gone - a gone worktree's
#: registration, a submodule removed after its deinit - every git command
#: run in it dies on "cannot chdir". Only reads that need no working tree are
#: run there (refs, reflogs, rev-list, ls-remote), so each is handed the
#: repository's own `objects/` as one. See `module_reads`.
_GITDIR_READS: set = set()


def git_env(path: str = "") -> dict:
    env = dict(os.environ)
    if path and path in _GITDIR_READS:
        env["GIT_WORK_TREE"] = os.path.join(path, "objects")
    env["GIT_TERMINAL_PROMPT"] = "0"
    env.setdefault("GIT_SSH_COMMAND", "ssh -o BatchMode=yes")
    env["LC_ALL"] = "C"
    # A READ TAKES NO OPTIONAL LOCK: `git status` otherwise refreshes the
    # index's stat cache and writes it back, and a dry run writes nothing.
    env["GIT_OPTIONAL_LOCKS"] = "0"
    return env


def git(path: str, *args: str, timeout: int = GIT_TIMEOUT):
    return run(["git", "-C", path, *args], env=git_env(path), timeout=timeout)


def git_out(path: str, *args: str):
    """stdout stripped, or None where git failed."""
    proc = git(path, *args)
    if proc.returncode != 0:
        return None
    return proc.stdout.strip()


def real(path: str) -> str:
    try:
        return os.path.realpath(path)
    except OSError:
        return path


def under(path: str, root: str) -> bool:
    """`path` IS `root` or sits beneath it - compared physically."""
    p, r = real(path), real(root)
    return p == r or p.startswith(r.rstrip(os.sep) + os.sep)


def tilde(path: str) -> str:
    home = os.environ.get("HOME", "")
    if home and (path == home or path.startswith(home.rstrip("/") + "/")):
        return "~" + path[len(home.rstrip("/")):]
    return path


def short(sha: str) -> str:
    return sha[:12] if sha and re.match(r"^[0-9a-f]{12,}$", sha) else (sha or "-")


def dir_size(path: str) -> int:
    total = 0
    for base, dirs, files in os.walk(path, followlinks=False):
        for name in files + dirs:
            try:
                total += os.lstat(os.path.join(base, name)).st_size
            except OSError:
                pass
    return total


def human_size(n: int) -> str:
    size = float(n)
    for unit in ("B", "KB", "MB", "GB"):
        if size < 1024 or unit == "GB":
            return f"{size:.0f} {unit}" if unit == "B" else f"{size:.1f} {unit}"
        size /= 1024
    return f"{n} B"


def rmtree(path: str) -> None:
    """`shutil.rmtree` that also clears git's read-only object files. A path
    that is already gone is already removed.

    A SYMLINK IS NEVER CHMOD-ED OR REMOVED THROUGH (#170 E4): `os.chmod`
    follows a link, so the retry for an entry that would not go changed the
    mode of whatever the link pointed at - a clone elsewhere - and a path that
    IS a link was "removed" by an rmtree that refused it in silence. A link
    at the top is refused; one inside is retried without a chmod."""
    if not os.path.lexists(path):
        return
    if os.path.islink(path):
        raise OSError(f"{path} is a symbolic link; nothing is removed through one")

    def clear(func, target, _exc):
        if not os.path.islink(target):
            try:
                os.chmod(target, stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)
            except OSError:
                pass
        func(target)
    shutil.rmtree(path, onerror=clear)


def sha256_of(path: str) -> str:
    digest = hashlib.sha256()
    with open(path, "rb") as fh:
        for block in iter(lambda: fh.read(1 << 20), b""):
            digest.update(block)
    return digest.hexdigest()


def safe_ref_part(text: str) -> str:
    """A path component folded into what a ref name takes."""
    folded = re.sub(r"[^A-Za-z0-9._-]+", "-", text).strip(".-") or "tree"
    folded = re.sub(r"\.\.+", ".", folded)
    if folded.endswith(".lock"):
        folded = folded[:-5] + "-lock"
    return folded[:80]


def register_text(text: str) -> str:
    """A register line's free text, fenced the way `lanes-edit.sh`'s writer
    fences a payload: no ` — ` (the line's own separator), no `, `, no
    newline, and none of the managed-owner vocabulary a legacy writer may
    never write (ruling 2026-10-04)."""
    out = text.replace("\r", " ").replace("\n", " ")
    out = out.replace(" — ", " - ").replace(", ", "; ")
    low = out.lower()
    for word in RESERVED:
        if word in low:
            out = re.sub("managed", "manag.ed", out, flags=re.IGNORECASE)
            break
    while out.startswith(("→", "←")):
        out = out[1:].lstrip()
    return out


# ============================================================ configuration

def _home() -> str:
    return os.environ.get("HOME") or os.path.expanduser("~")


def state_dir() -> str:
    base = os.environ.get("XDG_STATE_HOME") or os.path.join(_home(), ".local", "state")
    return os.path.join(base, "openRepoTools")


def sweeps_dir() -> str:
    return os.path.join(state_dir(), "sweeps")


def conf_path() -> str:
    named = os.environ.get("LANE_WORKTREES_CONF", "")
    if named:
        return named
    base = os.environ.get("XDG_CONFIG_HOME") or os.path.join(_home(), ".config")
    return os.path.join(base, "openRepoTools", "sweep.conf")


def load_conf() -> dict:
    """`sweep.conf`: `key=value` lines, `#` comments. An unknown key or a
    value that is not a whole number is a note and the default stands."""
    conf = dict(DEFAULT_CONF)
    path = conf_path()
    try:
        with open(path, encoding="utf-8", errors="surrogateescape") as fh:
            lines = fh.read().splitlines()
    except OSError:
        return conf
    for raw in lines:
        line = raw.split("#", 1)[0].strip()
        if not line or "=" not in line:
            continue
        key, value = (part.strip() for part in line.split("=", 1))
        if key not in DEFAULT_CONF:
            note(f"{path}: '{key}' is not a sweep.conf key; ignored")
            continue
        if not value.isdigit():
            note(f"{path}: {key}={value} is not a whole number; the default "
                 f"{DEFAULT_CONF[key]} stands")
            continue
        conf[key] = int(value)
    return conf


# =================================================== the helper (lanes-edit)

def lanes_edit_path() -> str:
    """`$LANES_EDIT`, else the `lanes-edit.sh` beside this file, else PATH -
    the order `lanes` and `lanes-index` already find it in."""
    named = os.environ.get("LANES_EDIT", "")
    if named:
        return named
    here = os.path.join(os.path.dirname(os.path.realpath(__file__)), "lanes-edit.sh")
    if os.access(here, os.X_OK):
        return here
    return shutil.which("lanes-edit.sh") or ""


class LaneFacts:
    """What #97's reconciliation says about one lane, read once."""

    def __init__(self, lane: str):
        self.lane = lane
        self.rc = 0
        self.root = ""
        self.state = "NONE"
        self.holder = "unknown"         # live | none | unknown
        self.holder_uuid = ""
        self.holder_detail = ""
        self.binding = "unknown"        # here | free | gone | elsewhere | unknown
        self.binding_at = ""
        self.managed = ""               # "" | owned | unknown
        self.managed_owner = ""
        self.verdict = ""
        self.verdict_why = ""
        self.dir = ""
        self.inventory: list = []
        self.inventory_rc = 0
        self.why = ""


class Helper:
    def __init__(self):
        self.path = lanes_edit_path()
        self._fetched = False

    def call(self, *args: str, env_extra: dict | None = None, fetch: bool = False,
             timeout: int = 300):
        if not self.path:
            return subprocess.CompletedProcess(args, 127, "", "no lanes-edit.sh beside this "
                                               "command or on PATH")
        env = dict(os.environ)
        env["LANES_INDEX"] = "off"
        if not fetch or self._fetched:
            env["LANES_NO_FETCH"] = "1"
        if env_extra:
            env.update(env_extra)
        proc = run([self.path, *args], env=env, timeout=timeout)
        if fetch:
            self._fetched = True
        return proc

    def canon_lane(self, lane: str):
        """(canonical spelling, refusal) - Amendment 15's resolver, as
        `lane-end` reads it: 0 with the row's spelling; 66 the alias table
        unreadable and 2 with 15(d)'s words a published case pair, both
        refusals; any other answer (a helper predating the subcommand) keeps
        the typed spelling."""
        proc = self.call("canon-lane", lane, fetch=True)
        out = proc.stdout.strip()
        if proc.returncode == 0 and out:
            return out, ""
        if proc.returncode == 66:
            return lane, ("the lane alias table could not be read (exit 66), so a renamed "
                          f"lane's old name cannot be resolved: {_first_line(proc.stderr)}")
        if proc.returncode == 2 and "Amendment 15(d)" in (proc.stderr or ""):
            return lane, ("the published register spells this lane two ways (Amendment "
                          f"15(d)): {_first_line(proc.stderr)}")
        return lane, ""

    def lane_facts(self, lane: str) -> LaneFacts:
        facts = LaneFacts(lane)
        proc = self.call("lane-reconcile", lane, fetch=True)
        facts.rc = proc.returncode
        if proc.returncode == 0:
            self._parse_reconcile(facts, proc.stdout)
        elif proc.returncode == 8:
            # NO CONTROL ROOT: no inventory exists, and the holder and the
            # binding are asked for on their own.
            facts.state = "NONE"
            self._holder_binding(facts)
        else:
            facts.why = (f"`lanes-edit.sh lane-reconcile {lane}` exited {proc.returncode}: "
                         + _first_line(proc.stderr))
            return facts
        dproc = self.call("lane-dir", lane)
        if dproc.returncode == 0:
            facts.dir = dproc.stdout.strip()
        elif dproc.returncode != 8:
            facts.why = (f"`lanes-edit.sh lane-dir {lane}` exited {dproc.returncode}: "
                         + _first_line(dproc.stderr))
            return facts
        tproc = self.call("lane-trees", lane)
        facts.inventory_rc = tproc.returncode
        if tproc.returncode == 0:
            for line in tproc.stdout.splitlines():
                if not line.strip():
                    continue
                fields = line.split(US)
                if len(fields) < 2 or not fields[0]:
                    # A ROW WITH NO ID IS AN UNREADABLE RECORD, never no record
                    # (#170 G9): skipped, a tree it names outside the scanned
                    # roots went unseen and the dry run could exit 0.
                    facts.inventory.append({"id": "", "path": "", "schema": "",
                                            "raw": line})
                    continue
                keys = ("id", "path", "branch", "head", "upstream", "dirty", "unpushed",
                        "writer", "observed", "checkout", "generation", "operation",
                        "schema")
                rec = dict(zip(keys, fields + [""] * (len(keys) - len(fields))))
                facts.inventory.append(rec)
        elif tproc.returncode not in (8, 1) or (tproc.returncode == 1 and facts.root):
            facts.why = (f"`lanes-edit.sh lane-trees {lane}` exited {tproc.returncode}: "
                         + _first_line(tproc.stderr))
        return facts

    def _parse_reconcile(self, facts: LaneFacts, text: str) -> None:
        for line in text.splitlines():
            parts = line.split(US)
            tag = parts[0]
            if tag == "MANAGED":
                facts.managed = "unknown" if parts[1:2] == ["unknown"] else "owned"
                facts.managed_owner = parts[1] if len(parts) > 1 else ""
            elif tag == "ROOT" and len(parts) > 1:
                facts.root = parts[1]
            elif tag == "STATE" and len(parts) > 1:
                facts.state = parts[1]
            elif tag == "HOLDER" and len(parts) > 1:
                facts.holder = parts[1]
                facts.holder_detail = parts[2] if len(parts) > 2 else ""
                if facts.holder == "live":
                    facts.holder_uuid = facts.holder_detail.split(" ")[0]
            elif tag == "BINDING" and len(parts) > 1:
                facts.binding = parts[1]
                facts.binding_at = parts[2] if len(parts) > 2 else ""
            elif tag == "VERDICT" and len(parts) > 1:
                facts.verdict = parts[1]
                facts.verdict_why = parts[2] if len(parts) > 2 else ""

    def _holder_binding(self, facts: LaneFacts) -> None:
        bproc = self.call("binding", facts.lane)
        if bproc.returncode == 0:
            cols = bproc.stdout.rstrip("\n").split("\t")
            where = cols[6] if len(cols) > 6 else "elsewhere"
            wstate = cols[7] if len(cols) > 7 else "unknown"
            facts.binding_at = f"{cols[0]}/{cols[1]}" if len(cols) > 1 else ""
            if where == "here":
                facts.binding = "here"
            elif wstate == "gone":
                facts.binding = "gone"
            else:
                facts.binding = "elsewhere"
        elif bproc.returncode == 8:
            facts.binding = "free"
        else:
            facts.binding = "unknown"
        hproc = self.call("live-holder", facts.lane)
        if hproc.returncode == 0:
            facts.holder = "live"
            facts.holder_detail = hproc.stdout.strip().replace(US, " ")
            facts.holder_uuid = facts.holder_detail.split(" ")[0]
        elif hproc.returncode == 8:
            facts.holder = "none"
        else:
            facts.holder = "unknown"
            facts.holder_detail = _first_line(hproc.stderr)
        mproc = self.call("managed-projection", facts.lane)
        if mproc.returncode == 0:
            facts.managed, facts.managed_owner = "owned", mproc.stdout.strip()
        elif mproc.returncode not in (8, 2):
            facts.managed = "unknown"

    def workspace_root(self) -> str:
        proc = self.call("workspace-root")
        return proc.stdout.strip() if proc.returncode == 0 else ""

    def fetch_register(self) -> str:
        """`--yes` ACTS ON A REGISTER READ NOW, or not at all (#170 A1): '' once
        the workspace repository's `origin/<branch>` is fetched, else why not.

        `lanes-edit.sh`'s own `log_sync` answers 0 on every way it can fail to
        fetch, and an inherited `LANES_NO_FETCH=1` skips the fetch outright, so a
        lane rebound on another host after this one's last fetch read as bound
        HERE and its trees were removed (Amendment 18(b)). The fetch is made
        directly, with an explicit refspec, and every later helper call reads
        the ref it left (`LANES_NO_FETCH=1`, set by `call` once `_fetched`)."""
        root = self.workspace_root()
        if not root:
            return ("the workspace repository could not be found (`lanes-edit.sh "
                    "workspace-root` answered nothing)")
        branch = os.environ.get("LANES_BRANCH", "") or "main"
        proc = git(root, "fetch", "--quiet", "--no-tags", "origin",
                   f"+refs/heads/{branch}:refs/remotes/origin/{branch}", timeout=GIT_NET_TIMEOUT)
        if proc.returncode != 0:
            return (f"`git -C {root} fetch origin {branch}` failed: " + _first_line(proc.stderr))
        self._fetched = True
        return ""

    def transcript_live(self, uuid: str, fresh: bool = False) -> str:
        """live | none | unknown - one live process carrying that transcript,
        asked once per session however many trees it recorded (`fresh` asks
        again: at the moment of an act the cached answer is minutes old)."""
        cache = self.__dict__.setdefault("_live", {})
        if fresh or uuid not in cache:
            proc = self.call("transcript-holders", uuid)
            cache[uuid] = {0: "live", 8: "none"}.get(proc.returncode, "unknown")
        return cache[uuid]

    def note_line(self, lane: str, text: str, session: str = ""):
        """The NOTED line, written AS THIS SESSION (#170 E7): `LANES_SESSION`
        is passed, so the line names the session that swept and not
        whatever the helper would find on its own."""
        extra = {"LANES_LANE": lane}
        if session:
            extra["LANES_SESSION"] = session
        return self.call("log", "NOTED", f"lane:{lane}", register_text(text),
                         env_extra=extra, fetch=False)


LANE_LINE_RE = re.compile(r"^(?:STARTED|PAUSED|RESUMED|ENDED|RETIRED) — lane [^,]+, "
                          r"session [^,]+, [^,]+, (.*)$")


def payload_field(payload: str, name: str) -> str:
    """One `<name> <value>` sub-field of a register payload, as
    `lanes-edit.sh`'s `payload_subfield` reads it: the `; `-separated part
    that starts with the name, its value up to the first space - or, quoted,
    up to the closing quote."""
    want = name + " "
    for part in payload.split("; "):
        if not part.startswith(want):
            continue
        value = part[len(want):].strip()
        if value.startswith('"'):
            end = value.find('"', 1)
            if end > 0:
                return value[1:end]
        return value.split(" ", 1)[0]
    return ""


def register_lane_dirs(helper) -> tuple:
    """({lane: its recorded dir}, why-not) - every lane log's last lane-kind
    line that carries a `dir`, read from the workspace repository's
    `origin/<branch>` in one `git grep`. Where #97 keeps that lane's control
    root (`<parent of dir>/.lane-state/<lane>`) follows from it."""
    root = helper.workspace_root() if helper.path else ""
    if not root:
        return {}, "the workspace repository could not be found"
    branch = os.environ.get("LANES_BRANCH", "") or "main"
    proc = git(root, "grep", "-E", "-e", r"^(STARTED|PAUSED|RESUMED|ENDED|RETIRED) — lane ",
               f"origin/{branch}", "--", "lanes/log")
    if proc.returncode not in (0, 1):
        return {}, (f"the register's lane logs on origin/{branch} could not be read: "
                    + _first_line(proc.stderr))
    out: dict = {}
    for line in proc.stdout.splitlines():
        _ref, _, rest = line.partition(":")
        fname, _, text = rest.partition(":")
        if not fname.endswith(".md"):
            continue
        m = LANE_LINE_RE.match(text)
        found = payload_field(m.group(1), "dir") if m else ""
        if found.startswith("/"):
            out[os.path.basename(fname)[:-3]] = found
    return out, ""


def _first_line(text: str) -> str:
    for line in (text or "").splitlines():
        if line.strip():
            return line.strip()
    return "no answer"


# ============================================================ liveness: who

class ProcScan:
    """Every process this account can see, and the paths it stands in or
    holds open: `/proc` where there is one, `lsof` where there is not
    (macOS), and every tmux pane's current path beside either.

    A scan that could not be made is `available = False`, and a tree whose
    liveness rests on it is never acted on - not "nobody is there".

    A PROCESS OF THIS ACCOUNT WHOSE `/proc` ENTRIES CANNOT BE READ (#170 item
    2) - not dumpable, as `ssh-agent` makes itself - is in `unknown`, never
    read as absent. Which tree it stands in cannot be read either, so it is
    placed by what can be: an absolute path in its command line, and its
    parent's working directory (a process starts where its parent stands).
    A tree either places it in is of unknown liveness and is never acted
    on. One that places it nowhere (an `ssh-agent` daemon, re-parented to
    init) holds no tree; one whose status cannot be read at all could be in
    any. A process of ANOTHER account is never a writer here and is passed
    over."""

    def __init__(self):
        self.entries: list = []      # (pid, comm, kind, path)
        self.unknown: list = []      # (pid, comm, [paths it is placed by], anywhere)
        self.available = False
        self.why = ""
        self._env_loaded = False
        self._scan()

    def _scan(self) -> None:
        me = os.getpid()
        uid = os.getuid()
        if os.environ.get(SEAM_NO_PROCSCAN, "") == "1":
            # THE SUITE'S SEAM (#170 C5): a host with no `/proc` and no `lsof`,
            # on a host that has both.
            self.why = "this host has no /proc and no lsof (the suite's seam)"
            return
        if os.path.isdir("/proc/self/fd"):
            self.available = True
            pending = []             # (pid, comm, ppid, hints, anywhere)
            for name in os.listdir("/proc"):
                if not name.isdigit() or int(name) == me:
                    continue
                base = "/proc/" + name
                status = _proc_status(base)
                if status == {}:
                    continue          # it exited
                if status is None:
                    # NOT EVEN ITS STATUS: whose it is comes from the entry's own
                    # owner, and where it stands cannot be placed at all.
                    try:
                        if os.stat(base).st_uid == uid:
                            pending.append((int(name), "?", 0, [], True))
                    except OSError:
                        pass
                    continue
                if status.get("State", "").startswith("Z"):
                    continue          # a zombie stands nowhere
                ids = (status.get("Uid", "").split() + ["-1", "-1"])[:2]
                ours = str(uid) in ids
                comm = status.get("Name", "?")
                hidden = False
                try:
                    self.entries.append((int(name), comm, "cwd", _proc_path(os.readlink(base + "/cwd"))))
                except (FileNotFoundError, ProcessLookupError):
                    continue
                except PermissionError:
                    hidden = True
                except OSError:
                    pass
                try:
                    fds = os.listdir(base + "/fd")
                except PermissionError:
                    hidden, fds = True, []
                except OSError:
                    fds = []
                for fd in fds:
                    try:
                        target = os.readlink(base + "/fd/" + fd)
                    except OSError:
                        continue
                    if target.startswith("/"):
                        self.entries.append((int(name), comm, "fd", _proc_path(target)))
                if hidden and ours:
                    ppid = int(status.get("PPid", "0") or 0) if status.get("PPid", "").isdigit() else 0
                    pending.append((int(name), comm, ppid, _cmdline_paths(base), False))
            cwd_of = {pid: where for pid, _c, kind, where in self.entries if kind == "cwd"}
            for pid, comm, ppid, hints, anywhere in pending:
                if ppid in cwd_of:
                    hints = hints + [cwd_of[ppid]]
                self.unknown.append((pid, comm, hints, anywhere))
        elif shutil.which("lsof"):
            proc = run(["lsof", "-nP", "-w", "-F", "pcfn", "-u", str(os.getuid())], timeout=120)
            if proc.stdout:
                self.available = True
                pid, comm, fd = 0, "", ""
                for line in proc.stdout.splitlines():
                    if not line:
                        continue
                    tag, value = line[0], line[1:]
                    if tag == "p":
                        pid, comm, fd = int(value or 0), "", ""
                    elif tag == "c":
                        comm = value
                    elif tag == "f":
                        fd = value
                    elif tag == "n" and pid and pid != me and value.startswith("/"):
                        self.entries.append((pid, comm, "cwd" if fd == "cwd" else "fd", value))
            else:
                self.why = "lsof answered nothing: " + _first_line(proc.stderr)
        else:
            self.why = "this host has no /proc and no lsof, so which process stands in a tree cannot be read"
        if shutil.which("tmux"):
            proc = run(["tmux", "list-panes", "-a", "-F",
                        "#{pane_pid}\t#{session_name}:#{window_index}.#{pane_index}\t#{pane_current_path}"],
                       timeout=30)
            if proc.returncode == 0:
                for line in proc.stdout.splitlines():
                    cols = line.split("\t")
                    if len(cols) == 3 and cols[2].startswith("/"):
                        pid = int(cols[0]) if cols[0].isdigit() else 0
                        # PHYSICAL, like every other path here: `/proc` and `lsof`
                        # answer resolved paths, and a pane keeps the spelling
                        # the shell was given (macOS's `/var` is `/private/var`).
                        self.entries.append((pid, "tmux pane " + cols[1], "pane", real(cols[2])))

    def load_environ(self) -> None:
        """The path-shaped values of every readable process's environment
        (HOME, TMPDIR, a suite's sandbox): Linux only, and only for the
        sandbox sweep, which is where a process's ROOT may be nowhere else."""
        if self._env_loaded:
            return
        self._env_loaded = True
        if not os.path.isdir("/proc/self"):
            return
        me = os.getpid()
        for name in os.listdir("/proc"):
            if not name.isdigit() or int(name) == me:
                continue
            try:
                with open(f"/proc/{name}/environ", "rb") as fh:
                    raw = fh.read()
            except OSError:
                continue
            for item in raw.split(b"\0"):
                if b"=/" not in item:
                    continue
                value = item.split(b"=", 1)[1].decode("utf-8", "replace")
                for piece in value.split(":"):
                    if piece.startswith("/"):
                        self.entries.append((int(name), "", "env", piece))

    def holders(self, path: str) -> list:
        root = real(path)
        out = []
        for pid, comm, kind, where in self.entries:
            if where == root or where.startswith(root.rstrip("/") + "/"):
                out.append((pid, comm, kind, where))
        return out

    def unknown_for(self, path: str) -> str:
        """Why a process of this account that `/proc` would not show could be
        in `path`, or '' (see the class: item 2)."""
        root = real(path)
        for pid, comm, hints, anywhere in self.unknown:
            if anywhere:
                return (f"process {pid} of this account could not be read at all (not even its "
                        "status), so it could be in any tree")
            for h in hints:
                if h == root or h.startswith(root.rstrip("/") + "/"):
                    return (f"process {pid} ({comm}) of this account could not be read (it is "
                            f"not dumpable), and {h} - its command line or its parent's "
                            "directory - places it here")
        return ""


def _proc_path(target: str) -> str:
    return target[:-10] if target.endswith(" (deleted)") else target


def _proc_status(base: str):
    """`/proc/<pid>/status` as {field: value}; {} where the process is gone,
    None where it is there and cannot be read."""
    try:
        with open(base + "/status", encoding="utf-8", errors="replace") as fh:
            text = fh.read()
    except (FileNotFoundError, ProcessLookupError):
        return {}
    except OSError:
        return None
    out = {}
    for line in text.splitlines():
        key, sep, value = line.partition(":")
        if sep:
            out[key.strip()] = value.strip()
    return out


def _cmdline_paths(base: str) -> list:
    """The absolute paths in a process's command line (`--x=/p` included)."""
    try:
        with open(base + "/cmdline", "rb") as fh:
            raw = fh.read()
    except OSError:
        return []
    out = []
    for arg in raw.split(b"\0"):
        text = arg.decode("utf-8", "surrogateescape")
        for piece in [text] + text.split("=", 1)[1:]:
            if piece.startswith("/"):
                out.append(real(piece))
    return out


def pid_alive(pid: int) -> bool:
    if pid <= 0:
        return False
    try:
        os.kill(pid, 0)
    except ProcessLookupError:
        return False
    except PermissionError:
        return True
    except OSError:
        return False
    return True


# ========================================================== merge evidence

def slug_of(url: str) -> str:
    u = (url or "").strip()
    if u.endswith(".git"):
        u = u[:-4]
    for prefix in ("https://github.com/", "http://github.com/", "ssh://git@github.com/",
                   "git@github.com:", "git://github.com/"):
        if u.startswith(prefix):
            rest = u[len(prefix):].strip("/")
            return rest if SLUG_RE.match(rest) else ""
    return ""


class MergeEvidence:
    """Whether a branch's work is MERGED, decided by the register's LANDED
    line for its pull request or by `gh`'s state for it - and never by
    `git branch --merged` alone, which a squash merge defeats."""

    def __init__(self, helper: Helper):
        self.helper = helper
        self.gh_ok = (os.environ.get("LANES_NO_GITHUB", "") != "1"
                      and bool(shutil.which("gh")))
        self.gh_why = "" if self.gh_ok else ("LANES_NO_GITHUB=1" if os.environ.get(
            "LANES_NO_GITHUB", "") == "1" else "no gh on PATH")
        self._prs: dict = {}
        self._landed = None
        self.register_why = ""

    def landed(self) -> set:
        if self._landed is not None:
            return self._landed
        self._landed = set()
        root = self.helper.workspace_root()
        if not root:
            self.register_why = "the workspace repository could not be found"
            return self._landed
        branch = os.environ.get("LANES_BRANCH", "main")
        proc = git(root, "grep", "-h", "-E", "-e", "^LANDED", f"origin/{branch}", "--",
                   "lanes/LANES.md", "lanes/log")
        if proc.returncode not in (0, 1):
            self.register_why = "the register could not be read: " + _first_line(proc.stderr)
            return self._landed
        for line in proc.stdout.splitlines():
            m = LANDED_OBJ_RE.match(line)
            if m:
                self._landed.add((m.group(1).lower(), int(m.group(2))))
                continue
            m = LANDED_ROW_RE.match(line)
            if m:
                self._landed.add((m.group(2).lower(), int(m.group(1))))
        return self._landed

    def prs(self, slug: str, branch: str):
        """Every pull request whose head is `branch`, ASKED BY THAT BRANCH
        (#170 E5): one `--limit 1000` list of the whole repository missed the
        PR of a branch older than its thousandth, which then read as none."""
        if not slug or not self.gh_ok:
            return None
        if (slug, branch) in self._prs:
            return self._prs[(slug, branch)]
        env = dict(os.environ, GH_PROMPT_DISABLED="1", GH_NO_UPDATE_NOTIFIER="1")
        proc = run(["gh", "pr", "list", "--repo", slug, "--state", "all", "--head", branch,
                    "--limit", "200", "--json", "number,state,mergedAt,headRefName,headRefOid,"
                    "baseRefName,isCrossRepository"],
                   env=env, timeout=120)
        rows = None
        if proc.returncode == 0:
            try:
                rows = json.loads(proc.stdout or "[]")
            except ValueError:
                rows = None
        if rows is None:
            self.gh_why = f"gh pr list --repo {slug} --head {branch} failed: " + _first_line(proc.stderr)
        self._prs[(slug, branch)] = rows
        return rows

    @staticmethod
    def default_branches(repo: str) -> set:
        """The branch a landing goes into: origin's HEAD where this clone
        knows it, else `main` or `master`."""
        head = git_out(repo, "symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD") or ""
        if head.startswith("origin/") and len(head) > len("origin/"):
            return {head[len("origin/"):]}
        return set(PROTECTED_BRANCHES)

    def judge(self, repo: str, slug: str, branch: str, tip: str) -> dict:
        """{'state': merged|moved|elsewhere|open|closed|none|unknown, 'pr': n,
        'source': ..., 'why': ...} for `branch` at `tip`."""
        rows = self.prs(slug, branch)
        if rows is None:
            return {"state": "unknown", "pr": 0, "source": "",
                    "why": "no pull request could be read for it (" + (self.gh_why or
                                                                       "not a GitHub origin") + ")"}
        mine = [r for r in rows if r.get("headRefName") == branch
                and not r.get("isCrossRepository")]
        if not mine:
            return {"state": "none", "pr": 0, "source": "gh", "why": "no pull request names it"}
        landed = self.landed()
        # AN OPEN PULL REQUEST PROTECTS ITS BRANCH BEFORE ANYTHING ELSE: an
        # older merged PR on the same branch says nothing about the work the
        # open one still carries - unless the register has LANDED that very PR.
        for row in sorted(mine, key=lambda r: -int(r.get("number", 0))):
            if row.get("state") == "OPEN" and (slug.lower(), int(row.get("number", 0))) not in landed:
                return {"state": "open", "pr": int(row["number"]), "source": "gh",
                        "why": f"PR #{row['number']} is open"}
        # ... AND WHILE GH STILL ANSWERS OPEN, THE REMOTE BRANCH STAYS (#170 A8):
        # a LANDED line that names the wrong number (#21 for #20) must not
        # delete the branch of a PR that is still open, which would close it.
        gh_open = [int(r.get("number", 0)) for r in mine if r.get("state") == "OPEN"]
        defaults = self.default_branches(repo)
        elsewhere = None
        for row in sorted(mine, key=lambda r: -int(r.get("number", 0))):
            number = int(row.get("number", 0))
            by_register = (slug.lower(), number) in landed
            by_gh = row.get("state") == "MERGED"
            if by_register or by_gh:
                # A MERGE INTO A RELEASE OR FEATURE BRANCH IS NOT A LANDING: only
                # a PR whose base is the default branch makes its branch merged.
                base = row.get("baseRefName") or ""
                if base not in defaults:
                    elsewhere = elsewhere or {
                        "state": "elsewhere", "pr": number, "source": "gh", "gh_open": gh_open,
                        "why": f"PR #{number} was merged into {base or 'a base gh did not name'}, "
                               f"not {'/'.join(sorted(defaults))}: not a landing"}
                    continue
                head = row.get("headRefOid") or ""
                contained = bool(tip) and (tip == head or (bool(head) and git(
                    repo, "merge-base", "--is-ancestor", tip, head).returncode == 0))
                source = "register LANDED" if by_register else "gh MERGED"
                if contained:
                    return {"state": "merged", "pr": number, "source": source, "head": head,
                            "gh_open": gh_open,
                            "why": f"PR #{number} is merged ({source}) and holds this tip"
                                   + (f"; gh still answers PR #{gh_open[0]} OPEN, so its remote "
                                      "branch is kept" if gh_open else "")}
                return {"state": "moved", "pr": number, "source": source, "head": head,
                        "gh_open": gh_open,
                        "why": f"PR #{number} is merged ({source}) but this branch has moved "
                               f"past its head {short(head)}"
                               + (f"; gh still answers PR #{gh_open[0]} OPEN" if gh_open else "")}
        if elsewhere:
            return elsewhere
        return {"state": "closed", "pr": int(mine[0].get("number", 0)), "source": "gh",
                "why": f"PR #{mine[0].get('number')} was closed unmerged"}


# ============================================================== the estate

def estate_repos(root: str, max_depth: int = 7) -> list:
    """Every repository under `root`: a walk that descends into a repository
    ONLY through nested checkouts and worktree containers, so the walk is the
    estate's shape and not every source directory in it."""
    found = []
    prune = set(CACHE_NAMES) | set(VENV_NAMES) | {".git", ".lane-state", ".tox"}

    def walk(d: str, depth: int, in_repo: bool) -> None:
        try:
            entries = list(os.scandir(d))
        except OSError:
            return
        is_repo = any(e.name == ".git" for e in entries)
        if is_repo:
            found.append(d)
        if depth >= max_depth:
            return
        for e in entries:
            if e.name in prune:
                continue
            try:
                if e.is_symlink() or not e.is_dir(follow_symlinks=False):
                    continue
            except OSError:
                continue
            if is_repo or in_repo:
                if (e.name in ("worktrees", ".worktrees", ".claude")
                        or os.path.lexists(os.path.join(e.path, ".git"))):
                    walk(e.path, depth + 1, True)
            else:
                walk(e.path, depth + 1, False)

    if root and os.path.isdir(root):
        walk(root, 0, False)
    return found


#: What the estate walk never enters: caches, tool environments and installed
#: packages. None of them holds a clone anything leans on, and node_modules
#: alone is most of an estate's directories.
WALK_SKIP = frozenset(CACHE_NAMES) | {".tox", ".nox", "site-packages"}


class EstateWalk:
    """Every repository and every `.lane-state` directory under one root,
    and every directory the walk could not read."""

    def __init__(self, root: str):
        self.root = root
        self.repos: list = []
        self.lane_states: list = []
        self.unread: list = []


def walk_estate(root: str) -> EstateWalk:
    """A walk of EVERY directory under `root` but the caches (#170 A11):
    `estate_repos` descends into a repository only through worktree
    containers, so a clone in a plain directory inside a repository (all of
    `xFactory/xFactories/*` on Eagle) was never seen, and nor was anything
    that leaned on a lane's clone from there. A directory that cannot be read
    is named in `unread`, so a caller can tell a complete answer from a
    partial one. Symlinks are never followed; a `.git` is never entered."""
    walk = EstateWalk(root)
    if not root or not os.path.isdir(root):
        walk.unread.append(f"the estate root {root or '(none)'} is not a directory")
        return walk
    stack = [root]
    while stack:
        d = stack.pop()
        try:
            with os.scandir(d) as it:
                entries = list(it)
        except FileNotFoundError:
            continue
        except OSError as exc:
            walk.unread.append(f"{d} ({exc.strerror or exc})")
            continue
        for e in entries:
            if e.name == ".git":
                walk.repos.append(d)
                continue
            try:
                if e.is_symlink() or not e.is_dir(follow_symlinks=False):
                    continue
            except OSError:
                continue
            if e.name == ".lane-state":
                walk.lane_states.append(e.path)
            elif e.name not in WALK_SKIP:
                stack.append(e.path)
    walk.repos.sort()
    walk.lane_states.sort()
    return walk


def git_common_dir(repo: str) -> str:
    dotgit = os.path.join(repo, ".git")
    if os.path.isdir(dotgit):
        return dotgit
    try:
        with open(dotgit, encoding="utf-8", errors="surrogateescape") as fh:
            first = fh.readline().strip()
    except OSError:
        return ""
    if not first.startswith("gitdir:"):
        return ""
    gitdir = first[len("gitdir:"):].strip()
    if not os.path.isabs(gitdir):
        gitdir = os.path.normpath(os.path.join(repo, gitdir))
    try:
        with open(os.path.join(gitdir, "commondir"), encoding="utf-8", errors="surrogateescape") as fh:
            common = fh.readline().strip()
        if not os.path.isabs(common):
            common = os.path.normpath(os.path.join(gitdir, common))
        return common
    except OSError:
        return gitdir


def local_remote_urls(common: str, unread: list | None = None) -> list:
    """(name, url) for every remote in that config whose URL is a local path.
    A config that exists and cannot be read is named in `unread`."""
    out = []
    try:
        with open(os.path.join(common, "config"), encoding="utf-8", errors="replace") as fh:
            text = fh.read()
    except FileNotFoundError:
        return out
    except OSError as exc:
        if unread is not None:
            unread.append(f"{os.path.join(common, 'config')} ({exc.strerror or exc})")
        return out
    section = ""
    for raw in text.splitlines():
        line = raw.strip()
        m = re.match(r'^\[remote\s+"([^"]+)"\]$', line)
        if m:
            section = m.group(1)
            continue
        if line.startswith("["):
            section = ""
            continue
        if section and re.match(r"^url\s*=", line):
            url = line.split("=", 1)[1].strip()
            if url.startswith(("/", "./", "../", "~", "file://")):
                out.append((section, url))
    return out


def dependents_map(repos: list, unread: list | None = None) -> dict:
    """{physical path a repository LEANS ON: [(dependent, how)]} - read from
    `objects/info/alternates` and from remotes whose URL is a local path. A
    clone something leans on is LOAD-BEARING and is never removed by a sweep.
    A repository whose git directory, alternates or config exists and cannot
    be read is named in `unread`: its leaning is unknown, never none."""
    deps: dict = {}
    for repo in repos:
        common = git_common_dir(repo)
        if not common:
            # A `.git` FILE THAT IS NO POINTER IS NO REPOSITORY: uv keeps an empty
            # one in its cache to stop git looking upward. Only one that cannot
            # be read leaves the leaning unknown.
            try:
                with open(os.path.join(repo, ".git"), "rb") as fh:
                    fh.read(1)
            except OSError as exc:
                if unread is not None:
                    unread.append(f"{repo}/.git ({exc.strerror or exc})")
            continue
        try:
            with open(os.path.join(common, "objects", "info", "alternates"),
                      encoding="utf-8", errors="surrogateescape") as fh:
                for raw in fh.read().splitlines():
                    alt = raw.strip()
                    if not alt or alt.startswith("#"):
                        continue
                    if not os.path.isabs(alt):
                        alt = os.path.normpath(os.path.join(common, "objects", alt))
                    target = real(alt)
                    deps.setdefault(target, []).append((repo, "alternates " + alt))
        except FileNotFoundError:
            pass
        except OSError as exc:
            if unread is not None:
                unread.append(f"{common}/objects/info/alternates ({exc.strerror or exc})")
        for name, url in local_remote_urls(common, unread):
            path = url[len("file://"):] if url.startswith("file://") else url
            path = os.path.expanduser(path)
            if not os.path.isabs(path):
                path = os.path.normpath(os.path.join(repo, path))
            deps.setdefault(real(path), []).append((repo, f"remote {name} -> {url}"))
    return deps


def leaned_on(path: str, common: str, deps: dict) -> list:
    """Who leans on the tree at `path` (and on its object store, for a clone)."""
    out = []
    targets = [real(path)]
    if common and not under(common, path):
        pass
    elif common:
        targets.append(real(common))
    for target, who in deps.items():
        for t in targets:
            if target == t or target.startswith(t.rstrip("/") + "/"):
                for dependent, how in who:
                    if not under(dependent, path):
                        out.append((dependent, how))
    seen, uniq = set(), []
    for item in out:
        if item not in seen:
            seen.add(item)
            uniq.append(item)
    return uniq


# ================================================================ the trees

class Tree:
    def __init__(self, path: str, owner: str, foreign_kind: str = ""):
        self.path = path
        self.real = real(path)
        self.owner = owner                 # inventory | foreign
        self.foreign_kind = foreign_kind   # registered | lane-root | clone | other-lane
        self.other_lane = ""
        self.sidecar: dict = {}
        self.checkout = ""                 # the repository's main worktree
        self.registered = False
        self.locked = False
        self.exists = os.path.isdir(path)
        self.is_git = False
        self.kind = ""                     # worktree | clone | main
        self.common = ""
        self.branch = ""
        self.detached = False
        self.unborn = False
        self.head = ""
        self.status_lines: list = []
        self.upstream = ""
        self.origin_url = ""
        self.remote_branch = ""            # the name it is (or would be) pushed as
        self.remote_tip = ""
        self.on_origin = None              # bool | None (unknown)
        self.ahead = 0
        self.behind = 0
        self.lane_trailer = ""
        self.remote_trailer = ""           # the Lane: trailer of origin/<branch>'s tip
        self.merge: dict = {}
        self.live = None                   # (True|None, why) | None = not live
        self.lean: list = []
        self.guard = ""
        self.read_error = ""
        self.disposition = ""
        self.reason = ""
        self.retire = False
        self.result = ""
        self.rescue = ""                   # origin ref a rescue went to
        self.bundle = ""
        self.ignored_archive = ""
        self.register = ""
        self.contested = False             # another lane's inventory names it too
        self.reg: dict = {}                # its registration, where git has one
        self.held_back = ""                # the bundle-only commit of its untracked files
        self.lost_bundle = ""              # the bundle of commits only its reflog named
        self.leg = None                    # T018: the assembly's leg that registers it

    @property
    def slice(self) -> str:
        return safe_ref_part(os.path.basename(self.path.rstrip("/")))

    @property
    def gitpath(self) -> str:
        """Where git is asked about it: the tree, or - its directory gone -
        the checkout that registers it."""
        return self.path if self.exists else (self.checkout or self.path)

    def label(self) -> str:
        if not self.exists:
            return "(gone)"
        if not self.is_git:
            return "(not a checkout)"
        if self.detached:
            return f"(detached) {short(self.head)}"
        if self.unborn:
            return f"{self.branch} (unborn)"
        return f"{self.branch} {short(self.head)}"


def worktree_registrations(checkout: str):
    """Every registration of `checkout`'s repository, its main worktree first
    - or None where git could not list them, which is never "none" (#170 G5):
    an empty answer let discovery report nothing to retire and exit 0."""
    # `-z` WHERE GIT HAS IT (2.36+): a path with a newline in it is one field,
    # not a registration of half its name and a line of noise (B3).
    proc = git(checkout, "worktree", "list", "--porcelain", "-z")
    if proc.returncode == 0:
        fields = proc.stdout.split("\0")
    else:
        proc = git(checkout, "worktree", "list", "--porcelain")
        if proc.returncode != 0:
            return None
        fields = proc.stdout.splitlines()
    out, cur = [], {}
    for line in fields + [""]:
        if not line:
            if cur:
                out.append(cur)
            cur = {}
            continue
        key, _, value = line.partition(" ")
        if key == "worktree":
            cur = {"path": value}
        elif key in ("HEAD", "branch"):
            cur[key.lower()] = value
        elif key in ("detached", "bare", "locked", "prunable"):
            cur[key] = value or True
    return out


def read_tree(tree: Tree) -> None:
    """Everything git says about one tree, NOW. A read that failed is said,
    never converted into a clean-looking value (R22, Amendment 7(d))."""
    if not tree.exists:
        return
    proc = git(tree.path, "rev-parse", "--show-toplevel", "--git-common-dir")
    if proc.returncode != 0:
        return
    lines = proc.stdout.strip().splitlines()
    if not lines or real(lines[0]) != tree.real:
        return            # a plain directory inside some other checkout
    tree.is_git = True
    common = lines[1] if len(lines) > 1 else ""
    if common and not os.path.isabs(common):
        common = os.path.normpath(os.path.join(tree.path, common))
    tree.common = common
    dotgit = os.path.join(tree.path, ".git")
    tree.kind = "worktree" if os.path.isfile(dotgit) else "clone"
    if tree.common:
        main = os.path.dirname(tree.common.rstrip("/")) if os.path.basename(
            tree.common.rstrip("/")) == ".git" else ""
        tree.checkout = main or tree.checkout
        if tree.kind == "clone" and main and real(main) == tree.real:
            tree.kind = "clone"
    branch = git(tree.path, "symbolic-ref", "-q", "--short", "HEAD")
    if branch.returncode == 0:
        tree.branch = branch.stdout.strip()
    elif branch.returncode == 1:
        tree.detached = True
    else:
        tree.read_error = "HEAD could not be read: " + _first_line(branch.stderr)
        return
    head = git(tree.path, "rev-parse", "--verify", "-q", "HEAD")
    if head.returncode == 0:
        tree.head = head.stdout.strip()
    elif tree.branch:
        tree.unborn = True
    else:
        tree.read_error = "HEAD names no commit"
        return
    status = git(tree.path, "status", "--porcelain=v1", "--ignore-submodules=none",
                 "--untracked-files=normal")
    if status.returncode != 0:
        tree.read_error = "git status failed: " + _first_line(status.stderr)
        return
    tree.status_lines = [ln for ln in status.stdout.splitlines() if ln.strip()]
    tree.origin_url = git_out(tree.path, "config", "--get", "remote.origin.url") or ""
    if tree.branch:
        up = git(tree.path, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}")
        tree.upstream = up.stdout.strip() if up.returncode == 0 else ""
        # THE BRANCH IS PUSHED UNDER ITS OWN NAME, NEVER ITS UPSTREAM'S. A branch
        # made with `worktree add -b <b> origin/main` TRACKS origin/main, and a
        # push "to its upstream" would put a lane's stray commits on main.
        target = tree.branch
        tree.remote_branch = target
        tip = git_out(tree.path, "rev-parse", "-q", "--verify", f"refs/remotes/origin/{target}")
        tree.remote_tip = tip or ""
        if tree.remote_tip:
            rmsg = git_out(tree.path, "log", "-1", "--format=%B", tree.remote_tip) or ""
            rtrailers = re.findall(r"^Lane:\s*(\S+)\s*$", rmsg, re.MULTILINE)
            tree.remote_trailer = rtrailers[-1] if rtrailers else ""
    if tree.head:
        if tree.remote_tip:
            counts = git_out(tree.path, "rev-list", "--left-right", "--count",
                             f"{tree.remote_tip}...{tree.head}")
            parts = (counts or "").split()
            if len(parts) != 2 or not all(x.isdigit() for x in parts):
                # NEVER ZERO FOR UNKNOWN: "0 ahead" is what lets a tree go
                # without a rescue.
                tree.read_error = (f"its distance from origin/{tree.remote_branch} could not "
                                   "be read (git rev-list failed)")
                return
            tree.behind, tree.ahead = int(parts[0]), int(parts[1])
        contains = git(tree.path, "for-each-ref", "--count=1", "--contains", tree.head,
                       "--format=%(refname)", "refs/remotes/origin/")
        if contains.returncode == 0:
            tree.on_origin = bool(contains.stdout.strip())
        msg = git_out(tree.path, "log", "-1", "--format=%B", "HEAD") or ""
        trailers = re.findall(r"^Lane:\s*(\S+)\s*$", msg, re.MULTILINE)
        tree.lane_trailer = trailers[-1] if trailers else ""


def snapshot_tree(path: str, tracked_only: bool = False) -> str:
    """The tree object `git add -A` would commit in `path` NOW (`git add -u`
    with `tracked_only`: no untracked file), written through a COPY of its
    index so the tree's own index is never touched. Empty where it could
    not be made."""
    index = git_out(path, "rev-parse", "--git-path", "index")
    if index is None:
        return ""
    if not os.path.isabs(index):
        index = os.path.join(path, index)
    fd, tmp = tempfile.mkstemp(prefix="lane-worktrees-index.")
    os.close(fd)
    try:
        if os.path.exists(index):
            shutil.copyfile(index, tmp)
        else:
            os.unlink(tmp)
        env = git_env()
        env["GIT_INDEX_FILE"] = tmp
        if run(["git", "-C", path, "add", "-u" if tracked_only else "-A"],
               env=env).returncode != 0:
            return ""
        proc = run(["git", "-C", path, "write-tree"], env=env)
        return proc.stdout.strip() if proc.returncode == 0 else ""
    finally:
        try:
            os.unlink(tmp)
        except OSError:
            pass


def tree_guards(tree: Tree) -> str:
    """What makes a tree unsafe to remove whatever its git state says: work a
    commit in it cannot carry (a submodule's own dirt, commits, branches, tags
    or stash; a clone nested inside it; an edit git status hides) and a
    registration someone locked."""
    if tree.locked:
        return ("its worktree registration is LOCKED - someone pinned it on purpose; "
                f"`git -C {tree.checkout or tree.path} worktree unlock {tree.path}` is theirs to run")
    if tree.kind == "clone":
        # A CLONE TAKES ITS LINKED WORKTREES' REPOSITORY WITH IT: a worktree
        # elsewhere - perhaps a live writer's - keeps its files and loses its
        # git directory.
        wl = git(tree.path, "worktree", "list", "--porcelain")
        if wl.returncode != 0:
            return "its worktree registrations could not be read"
        linked = [line[len("worktree "):] for line in wl.stdout.splitlines()
                  if line.startswith("worktree ")][1:]
        if linked:
            return (f"it is a CLONE whose git directory {len(linked)} linked worktree(s) share "
                    f"(e.g. {linked[0]}); its removal would take their repository with it - "
                    "remove or move them first")
        # A CLONE TAKES EVERY BRANCH, TAG AND STASH WITH IT, which a worktree
        # never does: each one must be on origin first - a TAG included
        # (#170 item 4), and a ref that cannot be checked keeps it.
        why = unpublished_ref(tree.path, "--remotes=origin", "origin")
        if why:
            return (f"it is a CLONE and {why}; a clone's removal takes every branch and tag "
                    "with it")
        stash = git(tree.path, "stash", "list")
        if stash.returncode != 0 or stash.stdout.strip():
            return "it is a CLONE with a stash, which its removal would take with it"
    # AN EDIT GIT STATUS DOES NOT SHOW (#170 A2): a file flagged
    # skip-worktree or assume-unchanged that differs from the index reads as
    # clean, and `git add -A` skips it, so no rescue would carry it.
    hidden = hidden_edits(tree.path)
    if hidden is None:
        return ("its skip-worktree and assume-unchanged flags could not be read, so whether it "
                "holds an edit git status hides is unknown")
    if hidden:
        return (f"{len(hidden)} file(s) flagged skip-worktree or assume-unchanged differ from "
                f"the index (e.g. {hidden[0]}): git status hides them and no rescue would carry "
                "them - `git update-index --no-skip-worktree --no-assume-unchanged <path>`, "
                "then sweep again")
    sub = git(tree.path, "submodule", "status", "--recursive")
    if sub.returncode != 0:
        return ("its submodules could not be listed (git submodule status failed), so whether "
                "they hold work its removal would take is unknown")
    subs = set()
    for line in sub.stdout.splitlines():
        parts = line[1:].split()
        if len(parts) >= 2:
            subs.add(os.path.normpath(os.path.join(tree.path, parts[1])))
    for line in sub.stdout.splitlines():
        if not line.strip() or line.startswith("-"):
            continue
        parts = line[1:].split()
        if len(parts) < 2:
            continue
        spath = os.path.join(tree.path, parts[1])
        dirt = git_out(spath, "status", "--porcelain")
        if dirt is None or dirt:
            return (f"submodule {parts[1]} holds uncommitted work (a commit in the tree "
                    "cannot carry it) - commit it there first")
        # ITS IGNORED WORK TOO: an `.env` in a submodule is archived by
        # nobody - the tree's own archive lists the superproject's only.
        extra = ignored_paths(spath)
        if extra is None or extra:
            return (f"submodule {parts[1]} holds ignored files no commit carries "
                    + (f"({extra[0]}{' and more' if len(extra) > 1 else ''})" if extra
                       else "(they could not be listed)")
                    + " - move them out first")
        # ITS BRANCHES, TAGS AND STASH, NOT ONLY ITS HEAD (#170 item 5): a
        # worktree's submodules keep their repositories under its own git
        # directory (`.git/worktrees/<id>/modules/`), and a clone's under
        # `.git/modules/` - either way the removal takes them.
        why = unpublished_ref(spath, "--remotes", "every remote of it")
        if why:
            return (f"submodule {parts[1]}: {why} - push it there first; the tree's removal "
                    "takes the submodule's repository with it")
        sstash = git(spath, "stash", "list")
        if sstash.returncode != 0 or sstash.stdout.strip():
            return (f"submodule {parts[1]} has a stash (or its stash could not be read), which "
                    "the tree's removal would take with it")
        # ITS HIDDEN EDITS TOO (#174, Copilot round 2): a file flagged
        # skip-worktree or assume-unchanged in the submodule reads as clean
        # there exactly as in the superproject (#170 A2).
        shidden = hidden_edits(spath)
        if shidden is None or shidden:
            return (f"submodule {parts[1]}: " + (
                "its skip-worktree and assume-unchanged flags could not be read" if shidden is None
                else f"{len(shidden)} file(s) flagged skip-worktree or assume-unchanged differ from "
                     f"its index (e.g. {shidden[0]})")
                + " - git status hides them and no rescue would carry them")
        # ITS REFLOGS TOO (#174, Copilot round 1): a commit made in the
        # submodule and checked out away from is in no branch, tag or stash -
        # only a reflog under its git directory names it - and the tree's
        # removal takes that directory, which no loss plan of the tree reads.
        why = unpublished_reflog(spath, "--remotes", "every remote of it")
        if why:
            return (f"submodule {parts[1]}: {why} - push it there first; the tree's removal "
                    "takes the submodule's repository with it")
    # EVERY SUBMODULE REPOSITORY ITS GIT DIRECTORY KEEPS (#174, Copilot round
    # 2), a DEINITIALIZED one's included: `git submodule status` shows it `-`,
    # its working tree is gone, and its repository - branches, stash, reflog -
    # stays under `<gitdir>/modules/<name>`, which the removal takes.
    gitdir = git_out(tree.path, "rev-parse", "--absolute-git-dir")
    if not gitdir:
        return "its git directory could not be read, so the submodule repositories it keeps are unknown"
    why = module_repos_why(gitdir)
    if why:
        return why + "; the tree's removal takes that repository with it"
    # A REPOSITORY NESTED ANYWHERE IN IT - a venv's `src/` (`pip install -e
    # git+...`), node_modules and a cache directory included (#170 A3; #174,
    # Copilot round 2) - and a directory that cannot be read keeps the tree.
    nested, unread = nested_repos(tree.path, subs)
    if nested:
        return (f"a repository is nested inside it at {nested[0]} - its work is "
                "no commit of this tree's; move it out first (a lane creates "
                "worktrees, never clones)")
    if unread:
        return (f"{unread[0]} inside it could not be read, so whether a repository is nested "
                "there is unknown")
    return ""


def nested_repos(top: str, exclude: set = frozenset(), bare: bool = False) -> tuple:
    """([every directory under `top` that holds a `.git`], [what could not
    be read]) - `top` itself, `exclude`d paths (a tree's submodules) and
    anything inside a nested repository are not entered. NOTHING ELSE IS
    PASSED OVER (#174, Copilot round 2): this walk is the proof that a removal
    takes no repository, and a checkout under a directory named
    `__pycache__` or `.pytest_cache` is still a checkout. With `bare`, a
    BARE repository (`is_bare_repository`) is found as one too."""
    found, unread = [], []
    stack = [top]
    while stack:
        d = stack.pop()
        try:
            with os.scandir(d) as it:
                entries = list(it)
        except FileNotFoundError:
            continue
        except OSError as exc:
            unread.append(f"{d} ({exc.strerror or exc})")
            continue
        if d != top and (any(e.name == ".git" for e in entries)
                         or (bare and _bare_shaped(entries))):
            found.append(d)
            continue
        for e in entries:
            if e.name == ".git":
                continue
            try:
                if e.is_symlink() or not e.is_dir(follow_symlinks=False):
                    continue
            except OSError:
                continue
            if os.path.normpath(e.path) not in exclude:
                stack.append(e.path)
    return sorted(found), unread


def _bare_shaped(entries: list) -> bool:
    """A directory listing that is a git directory's own: a `HEAD` beside an
    `objects/` - as `module_repos` reads one."""
    names = {e.name for e in entries}
    return "HEAD" in names and "objects" in names


def is_bare_repository(path: str) -> bool:
    """`path` is a git directory with no working tree of its own - a BARE
    repository (`git init --bare`, a mirror, `<x>.git`) - by its shape."""
    try:
        with os.scandir(path) as it:
            return _bare_shaped(list(it))
    except OSError:
        return False


def repository_within(path: str) -> str:
    """Why `path` holds a repository - it is one, or one lies anywhere inside
    it - or that what is inside could not be read; else ''. What a directory
    removal without a commit-level check (a cache, a sandbox) must never
    take. A BARE repository is one (#174 review R2): it has no `.git`, and a
    sandbox holding the only copy of a commit in one was removed as empty."""
    if os.path.lexists(os.path.join(path, ".git")) or is_bare_repository(path):
        return f"it is a repository ({path})"
    nested, unread = nested_repos(path, bare=True)
    if nested:
        return f"a repository lies inside it ({nested[0]})"
    if unread:
        return f"{unread[0]} inside it could not be read, so whether a repository lies there is unknown"
    return ""


def lost_tips(path: str, cands: list, keep: list):
    """The candidate objects at least one commit of whose history no ref or
    object in `keep` (rev-list arguments: `--remotes=origin`, `--branches`, a
    SHA) reaches - what a removal would take that is in no such place. A
    candidate object that is gone (a reflog entry gc took) is already lost
    and is not reported; one that is no commit cannot be proved kept and is.

    AN ANNOTATED TAG IS ITS OWN OBJECT (#174, Copilot round 1): a clone's tag
    reached here as the tag object, was reported lost however it was kept,
    and the self-check refused - and halted the sweep on - every clone with
    one. A tag object is kept where `keep` names it by SHA (a verified
    bundle's head, origin's own tag); its history is its peeled commit's,
    asked like any other. None where git could not answer."""
    want = []
    for c in cands:
        if c and c not in want and not re.match(r"^0+$", c):
            want.append(c)
    if not want:
        return []
    kept_objects = [k for k in keep if SHA_RE.match(k)]
    keep_refs = [k for k in keep if not SHA_RE.match(k)]
    query = want + [f"{k}^{{commit}}" for k in kept_objects]
    check = run(["git", "-C", path, "cat-file", "--batch-check=%(objectname) %(objecttype)"],
                env=git_env(path), input_text="\n".join(query) + "\n")
    lines = check.stdout.splitlines()
    if check.returncode != 0 or len(lines) != len(query):
        return None
    commits, odd, tags = [], [], []
    for sha, line in zip(want, lines[:len(want)]):
        parts = line.split()
        otype = parts[-1] if parts else ""
        if otype == "commit":
            commits.append(sha)
        elif otype == "tag":
            tags.append(sha)
        elif otype != "missing":
            odd.append(sha)
    # A SHA in `keep` counts through the commit it peels to: a tag object a
    # bundle carries keeps that commit's history too.
    keep_commits = [line.split()[0] for line in lines[len(want):]
                    if len(line.split()) == 2 and line.split()[1] == "commit"]
    if tags:
        peel = run(["git", "-C", path, "cat-file", "--batch-check=%(objectname) %(objecttype)"],
                   env=git_env(path), input_text="".join(f"{t}^{{commit}}\n" for t in tags))
        peeled = peel.stdout.splitlines()
        if peel.returncode != 0 or len(peeled) != len(tags):
            return None
        for tag, line in zip(tags, peeled):
            parts = line.split()
            if tag not in kept_objects:
                odd.append(tag)
            if len(parts) == 2 and parts[1] == "commit" and parts[0] not in commits:
                commits.append(parts[0])
    if not commits:
        return odd
    proc = run(["git", "-C", path, "rev-list", *commits, "--not", *keep_refs, *keep_commits],
               env=git_env(path), timeout=GIT_TIMEOUT)
    if proc.returncode != 0:
        return None
    out = set(proc.stdout.split())
    return [c for c in commits if c in out] + odd


def origin_tag_objects(path: str, tags: list) -> list:
    """The annotated tag objects among `tags` origin holds AS THEY ARE (its
    `ls-remote --tags` names that very object) - kept by origin, so not the
    only copy. Where origin cannot be asked, none: such a tag is bundled."""
    if not tags:
        return []
    proc = git(path, "ls-remote", "--tags", "origin", timeout=GIT_NET_TIMEOUT)
    if proc.returncode != 0:
        return []
    have = {line.split("\t", 1)[0] for line in proc.stdout.splitlines() if "\t" in line}
    return [t for t in tags if t in have]


def unpublished_ref(path: str, remotes: str, where: str) -> str:
    """'' when every branch, tag and HEAD of the repository at `path` is in a
    remote-tracking ref `remotes` names (`--remotes=origin`, `--remotes`);
    else which one is not, or that it could not be checked - both keep."""
    proc = git(path, "for-each-ref", "--format=%(refname)%09%(objecttype)%09%(objectname)"
               "%09%(*objecttype)%09%(*objectname)", "refs/heads/", "refs/tags/")
    if proc.returncode != 0:
        return "its branches and tags could not be read"
    tips: dict = {}
    for line in proc.stdout.splitlines():
        cols = line.split("\t")
        if len(cols) < 5:
            continue
        ref, otype, sha, ptype, psha = cols[:5]
        commit = sha if otype == "commit" else (psha if ptype == "commit" else "")
        if not commit:
            return (f"its tag {ref[len('refs/tags/'):]} names no commit, so whether {where} "
                    "holds it cannot be checked")
        tips.setdefault(commit, ref)
    head = git_out(path, "rev-parse", "-q", "--verify", "HEAD^{commit}")
    if head:
        tips.setdefault(head, "HEAD")
    lost = lost_tips(path, list(tips), [remotes])
    if lost is None:
        return f"whether {where} holds its branches and tags could not be read"
    if not lost:
        return ""
    ref = tips.get(lost[0], lost[0])
    for prefix, kind in (("refs/heads/", "branch "), ("refs/tags/", "tag ")):
        if ref.startswith(prefix):
            return f"its {kind}{ref[len(prefix):]} holds commits {where} lacks"
    return f"its {ref} holds commits {where} lacks"


def unpublished_reflog(path: str, remotes: str, where: str) -> str:
    """'' when every commit a reflog of the repository at `path` names is in
    a remote-tracking ref `remotes` names; else how many are not, or that
    the reflogs could not be read - both keep."""
    gitdir = git_out(path, "rev-parse", "--absolute-git-dir")
    if not gitdir:
        return f"its git directory could not be read, so whether {where} holds what its reflogs name is unknown"
    files, why = reflog_files(gitdir)
    if files is None:
        return why
    shas, why = reflog_shas(files)
    if shas is None:
        return why
    lost = lost_tips(path, shas, [remotes])
    if lost is None:
        return f"whether {where} holds the commits its reflogs name could not be read"
    if lost:
        return (f"its reflog names {len(lost)} commit(s) {where} lacks (e.g. {short(lost[0])}), "
                "in no branch, tag or stash")
    return ""


def hidden_edits(path: str):
    """Files flagged skip-worktree or assume-unchanged whose content in the
    tree differs from the index - edits `git status` does not show and `git
    add -A` does not take (#170 A2). A flagged file that is not there (a
    sparse checkout's) is no edit. None where git could not answer."""
    proc = git(path, "ls-files", "-v", "-s", "-z")
    if proc.returncode != 0:
        return None
    files, out = [], []
    for rec in proc.stdout.split("\0"):
        meta, sep, rel = rec.partition("\t")
        cols = meta.split()
        if not sep or len(cols) < 4:
            continue
        tag, mode, sha = cols[0], cols[1], cols[2]
        if not (tag == "S" or tag.islower()):
            continue
        full = os.path.join(path, rel)
        if not os.path.lexists(full) or mode == "160000":
            continue
        if mode == "120000":
            blob = git(path, "cat-file", "blob", sha)
            try:
                same = blob.returncode == 0 and os.readlink(full) == blob.stdout
            except OSError:
                same = False
            if not same:
                out.append(rel)
            continue
        files.append((rel, sha))
    if files:
        proc = run(["git", "-C", path, "hash-object", "--stdin-paths"], env=git_env(),
                   input_text="".join(rel + "\n" for rel, _ in files))
        got = proc.stdout.split()
        if proc.returncode != 0 or len(got) != len(files):
            return None
        out += [rel for (rel, sha), now in zip(files, got) if now != sha]
    return sorted(out)


def ignored_work(tree: Tree):
    """Ignored paths that are NOT caches: a commit cannot carry them, so they
    are archived beside the bundle before the tree goes. None where git could
    not list them - which is never read as "there are none"."""
    return ignored_paths(tree.path)


def ignored_paths(path: str):
    """`ignored_work` for any checkout path - a submodule's included."""
    proc = git(path, "ls-files", "--others", "--ignored", "--exclude-standard",
               "--directory", "-z")
    if proc.returncode != 0:
        return None
    out = []
    for rel in proc.stdout.split("\0"):
        rel = rel.rstrip("/")
        if not rel:
            continue
        # THE ENTRY'S OWN NAME DECIDES, never an ancestor's (#170 A6): with
        # `--directory` an ignored cache or build directory is listed as
        # itself, so a path BENEATH a directory named `build` is an ignored
        # file in a tracked directory - `docker/build/prod.env` - and was
        # dropped unarchived while `top.env` beside it was archived.
        name = os.path.basename(rel)
        if name in CACHE_NAMES:
            continue
        full = os.path.join(path, rel)
        if os.path.isdir(full) and (name in VENV_NAMES
                                    or os.path.exists(os.path.join(full, "pyvenv.cfg"))):
            continue
        if name in BUILD_NAMES or name.endswith(".egg-info"):
            continue
        if rel.endswith((".pyc", ".pyo")):
            continue
        out.append(rel)
    return out


# ================================================== an assembly's legs (T018)
#
# opensoft/openRepoTools#186, plan task T018 - A TRIAD'S CHECKOUTS. A checkout
# is an ASSEMBLY when its own `project.yaml` declares a leg other than itself:
# the standard's own rule, read by `lanes-edit.sh project-legs` - the one
# reader every lane tool asks, whose grammar is `lane-start`'s rung 5 line for
# line - and never here a second time. Its legs (`spec/`, `code/`) are
# submodules: their `.git` is a FILE, `git -C <assembly> worktree list` names
# none of their worktrees, and a feature's paired trees under
# `<assembly>/worktrees/<feature>/{spec,code}` are worktrees of the LEGS'
# repositories, so before this every reader below was blind to them.
#
# A CHECKOUT WITH NO `project.yaml` IS ASKED NOTHING: no helper call, no read,
# no row - which is every single-repository checkout, whose sweep, report and
# `add` are therefore byte for byte what they were.


class Leg:
    """One checkout an assembly mounts: a leg its `project.yaml` declares, or
    a dependency NESTED in one - an initialized submodule of the leg's own
    (`code/upstream/openRepoShape`). It is the ASSEMBLY'S, shared by every
    lane the assembly homes, as the assembly's own checkout is, so a sweep
    NEVER acts on the CHECKOUT: it is read - for the worktrees git registers
    in it and for its own dirty and unpushed state - and counted for this lane
    only where its HEAD's own `Lane:` trailer is this lane's. Its BRANCHES are
    another matter: under `--branches` they are rows like the checkout's own
    (`_branch_checkouts`), and `--yes` deletes a merged lane branch there
    exactly as it does in the checkout, bundling first."""

    def __init__(self, path: str, decl: str, repository: str, assembly: str,
                 nested_in: str = ""):
        self.path = path
        self.real = real(path)
        self.decl = decl                   # its path as declared, under the assembly
        self.repository = repository       # as the manifest names it ('' when nested)
        self.assembly = assembly
        self.nested_in = nested_in         # the leg it is a dependency of
        self.exists = os.path.isdir(path)
        self.checkout = False              # a checkout of its own is there
        self.read_error = ""
        self.branch = ""
        self.detached = False
        self.head = ""
        self.dirty = 0
        self.upstream = ""
        self.ahead = None
        self.on_origin = None
        self.origin_url = ""
        self.claimed = False               # its HEAD's own Lane: trailer is this lane's
        self.disposition = ""
        self.reason = ""
        self.retire = False

    def what(self) -> str:
        if self.nested_in:
            return f"a dependency nested in the {self.nested_in} leg ({self.decl})"
        return (f"the {self.decl} leg of {os.path.join(self.assembly, 'project.yaml')}"
                + (f" ({self.repository})" if self.repository else ""))

    def label(self) -> str:
        if not self.exists or not self.checkout:
            return "(not checked out)" if not self.read_error else "(unreadable)"
        if self.detached:
            return f"(detached) {short(self.head)}"
        if not self.head:
            return f"{self.branch} (unborn)"
        return f"{self.branch} {short(self.head)}"


def project_legs(helper, checkout: str) -> tuple:
    """([(repository, path as declared, where it is checked out)], why-not):
    the legs `<checkout>/project.yaml` declares, through `lanes-edit.sh
    project-legs`. A checkout with no `project.yaml` is asked nothing; a
    manifest that could not be read is never "no legs" (Amendment 7(d))."""
    if not checkout or not os.path.lexists(os.path.join(checkout, "project.yaml")):
        return [], ""
    proc = helper.call("project-legs", checkout)
    if proc.returncode == 8:
        return [], ""
    if proc.returncode != 0:
        return [], (f"`lanes-edit.sh project-legs {checkout}` exited {proc.returncode}: "
                    + _first_line(proc.stderr))
    rows = []
    for line in proc.stdout.splitlines():
        cols = line.split("\t")
        if len(cols) >= 3 and cols[1] and cols[2]:
            rows.append((cols[0], cols[1], cols[2]))
    return rows, ""


def probe_leg(leg: Leg) -> None:
    """Is a checkout of its own there? An uninitialized leg is an empty
    directory of the assembly's, and git answers the ASSEMBLY there - which is
    no checkout of the leg's. A `.git` of its own that git cannot read is a
    read that failed, never an absent leg.

    SO IS A LEG DIRECTORY THIS ACCOUNT CANNOT SEARCH (#255, openRepoShape-2's
    SHOULD on #246): whether a `.git` stands in it cannot be tested, and
    `os.path.lexists` answers False for a path it cannot reach exactly as for
    one that is not there - so the leg read `absent`, counted for nobody, and
    `lane-end` could end a lane over an unpushed commit in it. The `.git` is
    asked of `lstat` itself: only "no such file" (or "not a directory") is a
    leg with no `.git`; any other failure is a read that failed, the same
    `read_error` - `unreadable`, counted - in the words `lane-handoff` and
    `lane-reconcile` use for it. A leg directory that is not there at all is
    still `absent` (`leg.exists`)."""
    if not leg.exists:
        return
    proc = git(leg.path, "rev-parse", "--show-toplevel")
    if proc.returncode == 0 and real(proc.stdout.strip()) == leg.real:
        leg.checkout = True
        return
    said = (_first_line(proc.stderr) if proc.returncode != 0 else
            f"git answers {proc.stdout.strip()} there")
    try:
        os.lstat(os.path.join(leg.path, ".git"))
    except (FileNotFoundError, NotADirectoryError):
        return
    except OSError:
        leg.read_error = ("its directory cannot be searched, so whether it has a .git of its "
                          "own could not be tested, and git could not read it: " + said)
        return
    leg.read_error = "it has a .git of its own and git could not read it: " + said


def probe_uninitialized(leg: Leg) -> None:
    """A dependency `git submodule status` lists as `-`: `read_error` is set
    where its directory, or one above it, cannot be searched, and `checkout`
    where it holds a checkout of its own that git can read (#255).
    `probe_leg` alone cannot see a dependency beneath a directory this
    account cannot search: `Leg.exists` is `os.path.isdir`, which answers
    False for a path it cannot reach exactly as for one that is not there, so
    it would read `absent`. The path is asked of `stat` itself: only "no such
    file" (or "not a directory") is a dependency that is not there, which sets
    nothing; any other failure is a read that failed. A directory that is
    there is `probe_leg`'s: with no search it is unreadable through its
    `lstat` of the `.git`; holding a checkout git can read - a clone made by
    hand, which git does not count as initialized - it is a checkout; empty
    and searchable, it is a dependency never initialized and sets nothing."""
    try:
        os.stat(leg.path)
    except (FileNotFoundError, NotADirectoryError):
        return
    except OSError as err:
        leg.read_error = ("a directory above it cannot be searched, so whether it is there "
                          f"could not be tested: {err.strerror or err}")
        return
    probe_leg(leg)


def assembly_checkouts(helper, assembly: str) -> tuple:
    """([Leg], declared, why-not) - every leg `<assembly>/project.yaml`
    declares, and every dependency nested in one (the submodules
    `git submodule status --recursive` names in each leg: the initialized
    ones, and a `-` line only as below), in that order; `declared` is whether
    the manifest declares any leg at all, which is what makes
    `<assembly>/worktrees` the paired root.

    A DEPENDENCY GIT LISTS AS NOT INITIALIZED (A `-` LINE) IS NOT ALWAYS
    NOTHING TO READ (#255, lane 3's review). Git lists a dependency whose
    directory it cannot search - mode 000, or read and no search, or one
    beneath a directory it cannot search - as `-` too, and exits 0, exactly as
    for one never initialized. Skipped unread, it got no row, the sweep
    exited 0, and `lane-end` ended the lane over that dependency's unpushed
    commit. So a `-` line is probed (`probe_uninitialized`) and gets a row
    where the probe is a read that failed, or where its directory holds a
    checkout git can read - a clone made by hand, which git does not count as
    initialized, and which holds the lane's work all the same: it is read like
    an initialized one. A directory that is not there, or an empty one that
    can be searched, is a dependency never initialized and still adds none.
    The path on a `-` line is the whole of the rest of the line: git prints a
    describe suffix only on a ` ` or `+` line (`print_status` in
    builtin/submodule--helper.c), so none is trimmed from it."""
    rows, why = project_legs(helper, assembly)
    if why:
        return [], False, why
    out = []
    for repository, decl, path in rows:
        leg = Leg(path, decl, repository, assembly)
        probe_leg(leg)
        out.append(leg)
        if not leg.checkout:
            continue
        sub = git(path, "submodule", "status", "--recursive")
        if sub.returncode != 0:
            return out, True, (f"the dependencies nested in the {decl} leg ({path}) could not be "
                               "listed (git submodule status failed): " + _first_line(sub.stderr))
        for line in sub.stdout.splitlines():
            if not line.strip():
                continue
            parts = line[1:].split(" ", 1)
            if len(parts) < 2:
                continue
            rel = parts[1]
            if not line.startswith("-") and rel.endswith(")") and " (" in rel:
                rel = rel[:rel.rindex(" (")]
            nested = Leg(os.path.normpath(os.path.join(path, rel)), f"{decl}/{rel}", "",
                         assembly, nested_in=decl)
            if line.startswith("-"):
                # Declared and not initialized: nothing to read - unless git
                # could not search its directory, which it lists the same way,
                # or a checkout of its own stands there (a hand clone).
                probe_uninitialized(nested)
                if not (nested.read_error or nested.checkout):
                    continue
            else:
                probe_leg(nested)
            out.append(nested)
    return out, bool(rows), ""


def read_leg(leg: Leg, lane: str) -> None:
    """What git says about one leg checkout NOW. A read that failed is said,
    never converted into a clean-looking value (R22, Amendment 7(d))."""
    if not leg.checkout or leg.read_error:
        return
    branch = git(leg.path, "symbolic-ref", "-q", "--short", "HEAD")
    if branch.returncode == 0:
        leg.branch = branch.stdout.strip()
    elif branch.returncode == 1:
        leg.detached = True
    else:
        leg.read_error = "HEAD could not be read: " + _first_line(branch.stderr)
        return
    head = git(leg.path, "rev-parse", "--verify", "-q", "HEAD")
    if head.returncode == 0:
        leg.head = head.stdout.strip()
    elif not leg.branch:
        leg.read_error = "HEAD names no commit"
        return
    status = git(leg.path, "status", "--porcelain=v1", "--ignore-submodules=none",
                 "--untracked-files=normal")
    if status.returncode != 0:
        leg.read_error = "git status failed: " + _first_line(status.stderr)
        return
    leg.dirty = sum(1 for ln in status.stdout.splitlines() if ln.strip())
    leg.origin_url = git_out(leg.path, "config", "--get", "remote.origin.url") or ""
    if leg.head:
        contains = git(leg.path, "for-each-ref", "--count=1", "--contains", leg.head,
                       "--format=%(refname)", "refs/remotes/origin/")
        if contains.returncode != 0:
            leg.read_error = ("whether origin holds its HEAD could not be read: "
                              + _first_line(contains.stderr))
            return
        leg.on_origin = bool(contains.stdout.strip())
    if leg.branch:
        up = git(leg.path, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}")
        leg.upstream = up.stdout.strip() if up.returncode == 0 else ""
        if leg.upstream and leg.head:
            n = git_out(leg.path, "rev-list", "--count", "@{u}..HEAD")
            leg.ahead = int(n) if n and n.isdigit() else None
    leg.claimed = bool(lane) and bool(leg.head) and lane.lower() in own_trailers(leg.path) \
        and head_lane_trailer(leg.path).lower() == lane.lower()


def decide_leg(leg: Leg) -> None:
    """A leg's row. It is COUNTED - the sweep's exit 3, `lane-end`'s gate -
    where its HEAD's own `Lane:` trailer is this lane's and it holds work
    origin lacks or edits no commit carries, or where it could not be read;
    otherwise it is listed and counted for nobody, as the assembly's own
    checkout is. No `--yes` acts on one."""
    what = leg.what()
    if leg.read_error:
        leg.disposition, leg.retire = "unreadable", True
        leg.reason = (f"{what}: git could not be read through it ({leg.read_error}); a read that "
                      "failed is never a clean leg (Amendment 7(d)) - read it by hand: "
                      f"git -C {leg.path} status")
        return
    if not leg.exists or not leg.checkout:
        leg.disposition, leg.retire = "absent", False
        leg.reason = f"{what}: not checked out here, so it holds nothing to read"
        return
    parts = []
    if leg.dirty:
        parts.append(f"{leg.dirty} dirty or untracked path(s)")
    if leg.head and leg.on_origin is False:
        parts.append("its HEAD carries commit(s) no origin ref holds"
                     + (f" ({leg.ahead} ahead of {leg.upstream})" if leg.upstream and leg.ahead
                        else "")
                     + ("" if leg.origin_url else "; it has no origin remote"))
    if not parts:
        leg.disposition, leg.retire = "clean", False
        leg.reason = (f"{what}: clean, and its HEAD is on origin" if leg.head else
                      f"{what}: clean, on an unborn branch with nothing committed")
        return
    leg.disposition = ("dirty+unpushed" if len(parts) == 2 else
                       "dirty" if leg.dirty else "unpushed")
    leg.retire = leg.claimed
    leg.reason = what + ": " + "; ".join(parts) + (
        "; its HEAD's own Lane: trailer is this lane's, so it is COUNTED - commit and push from "
        "inside it: a sweep never acts on a leg checkout, which is the assembly's"
        if leg.claimed else
        "; listed and NOT counted - a leg checkout is the assembly's, shared by every lane it "
        "homes, and its HEAD's own Lane: trailer is not this lane's")


# ============================================================ the sweep

class Options:
    def __init__(self, ns):
        self.yes = bool(ns.yes)
        self.porcelain = bool(ns.porcelain)
        self.bundle = bool(ns.bundle)
        self.branches = bool(ns.branches)
        self.links = bool(ns.links)
        self.scratch = bool(ns.include_scratch)
        self.caches = bool(ns.include_caches)
        self.sandboxes = bool(ns.include_sandboxes)
        self.foreign = bool(ns.include_foreign)
        self.word = ns.word or ""
        self.live = list(ns.live or [])
        self.fetch = bool(ns.fetch) or self.yes
        self.estate = ns.estate or ""
        self.push_untracked = bool(ns.push_untracked)


class Item:
    """One row of the table that is not a tree: a branch, a scratch
    directory, a cache, a sandbox, a broken link."""

    def __init__(self, kind: str, path: str, disposition: str, reason: str,
                 retire: bool = False, detail: str = ""):
        self.kind = kind
        self.path = path
        self.disposition = disposition
        self.reason = reason
        self.retire = retire
        self.detail = detail
        self.size = 0
        self.result = ""
        self.extra: dict = {}


class Sweep:
    def __init__(self, lane: str, opts: Options):
        self.lane = lane
        self.opts = opts
        self.conf = load_conf()
        self.helper = Helper()
        self.facts = LaneFacts(lane)
        self.trees: list = []
        self.items: list = []
        self.refusal = ""
        self.notes: list = []
        self.caller = (os.environ.get("LANES_SESSION") or
                       os.environ.get("CLAUDE_CODE_SESSION_ID") or "")
        self.procs = None
        self.evidence = MergeEvidence(self.helper)
        self.stamp = utc_stamp()
        self.archive = ""
        self.failed = 0
        self.deps: dict = {}
        self.deps_partial: list = []
        self.walk_lane_states: list = []
        self.shape_repos: list = []
        self.estate_root = ""
        self.fetched: dict = {}
        self.rescues: list = []
        self.dispositions: list = []
        self.acting = False
        self.verified: dict = {}           # common dir -> SHAs a push of this run put on origin
        self.bundled: dict = {}            # common dir -> heads of this run's verified bundles
        self.halt = ""                     # why no further act is taken
        self.refused_check = ""            # the self-check's refusal: exit 2
        self.usage = ""                    # a usage error found once the trees are known: 64
        self.legs: list = []               # T018: the assembly's leg checkouts, where it is one
        self.paired_root = ""              # T018: <assembly>/worktrees, where it is one

    # ------------------------------------------------------------- reading
    def gather(self) -> None:
        if not self.helper.path:
            self.refusal = ("no lanes-edit.sh beside this command or on PATH: which trees "
                            "are this lane's is #97's inventory, and nothing else answers it")
            return
        # UNDER --yes THE REGISTER IS FETCHED FIRST, AND A FETCH THAT FAILED IS A
        # REFUSAL (#170 A1): a binding read from a ref as it stood here is not
        # where the lane is bound now, and from outside its binding a lane is
        # UNKNOWN, never dead (Amendment 18(b)). An inherited LANES_NO_FETCH=1
        # does not skip it.
        if self.opts.yes:
            why = self.helper.fetch_register()
            if why:
                self.refusal = ("--yes acts only on the register as origin holds it NOW, and it "
                                f"could not be fetched: {why}. Where the lane is bound is then "
                                "unknown, never 'here' (Amendment 18(b)); nothing was changed")
                return
        # THE CANONICAL SPELLING BEFORE ANY PATH IS DERIVED FROM IT: scratch,
        # caches, rescue names and the Lane: trailer comparison all use it.
        canon, why = self.helper.canon_lane(self.lane)
        if why:
            self.refusal = f"lane {self.lane}'s name could not be resolved: {why}"
            return
        if canon != self.lane:
            self.notes.append(f"lane {self.lane} -> {canon} (Amendment 15: the register row's "
                              "spelling is canonical)")
            self.lane = canon
            self.facts = LaneFacts(canon)
        self.facts = self.helper.lane_facts(self.lane)
        f = self.facts
        if f.why:
            self.refusal = f"a read failed, which is never an answer (Amendment 7(d)): {f.why}"
            return
        if f.managed == "owned":
            self.refusal = (f"lane {self.lane} is owned by the managed ledger (owner "
                            f"{f.managed_owner or 'unnamed'}): its worktrees are that ledger's "
                            "(ruling 2026-10-04: \"managed ledger owns enrolled lanes; #97 owns legacy\")")
            return
        if f.managed == "unknown":
            self.refusal = (f"whether the managed ledger owns lane {self.lane} is UNKNOWN; "
                            f"read it: lanes-edit.sh managed-projection {self.lane}")
            return
        self.estate_root = (self.opts.estate or os.environ.get("PROJECTS_ROOT", "")
                            or (os.path.dirname(f.dir.rstrip("/")) if f.dir else ""))
        # THE GATE DECIDES BEFORE ANYTHING IS WRITTEN - the fetch included. A
        # refused lane is still READ, so the table says what a sweep from the
        # right place would do; it is never fetched or touched from here.
        self._lane_gate()
        self.procs = ProcScan()
        if self.procs.unknown:
            self.notes.append(
                f"{len(self.procs.unknown)} process(es) of this account could not be read in "
                "/proc (" + ", ".join(f"{pid} {comm}" for pid, comm, _h, _a in
                                      self.procs.unknown[:6])
                + (" ..." if len(self.procs.unknown) > 6 else "") + "): a tree their command "
                "line or parent's directory places them in is of unknown liveness and kept")
        self._assembly()
        self._discover()
        self.usage = self._live_usage()
        if self.usage:
            return
        # THE ESTATE'S CHECKOUTS BY ITS SHAPE (a quick walk): beside each one
        # another lane's control root may sit.
        self.shape_repos = estate_repos(self.estate_root) if self.estate_root else []
        # WHO ELSE CLAIMS THESE TREES is read before anything is written: a
        # claim that cannot be read refuses, and a refusal never fetches.
        self._other_lanes()
        self._lane_claims()
        if self.opts.fetch and not self.refusal:
            self._fetch_all()
        for tree in self.trees:
            read_tree(tree)
        for leg in self.legs:
            read_leg(leg, self.lane)
        self._dependents()
        # EVERY CONTROL ROOT THE WHOLE-ESTATE WALK FOUND IS A PLACE ANOTHER LANE'S
        # CLAIM MAY BE (#174, Copilot round 2): `host/vendor/.lane-state`, with
        # `host` a repository and no log naming it, was found by that walk and
        # thrown away - and a clone another lane claims from there was removed.
        if self.walk_lane_states:
            self._other_lanes(self.walk_lane_states)
        for tree in self.trees:
            self._liveness(tree)
            if tree.is_git and tree.exists:
                tree.lean = leaned_on(tree.path, tree.common if tree.kind == "clone" else "",
                                      self.deps)
                if tree.branch and not tree.unborn and tree.branch not in PROTECTED_BRANCHES:
                    tree.merge = self.evidence.judge(tree.path, slug_of(tree.origin_url),
                                                     tree.branch, tree.head)

    def _assembly(self) -> None:
        """T018: WHERE THE LANE'S CHECKOUT IS AN ASSEMBLY, its legs and the
        dependencies nested in them are read, and `<assembly>/worktrees` - the
        paired `worktrees/<feature>/{spec,code}` the standard's three-leg root
        keeps its feature trees in - is a root of the table. A checkout with no
        `project.yaml` is asked nothing. A manifest, or a leg's own submodule
        list, that could not be read refuses: which checkouts the assembly
        holds would be unknown, never none (Amendment 7(d))."""
        d = self.facts.dir
        if not d or not os.path.isdir(d):
            return
        legs, declared, why = assembly_checkouts(self.helper, d)
        self.legs = legs
        if declared:
            self.paired_root = os.path.join(d, "worktrees")
        if why:
            self.refusal = self.refusal or (
                f"the legs of the assembly {d} could not be read, so which checkouts and "
                f"worktrees it holds is unknown (Amendment 7(d)): {why}")

    def _dependents(self) -> None:
        """What leans on the trees. WHERE A REMOVAL WOULD DELETE A DIRECTORY -
        a clone the table could take, or `--include-scratch` - the whole
        estate is walked (#170 A11): every directory but caches, so a clone
        in a plain directory inside a repository is seen, and what the walk
        could not read makes the scan PARTIAL, which removes no such
        directory. A worktree's removal deletes no objects (they are its
        checkout's), so where only worktrees are candidates the estate's
        shape is enough: on Eagle the whole walk is 683,000 directories."""
        whole = self.opts.scratch or any(
            t.exists and t.kind == "clone"
            and (t.owner != "foreign" or (self.opts.foreign and t.foreign_kind != "other-lane"))
            for t in self.trees)
        unread: list = []
        if whole:
            walk = walk_estate(self.estate_root)
            unread += walk.unread
            self.walk_lane_states = walk.lane_states
            self.deps = dependents_map(walk.repos, unread)
            self.deps_partial = unread
        else:
            self.deps = dependents_map(self.shape_repos, unread)
            self.deps_partial = []

    def _live_usage(self) -> str:
        """`--live` names writers' trees, so each path must name one of the
        table's (#170 B4): a misspelt path was accepted in silence, and the
        writer it meant to protect was not protected. `none` stands alone."""
        named = [n for n in self.opts.live if n != "none"]
        if named and len(named) != len(self.opts.live):
            return "--live none says no writer is live; it takes no path beside it"
        for n in named:
            if not any(under(t.path, n) or under(n, t.path) for t in self.trees):
                return (f"--live {n} names no tree of this sweep (misspelt?); the trees are "
                        "the lane's inventory, its roots and its checkout's registrations")
        return ""

    def _lane_claims(self) -> None:
        """A tree #97's inventory does not name is still THIS LANE'S when it
        stands under the lane's own worktree root (`.lane-worktrees/<lane>/`,
        which only this lane's starts make), or sits in the checkout's
        `.claude/worktrees` with its HEAD - a commit its origin's default
        branch lacks - carrying this lane's `Lane:` trailer as its own.
        Another lane's claim still wins. Without this a lane whose trees were
        never recorded read as "nothing to retire", and #163's gate passed it
        (B1).

        AND A LANE WITH NO SNAPSHOT AT ALL (#97's state NONE) BUT TREES OF ITS
        OWN IS REFUSED: which of them are its is recorded nowhere, so the gate
        does not pass it until a handoff records them or a person retires
        them.

        HEAD'S OWN TRAILER, NOT ANY OF THE BRANCH'S (#174 review R1): a tree
        another lane STACKED on this lane's commit carries this lane's trailer
        below its tip, and adopting it on that removed the other lane's tree
        and pushed its in-progress edit to `rescue/<this lane>/...` - and two
        lanes sharing one checkout both adopted it."""
        roots = self._roots()
        lane_root = roots[-1] if roots else ""
        claude_root = roots[0] if self.facts.dir and roots else ""
        for t in self.trees:
            # A CLONE IS NEVER ADOPTED: a lane creates worktrees, never clones,
            # so a standalone clone under its root stays FOREIGN - a finding for
            # a person's word, as #162's first protocol line has it.
            if t.owner != "foreign" or t.foreign_kind in ("other-lane", "clone", "paired-clone"):
                continue
            if lane_root and under(t.path, lane_root) and t.real != real(lane_root):
                t.owner = "lane-root"
            elif claude_root and t.exists and under(t.path, claude_root) \
                    and self.lane.lower() in own_trailers(t.path) \
                    and head_lane_trailer(t.path).lower() == self.lane.lower():
                t.owner = "trailer"
            # T018: THE PAIRED ROOT IS PER FEATURE, NOT PER LANE, exactly as
            # `.claude/worktrees` is per checkout - so a tree there is this
            # lane's by its HEAD's own trailer, the same rule, and by nothing else.
            elif self.paired_root and t.exists and under(t.path, self.paired_root) \
                    and t.real != real(self.paired_root) \
                    and self.lane.lower() in own_trailers(t.path) \
                    and head_lane_trailer(t.path).lower() == self.lane.lower():
                t.owner = "trailer"
        claimed = [t for t in self.trees if t.owner in ("lane-root", "trailer")]
        if self.facts.state == "NONE" and claimed:
            self.refusal = self.refusal or (
                f"lane {self.lane} has no #97 snapshot (state NONE), yet {len(claimed)} tree(s) "
                f"are its by where they stand or what they carry (e.g. {claimed[0].path}); which "
                "are its is recorded nowhere, so the gate does not pass - record them with a "
                "handoff (lane-handoff takes the inventory), or retire them by hand")

    def _roots(self) -> list:
        roots = []
        if self.facts.dir:
            roots.append(os.path.join(self.facts.dir, ".claude", "worktrees"))
            roots.append(os.path.join(os.path.dirname(self.facts.dir.rstrip("/")),
                                      ".lane-worktrees", self.lane))
        elif os.environ.get("PROJECTS_ROOT"):
            roots.append(os.path.join(os.environ["PROJECTS_ROOT"], ".lane-worktrees", self.lane))
        return roots

    def _discover(self) -> None:
        seen: dict = {}
        lane_dir = real(self.facts.dir) if self.facts.dir else ""
        # T018: A LEG CHECKOUT IS NO TREE OF THE TABLE, as the lane's own
        # checkout is none: it is the assembly's, and its own row says what it
        # holds. A record naming one is that row's, never a tree to remove.
        leg_by_real = {g.real: g for g in self.legs if g.checkout}
        for rec in self.facts.inventory:
            path = rec.get("path") or ""
            if not path or rec.get("schema") not in ("1",):
                # A RECORD THAT CANNOT BE READ IS A TREE WHOSE OWNER IS UNKNOWN:
                # skipping it would let the table say "nothing to retire", and
                # --include-foreign find the same path as nobody's.
                self.refusal = self.refusal or (
                    f"inventory record {rec.get('id') or '?'} is unreadable or of schema "
                    f"{rec.get('schema') or '?'}, so which trees are this lane's is not known "
                    "(Amendment 7(d)); read it: lanes-edit.sh lane-trees " + self.lane)
                continue
            if lane_dir and real(path) == lane_dir:
                continue
            if leg_by_real and real(path) in leg_by_real:
                continue
            t = Tree(path, "inventory")
            t.sidecar = rec
            t.checkout = rec.get("checkout") if rec.get("checkout") not in ("", "unknown") else ""
            seen[t.real] = t
        checkouts = []
        # A RECORDED DIRECTORY THAT IS NO REPOSITORY HAS NO REGISTRATIONS, as it
        # has no branches (#163, `_branch_checkouts`): an absence, never the
        # failed `git worktree list` that #170 G5 refuses.
        if self.facts.dir and os.path.isdir(self.facts.dir) and not no_repository(self.facts.dir):
            checkouts.append(self.facts.dir)
        for t in list(seen.values()):
            if t.checkout and os.path.isdir(t.checkout) and t.checkout not in checkouts:
                checkouts.append(t.checkout)
        # T018: EVERY LEG'S REGISTRATIONS TOO, LAST, so a tree registered in a
        # leg's repository is the leg's: `git -C <assembly> worktree list`
        # names none of them, and a code tree recorded under any other checkout
        # was the only one a sweep ever saw.
        for g in self.legs:
            if g.checkout and g.real not in [real(c) for c in checkouts]:
                checkouts.append(g.path)
        regs_by_real: dict = {}
        for co in checkouts:
            regs = worktree_registrations(co)
            if regs is None:
                self.refusal = self.refusal or (
                    f"the worktree registrations of {co} could not be read (git worktree list "
                    "failed), so which trees it holds is unknown (#170 G5, Amendment 7(d))")
                continue
            for i, reg in enumerate(regs):
                if i == 0 or reg.get("bare"):
                    continue          # the main worktree is the checkout itself
                regs_by_real[real(reg["path"])] = (co, reg)
        for rp, (co, reg) in regs_by_real.items():
            t = seen.get(rp)
            if t is None:
                t = Tree(reg["path"], "foreign",
                         "leg" if real(co) in leg_by_real else "registered")
                seen[t.real] = t
            t.leg = leg_by_real.get(real(co))
            t.registered = True
            t.checkout = t.checkout or co
            t.locked = bool(reg.get("locked"))
            t.reg = reg
        for root in self._roots():
            try:
                names = sorted(os.listdir(root))
            except (FileNotFoundError, NotADirectoryError):
                continue
            except OSError as exc:
                # A ROOT THAT CANNOT BE LISTED IS NOT AN ABSENT ROOT (#170 G1): read
                # as one, an empty inventory let the dry run exit 0 and clear #163's
                # gate over trees nobody could see.
                self.refusal = self.refusal or (
                    f"the lane's worktree root {root} could not be listed "
                    f"({exc.strerror or exc}), so which trees stand in it is unknown")
                continue
            for name in names:
                full = os.path.join(root, name)
                if not os.path.isdir(full) or os.path.islink(full):
                    continue
                if real(full) in seen:
                    continue
                if not os.path.lexists(os.path.join(full, ".git")):
                    continue
                kind = "clone" if os.path.isdir(os.path.join(full, ".git")) else "lane-root"
                seen[real(full)] = Tree(full, "foreign", kind)
        if self.paired_root:
            self._paired(seen)
        self.trees = sorted(seen.values(), key=lambda t: (t.owner != "inventory", t.path))

    def _paired(self, seen: dict) -> None:
        """T018: THE ASSEMBLY'S PAIRED FEATURE TREES, `<assembly>/worktrees/
        <feature>/<each declared leg path>` (and `<feature>` itself), that no
        registration above named - a registration lost, or a clone where a
        worktree belongs. A directory that cannot be listed refuses, as a lane
        root does (#170 G1): an unread root is never an empty one. One that can
        be listed and not SEARCHED is unread too - what stands in it cannot be
        told - and so is a leg's directory under a feature that cannot be
        searched: `lane-reconcile` and `lane-handoff` say the same of each."""
        root = self.paired_root
        try:
            names = sorted(os.listdir(root))
            unsearchable = not os.access(root, os.X_OK)
        except (FileNotFoundError, NotADirectoryError):
            return
        except OSError as exc:
            self.refusal = self.refusal or (
                f"the assembly's worktree root {root} could not be listed "
                f"({exc.strerror or exc}), so which feature trees stand in it is unknown")
            return
        if unsearchable:
            self.refusal = self.refusal or (
                f"the assembly's worktree root {root} could not be listed (it cannot be "
                "searched), so which feature trees stand in it is unknown")
            return
        decls = [g.decl for g in self.legs if not g.nested_in]
        for name in names:
            fdir = os.path.join(root, name)
            if not os.path.isdir(fdir) or os.path.islink(fdir):
                continue
            try:
                os.listdir(fdir)
                why = "" if os.access(fdir, os.X_OK) else "it cannot be searched"
            except OSError as exc:
                why = exc.strerror or str(exc)
            if why:
                self.refusal = self.refusal or (
                    f"the assembly's feature directory {fdir} could not be listed "
                    f"({why}), so which trees stand in it is unknown")
                continue
            for full in [fdir] + [os.path.join(fdir, d) for d in decls]:
                if not os.path.isdir(full) or os.path.islink(full) or real(full) in seen:
                    continue
                if not os.access(full, os.X_OK):
                    self.refusal = self.refusal or (
                        f"the assembly's paired tree directory {full} could not be searched, "
                        "so whether a tree stands in it is unknown")
                    continue
                if not os.path.lexists(os.path.join(full, ".git")):
                    continue
                kind = ("paired-clone" if os.path.isdir(os.path.join(full, ".git"))
                        else "paired")
                seen[real(full)] = Tree(full, "foreign", kind)

    def _other_lanes(self, extra: list = ()) -> None:
        """A FOREIGN tree another lane's inventory names is THAT lane's.

        EVERY LANE'S CONTROL ROOT IS READ, wherever #97 keeps it (#170 G6): a
        lane whose checkout is nested (`<estate>/group/repo`) keeps its
        `.lane-state` beside that checkout, not beside this lane's, so the
        parents read are #97's own rungs for every lane - the override, the
        parent of every lane's recorded `dir` in the register, `$PROJECTS_ROOT`
        - beside this lane's own and beside every checkout of the estate. A
        register that cannot be read refuses: whose claims were missed would
        be unknown.

        ONLY THIS SWEEP'S ESTATE, AND NEVER A REFUSAL WITH NO TREE (#174 review
        R3): the register names every lane on the workstation, so one pathless
        or unreadable sidecar in ANY estate made the sweep exit 2 for a lane
        with nothing on disk - every lane-end blocked. A recorded `dir` outside
        the estate root is not read, and with no tree a claim can take away
        nothing, so one that cannot be read is a note."""
        parents = set()
        if self.facts.root:
            parents.add(os.path.dirname(self.facts.root.rstrip("/")))
        if self.facts.dir:
            parents.add(os.path.join(os.path.dirname(self.facts.dir.rstrip("/")), ".lane-state"))
        if os.environ.get("LANES_LANE_STATE_ROOT"):
            parents.add(os.environ["LANES_LANE_STATE_ROOT"])
        if os.environ.get("PROJECTS_ROOT"):
            parents.add(os.path.join(os.environ["PROJECTS_ROOT"], ".lane-state"))
        parents.update(os.path.join(os.path.dirname(r.rstrip("/")), ".lane-state")
                       for r in self.shape_repos)
        parents.update(extra)
        claimed: dict = {}

        def unreadable(what: str) -> None:
            # A CLAIM THAT CANNOT BE READ IS NOT "NO CLAIM" (Amendment 7(d)) -
            # but a claim only takes a tree from this lane, so with no tree it
            # can lower nothing: a note then, never a refusal (#174 review R3).
            if not self.trees:
                text = (f"{what} could not be read; this lane has no tree for another lane's "
                        "claim to take, so the sweep goes on - read it, or move it aside if it "
                        "is debris")
                if text not in self.notes:
                    self.notes.append(text)
                return
            self.refusal = self.refusal or (
                f"{what} could not be read, so whether another lane claims these trees is "
                "unknown; read it, or move it aside if it is debris")

        dirs, why = register_lane_dirs(self.helper)
        if why:
            unreadable(f"every lane's recorded directory ({why})")
        for other, odir in dirs.items():
            if other.lower() == self.lane.lower():
                continue
            parent = os.path.join(os.path.dirname(odir.rstrip("/")), ".lane-state")
            # THIS SWEEP'S ESTATE ONLY (#174 review R3): a lane recorded in
            # another estate keeps its claims there.
            if self.estate_root and not under(parent, self.estate_root):
                continue
            parents.add(parent)
        for parent in sorted(parents):
            try:
                lanes = os.listdir(parent)
            except FileNotFoundError:
                continue
            except OSError:
                unreadable(f"the lane-state directory {parent}")
                continue
            for other in lanes:
                if other.lower() == self.lane.lower():
                    continue
                tdir = os.path.join(parent, other, "trees")
                try:
                    files = os.listdir(tdir)
                except (FileNotFoundError, NotADirectoryError):
                    continue          # that lane has no inventory: no claim
                except OSError:
                    unreadable(f"lane {other}'s inventory {tdir}")
                    continue
                for fname in files:
                    if fname.startswith(".") or not fname.endswith(".yaml"):
                        continue
                    path = ""
                    try:
                        with open(os.path.join(tdir, fname), encoding="utf-8", errors="surrogateescape") as fh:
                            for line in fh:
                                if line.startswith("path: "):
                                    path = line[6:].strip()
                                    break
                    except (OSError, UnicodeDecodeError):
                        unreadable(f"lane {other}'s inventory record {fname}")
                        continue
                    if not path:
                        unreadable(f"lane {other}'s inventory record {fname} (it names no path)")
                        continue
                    claimed[real(path)] = other
        for tree in self.trees:
            if tree.real not in claimed:
                continue
            tree.other_lane = claimed[tree.real]
            if tree.owner == "foreign":
                tree.foreign_kind = "other-lane"
            else:
                # BOTH INVENTORIES NAME IT: records are history and a path can
                # be reused, so which lane owns it now is not this sweep's to
                # decide.
                tree.contested = True

    def _branch_checkouts(self) -> list:
        checkouts = []
        # A RECORDED DIRECTORY THAT IS NO REPOSITORY HAS NO BRANCHES (#163): it
        # is an absence, not a read that failed - and as a `keep` row it held
        # `lane-end`'s gate shut for every lane started outside a checkout.
        if self.facts.dir and os.path.isdir(self.facts.dir) and not no_repository(self.facts.dir):
            checkouts.append(self.facts.dir)
        for t in self.trees:
            if t.owner == "inventory" and t.checkout and os.path.isdir(t.checkout) \
                    and real(t.checkout) not in [real(c) for c in checkouts]:
                checkouts.append(t.checkout)
        # T018: AND EVERY LEG'S, where the lane's checkout is an assembly: after
        # the split a lane's branches are made in its code leg, not in the root.
        for g in self.legs:
            if g.checkout and g.real not in [real(c) for c in checkouts]:
                checkouts.append(g.path)
        return checkouts

    def _fetch_all(self) -> None:
        commons: dict = {}
        probes = [t.path if t.exists else t.checkout for t in self.trees]
        if self.opts.branches:
            probes += self._branch_checkouts()
        probes += [g.path for g in self.legs if g.checkout]      # T018
        for probe in probes:
            if not probe or not os.path.isdir(probe):
                continue
            common = git_out(probe, "rev-parse", "--git-common-dir")
            if common is None:
                continue
            if not os.path.isabs(common):
                common = os.path.normpath(os.path.join(probe, common))
            commons.setdefault(real(common), probe)
        for common, probe in commons.items():
            if not git_out(probe, "config", "--get", "remote.origin.url"):
                self.fetched[common] = "no origin remote"
                continue
            # THE REFSPEC IS PINNED, as `status --fetch` pins it (#170 item 6):
            # a mirror-style `remote.origin.fetch` (`+refs/heads/*:refs/heads/*`)
            # would let `--prune` delete local-only branches before they were
            # classified or rescued. `--refmap` keeps the configured refspec from
            # mapping anything, and neither `fetch.pruneTags` nor the remote's
            # `pruneTags` may take a local tag with it.
            proc = git(probe, "-c", "fetch.pruneTags=false", "-c", "remote.origin.pruneTags=false",
                       "fetch", "--prune", "--quiet", "--recurse-submodules=no",
                       "--refmap=+refs/heads/*:refs/remotes/origin/*", "origin",
                       "+refs/heads/*:refs/remotes/origin/*", timeout=GIT_NET_TIMEOUT)
            self.fetched[common] = "ok" if proc.returncode == 0 else (
                "fetch failed: " + _first_line(proc.stderr))

    def _liveness(self, tree: Tree) -> None:
        if not tree.exists:
            return
        for named in self.opts.live:
            if named != "none" and (under(tree.path, named) or under(named, tree.path)):
                tree.live = (True, "the coordinator's writer count names it (--live)")
                return
        try:
            here = os.getcwd()
        except OSError:
            here = ""
        if here and under(here, tree.path):
            tree.live = (True, "this sweep was started from inside it")
            return
        if self.procs is not None and self.procs.available:
            holders = self.procs.holders(tree.path)
            if holders:
                pid, comm, kind, where = holders[0]
                tree.live = (True, f"process {pid} ({comm or '?'}) has its {kind} there")
                return
            hidden = self.procs.unknown_for(tree.path)
            if hidden:
                tree.live = (None, "liveness could not be read: " + hidden)
                return
        elif self.procs is not None:
            tree.live = (None, "liveness could not be read: " + self.procs.why)
            return
        writer = (tree.sidecar or {}).get("writer", "")
        if writer and UUID_RE.match(writer) and writer != self.caller:
            answer = self.helper.transcript_live(writer)
            if answer == "live":
                tree.live = (True, f"the session that recorded it ({writer[:8]}) is live")
                return
            if answer == "unknown":
                tree.live = (None, f"whether session {writer[:8]} is live could not be read")
                return
        if tree.owner != "inventory" and tree.is_git:
            quiet = self.conf["foreign_quiet_hours"] * 3600
            newest = _gitdir_activity(tree.path)
            if newest and time.time() - newest < quiet:
                tree.live = (True, ("FOREIGN" if tree.owner == "foreign" else
                                    "not in the inventory")
                             + f" and active {int((time.time() - newest) // 60)} min ago "
                                   f"(foreign trees quiet for {self.conf['foreign_quiet_hours']} h only)")

    def _lane_gate(self) -> None:
        f = self.facts
        if f.binding == "elsewhere":
            self.refusal = (f"lane {self.lane} is bound on {f.binding_at or 'another place'}, and "
                            "liveness is pronounced only from inside that binding (Amendment "
                            "18(b)): from here it is UNKNOWN, never dead. Sweep it from there, or "
                            "ask it to hand off: lane-start --request-handoff")
            return
        if f.binding == "unknown":
            self.refusal = (f"where lane {self.lane} is bound could not be read, and a read that "
                            "failed is never an answer (Amendment 7(d))")
            return
        if f.holder == "unknown":
            self.refusal = (f"whether a live session holds lane {self.lane} could not be read "
                            f"({f.holder_detail or 'the session records'}); that is not 'none'")
            return
        if f.holder == "live" and f.holder_uuid != self.caller:
            self.refusal = (f"lane {self.lane} is held by live session {f.holder_uuid or '?'} "
                            f"({f.holder_detail}); sweep it from inside that session, or after "
                            "it hands off")
            return
        if self.opts.yes and f.holder == "live" and f.state == "SWAPPING":
            self.refusal = (f"lane {self.lane} is SWAPPING in a live session: a handoff is in "
                            "flight and a sweep does not compete with it")
            return
        if self.opts.yes and f.holder == "live" and not self.opts.live:
            self.refusal = (f"lane {self.lane} is held by THIS session, and only its coordinator "
                            "knows which writers are live (ListAgents; Amendment 17 Addendum 1 "
                            "(i)). State the count: --live <worktree> for each live writer, or "
                            "--live none")
            return
        # A TREE THIS SESSION RECORDED IS ONE ITS OWN WRITERS MAY STAND IN (#170
        # B5): their liveness is never asked of the transcript - it is this
        # one - so the coordinator's count is wanted whatever the holder reads.
        mine = [r for r in f.inventory if self.caller and r.get("writer") == self.caller]
        if self.opts.yes and mine and not self.opts.live:
            self.refusal = (f"this session recorded {len(mine)} of lane {self.lane}'s trees (e.g. "
                            f"{mine[0].get('path') or '?'}), so its own writers may be in them, "
                            "and only its coordinator knows which are live (ListAgents; "
                            "Amendment 17 Addendum 1 (i)). State the count: --live <worktree> "
                            "for each live writer, or --live none")
            return
        if f.verdict == "indeterminate":
            self.refusal = (f"#97's reconciliation of lane {self.lane} is INDETERMINATE: "
                            + (f.verdict_why or "a read it rests on failed"))
            return
        if self.opts.yes and self.opts.foreign and not self.opts.word:
            self.refusal = ("--include-foreign acts on trees #97's inventory does not give to "
                            "this lane, and that takes a word: --word \"<the words, verbatim>\"")
            return

    # ---------------------------------------------------------- deciding
    def decide(self) -> None:
        for leg in self.legs:
            decide_leg(leg)
        for tree in self.trees:
            self._decide(tree)
            if tree.owner in ("lane-root", "trailer") and tree.disposition != "foreign":
                tree.reason += ("; not in #97's inventory - the lane's by "
                                + ("its root" if tree.owner == "lane-root" else
                                   "its branch's Lane: trailer"))
        if self.opts.branches:
            self._branches()
        if self.opts.scratch:
            self._scratch()
        if self.opts.caches:
            self._caches()
        if self.opts.sandboxes:
            self.items.extend(sandbox_items(self.procs or ProcScan(), self.conf))
        if self.opts.links and self.estate_root:
            self.items.extend(link_items(self.estate_root))

    def _decide(self, t: Tree) -> None:
        mine = t.owner != "foreign" or (self.opts.foreign and t.foreign_kind != "other-lane")

        def put(disp: str, why: str, retire: bool = True) -> None:
            t.disposition, t.reason, t.retire = disp, why, retire

        if t.owner == "foreign" and not mine:
            where = {"registered": "registered in the lane's checkout",
                     "lane-root": "under this lane's worktree root",
                     "clone": "a standalone CLONE under this lane's root (a lane creates "
                              "worktrees, never clones)",
                     "other-lane": f"lane {t.other_lane}'s inventory names it"}.get(t.foreign_kind, "")
            if t.foreign_kind in ("leg", "paired", "paired-clone"):
                where = self._leg_where(t)
            put("foreign", f"not in lane {self.lane}'s inventory (#97); {where}", False)
            if t.lean:
                t.reason += "; LOAD-BEARING: " + _lean_text(t.lean)
            return
        if t.contested:
            put("keep", f"lane {t.other_lane}'s inventory names it too; which lane owns it "
                        "now is a person's to say")
            return
        if not t.exists:
            if t.registered:
                if t.locked:
                    put("keep", "its directory is gone and the registration is LOCKED; "
                                f"git -C {t.checkout} worktree unlock {t.path} first")
                    return
                # ITS GIT DIRECTORY MAY KEEP SUBMODULE REPOSITORIES (#174, Copilot
                # round 2): `<admin>/modules/<name>`, with branches, a stash or a
                # reflog no remote holds, goes with the registration.
                admin = worktree_admin(t.checkout, t.path) if t.checkout else ""
                mod_why = module_repos_why(admin) if admin else ""
                if mod_why:
                    put("keep", "its directory is gone, and " + mod_why + "; pruning the "
                                "registration would take that repository with it")
                    return
                # THE REGISTRATION MAY BE THE ONLY POINTER TO ITS COMMITS (#170
                # A4): a detached HEAD, or a reflog entry, that no branch, tag or
                # origin holds is lost with the registration's git directory.
                try:
                    lost = self._lost(t)
                except _ActFailed as exc:
                    put("keep", "its directory is gone, and " + str(exc).replace(
                        "; left in place", ""))
                    return
                if lost:
                    put("rescue+prune", f"the directory is gone and git still registers it; its "
                                        f"registration names {len(lost)} commit(s) no branch, tag "
                                        "or origin holds (its HEAD or reflog): rescued - a rescue "
                                        "branch pushed where there is an origin, and a bundle - "
                                        "then that one registration pruned")
                else:
                    put("prune", "the directory is gone and git still registers it")
            else:
                put("gone", "the directory is gone and nothing registers it; the inventory "
                            "record is history", False)
            return
        if t.live is not None:
            if t.live[0]:
                put("live", "a live writer owns it: " + t.live[1])
            else:
                put("keep", t.live[1])
            return
        if not t.is_git:
            put("keep", "the path exists and is no checkout; it is left exactly as it is")
            return
        if t.read_error:
            put("keep", "git could not be read through it: " + t.read_error)
            return
        t.guard = tree_guards(t)
        if t.guard:
            put("keep", t.guard)
            return
        if t.lean:
            put("load-bearing", "LOAD-BEARING: " + _lean_text(t.lean)
                + "; remedy: in each dependent `git repack -a -d` then drop its "
                  "objects/info/alternates, or re-point the local remote - then sweep again")
            return
        if t.kind == "clone":
            partial = self._deps_partial(t.path)
            if partial:
                put("keep", "it is a CLONE, removed by deleting its directory, and the search for "
                            "repositories that lean on it (alternates, a local remote) was "
                            "partial: " + _first_few(partial)
                            + " - a dependent nobody saw would lose its objects")
                return
        if self.opts.fetch:
            state = self.fetched.get(real(t.common), "")
            if state and state not in ("ok", "no origin remote"):
                put("keep", f"origin could not be read ({state}), so what is published is unknown")
                return
        if not t.origin_url:
            if self.opts.bundle:
                put("bundle+remove", "no origin remote: a bundle is its only rescue")
            else:
                put("keep", "no origin remote, so nothing in it can be published; --bundle "
                            "archives it instead")
            return
        dirty = len(t.status_lines)
        if dirty:
            untracked = sum(1 for ln in t.status_lines if ln.startswith("?? "))
            if untracked and not self.opts.push_untracked:
                files = untracked_files(t.path)
                why = (lfs_untracked_why(t.path, files) if files is not None else
                       "its untracked files could not be listed, so whether git-lfs content "
                       "is among them is unknown" if lfs_configured(t.path) else "")
                if why:
                    put("keep", why)
                    return
            put("wip-rescue+remove", f"{dirty} dirty or untracked path(s): a WIP commit on "
                                     "a rescue branch, pushed, then removed"
                + (f"; its {untracked} untracked path(s) go to the bundle only, never pushed "
                   "(--push-untracked pushes them)" if untracked and not self.opts.push_untracked
                   else ""))
            return
        if t.unborn:
            put("remove", "an unborn branch with nothing in it")
            return
        if t.detached:
            if t.on_origin:
                put("remove", "detached at a commit origin holds")
            elif t.on_origin is None:
                put("keep", "detached, and whether origin holds the commit could not be read")
            else:
                put("rescue+remove", "detached with commits of its own: a rescue branch, pushed, "
                                     "then removed")
            return
        m = t.merge or {}
        if m.get("state") == "merged":
            put("remove+delete-branch", m["why"] + "; the local branch is deleted, and the "
                "remote one where it is this lane's and holds nothing the PR did not")
            return
        if t.remote_tip and t.ahead == 0:
            put("remove", "clean, and every commit is on origin/" + t.remote_branch
                + (f" ({m.get('why')})" if m.get("state") in ("open", "closed", "moved",
                                                                "elsewhere") else ""))
            return
        if not t.remote_tip and t.on_origin:
            put("remove", "clean, and its head is on origin")
            return
        if t.branch in PROTECTED_BRANCHES or (t.remote_tip and t.behind > 0):
            why = ("a default branch is never pushed by a sweep" if t.branch in PROTECTED_BRANCHES
                   else f"origin/{t.remote_branch} has {t.behind} commit(s) it lacks (diverged)")
            put("rescue+remove", f"{t.ahead or 'unpublished'} commit(s) not on origin and {why}: "
                                 "a rescue branch, pushed, then removed")
            return
        # A LANE'S UNREVIEWED COMMITS NEVER GO ONTO SOMEBODY'S BRANCH AS IS
        # (#170 A7): not onto one an open pull request names (a teammate's
        # under review), not onto one whose tip carries another lane's
        # `Lane:` trailer, and not where whether a PR names it could not be
        # read. They go to a rescue branch of this lane's instead.
        if t.remote_tip:
            other = t.remote_trailer if (t.remote_trailer and
                                         t.remote_trailer.lower() != self.lane.lower()) else ""
            # GH'S OPEN WINS HERE WHATEVER THE REGISTER SAYS (#174, Copilot round
            # 2): a LANDED line naming a PR gh still answers OPEN made the verdict
            # "moved", and the lane's commits were pushed onto that open branch.
            gh_open = m.get("gh_open") or []
            why = (m["why"] + ", and a lane's unreviewed commits never go onto an open pull "
                   "request's branch" if m.get("state") == "open" else
                   f"gh still answers PR #{gh_open[0]} OPEN for it, whatever the register's "
                   "LANDED line says, and a lane's unreviewed commits never go onto an open pull "
                   "request's branch" if gh_open else
                   f"origin/{t.remote_branch} carries lane {other}'s work (its tip's Lane: "
                   "trailer)" if other else
                   f"whether an open pull request names origin/{t.remote_branch} is unknown "
                   f"({m.get('why', 'not read')})" if m.get("state") == "unknown" else "")
            if why:
                put("rescue+remove", f"{t.ahead} commit(s) not on origin/{t.remote_branch}, and "
                                     f"{why}: a rescue branch, pushed, then removed - "
                                     f"origin/{t.remote_branch} is left as it is")
                return
        put("push+remove", (f"{t.ahead} commit(s) not on origin/{t.remote_branch}"
                            if t.remote_tip else f"origin has no {t.remote_branch}")
            + ": pushed as is, then removed")

    def _leg_where(self, t: Tree) -> str:
        """T018: where a tree of an assembly's legs stands, for its FOREIGN row."""
        paired = bool(self.paired_root) and under(t.path, self.paired_root)
        if t.foreign_kind == "paired-clone":
            return ("a standalone CLONE in the assembly's paired feature worktrees "
                    f"({self.paired_root}); a feature's trees are worktrees of its legs")
        if paired:
            return (f"in the assembly's paired feature worktrees ({self.paired_root})"
                    + (f", registered in {t.leg.what()}" if t.leg else "")
                    + "; its HEAD carries no Lane: trailer of this lane's as its own")
        return (f"registered in {t.leg.what() if t.leg else 'a leg of the assembly'}, outside "
                "every root a lane's trees are claimed in")

    def _branches(self) -> None:
        leg_by_real = {g.real: g for g in self.legs if g.checkout}
        for co in self._branch_checkouts():
            items = branch_items(co, self.lane, self.evidence)
            leg = leg_by_real.get(real(co))
            if leg is not None:
                # T018: WHICH CHECKOUT, since a branch name says nothing of it.
                for it in items:
                    it.detail = f"in {leg.what()}" + (f" · {it.detail}" if it.detail else "")
            if self.opts.fetch:
                common = git_out(co, "rev-parse", "--git-common-dir") or ""
                if common and not os.path.isabs(common):
                    common = os.path.normpath(os.path.join(co, common))
                state = self.fetched.get(real(common), "") if common else ""
                if state != "ok":
                    # A DELETE DECIDED ON STALE REFS IS NO DELETE: what origin
                    # holds now was not read.
                    for it in items:
                        if it.disposition in ("delete", "delete+remote"):
                            it.disposition = "keep"
                            it.reason = (f"origin could not be fetched in {co} "
                                         f"({state or 'not fetched'}); " + it.reason)
            self.items.extend(items)

    def _scratch(self) -> None:
        if not self.facts.dir:
            return
        root = os.path.join(os.path.dirname(self.facts.dir.rstrip("/")), ".lane-worktrees",
                            self.lane)
        try:
            names = sorted(os.listdir(root))
        except OSError:
            return
        for name in names:
            full = os.path.join(root, name)
            if not (name.endswith("-scratch") or name in ("briefs", "bin")):
                continue
            if os.path.islink(full) or not os.path.isdir(full):
                continue
            if os.path.lexists(os.path.join(full, ".git")):
                continue          # a checkout is a tree, and the table above has it
            holders = self.procs.holders(full) if self.procs and self.procs.available else []
            lean = leaned_on(full, "", self.deps)
            inner = self._trees_inside(full)
            if inner:
                # TREES UNDER IT ARE THE TABLE'S FIRST (#170 item 7): an archive
                # and an rmtree of the scratch would take a worktree nested in it
                # - FOREIGN, live or the lane's own - with its git directory.
                it = Item("scratch", full, "keep", f"{inner}: the tree table retires what is "
                          "under it first, and the scratch goes on a later sweep", True)
            elif lean:
                it = Item("scratch", full, "load-bearing", "LOAD-BEARING: " + _lean_text(lean)
                          + "; it is never removed until they are re-pointed", True)
            elif holders:
                pid, comm, kind, _w = holders[0]
                it = Item("scratch", full, "live", f"process {pid} ({comm}) has its {kind} there",
                          True)
            elif self.procs is not None and not self.procs.available:
                it = Item("scratch", full, "keep", "liveness could not be read: " + self.procs.why,
                          True)
            elif self.procs is not None and self.procs.unknown_for(full):
                it = Item("scratch", full, "keep", "liveness could not be read: "
                          + self.procs.unknown_for(full), True)
            elif self._deps_partial(full):
                it = Item("scratch", full, "keep", "it is removed by deleting its directory, and "
                          "the search for repositories that lean on it was partial: "
                          + _first_few(self._deps_partial(full)), True)
            else:
                it = Item("scratch", full, "archive+remove",
                          "lane scratch: tar and sha256 under the sweeps directory, then removed",
                          True)
            it.size = dir_size(full)
            self.items.append(it)

    def _caches(self) -> None:
        roots = []
        if self.facts.dir:
            roots.append(os.path.join(os.path.dirname(self.facts.dir.rstrip("/")),
                                      ".lane-worktrees", self.lane))
        for t in self.trees:
            if t.owner == "inventory" and t.exists and t.is_git \
                    and not any(under(t.path, r) for r in roots):
                roots.append(t.path)
        # NEVER FROM A TREE THAT IS NOT THIS LANE'S OR NOT PROVABLY FREE: a
        # FOREIGN tree (whoever's it is, live or not), a live one, one whose
        # liveness could not be read, one another lane also claims, one
        # something leans on. A tree kept for its git state (a locked
        # registration, a submodule's work) still gives up its caches: they
        # hold no work, and its liveness is asked below.
        protected = [t.path for t in self.trees if self._cache_protected(t)]
        # A CACHE INSIDE SCRATCH THAT IS BEING ARCHIVED GOES WITH THE SCRATCH
        # (and the archive leaves caches out): two acts on one path is one too many.
        archived = [i.path for i in self.items if i.kind == "scratch"
                    and i.disposition == "archive+remove"]
        trees = sorted((t.path for t in self.trees if t.exists), key=len, reverse=True)
        for root in roots:
            for path, size in find_caches(root):
                if any(under(path, lp) for lp in protected + archived):
                    continue
                owner = self._cache_owner(path, roots, trees)
                # LIVENESS IS THE OWNER'S, not the cache's: a writer stands in
                # the tree, not in its __pycache__.
                if self.procs is None or not self.procs.available or self.procs.holders(owner) \
                        or self.procs.unknown_for(owner):
                    continue
                if tracked(path) is not False:
                    continue          # tracked, or its tracking is unknown
                # A CACHE THAT HOLDS A REPOSITORY HOLDS WORK (#174, Copilot round
                # 1): `pip install -e git+...` clones into `<venv>/src/<pkg>`, and
                # the tree kept for that clone (#170 A3) still gave up its venv -
                # the clone and its unpushed fix with it.
                inner = repository_within(path)
                if inner:
                    it = Item("cache", path, "keep", f"{inner}: its work is no cache's, and the "
                              "directory is never removed while it is there", False)
                    it.size = size
                    it.extra["owner"] = owner
                    self.items.append(it)
                    continue
                it = Item("cache", path, "remove", "a dependency cache or bytecode: it holds no "
                          "work and is rebuilt", False)
                it.size = size
                it.extra["owner"] = owner
                self.items.append(it)

    @staticmethod
    def _cache_protected(t: Tree) -> bool:
        return (t.disposition in ("foreign", "live", "load-bearing") or t.live is not None
                or t.contested or t.result.startswith(("FAILED", "left")))

    def _trees_inside(self, path: str) -> str:
        """Why `path` holds trees, or '': a tree of the table under it, or
        any `.git` beneath it (or a directory there that cannot be read)."""
        for t in self.trees:
            if t.real != real(path) and under(t.path, path):
                return f"a tree of this table lies under it ({t.path})"
        nested, unread = nested_repos(path)
        if nested:
            return f"a repository lies under it ({nested[0]})"
        if unread:
            return f"{unread[0]} under it could not be read, so whether a tree lies there is unknown"
        return ""

    def _deps_partial(self, path: str) -> list:
        """Why the search for what leans on `path` is not complete: what the
        estate walk could not read, and `path` lying outside the estate root
        it searched."""
        out = list(self.deps_partial)
        if self.estate_root and not under(path, self.estate_root):
            out.append(f"{path} lies outside the estate root {self.estate_root}, the only place "
                       "dependents were looked for")
        return out

    @staticmethod
    def _cache_owner(path: str, roots: list, trees: list) -> str:
        """The tree a cache is in, else the top directory under the lane's
        root it is in, else the root."""
        for t in trees:
            if under(path, t):
                return t
        for r in roots:
            if under(path, r):
                rel = os.path.relpath(path, r).split(os.sep)
                return os.path.join(r, rel[0]) if rel and rel[0] not in ("", ".") else r
        return path

    # ------------------------------------------------------------ output
    def retire_count(self) -> int:
        return (sum(1 for t in self.trees if t.retire)
                + sum(1 for i in self.items if i.retire)
                + sum(1 for g in self.legs if g.retire))

    def render(self) -> None:
        f = self.facts
        mode = "YES - performing the table" if self.opts.yes else \
            "DRY RUN - nothing is changed; --yes performs it"
        say(f"{PROG} sweep {self.lane} - {mode}")
        holder = f.holder
        if f.holder == "live":
            holder = f"live {f.holder_uuid[:8]}" + (" (this session)" if f.holder_uuid == self.caller else "")
        say(f"  lane: state {f.state} · holder {holder} · binding {f.binding}"
            + (f" ({f.binding_at})" if f.binding_at else "") + (f" · verdict {f.verdict}" if f.verdict else ""))
        say(f"  checkout: {f.dir or '(no recorded directory)'} · inventory "
            f"{len(f.inventory)} tree(s) · origin "
            + ("fetched now" if self.opts.fetch else "as of the last fetch"))
        ev = self.evidence
        say("  merged means: the register's LANDED line for the branch's PR, or gh's MERGED"
            + (f" (gh: {ev.gh_why})" if ev.gh_why else "")
            + (f" (register: {ev.register_why})" if ev.register_why else ""))
        if self.refusal:
            say("")
            say(f"REFUSED: {self.refusal}")
        for line in self.notes:
            say(f"  note: {line}")
        say("")
        say("TREES")
        if not self.trees:
            say("  (none: no inventory tree, no registration, nothing under the lane's roots)")
        for t in self.trees:
            res = f" -> {t.result}" if t.result else ""
            say(f"  {t.disposition:<22} {t.path}")
            say(f"  {'':<22}   {t.label()} · {t.reason}{res}")
        if self.legs:
            # T018: THE ASSEMBLY'S OWN CHECKOUTS, read and never acted on.
            say("")
            say(f"LEGS (the assembly's own checkouts, from {os.path.join(f.dir, 'project.yaml')}; "
                "read, and never acted on)")
            for g in self.legs:
                say(f"  {g.disposition:<22} {g.path}")
                say(f"  {'':<22}   {g.label()} · {g.reason}")
        kinds = (("branch", "BRANCHES (--branches)"), ("scratch", "SCRATCH (--include-scratch)"),
                 ("cache", "CACHES (--include-caches)"), ("sandbox", "SANDBOXES (--include-sandboxes)"),
                 ("link", "BROKEN LINKS (--links)"))
        for kind, title in kinds:
            rows = [i for i in self.items if i.kind == kind]
            if not rows:
                continue
            say("")
            say(title)
            for i in rows:
                size = f" · {human_size(i.size)}" if i.size else ""
                res = f" -> {i.result}" if i.result else ""
                say(f"  {i.disposition:<22} {i.path}{size}")
                say(f"  {'':<22}   {i.detail + ' · ' if i.detail else ''}{i.reason}{res}")
        say("")
        foreign = sum(1 for t in self.trees if t.disposition == "foreign")
        live = sum(1 for t in self.trees if t.disposition == "live")
        say(f"SUMMARY: {len(self.trees)} tree(s): {self.retire_count()} to retire · "
            f"{live} live · {foreign} foreign"
            + (f" · {len(self.legs)} leg checkout(s), "
               f"{sum(1 for g in self.legs if g.retire)} counted" if self.legs else "")
            + (f" · archive {tilde(self.archive)}" if self.archive else ""))

    def porcelain(self) -> None:
        f = self.facts
        prow("lane", self.lane, f.state or "-", f.holder or "-",
             f.holder_uuid or "-", f.binding or "-", f.verdict or "-")
        if self.refusal:
            prow("refused", self.refusal)
        for t in self.trees:
            prow("tree", t.disposition, "retire" if t.retire else "-", t.path,
                 t.branch or ("(detached)" if t.detached else "-"), t.head or "-",
                 t.reason + (f" -> {t.result}" if t.result else ""))
        for g in self.legs:      # T018
            prow("leg", g.disposition, "retire" if g.retire else "-", g.path,
                 g.branch or ("(detached)" if g.detached else "-"), g.head or "-", g.reason)
        for i in self.items:
            prow(i.kind, i.disposition, "retire" if i.retire else "-", i.path,
                 i.detail or "-", str(i.size), i.reason + (f" -> {i.result}" if i.result else ""))
        prow("summary", self.retire_count(), len(self.trees),
             sum(1 for t in self.trees if t.disposition == "live"),
             sum(1 for t in self.trees if t.disposition == "foreign"))

    # -------------------------------------------------------------- acting
    def act(self) -> None:
        self.acting = True
        os.umask(0o077)
        for t in self.trees:
            if t.disposition in ("live", "foreign", "keep", "gone", "load-bearing", ""):
                continue
            if self.halt:
                # NOTHING MORE IS ACTED ON once a self-check refused or the
                # register refused a line (#170 B6): what is left is exactly
                # as the table found it.
                t.result = "left: " + self.halt
                continue
            try:
                self._act_tree(t)
            except _Keep as exc:
                t.disposition = "keep"
                t.result = "left: " + str(exc)
            except _SelfCheck as exc:
                t.result = "REFUSED by the self-check: " + str(exc)
                self.refused_check = f"{t.path}: {exc}"
                self.halt = (f"the self-check refused {t.path} ({exc}); no further act is taken "
                             "until that is understood")
                note(f"REFUSED by the self-check before removing {t.path}: {exc}")
            except _ActFailed as exc:
                t.result = "FAILED: " + str(exc)
                self.failed += 1
            except OSError as exc:
                t.result = f"FAILED: {exc}"
                self.failed += 1
            self._record(t)
        for i in self.items:
            if self.halt:
                if i.retire or i.disposition in ("remove", "archive+remove", "delete",
                                                 "delete+remote"):
                    i.result = "left: " + self.halt
                continue
            try:
                self._act_item(i)
            except _SelfCheck as exc:
                i.result = "REFUSED by the self-check: " + str(exc)
                self.refused_check = f"{i.path}: {exc}"
                self.halt = (f"the self-check refused {i.kind} {i.path} ({exc}); no further act "
                             "is taken until that is understood")
                note(f"REFUSED by the self-check before deleting {i.kind} {i.path}: {exc}")
                self._archive_dir()
            except (_ActFailed, OSError) as exc:
                i.result = "FAILED: " + str(exc)
                self.failed += 1
        self._register_items()
        if self.archive:
            self._write_disposition()

    def _archive_dir(self) -> str:
        """This invocation's archive, created EXCLUSIVELY: two sweeps started
        in one second never share - and overwrite - one ledger."""
        if not self.archive:
            parent = os.path.join(sweeps_dir(), safe_ref_part(self.lane))
            os.makedirs(parent, mode=0o700, exist_ok=True)
            path, n = os.path.join(parent, self.stamp), 1
            while True:
                try:
                    os.mkdir(path, 0o700)
                    break
                except FileExistsError:
                    n += 1
                    path = os.path.join(parent, f"{self.stamp}-{n}")
            self.archive = path
        return self.archive

    def _rescue_name(self, t: Tree, suffix: str = "") -> str:
        base = f"rescue/{safe_ref_part(self.lane)}/{t.slice}-{self.stamp}{suffix}"
        name, n = base, 1
        while git(t.gitpath, "rev-parse", "-q", "--verify", f"refs/heads/{name}").returncode == 0:
            n += 1
            name = f"{base}-{n}"
        return name

    def _occupied(self, path: str, writer: str = "") -> str:
        """Why `path` may NOT be touched now, or '' - asked afresh: processes,
        open files and tmux panes, and the session that recorded it. A scan
        that could not be made is a reason, never a pass."""
        fresh = ProcScan()
        if not fresh.available:
            return "liveness could not be read: " + (fresh.why or "no process scan")
        held = fresh.holders(path)
        if held:
            pid, comm, kind, _w = held[0]
            return f"process {pid} ({comm or '?'}) has its {kind} there"
        hidden = fresh.unknown_for(path)
        if hidden:
            return "liveness could not be read: " + hidden
        if writer and UUID_RE.match(writer) and writer != self.caller:
            answer = self.helper.transcript_live(writer, fresh=True)
            if answer != "none":
                return (f"the session that recorded it ({writer[:8]}) is "
                        + ("live" if answer == "live" else "of unknown liveness"))
        return ""

    # ---------------------------------------------- what a removal would take
    def _common(self, path: str) -> str:
        common = git_out(path, "rev-parse", "--git-common-dir") or ""
        if common and not os.path.isabs(common):
            common = os.path.normpath(os.path.join(path, common))
        return real(common) if common else ""

    def _loss_plan(self, t: Tree):
        """(where git is asked, candidate commits, what keeps them) for the
        removal `t`'s disposition makes - or why it could not be read.

        A WORKTREE'S removal takes its own git directory: its HEAD and that
        HEAD's reflog (and, where its branch is deleted after it, the branch
        and the branch's reflog). Every branch, tag and stash of the
        repository stays. A PRUNED registration takes the same, read from its
        git directory, the working tree being gone already. A CLONE's removal
        takes everything in it: every ref but its remote-tracking ones, and
        every reflog."""
        if not t.exists:
            co = t.checkout
            admin = worktree_admin(co, t.path) if co else ""
            if not admin:
                return None, f"the git directory of its registration in {co or '?'} was not found"
            logs, why = reflog_shas([os.path.join(admin, "logs", "HEAD")])
            if logs is None:
                return None, why
            return (co, [(t.reg or {}).get("head", "")] + logs,
                    ["--remotes=origin", "--branches", "--tags", "--glob=refs/stash"]), ""
        gitdir = git_out(t.path, "rev-parse", "--absolute-git-dir")
        if not gitdir:
            return None, "its git directory could not be read"
        if t.kind == "clone":
            refs = git(t.path, "for-each-ref", "--format=%(refname) %(objectname) %(objecttype)",
                       "refs/")
            if refs.returncode != 0:
                return None, "its refs could not be read"
            rows = [line.split(" ") for line in refs.stdout.splitlines()
                    if len(line.split(" ")) == 3 and not line.startswith("refs/remotes/")]
            tips = [sha for _ref, sha, _otype in rows]
            tags = [sha for ref, sha, otype in rows if ref.startswith("refs/tags/") and otype == "tag"]
            files, why = reflog_files(gitdir)
            if files is None:
                return None, why
            logs, why = reflog_shas(files)
            if logs is None:
                return None, why
            return (t.path, tips + [t.head] + logs,
                    ["--remotes=origin"] + origin_tag_objects(t.path, tags)), ""
        logs_files = [os.path.join(gitdir, "logs", "HEAD")]
        keep = ["--remotes=origin"]
        if t.disposition == "remove+delete-branch" and t.branch:
            logs_files.append(os.path.join(t.common, "logs", "refs", "heads", *t.branch.split("/")))
            keep.append(f"--exclude={t.branch}")
        keep += ["--branches", "--tags", "--glob=refs/stash"]
        logs, why = reflog_shas(logs_files)
        if logs is None:
            return None, why
        return (t.path, [t.head] + logs, keep), ""

    def _lost(self, t: Tree) -> list:
        """The commits the removal would take that are on origin, in a
        surviving ref, pushed and seen this run, or in a bundle of this run -
        none of them. Raises when that cannot be read: unknown is never
        'nothing'."""
        plan, why = self._loss_plan(t)
        if plan is None:
            raise _ActFailed("which commits its removal would take could not be read (" + why
                             + "); left in place" + (f"; its rescue is at {t.rescue}"
                                                     if t.rescue else ""))
        where, cands, keep = plan
        common = self._common(where)
        kept = sorted(self.verified.get(common, set()) | self.bundled.get(common, set()))
        lost = lost_tips(where, cands, keep + kept)
        if lost is None:
            raise _ActFailed("whether its commits are kept elsewhere could not be read (git "
                             "rev-list failed); left in place")
        return lost

    def _branch_lost(self, co: str, branch: str, tip: str) -> list:
        """What deleting `refs/heads/<branch>` (at `tip`) in `co` would take
        that no origin ref, other branch, tag, stash, push or bundle of this
        run keeps: its tip and every commit its reflog names. Raises when
        that cannot be read - unknown is never 'nothing'."""
        common = self._common(co)
        if not common:
            raise _ActFailed("the repository's git directory could not be read; the branch is left")
        logs, why = reflog_shas([os.path.join(common, "logs", "refs", "heads", *branch.split("/"))])
        if logs is None:
            raise _ActFailed(why + "; the branch is left")
        keep = ["--remotes=origin", f"--exclude={branch}", "--branches", "--tags",
                "--glob=refs/stash"]
        kept = sorted(self.verified.get(common, set()) | self.bundled.get(common, set()))
        lost = lost_tips(co, [tip] + logs, keep + kept)
        if lost is None:
            raise _ActFailed("whether its commits are kept elsewhere could not be read (git "
                             "rev-list failed); the branch is left")
        return lost

    def _self_check(self, t: Tree) -> None:
        """NOTHING IS DELETED THAT IS NOT FIRST ON ORIGIN OR IN A BUNDLE, asked
        one last time of git itself, right before the removal: every commit
        the removal would take must be reachable from origin, from a ref that
        stays, from a push seen on origin this run, or from the heads of a
        bundle this run wrote and verified. Anything else is a defect in the
        table above, and the sweep stops (exit 2) rather than act on it."""
        try:
            lost = self._lost(t)
        except _ActFailed as exc:
            raise _SelfCheck(str(exc))
        if lost:
            raise _SelfCheck(f"{len(lost)} commit(s) it would take are neither on origin nor in a "
                             f"bundle (e.g. {short(lost[0])})")

    def _bundle_lost(self, t: Tree, lost: list, label: str) -> str:
        """A bundle of commits no ref names: each is given a temporary ref
        (`refs/lane-worktrees/<slice>-<UTC>/<label>-<n>`, the name it carries
        in the bundle), the bundle is made and verified, and the refs are
        deleted. The ledger records it as the only copy."""
        return self._bundle_lost_at(t.gitpath, t.slice, lost, label, t.checkout or "", t.path)

    def _bundle_lost_at(self, where: str, slice_: str, lost: list, label: str,
                        checkout: str, what: str) -> str:
        prefix = f"refs/lane-worktrees/{slice_}-{self.stamp}"
        made = []
        try:
            for n, sha in enumerate(lost, 1):
                ref = f"{prefix}/{label}-{n}"
                self._git_or_fail(where, "update-ref", ref, sha, "")
                made.append(ref)
            out = self._bundle_at(where, slice_, made)
        finally:
            for ref in made:
                git(where, "update-ref", "-d", ref)
        self._ledger_row(out, lost[0], checkout, what)
        return out

    def _ledger_bundle(self, t: Tree, bundle: str, sha: str) -> None:
        self._ledger_row(bundle, sha, t.checkout or "", t.path)

    def _ledger_row(self, bundle: str, sha: str, checkout: str, what: str) -> None:
        """`rescues.tsv`'s row for a bundle that is the ONLY copy of what it
        holds (#170 item 8): origin `-`, branch `bundle:<file>`. `--expire`
        reads such a row as "no other copy" and never expires the archive."""
        if sha:
            self.rescues.append(("-", f"bundle:{os.path.basename(bundle)}", sha, checkout, what))

    def _act_tree(self, t: Tree) -> None:
        # LIVENESS IS ASKED AGAIN AT THE MOMENT OF THE ACT, not trusted from the
        # read: a writer may have started in the tree since the table was made.
        writer = (t.sidecar or {}).get("writer", "")
        if t.exists:
            why = self._occupied(t.path, writer)
            if why:
                t.disposition = "live" if not why.startswith("liveness") else "keep"
                t.result = f"left: {why}"
                return
        if t.disposition in ("prune", "rescue+prune"):
            self._act_prune(t)
            return
        guard = tree_guards(t) if t.exists else ""
        if guard:
            t.disposition = "keep"
            t.result = "left: " + guard
            return
        # A SUBMODULE'S ANNOTATED TAG IS AN OBJECT OF ITS OWN (#174, Copilot
        # round 2) - asked here, at the act, because it asks each remote.
        why = module_tags_why(git_out(t.path, "rev-parse", "--absolute-git-dir") or "")
        if why:
            raise _Keep(why + "; the tree's removal takes that repository with it")
        # 1. RESCUE OR PUBLISH FIRST - nothing below removes what is not on
        # origin or in a bundle.
        disp = t.disposition
        refs_to_bundle: list = []
        drop_after_bundle: list = []
        only_copy = ""
        wip_tree = ""
        if disp == "wip-rescue+remove":
            wip_tree, refs_to_bundle, drop_after_bundle, only_copy = self._wip_rescue(t)
        elif disp == "rescue+remove":
            name = self._rescue_name(t)
            self._git_or_fail(t.path, "update-ref", f"refs/heads/{name}", t.head, "")
            self._push(t, name, name)
            refs_to_bundle = [name]
            t.rescue = f"origin/{name}"
        elif disp == "push+remove":
            self._push(t, t.branch, t.remote_branch or t.branch)
            refs_to_bundle = [t.branch] if self.opts.bundle else []
            t.rescue = f"origin/{t.remote_branch or t.branch}"
        elif disp in ("remove", "remove+delete-branch"):
            refs_to_bundle = [t.branch or "HEAD"] if self.opts.bundle and t.head else []
        elif disp == "bundle+remove":
            if t.kind == "clone" and lfs_store_holds(t.common):
                raise _Keep(LFS_STORE_WHY)
            refs_to_bundle = [t.branch or "HEAD"]
            only_copy = t.head
        # 2. THE BUNDLE - always for a rescue, and for everything under --bundle.
        if refs_to_bundle:
            try:
                t.bundle = self._bundle(t, refs_to_bundle)
            finally:
                for ref in drop_after_bundle:
                    git(t.path, "update-ref", "-d", ref)
            if only_copy:
                self._ledger_bundle(t, t.bundle, only_copy)
        # 2b. WHAT THE REMOVAL WOULD STILL TAKE (#170 A5): a commit only the
        # tree's reflog names (an amend, a reset, a checkout away from a
        # detached commit) is in no ref, so no push and no ref bundle above
        # carried it. It goes to a bundle of its own.
        lost = self._lost(t)
        # A CLONE'S LOCAL GIT-LFS STORE GOES WITH IT (#174, Copilot round 2): a
        # bundle carries the pointers of commits only it holds, never the bytes
        # under `.git/lfs/objects`, and no push uploaded them.
        if lost and t.kind == "clone" and lfs_store_holds(t.common):
            raise _Keep(LFS_STORE_WHY)
        if lost and os.environ.get(SEAM_NO_LOSS_BUNDLE, "") != "1":
            t.lost_bundle = self._bundle_lost(t, lost, "unreachable")
        # 3. IGNORED WORK no commit could carry.
        extra = ignored_work(t)
        if extra is None:
            raise _ActFailed("its ignored files could not be listed, so whether it holds an "
                             ".env or local configuration no commit carries is unknown; left "
                             "in place" + (f"; its rescue is at {t.rescue}" if t.rescue else ""))
        # TAKEN BEFORE THE ARCHIVE and compared at removal, so a change made
        # while the tar ran leaves the tree too.
        ignored_before = ignored_print(t.path, extra)
        if extra:
            size = sum(dir_size(os.path.join(t.path, r)) if os.path.isdir(os.path.join(t.path, r))
                       else os.lstat(os.path.join(t.path, r)).st_size for r in extra
                       if os.path.lexists(os.path.join(t.path, r)))
            cap = self.conf["ignored_archive_mb"] * 1024 * 1024
            if size > cap:
                raise _ActFailed(f"{human_size(size)} of ignored files no commit can carry "
                                 f"(over ignored_archive_mb={self.conf['ignored_archive_mb']}): "
                                 "move or delete them by hand, or raise the cap; left in place"
                                 + (f"; its rescue is at {t.rescue}" if t.rescue else ""))
            t.ignored_archive = self._tar(t.path, extra,
                                          f"{t.slice}-ignored.tar.gz")
        # 4. NOTHING MOVED, PROVED AGAIN AT THE MOMENT OF REMOVAL: no writer
        # arrived while the pushes, bundles and archives ran; the head is the
        # one classified; and the content is clean - or, for a WIP rescue, is
        # byte for byte the tree the rescue commit carries.
        why = self._occupied(t.path, writer)
        if why:
            raise _ActFailed(f"{why} - it arrived while the rescue was made; left in place"
                             + (f"; its rescue is at {t.rescue}" if t.rescue else ""))
        # THE IGNORED FILES ARE THE ONES ARCHIVED: none changed, appeared or
        # went since the list was taken - neither `git status` nor the WIP
        # snapshot sees an `.env`.
        again_ignored = ignored_work(t)
        if again_ignored is None or ignored_print(t.path, again_ignored) != ignored_before:
            raise _ActFailed("its ignored files changed after they were archived (or could not "
                             "be listed again); left in place"
                             + (f"; its rescue is at {t.rescue}" if t.rescue else ""))
        head_now = git_out(t.path, "rev-parse", "-q", "--verify", "HEAD") or ""
        if head_now != t.head:
            raise _ActFailed(f"HEAD moved from {short(t.head) or 'none'} to "
                             f"{short(head_now) or 'none'} under the sweep; left in place"
                             + (f"; its rescue is at {t.rescue}" if t.rescue else ""))
        if wip_tree:
            if snapshot_tree(t.path) != wip_tree:
                raise _ActFailed("the tree changed after its rescue was taken; left in place; "
                                 f"the rescue is at {t.rescue or tilde(t.bundle)}")
        else:
            again = git(t.path, "status", "--porcelain=v1", "--ignore-submodules=none",
                        "--untracked-files=normal")
            if again.returncode != 0 or again.stdout.strip():
                raise _ActFailed("the tree changed under the sweep (git status is not clean); "
                                 "left in place"
                                 + (f"; its rescue is at {t.rescue}" if t.rescue else ""))
        # 5. THE INVARIANT, ASKED OF GIT ONE LAST TIME (see `_self_check`).
        self._self_check(t)
        checkout = t.checkout or os.path.dirname(t.common.rstrip("/"))
        if t.kind == "worktree":
            co = t.checkout if t.checkout and os.path.isdir(t.checkout) else t.path
            proc = git(co, "worktree", "remove", "--force", t.path)
            if proc.returncode != 0:
                raise _ActFailed("git worktree remove: " + _first_line(proc.stderr))
        else:
            rmtree(t.path)
        if os.path.exists(t.path):
            raise _ActFailed("the directory is still there after the removal")
        t.result = "removed" + ((f"; pushed to {t.rescue}" if disp == "push+remove" else
                                 f"; rescue {t.rescue}") if t.rescue else "")
        if t.held_back:
            t.result += "; untracked files in the bundle only"
        if t.lost_bundle:
            t.result += f"; commits no other ref keeps {tilde(t.lost_bundle)}"
        # 6. THE BRANCHES OF A MERGED TREE - each deleted only AT THE SHA THAT
        # WAS JUDGED merged, so a branch that moved under the sweep is kept.
        if disp == "remove+delete-branch" and t.branch:
            co = t.checkout if t.checkout and os.path.isdir(t.checkout) else checkout
            done, why = delete_local_branch(co, t.branch, t.head)
            t.result += "; local branch deleted" if done else "; local branch NOT deleted: " + why
            if not done:
                self.failed += 1
            head = (t.merge or {}).get("head", "")
            ours = t.lane_trailer.lower() == self.lane.lower()
            gh_open = (t.merge or {}).get("gh_open") or []
            if t.remote_tip and gh_open:
                t.result += f"; remote branch kept (gh still answers PR #{gh_open[0]} OPEN)"
            elif t.remote_tip and ours and head and (t.remote_tip == head or git(
                    co, "merge-base", "--is-ancestor", t.remote_tip, head).returncode == 0):
                done, why = delete_remote_branch(co, t.remote_branch, t.remote_tip)
                t.result += "; remote branch deleted" if done else "; remote branch NOT deleted: " + why
                if not done:
                    self.failed += 1
            elif t.remote_tip:
                t.result += ("; remote branch kept (" + ("its Lane: trailer is "
                             + (t.lane_trailer or "absent") if not ours else
                             "it holds commits the PR head does not") + ")")
        self._note(t, f"swept {t.path}: {disp} (was {t.label()})"
                   + ((f"; pushed to {t.rescue}" if disp == "push+remove" else
                       f"; rescue {t.rescue}") if t.rescue else "")
                   + (f"; bundle {tilde(t.bundle)}" if t.bundle else "")
                   + ("; untracked files in the bundle only" if t.held_back else "")
                   + (f"; commits no other ref keeps {tilde(t.lost_bundle)}" if t.lost_bundle
                      else "")
                   + (f"; ignored files {tilde(t.ignored_archive)}" if t.ignored_archive else "")
                   + f"; archive {tilde(self._archive_dir())}")

    def _act_prune(self, t: Tree) -> None:
        """A registration whose directory is gone: that one registration is
        removed - after the commits only its git directory names (its HEAD,
        detached, and that HEAD's reflog) are rescued (#170 A4)."""
        if os.path.lexists(t.path):
            # IT CAME BACK while the sweep ran (a remount, a restore): it is a
            # tree again, and it is classified afresh by the next sweep, never
            # removed on a reading taken while it was gone.
            t.disposition = "keep"
            t.result = "left: the directory reappeared after it was read as gone; sweep again"
            return
        co = t.checkout
        admin = worktree_admin(co, t.path) if co else ""
        mod_why = (module_repos_why(admin) or module_tags_why(admin)) if admin else ""
        if mod_why:
            raise _Keep("its git directory keeps a submodule repository that would be lost: "
                        + mod_why)
        lost = self._lost(t)
        head = (t.reg or {}).get("head", "")
        if lost and head in lost and git_out(co, "config", "--get", "remote.origin.url"):
            name = self._rescue_name(t)
            self._git_or_fail(co, "update-ref", f"refs/heads/{name}", head, "")
            self._push(t, name, name)
            t.rescue = f"origin/{name}"
            t.bundle = self._bundle(t, [name])
            lost = self._lost(t)
        if lost:
            t.lost_bundle = self._bundle_lost(t, lost, "pruned")
        self._self_check(t)
        # THAT ONE REGISTRATION: `git worktree prune` would unregister every
        # missing tree of the repository - another lane's, a FOREIGN one,
        # one on a disk that is not mounted - and say nothing of it.
        proc = git(co, "worktree", "remove", t.path)
        if proc.returncode != 0:
            raise _ActFailed("git worktree remove " + t.path + ": " + _first_line(proc.stderr)
                             + " - a repository-wide prune is never run in its place")
        regs = worktree_registrations(co)
        if regs is None:
            raise _ActFailed("the registrations could not be read again after the prune")
        if t.path in [r["path"] for r in regs]:
            raise _ActFailed("the registration is still there after the prune")
        t.result = "pruned" + (f"; rescue {t.rescue}" if t.rescue else "") + (
            f"; bundle {tilde(t.bundle)}" if t.bundle else "") + (
            f"; commits no other ref keeps {tilde(t.lost_bundle)}" if t.lost_bundle else "")
        self._note(t, f"swept {t.path}: stale registration pruned in {co}"
                   + (f"; rescue {t.rescue}" if t.rescue else "")
                   + (f"; bundle {tilde(t.bundle)}" if t.bundle else "")
                   + (f"; commits no other ref keeps {tilde(t.lost_bundle)}" if t.lost_bundle
                      else ""))

    def _wip_rescue(self, t: Tree) -> tuple:
        """The WIP rescue: (the tree `git add -A` makes, refs to bundle, refs
        to delete once bundled, the commit only the bundle holds).

        THE WORK IS COMMITTED BESIDE THE TREE, NOT INTO IT: a temporary index
        takes the add, and `commit-tree` + `update-ref` make the rescue
        branch. The tree's own branch, index and files are never moved, so a
        push that fails leaves it exactly as it was.

        UNTRACKED FILES ARE NEVER PUSHED unless `--push-untracked` (#170 A9):
        an un-ignored `gcp-service-account.json` is a file nobody meant to
        commit. The pushed WIP commit carries the tracked changes only, and a
        second commit on it with every untracked file goes to the bundle
        alone - through a temporary ref, so it is in no branch."""
        full = snapshot_tree(t.path)
        if not full:
            raise _ActFailed("the tree's content could not be snapshotted (git add -A into "
                             "a temporary index failed); left in place")
        # WHAT IS UNTRACKED IS READ FROM THE SNAPSHOTS, NOW - never from the
        # table's status (#174, Copilot round 1): a tree with only tracked
        # changes when it was read, and an untracked key file by the time of
        # the rescue, pushed the key with the full snapshot. The pushed one is
        # tracked-only unless `--push-untracked`, and whatever the full one
        # holds beyond it goes to the bundle alone.
        pushed = full if self.opts.push_untracked else snapshot_tree(t.path, tracked_only=True)
        if not pushed:
            raise _ActFailed("the tree's tracked changes could not be snapshotted; left in place")
        untracked: list = []
        if pushed != full:
            untracked = tree_paths_between(t.path, pushed, full)
            if untracked is None:
                raise _ActFailed("which files would go to the bundle only could not be read (git "
                                 "diff-tree failed); left in place")
        hold = pushed != full
        if hold:
            # A BUNDLE CARRIES A GIT-LFS POINTER, NEVER ITS BYTES (#174, Copilot
            # round 1): checked BEFORE anything is pushed.
            why = lfs_untracked_why(t.path, untracked)
            if why:
                raise _Keep(why)
        head_tree = (git_out(t.path, "rev-parse", "HEAD^{tree}") if t.head else EMPTY_TREE) or ""
        subject = f"WIP({self.lane}): {t.slice} swept {self.stamp}; swept, not reviewed"
        body = (f"Rescued by {PROG} sweep from {t.path} (branch "
                f"{t.branch or 'detached'} at {short(t.head) if t.head else 'no commit'}). "
                "The tree held uncommitted or untracked work when its lane was swept; "
                "this commit is that work as it stood, unreviewed.")
        tip = t.head
        if pushed != head_tree:
            tip = self._commit_tree(t.path, pushed, t.head, subject, body)
        refs, drop = [], []
        if tip and (tip != t.head or t.on_origin is not True):
            name = self._rescue_name(t)
            self._git_or_fail(t.path, "update-ref", f"refs/heads/{name}", tip, "")
            self._push(t, name, name)
            refs.append(name)
            t.rescue = f"origin/{name}"
        only = ""
        if hold:
            held = self._commit_tree(
                t.path, full, tip,
                f"WIP({self.lane}): {t.slice} untracked files {self.stamp}; bundle only, never "
                "pushed",
                f"The {len(untracked)} untracked path(s) of {t.path} when its lane was swept "
                f"(e.g. {untracked[0] if untracked else '?'}). They were never pushed: an "
                "untracked file may be a "
                "credential nobody meant to commit. This commit is in this bundle only.")
            tmp = f"refs/lane-worktrees/{t.slice}-{self.stamp}/untracked"
            self._git_or_fail(t.path, "update-ref", tmp, held, "")
            refs.append(tmp)
            drop.append(tmp)
            t.held_back = held
            only = held
        return full, refs, drop, only

    def _git_or_fail(self, path: str, *args: str) -> str:
        proc = git(path, *args)
        if proc.returncode != 0:
            raise _ActFailed(f"git {' '.join(args)}: " + _first_line(proc.stderr))
        return proc.stdout

    def _commit_tree(self, path: str, tree: str, parent: str, subject: str, body: str) -> str:
        """A commit of `tree` on `parent`, made without touching any branch,
        index or file - unsigned, because a rescue must not wait on a key."""
        args = []
        if not git_out(path, "config", "--get", "user.email"):
            args += ["-c", "user.email=lane-worktrees@localhost", "-c", "user.name=lane-worktrees"]
        msg = f"{subject}\n\n{body}\n\nLane: {self.lane}\n"
        argv = ["git", "-C", path, *args, "commit-tree", "--no-gpg-sign", tree]
        if parent:
            argv += ["-p", parent]
        proc = run(argv + ["-F", "-"], env=git_env(), input_text=msg)
        if proc.returncode != 0 or not proc.stdout.strip():
            raise _ActFailed("git commit-tree: " + _first_line(proc.stderr + proc.stdout))
        return proc.stdout.strip()

    def _push(self, t: Tree, local: str, remote: str) -> None:
        where = t.gitpath
        # GIT-LFS UPLOADS ITS OBJECTS FROM ITS PRE-PUSH HOOK (#170 A10): with
        # `--no-verify` a rescued branch reached origin as pointers only, and
        # the tree - the one copy of the objects - was then removed. Where the
        # repository has git-lfs configured the hooks run; a hook that
        # refuses leaves the tree.
        verify = [] if lfs_configured(where) else ["--no-verify"]
        proc = git(where, "push", *verify, "--quiet", "origin",
                   f"refs/heads/{local}:refs/heads/{remote}", timeout=GIT_NET_TIMEOUT)
        if proc.returncode != 0:
            raise _ActFailed(f"git push origin {local}:{remote}: " + _first_line(proc.stderr))
        local_sha = git_out(where, "rev-parse", f"refs/heads/{local}") or ""
        seen = git(where, "ls-remote", "origin", f"refs/heads/{remote}", timeout=GIT_NET_TIMEOUT)
        got = seen.stdout.split()[0] if seen.returncode == 0 and seen.stdout.split() else ""
        if not local_sha or got != local_sha:
            raise _ActFailed(f"origin does not show refs/heads/{remote} at {short(local_sha)} "
                             f"after the push (it shows {short(got) or 'nothing'}); the tree is left")
        self.verified.setdefault(self._common(where), set()).add(local_sha)
        # ONLY A RESCUE BRANCH PINS ITS ARCHIVE (`--expire`): a branch pushed as
        # is is the lane's own, and its later merge and deletion is no loss.
        if remote.startswith("rescue/"):
            origin = t.origin_url or git_out(where, "config", "--get", "remote.origin.url") or ""
            self.rescues.append((origin, remote, local_sha, t.checkout or "", t.path))

    def _bundle(self, t: Tree, refs: list) -> str:
        """A bundle of `refs` holding what origin's default branch lacks (its
        prerequisites are on origin), or the whole history where that would be
        empty or origin has no default branch here. Its heads are recorded:
        they are what the self-check counts as bundled."""
        return self._bundle_at(t.gitpath, t.slice, refs)

    def _bundle_at(self, where: str, slice_: str, refs: list) -> str:
        out = os.path.join(self._archive_dir(), f"{slice_}.bundle")
        n = 1
        while os.path.exists(out):
            n += 1
            out = os.path.join(self._archive_dir(), f"{slice_}-{n}.bundle")
        base = git_out(where, "symbolic-ref", "-q", "refs/remotes/origin/HEAD") or ""
        if not base and git(where, "rev-parse", "-q", "--verify",
                            "refs/remotes/origin/main").returncode == 0:
            base = "refs/remotes/origin/main"
        proc = git(where, "bundle", "create", out, *refs, "--not", base) if base else None
        if proc is None or proc.returncode != 0:
            if os.path.exists(out):
                os.unlink(out)
            proc = git(where, "bundle", "create", out, *refs)
        if proc.returncode != 0:
            raise _ActFailed("git bundle create: " + _first_line(proc.stderr))
        check = git(where, "bundle", "verify", out)
        if check.returncode != 0:
            raise _ActFailed("git bundle verify: " + _first_line(check.stderr))
        heads = git(where, "bundle", "list-heads", out)
        if heads.returncode != 0:
            raise _ActFailed("git bundle list-heads: " + _first_line(heads.stderr))
        self.bundled.setdefault(self._common(where), set()).update(
            line.split()[0] for line in heads.stdout.splitlines() if line.split())
        return out

    def _tar(self, base: str, rels: list, name: str) -> str:
        """`rels` under `base`, archived and read back: EVERY ENTRY SELECTED IS
        IN IT. Caches and bytecode are left out only BENEATH a selected entry,
        never because of the entry's own ancestors (#174, Copilot round 1):
        `docker/node_modules/prod.env` is an ignored file in a tracked
        directory, selected on its own name, and a filter that read every
        part of the member's path dropped it - while `top.env` beside it kept
        the archive non-empty, so it verified and the tree went."""
        out = os.path.join(self._archive_dir(), name)
        n = 1
        while os.path.exists(out):
            n += 1
            out = os.path.join(self._archive_dir(), name.replace(".tar.gz", f"-{n}.tar.gz"))
        wanted = [rel.rstrip("/") for rel in rels]
        try:
            with tarfile.open(out, "w:gz") as tf:
                for rel in wanted:
                    tf.add(os.path.join(base, rel), arcname=rel, recursive=True,
                           filter=_no_caches_beneath(rel))
            with tarfile.open(out, "r:gz") as tf:
                names = set(tf.getnames())
        except (OSError, tarfile.TarError) as exc:
            raise _ActFailed(f"the archive {out} could not be written: {exc}")
        missing = [rel for rel in wanted if rel not in names]
        if not names or missing:
            raise _ActFailed(f"the archive {out} came back without "
                             + (f"{missing[0]}{' and more' if len(missing) > 1 else ''}"
                                if missing else "anything") + "; left in place")
        return out

    def _act_item(self, i: Item) -> None:
        if i.kind == "scratch" and i.disposition == "archive+remove":
            why = self._occupied(i.path)
            if why:
                i.disposition = "live" if not why.startswith("liveness") else "keep"
                i.result = "left: " + why
                return
            inner = self._trees_inside(i.path)
            if inner:
                i.disposition, i.result = "keep", "left: " + inner
                return
            before = content_print(i.path)
            parent, name = os.path.dirname(i.path), os.path.basename(i.path)
            i.extra["archive"] = self._tar(parent, [name], f"scratch-{safe_ref_part(name)}.tar.gz")
            # WHAT IS REMOVED IS WHAT WAS ARCHIVED: no writer arrived while the
            # tar ran, and nothing outside the caches changed.
            why = self._occupied(i.path)
            if why:
                raise _ActFailed(f"{why} - it arrived while the archive was made; left in place; "
                                 f"the archive is {tilde(i.extra['archive'])}")
            if content_print(i.path) != before or self._trees_inside(i.path):
                raise _ActFailed("it changed while it was archived; left in place; the archive "
                                 f"{tilde(i.extra['archive'])} is of what it was")
            rmtree(i.path)
            i.result = "archived and removed"
        elif i.kind == "cache" and i.disposition == "remove":
            # (14) THE OWNING TREE IS ASKED AGAIN: a writer may have started
            # there while the rescues ran, or the tree's own act left it live.
            owner = i.extra.get("owner") or i.path
            left = [t for t in self.trees if under(i.path, t.path) and self._cache_protected(t)]
            why = ("its tree was left: " + (left[0].result or left[0].reason)) if left \
                else self._occupied(owner) or repository_within(i.path)
            if why:
                i.disposition = "keep"
                i.result = "left: " + why
                return
            rmtree(i.path)
            i.result = "removed"
        elif i.kind == "sandbox" and i.disposition == "remove":
            # ITS OWNER IS ASKED AGAIN: a pytest `.lock` may name a live pid
            # now - a run that works outside the directory holds nothing in it.
            if i.extra.get("kind") == "pytest":
                owner = _lock_pid(os.path.join(i.path, ".lock"))
                if owner and pid_alive(owner):
                    i.disposition, i.result = "live", f"left: its .lock names live pid {owner}"
                    return
            if i.extra.get("kind") == "mktemp":
                # NEVER A `tmp.*` (#170 item 1, option (b)): the table lists one
                # and never asks this, so reaching here is a defect - it is left.
                i.disposition, i.result = "list", "left: a tmp.* is listed, never removed"
                return
            fresh = ProcScan()
            fresh.load_environ()
            if not fresh.available or fresh.holders(i.path) or fresh.unknown_for(i.path):
                i.disposition, i.result = "live", "left: a process arrived in it, or one of " \
                                                  "this account that cannot be read is placed there"
                return
            rmtree(i.path)
            i.result = "removed"
        elif i.kind == "branch" and i.disposition in ("delete", "delete+remote"):
            # THE BRANCH'S REFLOG GOES WITH THE REF (#174, Copilot round 1): an
            # experiment committed on it and reset away from is reachable from
            # that reflog alone, and `update-ref -d` deleted its last pointer
            # with no loss plan, no bundle and no self-check. The same three a
            # tree's branch delete has are asked here.
            co, branch, tip = i.extra["checkout"], i.extra["branch"], i.extra.get("tip", "")
            slice_ = "branch-" + safe_ref_part(branch)
            lost = self._branch_lost(co, branch, tip)
            if lost and os.environ.get(SEAM_NO_LOSS_BUNDLE, "") != "1":
                i.extra["bundle"] = self._bundle_lost_at(co, slice_, lost, "reflog", co,
                                                         f"{co} branch {branch}")
            try:
                still = self._branch_lost(co, branch, tip)
            except _ActFailed as exc:
                raise _SelfCheck(str(exc))
            if still:
                raise _SelfCheck(f"{len(still)} commit(s) its delete would take are neither on "
                                 f"origin nor in a bundle (e.g. {short(still[0])})")
            done, why = delete_local_branch(co, branch, tip)
            if not done:
                raise _ActFailed("the local branch was not deleted: " + why)
            i.result = "local branch deleted"
            if i.extra.get("bundle"):
                i.result += f"; commits only its reflog named {tilde(i.extra['bundle'])}"
            if i.disposition == "delete+remote":
                done, why = delete_remote_branch(i.extra["checkout"], i.extra["branch"],
                                                 i.extra.get("remote_tip", ""))
                i.extra["remote_deleted"] = done
                if done:
                    i.result += "; remote branch deleted"
                else:
                    i.result += "; remote branch NOT deleted: " + why
                    self.failed += 1

    def _register_items(self) -> None:
        done = [i for i in self.items if i.result
                and not i.result.startswith(("FAILED", "left", "REFUSED"))]
        scratch = [i for i in done if i.kind == "scratch"]
        branches = [i for i in done if i.kind == "branch"]
        cleaned = [i for i in done if i.kind in ("cache", "sandbox")]
        if scratch:
            self._note(None, "swept scratch: " + "; ".join(
                f"{os.path.basename(i.path)} archived to {tilde(i.extra.get('archive', ''))}"
                for i in scratch))
        if branches:
            self._note(None, "swept branches merged by PR: " + "; ".join(
                f"{i.extra['branch']} (PR #{i.extra.get('pr')}"
                f"{' + remote' if i.extra.get('remote_deleted') else ''})"
                for i in branches))
        if cleaned:
            total = sum(i.size for i in cleaned)
            self._note(None, f"swept {len(cleaned)} cache or sandbox director(ies) "
                             f"({human_size(total)}): they hold no work and are rebuilt")

    def _note(self, t, text: str) -> None:
        if self.opts.foreign and self.opts.word and t is not None and t.owner == "foreign":
            text += f"; foreign on the word '{self.opts.word}'"
        proc = self.helper.note_line(self.lane, text, self.caller)
        line = "NOTED " + utc_now() if proc.returncode == 0 else \
            f"NOT WRITTEN (lanes-edit.sh log exited {proc.returncode}: {_first_line(proc.stderr)})"
        if t is not None:
            t.register = line
        if proc.returncode != 0:
            self.failed += 1
            # THE FIRST LINE THE REGISTER REFUSES STOPS THE SWEEP (#170 B6): an
            # act the register does not record is one nobody can find, and a
            # register that refused one line will refuse the next.
            self.halt = self.halt or ("the register refused a line (NOT WRITTEN); nothing more "
                                      "is acted on until it is written by hand")
            # THE COMMAND IS QUOTED FOR THE SHELL (#170 E6): it is printed to be
            # pasted, and a path with a quote or a `$` in it broke it. It names
            # the session that swept, as the line it stands for does (#170 E7;
            # #179, Copilot round 1) - each value quoted, never the assignment.
            assign = [f"LANES_LANE={shlex.quote(self.lane)}"]
            if self.caller:
                assign.append(f"LANES_SESSION={shlex.quote(self.caller)}")
            note(f"the register line for {t.path if t else 'this sweep'} was NOT written "
                 f"(exit {proc.returncode}); write it by hand: "
                 + " ".join(assign + [shlex.quote(a) for a in (
                     "lanes-edit.sh", "log", "NOTED", f"lane:{self.lane}",
                     register_text(text))]))
        self.dispositions.append((t.path if t else "-", text, line))

    def _record(self, t: Tree) -> None:
        if not self.archive and (t.rescue or t.bundle or t.result):
            self._archive_dir()
        if self.archive:
            self._write_disposition()

    def _write_disposition(self) -> None:
        d = self._archive_dir()
        lines = [f"# Sweep of lane {self.lane} - {self.stamp}", "",
                 f"Run by `{PROG} sweep {self.lane} --yes` on "
                 f"{os.environ.get('LANES_WORKSTATION') or socket.gethostname()} as "
                 f"{getpass.getuser()}. Nothing here was deleted that was not first on origin "
                 "or in this directory.", ""]
        if self.refused_check:
            lines += [f"**REFUSED by the self-check** (exit 2): {self.refused_check}. Nothing "
                      "after it was acted on.", ""]
        elif self.halt:
            lines += [f"**Stopped:** {self.halt}.", ""]
        if any(r[0] == "-" for r in self.rescues):
            lines += ["A `bundle:` row in `rescues.tsv` is a bundle that is the ONLY copy of what "
                      "it holds (untracked files, commits only a reflog named, a tree with no "
                      "origin); `sweep --expire` never removes this archive. Its commits are "
                      "under `refs/lane-worktrees/*` or `refs/heads/*` in the bundle: "
                      "`git fetch <bundle> 'refs/*:refs/restored/*'`.", ""]
        if self.opts.word:
            lines += [f"Foreign trees were included on the word: \"{self.opts.word}\"", ""]
        lines += ["| tree | was | disposition | why | result | rescue | bundle | register |",
                  "|---|---|---|---|---|---|---|---|"]

        def cell(text: str) -> str:
            return (text or "-").replace("|", "\\|")

        for t in self.trees:
            if not t.result:
                continue
            bundles = ", ".join(os.path.basename(b) for b in (t.bundle, t.lost_bundle) if b)
            lines.append("| " + " | ".join(cell(x) for x in (
                t.path, t.label(), t.disposition, t.reason, t.result, t.rescue,
                bundles, t.register)) + " |")
        rows = [i for i in self.items if i.result]
        if rows:
            lines += ["", "| item | kind | disposition | result | size |", "|---|---|---|---|---|"]
            for i in rows:
                lines.append("| " + " | ".join(cell(x) for x in (
                    i.path, i.kind, i.disposition, i.result, human_size(i.size))) + " |")
        lines += ["", "## Register lines", ""]
        for path, text, line in self.dispositions:
            lines.append(f"- `{path}`: {line} - {text}")
        lines.append("")
        with open(os.path.join(d, "DISPOSITION.md"), "w", encoding="utf-8", errors="surrogateescape") as fh:
            fh.write("\n".join(lines))
        with open(os.path.join(d, "rescues.tsv"), "w", encoding="utf-8", errors="surrogateescape") as fh:
            fh.write("# origin\tbranch\tsha\tcheckout\ttree\n")
            for row in self.rescues:
                fh.write("\t".join(row) + "\n")
        write_manifest(d)


class _ActFailed(Exception):
    pass


class _Keep(Exception):
    """Found at the moment of the act, before anything was changed: the tree
    is kept, as the table would have kept it had it been there to see."""


class _SelfCheck(Exception):
    """The invariant's last check refused a removal: exit 2, and no further act."""


#: The suite's seam: skips the reflog bundle (#170 A5) so the self-check is
#: what stands between the removal and a commit only a reflog names.
SEAM_NO_LOSS_BUNDLE = "LANE_WORKTREES_SEAM_NO_LOSS_BUNDLE"

#: The suite's seam: a process scan that cannot be made (#170 C5).
SEAM_NO_PROCSCAN = "LANE_WORKTREES_SEAM_NO_PROCSCAN"

EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"


def worktree_admin(checkout: str, path: str) -> str:
    """The git directory (`<common>/worktrees/<id>`) of `checkout`'s
    registration of `path` - found by the `gitdir` file that points at
    `<path>/.git` - or ''."""
    common = git_out(checkout, "rev-parse", "--git-common-dir") or ""
    if not common:
        return ""
    if not os.path.isabs(common):
        common = os.path.normpath(os.path.join(checkout, common))
    want = {os.path.normpath(os.path.join(path, ".git")), real(os.path.join(path, ".git"))}
    base = os.path.join(common, "worktrees")
    try:
        ids = sorted(os.listdir(base))
    except OSError:
        return ""
    for wid in ids:
        try:
            with open(os.path.join(base, wid, "gitdir"), encoding="utf-8",
                      errors="surrogateescape") as fh:
                target = fh.readline().strip()
        except OSError:
            continue
        if target and (os.path.normpath(target) in want or real(target) in want):
            return os.path.join(base, wid)
    return ""


def reflog_files(gitdir: str) -> tuple:
    """([every reflog file under `<gitdir>/logs`], why-not). `os.walk` passes
    over a directory it cannot list IN SILENCE, and a reflog never read names
    no commit - so a clone whose `logs/refs/heads` was unreadable had its
    reflog-only commits left out of the loss plan, and the rescue and the
    self-check both approved its deletion (#174, Copilot round 1). A `logs`
    directory that is not there is an empty answer; anything else that
    could not be listed is unknown, never empty."""
    top = os.path.join(gitdir, "logs")
    errors: list = []

    def failed(exc: OSError) -> None:
        if isinstance(exc, FileNotFoundError) and \
                os.path.normpath(str(exc.filename or "")) == os.path.normpath(top):
            return
        errors.append(exc)

    files: list = []
    for base, _dirs, names in os.walk(top, onerror=failed):
        files += [os.path.join(base, n) for n in names]
    if errors:
        exc = errors[0]
        return None, (f"{exc.filename or top} could not be listed "
                      f"({exc.strerror or exc}), so the commits its reflogs name are unknown")
    return files, ""


def reflog_shas(files: list) -> tuple:
    """([every commit the reflog files name, old and new], why-not): a
    missing file is an empty reflog; one that cannot be read is unknown."""
    out: list = []
    for path in files:
        try:
            with open(path, encoding="utf-8", errors="surrogateescape") as fh:
                for line in fh:
                    for sha in line.split(" ", 2)[:2]:
                        if re.match(r"^[0-9a-f]{40}([0-9a-f]{24})?$", sha) and \
                                not re.match(r"^0+$", sha) and sha not in out:
                            out.append(sha)
        except FileNotFoundError:
            continue
        except OSError as exc:
            return None, f"the reflog {path} could not be read ({exc.strerror or exc})"
    return out, ""


def untracked_files(path: str):
    """Every untracked, un-ignored file in the checkout at `path`, one by
    one (never a directory), or None where git could not list them."""
    proc = git(path, "ls-files", "--others", "--exclude-standard", "-z")
    if proc.returncode != 0:
        return None
    return [rel for rel in proc.stdout.split("\0") if rel]


def tree_paths_between(path: str, a: str, b: str):
    """The paths whose entries differ between trees `a` and `b`, or None."""
    proc = git(path, "diff-tree", "-r", "-z", "--no-renames", "--name-only", a, b)
    if proc.returncode != 0:
        return None
    return [rel for rel in proc.stdout.split("\0") if rel]


def lfs_untracked_why(path: str, rels: list) -> str:
    """Why untracked files `rels` cannot go to a bundle that is their only
    copy, or '': where git-lfs is configured, one its attributes give
    `filter=lfs` is snapshotted as a POINTER - its bytes go to the local
    LFS store, which no bundle carries and the tree's (or the clone's)
    removal may take, and it is never pushed (#170 A9). A person moves it
    out, or commits and pushes it."""
    if not rels or not lfs_configured(path):
        return ""
    proc = run(["git", "-C", path, "check-attr", "-z", "--stdin", "filter"], env=git_env(),
               input_text="".join(rel + "\0" for rel in rels))
    if proc.returncode != 0:
        return ("whether its untracked files are git-lfs content could not be read (git "
                "check-attr failed), and a bundle would carry a pointer, never the bytes")
    fields = proc.stdout.split("\0")
    hits = [fields[i] for i in range(0, len(fields) - 2, 3) if fields[i + 2] == "lfs"]
    if not hits:
        return ""
    return (f"{len(hits)} untracked file(s) are git-lfs content (e.g. {hits[0]}): untracked files "
            "go to a bundle only, a bundle carries an LFS pointer and never its bytes, and they "
            "are never pushed - move them out, or commit and push them, then sweep again")


LFS_STORE_WHY = ("it is a CLONE with a local git-lfs store (.git/lfs/objects), and commits only a "
                 "bundle would keep may point into it - a bundle carries LFS pointers, never their "
                 "bytes, and the clone's removal takes the store; push them with git-lfs, or move "
                 "the store's objects out, then sweep again")


def lfs_store_holds(common: str) -> bool:
    """`<common>/lfs/objects` holds at least one file - a local git-lfs store
    whose bytes a clone's removal would take. One that cannot be read is
    read as holding some."""
    top = os.path.join(common or "", "lfs", "objects")
    if not common or not os.path.lexists(top):
        return False
    errors: list = []
    for _base, _dirs, files in os.walk(top, onerror=errors.append):
        if files:
            return True
    return bool(errors)


def module_repos(gitdir: str) -> tuple:
    """([every submodule repository kept under `<gitdir>/modules`, nested
    ones included], [what could not be read])."""
    top = os.path.join(gitdir, "modules")
    found, unread = [], []
    stack = [top]
    while stack:
        d = stack.pop()
        try:
            with os.scandir(d) as it:
                entries = list(it)
        except FileNotFoundError:
            continue
        except OSError as exc:
            unread.append(f"{d} ({exc.strerror or exc})")
            continue
        names = {e.name for e in entries}
        if d != top and "HEAD" in names and "objects" in names:
            found.append(d)
            if "modules" in names:
                stack.append(os.path.join(d, "modules"))
            continue
        for e in entries:
            try:
                if e.is_dir(follow_symlinks=False):
                    stack.append(e.path)
            except OSError:
                unread.append(e.path)
    return sorted(found), unread


def module_repos_why(gitdir: str) -> str:
    """Why a submodule repository `gitdir` keeps would lose work with it, or
    '' (#174, Copilot round 2): any branch, tag or HEAD, stash or reflog entry
    that no remote of that repository holds. A DEINITIALIZED submodule keeps
    its repository there with no working tree, so no other guard reads it;
    `git stash list` wants a working tree, so the stash is read as its ref."""
    mods, unread = module_repos(gitdir)
    if unread:
        return (f"{unread[0]} could not be read, so whether a submodule repository kept there "
                "holds work is unknown")
    for m in mods:
        why = module_reads(m, _module_repo_why, m, os.path.relpath(m, gitdir))
        if why:
            return why
    return ""


def module_reads(m: str, fn, *args):
    """`fn(*args)` with every git command in the submodule repository `m`
    run as a git directory alone (`_GITDIR_READS`)."""
    _GITDIR_READS.add(m)
    try:
        return fn(*args)
    finally:
        _GITDIR_READS.discard(m)


def _module_repo_why(m: str, rel: str) -> str:
    why = unpublished_ref(m, "--remotes", "every remote of it")
    if why:
        return f"the submodule repository {rel}: {why}"
    stash = git(m, "for-each-ref", "--count=1", "--format=%(refname)", "refs/stash")
    if stash.returncode != 0 or stash.stdout.strip():
        return f"the submodule repository {rel} has a stash (or it could not be read)"
    why = unpublished_reflog(m, "--remotes", "every remote of it")
    if why:
        return f"the submodule repository {rel}: {why}"
    return ""


def module_tags_why(gitdir: str) -> str:
    """Why an ANNOTATED tag of a submodule repository kept under
    `<gitdir>/modules` would be lost with it, or '' (#174, Copilot round 2).
    A tag object - its message, its signature - is its own object, and
    `module_repos_why` asks only whether the commit it names is published, so
    a local tag on a published commit passed. A tag object no remote of that
    repository answers to `ls-remote --tags` AS THAT VERY OBJECT keeps it, and
    so does a remote that cannot be asked. It asks each remote, so it runs at
    the act only (the dry run asks no remote)."""
    if not gitdir:
        return "its git directory could not be read, so the submodule repositories it keeps are unknown"
    mods, unread = module_repos(gitdir)
    if unread:
        return (f"{unread[0]} could not be read, so whether a submodule repository kept there "
                "holds work is unknown")
    for m in mods:
        why = module_reads(m, _module_tags_why, m, os.path.relpath(m, gitdir))
        if why:
            return why
    return ""


def _module_tags_why(m: str, rel: str) -> str:
    proc = git(m, "for-each-ref", "--format=%(objecttype) %(objectname) %(refname)",
               "refs/tags/")
    if proc.returncode != 0:
        return f"the tags of the submodule repository {rel} could not be read"
    tags: dict = {}
    for line in proc.stdout.splitlines():
        cols = line.split(" ", 2)
        if len(cols) == 3 and cols[0] == "tag":
            tags.setdefault(cols[1], cols[2][len("refs/tags/"):])
    if not tags:
        return ""
    remotes = git(m, "remote")
    have: set = set()
    for remote in (remotes.stdout.split() if remotes.returncode == 0 else []):
        ls = git(m, "ls-remote", "--tags", remote, timeout=GIT_NET_TIMEOUT)
        if ls.returncode == 0:
            have.update(line.split("\t", 1)[0] for line in ls.stdout.splitlines()
                        if "\t" in line)
    missing = sorted(name for sha, name in tags.items() if sha not in have)
    if missing:
        return (f"the submodule repository {rel}: its annotated tag {missing[0]} is a tag "
                "object no remote of it holds, or that none could be asked about - its "
                f"message would be lost; push it (git push <remote> tag {missing[0]}) first")
    return ""


def lfs_configured(path: str) -> bool:
    """git-lfs is set up for the repository at `path` (any `filter.lfs.*`
    in its configuration, the global one included)."""
    proc = git(path, "config", "--get-regexp", r"^filter\.lfs\.")
    return proc.returncode == 0 and bool(proc.stdout.strip())


def delete_local_branch(checkout: str, branch: str, expect: str):
    """(done, why): `refs/heads/<branch>` deleted only while it is still at
    `expect` and no worktree has it checked out - `update-ref -d` with the
    old value, so a branch that moved under the sweep is never lost."""
    if not expect:
        return False, "the SHA it was judged at is not known"
    regs = worktree_registrations(checkout)
    if regs is None:
        return False, "the worktree registrations could not be read, so whether one holds it is unknown"
    for reg in regs:
        if reg.get("branch") == f"refs/heads/{branch}":
            return False, f"it is checked out in {reg.get('path')} now"
    proc = git(checkout, "update-ref", "-d", f"refs/heads/{branch}", expect)
    if proc.returncode != 0:
        return False, (f"it is no longer at {short(expect)} (" + _first_line(proc.stderr) + ")")
    return True, ""


def delete_remote_branch(checkout: str, branch: str, expect: str):
    """(done, why): origin's branch deleted only while origin still has it at
    `expect` - a lease, so commits pushed there since are never discarded."""
    if not expect:
        return False, "the SHA it was judged at is not known"
    proc = git(checkout, "push", "--no-verify", "--porcelain",
               f"--force-with-lease=refs/heads/{branch}:{expect}", "origin",
               f":refs/heads/{branch}", timeout=GIT_NET_TIMEOUT)
    if proc.returncode != 0:
        return False, _first_line(proc.stderr or proc.stdout)
    return True, ""


def _first_few(reasons: list) -> str:
    return "; ".join(reasons[:3]) + (f"; and {len(reasons) - 3} more" if len(reasons) > 3 else "")


def _lean_text(lean: list) -> str:
    return "; ".join(f"{dep} leans on it ({how})" for dep, how in lean[:5]) + (
        f"; and {len(lean) - 5} more" if len(lean) > 5 else "")


def own_trailers(path: str) -> set:
    """The `Lane:` trailers of the commits HEAD has that origin's default
    branch lacks, lower-cased - empty where there is no such base to tell the
    branch's own commits by."""
    base = git_out(path, "symbolic-ref", "-q", "refs/remotes/origin/HEAD") or ""
    if not base and git(path, "rev-parse", "-q", "--verify",
                        "refs/remotes/origin/main").returncode == 0:
        base = "refs/remotes/origin/main"
    if not base:
        return set()
    text = git_out(path, "log", "--format=%B", f"{base}..HEAD") or ""
    return {v.lower() for v in re.findall(r"^Lane:\s*(\S+)\s*$", text, re.MULTILINE)}


def head_lane_trailer(path: str) -> str:
    """HEAD's own `Lane:` trailer (its last one), read as `read_tree` reads
    `lane_trailer` - '' where it has none or HEAD could not be read. Trailer
    adoption is decided before `read_tree` runs, so it asks here (#174
    review R1)."""
    msg = git_out(path, "log", "-1", "--format=%B", "HEAD") or ""
    trailers = re.findall(r"^Lane:\s*(\S+)\s*$", msg, re.MULTILINE)
    return trailers[-1] if trailers else ""


def _gitdir_activity(path: str) -> float:
    gitdir = git_out(path, "rev-parse", "--absolute-git-dir")
    newest = 0.0
    if not gitdir:
        return newest
    for name in ("index", "HEAD", os.path.join("logs", "HEAD")):
        try:
            newest = max(newest, os.stat(os.path.join(gitdir, name)).st_mtime)
        except OSError:
            pass
    return newest


def ignored_print(base: str, rels: list) -> list:
    """The ignored paths `rels` under `base`, every file in them with its size
    and mtime: what the ignored archive carries, compared again at removal."""
    out = []
    for rel in sorted(rels):
        full = os.path.join(base, rel)
        if os.path.isdir(full) and not os.path.islink(full):
            out += [(rel, *row) for row in content_print(full)]
        else:
            try:
                st = os.lstat(full)
                out.append((rel, "", st.st_size, st.st_mtime_ns))
            except OSError:
                out.append((rel, "", -1, -1))
    return out


def content_print(root: str) -> list:
    """Every non-cache file under `root` with its size and mtime - what an
    archive of it carries, so a change made while it was archived shows."""
    out = []
    for base, dirs, files in os.walk(root):
        dirs[:] = sorted(d for d in dirs if d not in CACHE_NAMES and d not in VENV_NAMES)
        for name in sorted(files):
            if name.endswith((".pyc", ".pyo")):
                continue
            full = os.path.join(base, name)
            try:
                st = os.lstat(full)
            except OSError:
                out.append((os.path.relpath(full, root), -1, -1))
                continue
            out.append((os.path.relpath(full, root), st.st_size, st.st_mtime_ns))
    return out


def _no_caches_beneath(rel: str):
    """A tar member filter for the entry `rel`: caches and bytecode hold no
    work, so an archive never carries one BENEATH it - judged by the parts
    of the member's path below `rel`, never by `rel`'s own ancestors. The
    entry itself is always carried: it was selected by its own name."""
    prefix = rel + "/"

    def keep(info):
        if info.name == rel:
            return info
        tail = info.name[len(prefix):] if info.name.startswith(prefix) else info.name
        if any(p in CACHE_NAMES or p in VENV_NAMES for p in tail.split("/")):
            return None
        if tail.endswith((".pyc", ".pyo")):
            return None
        return info
    return keep


def write_manifest(d: str) -> None:
    rows = []
    for base, _dirs, files in os.walk(d):
        for name in files:
            full = os.path.join(base, name)
            rel = os.path.relpath(full, d)
            if rel == "MANIFEST.sha256":
                continue
            rows.append(f"{sha256_of(full)}  {rel}")
    with open(os.path.join(d, "MANIFEST.sha256"), "w", encoding="utf-8", errors="surrogateescape") as fh:
        fh.write("\n".join(sorted(rows, key=lambda r: r.split("  ", 1)[1])) + "\n")


def tracked(path: str):
    """True when git tracks something under `path`, False when it tracks
    nothing there or `path` is in no repository, None when a repository's
    index could not be read - which is never "untracked"."""
    parent = os.path.dirname(path.rstrip("/"))
    inside = git(parent, "rev-parse", "--is-inside-work-tree")
    if inside.returncode != 0 or inside.stdout.strip() != "true":
        return False
    proc = git(parent, "ls-files", "-z", "--", os.path.basename(path.rstrip("/")))
    if proc.returncode != 0:
        return None
    return bool(proc.stdout.strip("\0"))


def find_caches(root: str) -> list:
    out = []
    for base, dirs, _files in os.walk(root, followlinks=False):
        keep = []
        for d in dirs:
            full = os.path.join(base, d)
            if d == ".git" or os.path.islink(full):
                continue
            is_venv = os.path.isfile(os.path.join(full, "pyvenv.cfg")) or (
                d in VENV_NAMES and (os.path.exists(os.path.join(full, "bin", "activate"))
                                     or os.path.exists(os.path.join(full, "Scripts", "activate"))))
            if d in CACHE_NAMES or is_venv:
                out.append((full, dir_size(full)))
            else:
                keep.append(d)
        dirs[:] = keep
    return out


def no_repository(path: str) -> bool:
    """True only where git answers that there is NO repository at `path` -
    which has no branches - and never where one could not be read. A `.git`
    entry of its own IS a repository, however broken: git answers "not a git
    repository" for a gitdir it cannot read too (#170 G5)."""
    if os.path.lexists(os.path.join(path, ".git")):
        return False
    proc = git(path, "rev-parse", "--git-dir")
    return proc.returncode != 0 and "not a git repository" in (proc.stderr or "").lower()


def branch_items(checkout: str, lane: str, evidence: MergeEvidence) -> list:
    """`--branches`: local branches NO worktree has checked out. Merged by
    PR evidence: deleted (and the remote where it is this lane's and holds
    nothing the PR did not). Unmerged with a missing, diverged or behind
    upstream: LISTED with tip, distance and age, never deleted. A branch an
    open PR names is never touched; rescue branches are never touched."""
    out = []
    regs = worktree_registrations(checkout)
    if regs is None:
        return [Item("branch", checkout, "keep", "the worktree registrations could not be read, "
                     "so which branches a worktree holds is unknown", True)]
    held = {r.get("branch", "")[len("refs/heads/"):] for r in regs if r.get("branch")}
    fmt = "%(refname:short)\t%(objectname)\t%(upstream:short)\t%(committerdate:iso-strict)"
    proc = git(checkout, "for-each-ref", f"--format={fmt}", "refs/heads/")
    if proc.returncode != 0:
        return [Item("branch", checkout, "keep", "the branches could not be read: "
                     + _first_line(proc.stderr), True)]
    origin = git_out(checkout, "config", "--get", "remote.origin.url") or ""
    slug = slug_of(origin)
    default = (git_out(checkout, "symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD")
               or "origin/main")
    for line in proc.stdout.splitlines():
        cols = line.split("\t")
        if len(cols) < 4:
            continue
        name, tip, upstream, date = cols
        if name in held or name in PROTECTED_BRANCHES or name.startswith("rescue/"):
            continue
        msg = git_out(checkout, "log", "-1", "--format=%B", tip) or ""
        trailers = re.findall(r"^Lane:\s*(\S+)\s*$", msg, re.MULTILINE)
        owner = trailers[-1] if trailers else ""
        ours = owner.lower() == lane.lower()
        counts = git_out(checkout, "rev-list", "--left-right", "--count", f"{default}...{tip}") or ""
        behind_main, ahead_main = (counts.split() + ["?", "?"])[:2]
        age = _age_days(date)
        detail = (f"{short(tip)} · {ahead_main} ahead/{behind_main} behind {default} · last "
                  f"commit {date[:10]} ({age} d) · owner {owner or 'unknown'}")
        judged = evidence.judge(checkout, slug, name, tip) if slug else {
            "state": "unknown", "why": "not a GitHub origin"}
        # PUBLISHED MEANS ORIGIN'S OWN REF FOR IT HOLDS THE TIP (#170 G7): a
        # branch made from origin/main TRACKS main, so its configured upstream
        # says "ahead" even once it is pushed under its own name.
        published = _on_origin_as(checkout, name, tip)
        it = None
        if judged["state"] == "open":
            # THE DELETE PROTECTION STAYS; THE COUNT DOES NOT HIDE IT (#170 G2):
            # a lane-owned branch an open PR names, holding commits origin's
            # ref for it lacks, is unfinished work, and a read that failed is
            # never "finished".
            it = Item("branch", name, "keep", judged["why"] + ": an open PR's branch is never touched"
                      + ("" if published is True else
                         "; origin's ref for it lacks its tip" if published is False else
                         "; whether origin holds its tip could not be read"),
                      ours and published is not True, detail)
        elif judged["state"] == "merged":
            remote_tip = git_out(checkout, "rev-parse", "-q", "--verify",
                                 f"refs/remotes/origin/{name}") or ""
            head = judged.get("head", "")
            remote_ok = bool(remote_tip) and ours and not judged.get("gh_open") and (
                remote_tip == head or git(checkout, "merge-base", "--is-ancestor", remote_tip,
                                          head).returncode == 0)
            it = Item("branch", name, "delete+remote" if remote_ok else "delete",
                      judged["why"] + ("; the remote branch too (this lane's, nothing beyond the PR)"
                                       if remote_ok else ""), ours, detail)
            it.extra.update(pr=judged.get("pr"), tip=tip, remote_tip=remote_tip if remote_ok else "")
        elif published is True:
            continue
        else:
            up_state = "missing"
            if upstream:
                up_tip = git_out(checkout, "rev-parse", "-q", "--verify", f"refs/remotes/{upstream}") \
                    if not upstream.startswith("refs/") else None
                if up_tip:
                    c = (git_out(checkout, "rev-list", "--left-right", "--count",
                                 f"{up_tip}...{tip}") or "").split()
                    if len(c) == 2 and all(x.isdigit() for x in c):
                        b, a = int(c[0]), int(c[1])
                        up_state = ("diverged" if a and b else "ahead" if a else
                                    "behind" if b else "published")
                    else:
                        # NEVER "0 0" FOR UNKNOWN: that would read as published
                        # and drop the branch from the list.
                        up_state = "unreadable (git rev-list failed)"
                else:
                    up_state = "missing"
            if up_state in ("published", "behind"):
                continue
            it = Item("branch", name, "list", f"unmerged ({judged.get('why', '')}); upstream "
                      f"{upstream or 'none'} is {up_state}: a person disposes of it (push it to "
                      f"rescue/{safe_ref_part(lane)}/..., or a word) - never deleted", ours, detail)
        it.extra.update(checkout=checkout, branch=name)
        out.append(it)
    return out


def _on_origin_as(checkout: str, name: str, tip: str):
    """True when `refs/remotes/origin/<name>` holds `tip`, False when it is
    absent or does not, None when that could not be read."""
    ref = git(checkout, "rev-parse", "-q", "--verify", f"refs/remotes/origin/{name}")
    if ref.returncode == 1 and not ref.stdout.strip():
        return False
    if ref.returncode != 0:
        return None
    remote = ref.stdout.strip()
    if remote == tip:
        return True
    anc = git(checkout, "merge-base", "--is-ancestor", tip, remote)
    return {0: True, 1: False}.get(anc.returncode)


def _age_days(iso: str) -> int:
    try:
        when = _dt.datetime.fromisoformat(iso.replace("Z", "+00:00"))
        return max(0, (_dt.datetime.now(_dt.timezone.utc) - when).days)
    except ValueError:
        return 0


def sandbox_roots() -> list:
    named = os.environ.get("LANE_WORKTREES_SANDBOX_ROOTS", "")
    if named:
        return [p for p in named.split(os.pathsep) if p]
    roots = []
    for cand in ("/tmp", os.environ.get("TMPDIR", ""), tempfile.gettempdir()):
        if cand and os.path.isdir(cand) and real(cand) not in [real(r) for r in roots]:
            roots.append(cand)
    return roots


#: `tests/run.sh` writes its pid here in every run root it makes.
RUN_MARKER = ".openrepotools-run"


def sandbox_provenance(path: str) -> tuple:
    """(which suite marked `path`, the pid the mark names) - pytest's `.lock`
    in a numbered directory, `tests/run.sh`'s marker, or run.sh's run-root
    layout (`basetemp/` beside `tmp/`) - or ('', 0): no proof a suite made
    it."""
    for name, what in ((".lock", "a pytest .lock"), (RUN_MARKER, "tests/run.sh's marker")):
        # A MARK THAT NAMES NO PID PROVES NOTHING (#174, Copilot round 2): an
        # empty or malformed `.lock` made a person's `tmp.*` a suite's, past
        # the 14-day rule and the repository guard.
        pid = _lock_pid(os.path.join(path, name))
        if pid > 0:
            return what, pid
    if os.path.isdir(os.path.join(path, "basetemp")) and os.path.isdir(os.path.join(path, "tmp")):
        return "tests/run.sh's run-root layout", 0
    return "", 0


def _size_and_newest(path: str) -> tuple:
    """(bytes, newest mtime) of everything under `path`, itself included: a
    sandbox written to last week in a subdirectory is not a month old."""
    total, newest = 0, 0.0
    try:
        newest = os.lstat(path).st_mtime
    except OSError:
        pass
    for base, dirs, files in os.walk(path, followlinks=False):
        for name in files + dirs:
            try:
                st = os.lstat(os.path.join(base, name))
            except OSError:
                continue
            total += st.st_size
            newest = max(newest, st.st_mtime)
    return total, newest


def sandbox_items(procs: ProcScan, conf: dict) -> list:
    """Killed-suite sandboxes: `tmp.*` and `pytest-of-$USER/pytest-*` whose
    owning process is gone, and `tests/run.sh`'s own run roots. They hold no
    work; one a live process stands in, holds open or names in its
    environment is never touched, and neither is one younger than the
    minimum age (`sandbox_min_age_minutes`).

    A `tmp.*` IS LISTED AND NEVER REMOVED (#170 item 1, option (b); #174
    review R2): `mktemp -d` records no owner, so nothing proves a suite made
    one - any `tmp.*` may be a person's checkout or saved scratch. Only
    `pytest-of-$USER/pytest-*` and run.sh's `<UTC>-<pid>` run roots are
    removed."""
    out = []
    unreadable: list = []
    uid = os.getuid()
    procs.load_environ()
    min_age = conf["sandbox_min_age_minutes"] * 60
    user = getpass.getuser()
    candidates = []
    for root in sandbox_roots():
        try:
            names = os.listdir(root)
        except OSError:
            continue
        for name in names:
            full = os.path.join(root, name)
            if name.startswith("tmp.") and os.path.isdir(full) and not os.path.islink(full):
                candidates.append((full, "mktemp", 0))
            if name == f"pytest-of-{user}" and os.path.isdir(full):
                try:
                    subs = os.listdir(full)
                except OSError as exc:
                    # UNREADABLE IS A ROW, never a traceback and never "empty".
                    it = Item("sandbox", full, "keep", "its pytest sandboxes could not be listed ("
                              + (exc.strerror or str(exc)) + "), so none of them is touched",
                              False)
                    unreadable.append(it)
                    continue
                for sub in subs:
                    sp = os.path.join(full, sub)
                    if sub.startswith("pytest-") and os.path.isdir(sp) and not os.path.islink(sp):
                        candidates.append((sp, "pytest", _lock_pid(os.path.join(sp, ".lock"))))
    runs = os.path.join(state_dir(), "tmp")
    try:
        for name in os.listdir(runs):
            full = os.path.join(runs, name)
            if os.path.isdir(full) and not os.path.islink(full):
                m = re.search(r"-(\d+)$", name)
                candidates.append((full, "run.sh", int(m.group(1)) if m else 0))
    except OSError:
        pass
    now = time.time()
    for path, kind, pid in candidates:
        try:
            st = os.lstat(path)
        except OSError:
            continue
        if st.st_uid != uid:
            continue
        prov = ""
        if kind == "mktemp":
            prov, pid = sandbox_provenance(path)
        if pid and pid_alive(pid):
            continue
        if not procs.available or procs.holders(path) or procs.unknown_for(path):
            continue
        if kind == "mktemp":
            size, newest = _size_and_newest(path)
            age = now - newest
            if age < min_age:
                continue
            # EVERY `tmp.*` IS LISTED, NEVER REMOVED (#170 item 1, option (b);
            # #174 review R2). Untouched for aging_days was the one rule that
            # removed a real one, and it removed a person's tmp.* holding notes
            # and one holding a BARE repository with the only copy of a commit,
            # with no archive; a mark is a file anybody may have left, and
            # run.sh writes its own in its run roots, never in a tmp.*.
            inner = repository_within(path)
            mark = (f"{prov}" + (f" (pid {pid}, gone)" if pid else "")) if prov else (
                "no mark of a test suite (no pytest .lock, no tests/run.sh marker or run-root "
                "layout)")
            it = Item("sandbox", path, "list", f"a tmp.* directory with {mark}, last written "
                      f"{int(age // 86400)} d ago" + (f", and {inner}" if inner else "")
                      + ": `mktemp -d` records no owner, so it may be a person's checkout or "
                      "scratch - listed, never removed (#170 item 1)", False)
            it.size = size
        else:
            it = Item("sandbox", path, "remove", f"a {kind} sandbox whose owning process is gone"
                      + (f" (pid {pid})" if pid else ""), False)
            it.size = dir_size(path)
        it.extra["kind"] = kind
        out.append(it)
    return out + unreadable


def _lock_pid(path: str) -> int:
    try:
        with open(path, encoding="utf-8", errors="surrogateescape") as fh:
            text = fh.read().strip()
        return int(text) if text.isdigit() else 0
    except (OSError, ValueError):
        return 0


def link_items(root: str) -> list:
    """`--links`: every symlink under the estate and every worktree `.git`
    gitdir pointer, checked to resolve; the broken ones, with target and age.

    A `scandir` walk: an entry's kind comes from the directory read itself, so
    only a symlink or a `.git` FILE costs a call of its own - the estate is tens
    of thousands of directories, and `os.walk`'s `lstat` of each was minutes."""
    out = []
    prune = set(CACHE_NAMES) | set(VENV_NAMES) | {".git", ".tox", ".nox"}
    now = time.time()
    stack = [root]
    while stack:
        d = stack.pop()
        try:
            entries = list(os.scandir(d))
        except OSError:
            continue
        for e in entries:
            try:
                if e.is_symlink():
                    if not os.path.exists(e.path):
                        try:
                            target = os.readlink(e.path)
                            age = int((now - os.lstat(e.path).st_mtime) // 86400)
                        except OSError:
                            target, age = "?", 0
                        out.append(Item("link", e.path, "broken", f"symlink to {target} that "
                                        f"does not resolve ({age} d old)", False))
                    continue
                if e.name == ".git":
                    if e.is_file(follow_symlinks=False):
                        _gitdir_pointer(e.path, d, now, out)
                    continue
                if e.name in prune:
                    continue
                if e.is_dir(follow_symlinks=False):
                    stack.append(e.path)
            except OSError:
                continue
    return sorted(out, key=lambda i: i.path)


def _gitdir_pointer(path: str, base: str, now: float, out: list) -> None:
    try:
        with open(path, encoding="utf-8", errors="surrogateescape") as fh:
            first = fh.readline().strip()
    except OSError:
        return
    if not first.startswith("gitdir:"):
        return
    target = first[7:].strip()
    resolved = target if os.path.isabs(target) else os.path.join(base, target)
    if not os.path.isdir(resolved):
        try:
            age = int((now - os.lstat(path).st_mtime) // 86400)
        except OSError:
            age = 0
        out.append(Item("link", path, "broken", f"gitdir pointer to {target} that does "
                        f"not resolve ({age} d old)", False))


# ================================================================== expire

def expiry_rows(conf: dict) -> list:
    """(archive, age in days, keep|expire, why) for every archive under the
    sweeps directory - `sweep --expire`'s table, and the report's. An archive
    past retention is `expire` only while it is whole (`archive_ledger`) and
    every rescue branch it records is on origin at the rescued SHA; one whose
    rescue left or moved is the only copy and is kept."""
    root = sweeps_dir()
    keep_days = conf["retention_days"]
    now = _dt.datetime.now(_dt.timezone.utc)
    rows = []
    try:
        lanes = sorted(os.listdir(root))
    except OSError:
        lanes = []
    for lane in lanes:
        ldir = os.path.join(root, lane)
        if not os.path.isdir(ldir):
            continue
        for stamp in sorted(os.listdir(ldir)):
            adir = os.path.join(ldir, stamp)
            if not os.path.isdir(adir):
                continue
            m = STAMP_RE.match(stamp)
            if m:
                when = _dt.datetime.strptime(m.group(1), "%Y%m%dT%H%M%SZ").replace(
                    tzinfo=_dt.timezone.utc)
            else:
                when = _dt.datetime.fromtimestamp(os.stat(adir).st_mtime, _dt.timezone.utc)
            age = (now - when).days
            if age < keep_days:
                rows.append((adir, age, "keep", f"{age} d old; retention {keep_days} d"))
                continue
            ledger, why = archive_ledger(adir)
            if ledger is None:
                rows.append((adir, age, "keep", why + " - its rescues cannot be checked, so it "
                             "is kept"))
                continue
            missing = []
            for origin, branch, sha in ledger:
                if origin == "-" or branch.startswith("bundle:"):
                    # A BUNDLE THAT IS THE ONLY COPY (#170 item 8): nothing on
                    # any origin holds what it holds.
                    missing.append(f"{branch} (that bundle is the only copy of {short(sha)})")
                    continue
                proc = run(["git", "ls-remote", origin, f"refs/heads/{branch}"], env=git_env(),
                           timeout=GIT_NET_TIMEOUT)
                got = proc.stdout.split()[0] if proc.returncode == 0 and proc.stdout.split() else ""
                if proc.returncode != 0:
                    missing.append(f"{branch} (origin could not be read)")
                elif not got:
                    missing.append(f"{branch} (gone from origin)")
                elif got != sha:
                    # THE RESCUED COMMIT, NOT THE NAME: a branch reset or remade
                    # at another commit no longer holds the work.
                    missing.append(f"{branch} (on origin at {short(got)}, not the rescued "
                                   f"{short(sha)})")
            if missing:
                rows.append((adir, age, "keep", "not every rescue is on origin: "
                             + "; ".join(missing) + " - this archive is the only copy"))
            else:
                rows.append((adir, age, "expire", f"{age} d old and every rescue branch is on origin"))
    return rows


def expire(opts) -> int:
    conf = load_conf()
    root = sweeps_dir()
    keep_days = conf["retention_days"]
    rows = expiry_rows(conf)
    failed = 0
    for i, (adir, age, disp, why) in enumerate(rows):
        if disp == "expire" and opts.yes:
            try:
                rmtree(adir)
                rows[i] = (adir, age, "expired", why)
                os.makedirs(root, exist_ok=True)
                with open(os.path.join(root, "EXPIRED.log"), "a", encoding="utf-8", errors="surrogateescape") as fh:
                    fh.write(f"{utc_now()}\t{adir}\t{why}\n")
            except OSError as exc:
                rows[i] = (adir, age, "FAILED", str(exc))
                failed += 1
    if opts.porcelain:
        for adir, age, disp, why in rows:
            prow("archive", disp, adir, age, why)
    else:
        say(f"{PROG} sweep --expire - " + ("YES" if opts.yes else "DRY RUN") +
            f" - retention {keep_days} d ({conf_path()})")
        if not rows:
            say("  (no archives under " + tilde(root) + ")")
        for adir, age, disp, why in rows:
            say(f"  {disp:<8} {tilde(adir)} · {why}")
    if failed:
        return 1
    if not opts.yes and any(r[2] == "expire" for r in rows):
        return 3
    return 0


SHA_RE = re.compile(r"^[0-9a-f]{40}([0-9a-f]{24})?$")


def archive_ledger(adir: str):
    """(rescues, why): the archive's rescue ledger, or None with why it cannot
    be trusted. An archive is trusted only when it is WHOLE: `MANIFEST.sha256`
    is there and every file in the archive is in it at its digest (an
    interrupted sweep leaves a bundle the ledger never recorded), and
    `rescues.tsv` is there and every row of it is origin, branch and a full
    SHA. A header-only ledger in a whole archive is a verified "no rescues"."""
    manifest = os.path.join(adir, "MANIFEST.sha256")
    listed: dict = {}
    try:
        with open(manifest, encoding="utf-8", errors="surrogateescape") as fh:
            for line in fh:
                if not line.strip():
                    continue
                digest, sep, rel = line.rstrip("\n").partition("  ")
                if not sep or not re.match(r"^[0-9a-f]{64}$", digest):
                    return None, "its MANIFEST.sha256 is malformed"
                listed[rel] = digest
    except OSError:
        return None, "it has no readable MANIFEST.sha256 (the sweep that made it did not finish)"
    for rel in listed:
        if not os.path.isfile(os.path.join(adir, rel)):
            return None, f"{rel} is in its MANIFEST.sha256 and no longer in it"
    for base, _dirs, files in os.walk(adir):
        for name in files:
            full = os.path.join(base, name)
            rel = os.path.relpath(full, adir)
            if rel == "MANIFEST.sha256":
                continue
            if rel not in listed:
                return None, f"{rel} is in it and not in its MANIFEST.sha256"
            try:
                with open(full, "rb") as fh:
                    if hashlib.sha256(fh.read()).hexdigest() != listed[rel]:
                        return None, f"{rel} does not match its MANIFEST.sha256"
            except OSError:
                return None, f"{rel} could not be read"
    if "rescues.tsv" not in listed:
        return None, "it has no rescues.tsv"
    out = []
    try:
        with open(os.path.join(adir, "rescues.tsv"), encoding="utf-8", errors="surrogateescape") as fh:
            for line in fh:
                if line.startswith("#") or not line.strip():
                    continue
                cols = line.rstrip("\n").split("\t")
                if len(cols) < 3 or not cols[0] or not cols[1] or not SHA_RE.match(cols[2]):
                    return None, "its rescues.tsv has a row that is not origin, branch, sha"
                out.append((cols[0], cols[1], cols[2]))
    except OSError:
        return None, "its rescues.tsv could not be read"
    return out, ""


# ================================================================== report

EVIDENCE_NAME_RE = re.compile(
    r"(^canary-|^deployment-evidence$|-evidence$|^evidence$|-report\.md$|REPORT[^/]*\.md$|"
    r"^junit[^/]*\.xml$|^MANIFEST)", re.IGNORECASE)
HANDOFF_PATHS = ("handoffs/", "lanes/")
#: `rescue/<lane>/<slice>-<UTC>` names its lane; a rescue branch of another
#: convention (`rescue/<slug>`) names none.
LANE_SHAPED_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*-[0-9]+$")


def estate_root_default() -> str:
    for cand in (os.environ.get("PROJECTS_ROOT", ""), os.path.join(_home(), "projects"),
                 os.path.join(_home(), "Projects")):
        if cand and os.path.isdir(cand):
            return cand
    return ""


def _under_container(path: str, root: str) -> str:
    """The worktree container a path sits in - `.lane-worktrees/<lane>`,
    `.claude/worktrees`, `worktrees`, `<x>-worktrees` - or ''."""
    rel = os.path.relpath(path, root)
    parts = rel.split(os.sep)
    for i, p in enumerate(parts[:-1]):
        if p == ".lane-worktrees" and i + 1 < len(parts) - 1:
            return os.sep.join(parts[:i + 2])
        if p in ("worktrees", ".worktrees") or p.endswith("-worktrees"):
            return os.sep.join(parts[:i + 1])
    return ""


def _du(paths: list) -> dict:
    """{path: bytes} through `du -sk`, which walks in C: an estate's ignored
    trees are millions of files, and a Python walk of them is minutes."""
    out: dict = {}
    for i in range(0, len(paths), 64):
        chunk = paths[i:i + 64]
        proc = run(["du", "-sk", "--", *chunk], timeout=600)
        for line in proc.stdout.splitlines():
            size, _, path = line.partition("\t")
            if size.isdigit():
                out[path] = int(size) * 1024
    return out


def _lane_parents(root: str, repos: list) -> list:
    """Every directory a lane's control root and worktree root can sit in.
    #97 puts `.lane-state/<lane>` - and lane-start `.lane-worktrees/<lane>` -
    beside the lane's recorded `dir`, so a lane whose checkout is nested
    (`<estate>/InkRouter/IRRS`) keeps them in `<estate>/InkRouter`, not at
    the estate's top."""
    out = [root] if root else []
    for r in repos:
        parent = os.path.dirname(r.rstrip(os.sep))
        if parent and parent not in out:
            out.append(parent)
    return out


def _lane_state_dirs(parents: list) -> list:
    dirs = []
    cands = [os.environ.get("LANES_LANE_STATE_ROOT", "")]
    cands += [os.path.join(p, ".lane-state") for p in parents]
    for cand in cands:
        if cand and os.path.isdir(cand) and cand not in dirs:
            dirs.append(cand)
    return dirs


def _lane_tree_roots(parents: list) -> list:
    """`<parent>/.lane-worktrees`, each that exists."""
    out = []
    for p in parents:
        cand = os.path.join(p, ".lane-worktrees")
        if os.path.isdir(cand) and cand not in out:
            out.append(cand)
    return out


class Report:
    """`sweep --all --dry-run --report`: the estate's leftovers as one
    document. It reads every lane's #97 inventory, the workspace register and
    the disk; it changes nothing."""

    def __init__(self, opts):
        self.opts = opts
        self.conf = load_conf()
        self.root = opts.estate or estate_root_default()
        self.helper = Helper()
        self.sections: list = []
        self.counts: dict = {}
        self.now = time.time()
        self.unreadable: dict = {}
        # WHAT BEARS ON THE LANES' RECORDS, apart (#179, Copilot round 1): an
        # orphan or aging section empty beside an unread snapshot, inventory,
        # register line or inventory tree may be missing rows; an ignored-file
        # listing or a canonical checkout's registrations that failed say
        # nothing of either, and must not turn their "none" into "unknown".
        self.records_unread: dict = {}

    def _unread(self, key: str, why: str, record: bool = True) -> None:
        """A read that failed: a row of "Records the report could not read",
        and - where it is one of the lanes' records - one an empty orphan or
        aging section names."""
        self.unreadable.setdefault(key, why)
        if record:
            self.records_unread.setdefault(key, why)

    def add(self, title: str, rows: list, empty: str, note_text: str = "",
            partial: bool = False) -> None:
        """`partial`: the section is read from the lanes' records, so an
        empty one says "none found" only beside what could not be read."""
        self.sections.append((title, rows, empty, note_text, partial))
        self.counts[title] = len(rows)

    def _recorded_parents(self) -> list:
        """Every directory a `.lane-state` stands in beyond the checkouts'
        parents (#170 B8). Two sources, and what neither could read is a
        finding:

          * a WALK of the estate, every directory but the caches: a lane whose
            checkout sits in a plain directory inside a repository
            (`xFactory/xFactories/<x>` on Eagle) keeps `.lane-state` there,
            and `estate_repos` never enters such a directory;
          * the register: each lane's recorded `dir`, whose parent #97 puts
            the lane's control root in, wherever it is."""
        out: list = []
        if self.root:
            walk = walk_estate(self.root)
            for d in walk.lane_states:
                out.append(os.path.dirname(d))
            for what in walk.unread:
                self._unread(what, "could not be listed: a lane's records under it are not read")
        if self.helper.path:
            dirs, why = register_lane_dirs(self.helper)
            if why:
                self._unread("the register's lane logs", why)
            for d in dirs.values():
                out.append(os.path.dirname(d.rstrip(os.sep)))
        return [p for i, p in enumerate(out) if p and p not in out[:i]]

    # ------------------------------------------------------------- reading
    def build(self) -> None:
        repos = estate_repos(self.root) if self.root else []
        self.deps = dependents_map(repos)
        clones = [r for r in repos if os.path.isdir(os.path.join(r, ".git"))]
        canonical = self._canonical(clones)
        self.canonical = canonical
        # T018: EVERY CANONICAL ASSEMBLY'S LEGS, each a checkout of its own.
        self.legs = self._assembly_legs(canonical)
        parents = _lane_parents(self.root, canonical) + self._recorded_parents()
        self.state_dirs = _lane_state_dirs(parents)
        self.tree_roots = _lane_tree_roots(parents)
        self._foreign(clones, canonical)
        closed = self._closed_lanes()
        self._orphans(closed)
        self._branches(canonical)
        self._root_main(canonical)
        if self.legs:
            self._legs_section()
        self._aging(canonical)
        self._caches_and_sandboxes()
        self._ignored_and_evidence(repos, canonical)
        self._archives()
        self._workspace_hygiene()
        self._status_all()
        self.add("Records the report could not read", [[p, w] for p, w in
                                                      sorted(self.unreadable.items())], "none",
                 "UNKNOWN IS NEVER ABSENT: a lane snapshot, inventory or tree the report "
                 "could not read is one whose findings above may be missing.")

    # A READ THAT FAILED IS A FINDING, never an empty answer (Amendment 7(d)):
    # an inventory the report could not read would otherwise make it clean.
    def _ls(self, d: str, record: bool = True) -> list:
        try:
            return sorted(os.listdir(d))
        except (FileNotFoundError, NotADirectoryError):
            return []
        except OSError as exc:
            self._unread(d, f"could not be listed ({exc.strerror or exc})", record)
            return []

    def _record(self, path: str, need_path: bool = False) -> dict:
        try:
            with open(path, encoding="utf-8") as fh:
                text = fh.read()
        except FileNotFoundError:
            return {}
        except (OSError, UnicodeDecodeError) as exc:
            self._unread(path, "could not be read ("
                         + (getattr(exc, "strerror", "") or str(exc)) + ")")
            return {}
        out = {}
        for line in text.splitlines():
            key, sep, value = line.partition(": ")
            if sep:
                out[key.strip()] = value.strip()
        if need_path and not out.get("path"):
            self._unread(path, "an inventory record that names no path")
        return out

    def _tree_records(self, tdir: str) -> list:
        return [self._record(os.path.join(tdir, f), need_path=True) for f in self._ls(tdir)
                if f.endswith(".yaml") and not f.startswith(".")]

    def _canonical(self, clones: list) -> list:
        """The estate's own checkouts: a clone outside every worktree
        container. Of several clones of one origin, the one whose folder is
        named for the repository is the checkout and the rest are duplicates."""
        by_origin: dict = {}
        for c in clones:
            if _under_container(c, self.root):
                continue
            url = git_out(c, "config", "--get", "remote.origin.url") or ""
            key = (slug_of(url) or url).lower().rstrip("/")
            by_origin.setdefault(key, []).append(c)
        self.duplicates: dict = {}
        out = []
        for key, group in by_origin.items():
            if not key or len(group) == 1:
                out.extend(group)
                continue
            name = key.rsplit("/", 1)[-1].replace(".git", "")
            named = [g for g in group if os.path.basename(g).lower() == name]
            # ONE CHECKOUT PER ORIGIN, CHOSEN DETERMINISTICALLY: the one named
            # for the repository, the shallowest of several so named, and the
            # shallowest of all where none is. The rest are duplicates - never
            # all of them canonical, which would hide the finding.
            pool = named or group
            keep = sorted(pool, key=lambda g: (g.count(os.sep), g))[0]
            out.append(keep)
            for g in group:
                if g != keep:
                    self.duplicates[g] = (keep, len(named) != 1)
        return sorted(out)

    def _assembly_legs(self, canonical: list) -> list:
        """T018 (opensoft/openRepoTools#186): THE LEGS OF EVERY CANONICAL
        ASSEMBLY - a checkout whose own `project.yaml` declares a leg other than
        itself, read by `lanes-edit.sh project-legs`. A leg is a submodule, its
        `.git` a FILE, so no leg was ever canonical and the branches all lane
        work makes after a split never reached this report. Each is read as a
        checkout of its own, its rows naming its assembly. A checkout with no
        `project.yaml` is asked nothing, so a report of single repositories is
        what it always was; a manifest that could not be read is a row of the
        last section."""
        legs = []
        for c in canonical:
            rows, why = project_legs(self.helper, c)
            if why:
                self._unread(c, f"its legs could not be read: {why}", record=False)
                continue
            for repository, decl, path in rows:
                leg = Leg(path, decl, repository, c)
                probe_leg(leg)
                if leg.checkout or leg.read_error:
                    legs.append(leg)
        return legs

    def _leg_cell(self, path: str) -> str:
        """'' for a canonical checkout; for a leg, which assembly it is under -
        by its real path too, because `_checkouts` keeps the FIRST spelling of
        a checkout that is both, and that is the canonical one."""
        legs = getattr(self, "legs", [])
        if not legs:
            return ""
        for g in legs:
            if g.path == path:
                return f"the {g.decl} leg of {g.assembly}"
        rp = real(path)
        for g in legs:
            if g.real == rp:
                return f"the {g.decl} leg of {g.assembly}"
        return ""

    def _checkouts(self, canonical: list) -> list:
        """The checkouts the branch, `main` and rescue sections read: every
        canonical checkout, then every leg checked out here (T018), EACH ONCE
        BY ITS REAL PATH, in that order. A leg whose `.git` is a directory - a
        clone, not a submodule's gitfile - is also one of the estate's
        canonical checkouts, and read once as each it printed every branch row
        and its `main` row twice (the read-only triad measurement of
        2026-10-17, finding 1). With no leg checked out it is `canonical`
        itself, untouched, so a report of single repositories cannot move."""
        legs = [g.path for g in getattr(self, "legs", []) if g.checkout]
        if not legs:
            return list(canonical)
        out, seen = [], set()
        for c in canonical + legs:
            r = real(c)
            if r in seen:
                continue
            seen.add(r)
            out.append(c)
        return out

    def _legs_section(self) -> None:
        """T018: every leg checkout holding edits no commit carries or commits
        no origin ref holds - or that could not be read - under its assembly."""
        rows = []
        for g in self.legs:
            read_leg(g, "")
            decide_leg(g)
            if g.disposition in ("clean", "absent"):
                continue
            state = (g.read_error if g.disposition == "unreadable" else
                     "; ".join(p for p in (
                         f"{g.dirty} dirty or untracked path(s)" if g.dirty else "",
                         ("HEAD carries commit(s) no origin ref holds"
                          + (f" ({g.ahead} ahead of {g.upstream})" if g.upstream and g.ahead
                             else "")) if g.head and g.on_origin is False else "") if p))
            rows.append([g.path, f"the {g.decl} leg of {g.assembly}", g.label(), state])
        self.add("Assembly legs with uncommitted or unpublished work", rows,
                 "none: every leg checkout is clean and its HEAD is on origin",
                 "A leg is a checkout of its own under its assembly (T018): it is the "
                 "assembly's, shared by every lane it homes, and a person's to commit and push.")

    def _foreign(self, clones: list, canonical: list) -> None:
        rows = []
        for c in clones:
            container = _under_container(c, self.root)
            why = ""
            if container:
                why = f"a CLONE inside {container}, where a lane makes worktrees"
            elif c in self.duplicates:
                first, ambiguous = self.duplicates[c]
                why = f"a second clone of {first}'s origin" + (
                    " (no single clone is named for the repository; the shallowest is "
                    "taken as its checkout)" if ambiguous else "")
            if not why:
                continue
            # `%cd --date=short`, never `%cs`: `%cs` is git 2.21's, and an older git
            # prints the placeholder itself where the date belongs (#170 (d)).
            last = git_out(c, "log", "-1", "--date=short", "--format=%cd") or "?"
            msg = git_out(c, "log", "-1", "--format=%B") or ""
            owner = (re.findall(r"^Lane:\s*(\S+)", msg, re.MULTILINE) or ["-"])[-1]
            lean = leaned_on(c, git_common_dir(c), self.deps)
            rows.append([c, why, human_size(dir_size(c)), last, owner,
                         ("LOAD-BEARING: " + _lean_text(lean)) if lean else ""])
        self.add("FOREIGN repositories", rows, "none: every clone is the estate's own checkout",
                 "A lane creates worktrees, never clones. Each is a person's to dispose of.")

    def _closed_lanes(self) -> dict:
        """{lane: why} for every lane whose snapshot is CLOSED or whose log's
        last lane-kind line is ENDED or RETIRED."""
        closed: dict = {}
        for d in self.state_dirs:
            for lane in self._ls(d):
                if self._record(os.path.join(d, lane, "lane-state.yaml")).get("state") == "CLOSED":
                    closed[lane] = "its snapshot is CLOSED"
        wroot = self.helper.workspace_root() if self.helper.path else ""
        if wroot:
            branch = os.environ.get("LANES_BRANCH", "main")
            proc = git(wroot, "grep", "-E", "-e",
                       "^(STARTED|RESUMED|PAUSED|ENDED|RETIRED) — lane ",
                       f"origin/{branch}", "--", "lanes/log")
            if proc.returncode not in (0, 1):
                # A GREP THAT FAILED IS NO "NO EVENTS" (#170 G3): exit 1 is no
                # match; anything else - a ref that is not there, a repository
                # git cannot read - leaves every log-only ENDED lane unknown.
                self._unread(f"the register's ENDED and RETIRED lines (origin/{branch})",
                             f"git grep exited {proc.returncode}: " + _first_line(proc.stderr))
            last: dict = {}
            for line in proc.stdout.splitlines():
                _ref, _, rest = line.partition(":")
                fname, _, text = rest.partition(":")
                last[os.path.basename(fname)[:-3]] = text.split(" ", 1)[0]
            for lane, verb in last.items():
                if verb in ("ENDED", "RETIRED"):
                    closed.setdefault(lane, f"its log ends {verb}")
        return closed

    def _orphans(self, closed: dict) -> None:
        rows = []
        seen = set()
        for d in self.state_dirs:
            for lane in sorted(closed):
                for rec in self._tree_records(os.path.join(d, lane, "trees")):
                    path = rec.get("path", "")
                    if path and os.path.isdir(path) and real(path) not in seen:
                        seen.add(real(path))
                        rows.append([path, lane, closed[lane], "lane-worktrees sweep " + lane])
        for lane, lroot in [(ln, r) for ln in sorted(closed) for r in self.tree_roots]:
            ldir = os.path.join(lroot, lane)
            for n in self._ls(ldir):
                full = os.path.join(ldir, n)
                if os.path.isdir(full) and real(full) not in seen:
                    seen.add(real(full))
                    rows.append([full, lane, closed[lane], f"lane-worktrees sweep {lane} "
                                 "--include-scratch --include-foreign --word \"<yours>\""])
        self.add("Orphaned worktrees of ENDED lanes", rows, "none", partial=True)

    def _branches(self, canonical: list) -> None:
        rows = []
        fmt = ("%(refname:short)%1f%(objectname:short=12)%1f%(upstream:short)%1f"
               "%(upstream:track)%1f%(committerdate:iso-strict)%1f%(contents:trailers)%00")
        for c in self._checkouts(canonical):
            proc = git(c, "for-each-ref", f"--format={fmt}", "refs/heads/")
            if proc.returncode != 0:
                continue
            regs = worktree_registrations(c)
            if regs is None:
                self._unread(c, "its worktree registrations could not be listed", record=False)
                regs = []
            held = {r.get("branch", "")[len("refs/heads/"):] for r in regs if r.get("branch")}
            for rec in proc.stdout.split("\0"):
                cols = rec.strip("\n").split("\x1f")
                if len(cols) < 6:
                    continue
                name, tip, up, track, date, trailers = cols[:6]
                if name in PROTECTED_BRANCHES or name.startswith("rescue/"):
                    continue
                if not up:
                    state = "no upstream"
                elif "gone" in track:
                    state = f"upstream {up} gone"
                elif "ahead" in track and "behind" in track:
                    state = f"diverged from {up} {track}"
                elif "ahead" in track:
                    state = f"unpushed {track}"
                else:
                    continue
                owner = (re.findall(r"^Lane:\s*(\S+)", trailers, re.MULTILINE) or ["-"])[-1]
                rows.append([f"{c} {name}", tip, state, f"{_age_days(date)} d",
                             owner, "held by a worktree" if name in held else ""]
                            + ([self._leg_cell(c)] if self._leg_cell(c) else []))
        rows.sort(key=lambda r: -int(r[3].split()[0]))
        self.add("Unmerged branches with a missing, diverged or unpushed upstream", rows,
                 "none", "Listed, never deleted: a person pushes each to rescue/<lane>/... "
                 "or gives a word.")

    def _root_main(self, canonical: list) -> None:
        rows = []
        for c in self._checkouts(canonical):
            if git(c, "rev-parse", "-q", "--verify", "refs/heads/main").returncode != 0 or \
                    git(c, "rev-parse", "-q", "--verify", "refs/remotes/origin/main").returncode != 0:
                continue
            n = git_out(c, "rev-list", "--count", "refs/remotes/origin/main..refs/heads/main") or "0"
            if not n.isdigit() or int(n) == 0:
                continue
            paths = (git_out(c, "diff", "--name-only", "refs/remotes/origin/main...refs/heads/main")
                     or "").splitlines()
            only = bool(paths) and all(p.startswith(HANDOFF_PATHS) for p in paths)
            rows.append([c, f"local main ahead of origin by {n}",
                         "only handoff/register paths" if only else f"{len(paths)} path(s)",
                         "Amendment 4: handoffs and the register live in the workspace "
                         "repository and are pushed per write" if only else
                         "push it through a pull request, or a person's word"]
                        + ([self._leg_cell(c)] if self._leg_cell(c) else []))
        self.add("Root main divergence", rows, "none")

    def _aging(self, canonical: list) -> None:
        aging = self.conf["aging_days"]
        rows = []
        rescue_count = 0
        seen = set()
        for c in self._checkouts(canonical):
            proc = git(c, "for-each-ref", "--format=%(refname)\t%(committerdate:iso-strict)",
                       "refs/heads/rescue/", "refs/remotes/origin/rescue/")
            for line in proc.stdout.splitlines():
                ref, _, date = line.partition("\t")
                name = ref.split("/rescue/", 1)[1]
                if (c, name) in seen:
                    continue
                seen.add((c, name))
                rescue_count += 1
                age = _age_days(date)
                if age >= aging:
                    first = name.split("/", 1)[0] if "/" in name else ""
                    owner = first if LANE_SHAPED_RE.match(first) else "-"
                    rows.append([f"{c} rescue/{name}", f"rescue branch, {age} d",
                                 owner, "awaiting disposition"]
                                + ([self._leg_cell(c)] if self._leg_cell(c) else []))
        for d in self.state_dirs:
            for lane in self._ls(d):
                for rec in self._tree_records(os.path.join(d, lane, "trees")):
                    path = rec.get("path", "")
                    if not path or not os.path.isdir(path):
                        continue
                    dirt = git_out(path, "status", "--porcelain")
                    if dirt is None:
                        self._unread(path, f"lane {lane}'s inventory tree; git status could "
                                           "not be read in it")
                        continue
                    if not dirt:
                        continue
                    newest = _gitdir_activity(path)
                    age = int((self.now - newest) // 86400) if newest else 0
                    if age >= aging:
                        rows.append([path, f"dirty inventory tree, {age} d", lane,
                                     "awaiting disposition"])
        self.rescue_count = rescue_count
        self.add(f"Awaiting disposition (older than {aging} days)", rows, "none", partial=True)

    def _caches_and_sandboxes(self) -> None:
        rows = []
        roots = []
        for lroot in getattr(self, "tree_roots", []):
            try:
                roots += [os.path.join(lroot, n) for n in sorted(os.listdir(lroot))]
            except OSError:
                pass
        for c in getattr(self, "canonical", []):
            wt = os.path.join(c, ".claude", "worktrees")
            if os.path.isdir(wt):
                roots.append(wt)
        for root in roots:
            for path, size in find_caches(root):
                rows.append([path, "cache", size])
        for it in sandbox_items(ProcScan(), self.conf):
            rows.append([it.path, "sandbox", it.size])
        rows.sort(key=lambda r: -r[2])
        total = sum(r[2] for r in rows)
        self.add(f"Caches and sandboxes ({human_size(total)} in all)",
                 [[p, k, human_size(s)] for p, k, s in rows], "none",
                 "Removed without archiving by `--include-caches` / `--include-sandboxes`.")

    def _ignored_and_evidence(self, repos: list, canonical: list) -> None:
        threshold = self.conf["ignored_report_mb"] * 1024 * 1024
        big, evidence = [], []
        for r in repos:
            proc = git(r, "ls-files", "--others", "--ignored", "--exclude-standard",
                       "--directory", "-z")
            if proc.returncode != 0:
                self._unread(r, "its ignored files could not be listed", record=False)
            # EVERY IGNORED ENTRY IS EVIDENCE-SHAPED OR NOT BY ITS NAME (#170 item
            # 9): an ignored `junit-results.xml` is a FILE, and keeping only the
            # directories (which `du` sizes) dropped it from the evidence match.
            entries = [p for p in proc.stdout.split("\0") if p] if proc.returncode == 0 else []
            ignored = [p.rstrip("/") for p in entries if p.endswith("/")]
            sizes = _du([os.path.join(r, p) for p in ignored]) if ignored else {}
            for path, size in sizes.items():
                if size >= threshold:
                    big.append([path, human_size(size)])
            # THE UNTRACKED LISTING IS READ LIKE THE IGNORED ONE (#179, Copilot
            # round 2): one that failed is a finding, and its stdout is never
            # taken for the repository's untracked files.
            unt = git(r, "ls-files", "--others", "--exclude-standard", "--directory", "-z")
            if unt.returncode != 0:
                self._unread(r, "its untracked files could not be listed", record=False)
            untracked = unt.stdout.split("\0") if unt.returncode == 0 else []
            for p in (untracked + entries):
                name = p.rstrip("/").rsplit("/", 1)[-1]
                if p and EVIDENCE_NAME_RE.search(name):
                    evidence.append([os.path.join(r, p.rstrip("/")), "untracked or ignored in "
                                     + os.path.basename(r)])
        if self.root:
            repo_set = set(repos)
            for n in self._ls(self.root, record=False):
                full = os.path.join(self.root, n)
                if full in repo_set or not os.path.isdir(full) or n.startswith(".lane-") \
                        or n.endswith("-worktrees"):
                    continue
                if EVIDENCE_NAME_RE.search(n) or self._holds_reports(full):
                    evidence.append([full, "a directory of reports beside the repositories"])
        big.sort(key=lambda r: r[0])
        self.add(f"Ignored directories over {self.conf['ignored_report_mb']} MB", big, "none")
        self.add("Evidence-shaped paths inside the estate", evidence, "none",
                 "One evidence root per repository: ${XDG_STATE_HOME:-$HOME/.local/state}/"
                 "openRepoTools/evidence/<repo>/<UTC>-<slug>/. Each is a finding to move.")

    @staticmethod
    def _holds_reports(d: str) -> bool:
        try:
            for e in os.scandir(d):
                if e.is_file() and EVIDENCE_NAME_RE.search(e.name):
                    return True
                if e.is_dir(follow_symlinks=False) and not e.name.startswith("."):
                    for f in os.scandir(e.path):
                        if f.is_file() and EVIDENCE_NAME_RE.search(f.name):
                            return True
        except OSError:
            return False
        return False

    def _archives(self) -> None:
        """`sweep --expire`'s dry run, folded in: what WOULD expire, and what
        is past retention but kept because its rescue left origin. Expiring
        stays an act (`sweep --expire --yes`); the report changes nothing."""
        root = sweeps_dir()
        keep = self.conf["retention_days"]
        rows = []
        for adir, age, disp, why in expiry_rows(self.conf):
            if disp == "expire":
                rows.append([adir, f"{age} d", "would expire: lane-worktrees sweep --expire --yes"])
            elif age >= keep:
                rows.append([adir, f"{age} d", "kept past retention: " + why])
        self.sweeps_size = dir_size(root) if os.path.isdir(root) else 0
        self.add(f"Sweep archives past retention ({keep} days)", rows, "none")

    def _workspace_hygiene(self) -> None:
        rows = []
        wroot = self.helper.workspace_root() if self.helper.path else ""
        if wroot:
            # SURROGATEESCAPE, as every other reader here (#170 G10): one byte that
            # is not UTF-8 in the workspace's `.gitignore` aborted the report.
            try:
                with open(os.path.join(wroot, ".gitignore"), encoding="utf-8",
                          errors="surrogateescape") as fh:
                    have = set(fh.read().splitlines())
            except OSError:
                have = set()
            want = ["__pycache__/", "*.pyc", "*.pyo", ".pytest_cache/", ".mypy_cache/",
                    ".ruff_cache/", "node_modules/", ".venv/", "venv/", "site-packages/"]
            missing = [w for w in want if w not in have]
            if missing:
                # THE PATH IS QUOTED (#170 item 10): the command is offered to be
                # pasted, and a workspace path with a space in it split the redirect.
                rows.append([os.path.join(wroot, ".gitignore"), "lacks " + " ".join(missing),
                             "printf '%s\\n' " + " ".join(f"'{m}'" for m in missing)
                             + " >> " + shlex.quote(os.path.join(wroot, ".gitignore"))])
            hist = git(wroot, "log", "--all", "--format=", "--name-only", "--",
                       "*.pyc", "*__pycache__*")
            paths = sorted({p for p in hist.stdout.splitlines() if p})
            if paths:
                rows.append([wroot, f"{len(paths)} bytecode path(s) in history, e.g. {paths[0]}",
                             "a person's word: history is rewritten only on one"])
        self.add("Workspace repository hygiene", rows, "none")

    def _status_all(self) -> None:
        # `LANE_WORKTREES_STATUS` names the estate command when it is not
        # beside this one (a test names a fake; a person, their own build).
        exe = os.environ.get("LANE_WORKTREES_STATUS", "")
        if not exe:
            exe = os.path.join(os.path.dirname(os.path.realpath(__file__)), "status")
            if not os.access(exe, os.X_OK):
                exe = shutil.which("status") or ""
        rows = []
        why = ""
        if not exe:
            why = "no `status` beside this command or on PATH"
        else:
            env = dict(os.environ)
            if self.root:
                # THE ESTATE THIS REPORT READ, however it was resolved: from
                # `--estate`, `$PROJECTS_ROOT` or the home default.
                env["PROJECTS_DIR"] = self.root
            proc = run([exe, "--all"], env=env, timeout=600)
            if proc.returncode not in (0, 1):
                why = f"`status --all` exited {proc.returncode}: " + _first_line(proc.stderr)
            context = ""
            for line in proc.stdout.splitlines():
                s = line.strip()
                if line.startswith("=== "):
                    context = s.strip("= ").replace("status ", "")
                elif s.startswith("- "):
                    rows.append([context, s[2:]])
        self.add("`status --all` findings (pin drift and tracking are reported as what they "
                 "are, never counted as dirt)", rows, "none", why)

    # ------------------------------------------------------------ writing
    def markdown(self) -> str:
        host = os.environ.get("LANES_WORKSTATION") or socket.gethostname()
        out = [f"# Estate report - {host} - {utc_now()}", "",
               f"`{PROG} sweep --all --dry-run --report` over `{self.root or '(no estate)'}`. "
               "It changed nothing.", "",
               "| finding | count |", "|---|---|"]
        for title, rows, _e, _n, _p in self.sections:
            out.append(f"| {title} | {len(rows)} |")
        out.append(f"| rescue branches (all ages) | {getattr(self, 'rescue_count', 0)} |")
        out.append(f"| the sweeps directory | {human_size(getattr(self, 'sweeps_size', 0))} |")
        for title, rows, empty, note_text, partial in self.sections:
            out += ["", f"## {title}", ""]
            if note_text:
                out += [note_text, ""]
            if not rows and partial and self.records_unread:
                # UNKNOWN IS NEVER "NONE" (#170 B8): a lane whose records could
                # not be read may hold exactly what this section looks for.
                out.append(f"_none found in what could be read; {len(self.records_unread)} "
                           "record(s) could not be read (see \"Records the report could not "
                           "read\")_")
                continue
            if not rows:
                out.append(f"_{empty}_")
                continue
            for r in rows[:500]:
                out.append("- " + " · ".join(str(c).replace("\n", " ") for c in r if c != ""))
            if len(rows) > 500:
                out.append(f"- ... and {len(rows) - 500} more")
        return "\n".join(out) + "\n"


def report_all(opts) -> int:
    rep = Report(opts)
    rep.build()
    text = rep.markdown()
    post = opts.post or ""
    if not post:
        sys.stdout.write(text)
        return 0
    m = re.match(r"^([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)#(\d+)$", post)
    if m:
        fd, tmp = tempfile.mkstemp(prefix="lane-worktrees-report.", suffix=".md")
        # SURROGATEESCAPE (#170 G11, G12): a row holding a path that is not
        # UTF-8 is that path's bytes, never an internal error before `gh` runs.
        with os.fdopen(fd, "w", encoding="utf-8", errors="surrogateescape") as fh:
            fh.write(text)
        try:
            proc = run(["gh", "issue", "comment", m.group(2), "--repo", m.group(1),
                        "--body-file", tmp], timeout=120)
        finally:
            os.unlink(tmp)
        if proc.returncode != 0:
            note(f"the report could not be posted to {post}: " + _first_line(proc.stderr))
            sys.stdout.write(text)
            return 1
        say(f"{PROG}: the report was posted to {post}: {proc.stdout.strip()}")
        return 0
    try:
        os.makedirs(os.path.dirname(os.path.abspath(post)), exist_ok=True)
        with open(post, "w", encoding="utf-8", errors="surrogateescape") as fh:
            fh.write(text)
    except OSError as exc:
        note(f"the report could not be written to {post}: {exc}")
        sys.stdout.write(text)
        return 1
    say(f"{PROG}: the report is at {post}")
    return 0


# ======================================================= add: the creation

#: A slice is one path component under the lane's own root, and the default
#: branch name: what a ref and a directory both take.
SLICE_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")


def git_common_dir_of(path: str) -> str:
    """The physical path of `path`'s repository's common git directory, or ''.
    A leg and every worktree of its repository share one, under the
    assembly's `.git/modules/`."""
    common = git_out(path, "rev-parse", "--git-common-dir")
    if not common:
        return ""
    if not os.path.isabs(common):
        common = os.path.join(path, common)
    return real(common)


def add_tree(ns) -> int:
    """`add <lane> <slice>` - the one act that MAKES a lane's worktree, and it
    records the tree in the lane's #97 inventory in the same act
    (opensoft/openRepoTools#163). A tree made by hand is legal git and FOREIGN
    to every sweep until something records it; lane openRepoTools-3's own
    inventory was empty while it had nine trees, so the gate could not tell
    which were its. Nothing in this tooling made a worktree before this verb,
    so this is where creation and the record become one act.

    The tree goes under the lane's own root, `<parent of its dir>/
    .lane-worktrees/<lane>/<slice>`, which only this lane's acts make, as a
    `git worktree` of the lane's checkout (or `--checkout <dir>`). A new
    branch is made with `--no-track`: a branch made from `origin/main` that
    tracked it would push a writer's commits at main on a bare `git push`.

    Exit 0: made and recorded, the path on stdout. 2: refused, and nothing
    was made - including a record that failed after the tree was made, or a
    `git worktree add` that failed after making it (a failing `post-checkout`
    hook), when the fresh tree and the branch this act made are taken back
    out. 1: something it made is left and not recorded; the commands are
    printed. 64: usage."""
    lane, slice_name = ns.lane or "", ns.slice or ""
    usage = ("lane-worktrees add <lane> <slice> [--branch <name>] [--from <ref>] "
             "[--checkout <dir>] [--dry-run]")
    if not lane or not slice_name:
        note(f"usage: {usage}")
        return 64
    if not re.match(r"^[A-Za-z0-9][A-Za-z0-9._-]*$", lane):
        note(f"usage: '{lane}' is not a lane name")
        return 64
    if not SLICE_RE.match(slice_name) or ".." in slice_name or slice_name.endswith(".lock"):
        note(f"usage: '{slice_name}' is not a slice name (letters, digits, . _ -; one path "
             "component, which is also the default branch name)")
        return 64
    if ns.checkout is not None and not os.path.isabs(ns.checkout):
        note("usage: --checkout takes an absolute path")
        return 64

    def refuse(why: str) -> int:
        note(f"REFUSED, nothing was made: {why}")
        return 2

    helper = Helper()
    if not helper.path:
        return refuse("no lanes-edit.sh beside this command or on PATH, and the record is its")
    canon, why = helper.canon_lane(lane)
    if why:
        return refuse(f"lane {lane}'s name could not be resolved: {why}")
    lane = canon
    mproc = helper.call("managed-projection", lane)
    if mproc.returncode == 0:
        return refuse(f"lane {lane} is owned by the managed ledger (owner "
                      f"{mproc.stdout.strip() or 'unnamed'}), and its worktrees are that "
                      "ledger's (ruling 2026-10-04)")
    if mproc.returncode != 8:
        return refuse(f"whether the managed ledger owns lane {lane} is UNKNOWN (`lanes-edit.sh "
                      f"managed-projection {lane}` exited {mproc.returncode}); an ownership "
                      "nobody could read is never read as legacy (Amendment 7(d))")
    dproc = helper.call("lane-dir", lane)
    if dproc.returncode == 8:
        return refuse(f"lane {lane} records no directory (Amendment 11(c)), so it has no "
                      "root to make a tree under; start it with lane-start first")
    if dproc.returncode != 0 or not dproc.stdout.strip():
        return refuse(f"`lanes-edit.sh lane-dir {lane}` exited {dproc.returncode}: "
                      + _first_line(dproc.stderr))
    lane_dir = dproc.stdout.strip().rstrip("/") or "/"
    checkout = (ns.checkout or lane_dir).rstrip("/") or "/"
    top = git_out(checkout, "rev-parse", "--show-toplevel")
    if top is None or real(top) != real(checkout):
        return refuse(f"{checkout} is not the root of a git checkout"
                      + (f" ({top} is)" if top else ""))
    # T018 (opensoft/openRepoTools#186): THE LEG A TREE IS MADE FROM IS WHAT IS
    # RECORDED. Where the lane's checkout is an ASSEMBLY, a tree made from one
    # of its legs - the leg itself, or any worktree of that leg's repository -
    # is recorded with the LEG as its checkout, which every reader of the
    # inventory enumerates. A checkout with no `project.yaml` is asked nothing,
    # and the record is the checkout given, as always.
    record_co, leg_line, assembly_hint = checkout, "", ""
    legs, why = project_legs(helper, lane_dir)
    if why:
        return refuse(f"the legs of the assembly {lane_dir} could not be read, so which "
                      f"checkout a tree of it belongs to is unknown (Amendment 7(d)): {why}")
    if legs:
        made_from = git_common_dir_of(checkout)
        for repository, decl, lpath in legs:
            if made_from and os.path.isdir(lpath) and git_common_dir_of(lpath) == made_from \
                    and real(git_out(lpath, "rev-parse", "--show-toplevel") or "") == real(lpath):
                record_co = lpath
                leg_line = (f" - the {decl} leg of {os.path.join(lane_dir, 'project.yaml')}"
                            + (f" ({repository})" if repository else ""))
                break
        if not leg_line and real(checkout) == real(lane_dir):
            assembly_hint = (f"lane {lane}'s checkout {lane_dir} is an ASSEMBLY, and this tree is "
                             "of the assembly itself: a worktree does not check its legs out ("
                             + ", ".join(f"{d}/" for _r, d, _p in legs) + " are empty in it). A "
                             "tree of a leg is made from that leg: "
                             + " or ".join(f"--checkout {os.path.join(lane_dir, d)}"
                                           for _r, d, _p in legs))
    # THE RECORD MUST HAVE SOMEWHERE TO GO BEFORE THE TREE IS MADE: a tree
    # this act could not record is the very thing it exists to stop.
    tproc = helper.call("lane-trees", lane)
    if tproc.returncode not in (0, 8):
        return refuse(f"lane {lane}'s inventory cannot be read or written (`lanes-edit.sh "
                      f"lane-trees {lane}` exited {tproc.returncode}: "
                      + _first_line(tproc.stderr) + ")")
    root = os.path.join(os.path.dirname(lane_dir), ".lane-worktrees", lane)
    path = os.path.join(root, slice_name)
    if os.path.lexists(path):
        return refuse(f"{path} already exists; a tree is made where nothing is, and one "
                      f"already there is recorded with: lanes-edit.sh set-lane-tree {lane} {path}")
    branch = ns.branch or slice_name
    if git(checkout, "check-ref-format", "--branch", branch).returncode != 0:
        return refuse(f"'{branch}' is not a branch name git takes")
    exists = git(checkout, "rev-parse", "-q", "--verify", f"refs/heads/{branch}").returncode == 0
    if exists and ns.from_ref:
        return refuse(f"branch {branch} already exists, so --from {ns.from_ref} would be "
                      "ignored; name a new branch, or drop --from to check this one out")
    start = ns.from_ref or "HEAD"
    # THE COMMIT A NEW BRANCH STARTS AT, resolved ONCE and handed to git as an
    # id: the take-back below deletes the branch only while it still points
    # here, so a commit made on it meanwhile (a `post-checkout` hook can make
    # one) is never made unreachable.
    start_oid = ""
    if not exists:
        start_oid = git_out(checkout, "rev-parse", "-q", "--verify", f"{start}^{{commit}}") or ""
        if not start_oid:
            return refuse(f"'{start}' names no commit in {checkout}")
    argv = (["worktree", "add", path, branch] if exists else
            ["worktree", "add", "--no-track", "-b", branch, path, start_oid])
    if ns.dry_run:
        note(f"DRY RUN, nothing was made: git -C {checkout} {' '.join(argv)}; then "
             f"lanes-edit.sh set-lane-tree {lane} {path} --checkout {record_co}{leg_line}")
        if assembly_hint:
            note(assembly_hint)
        say(path)
        return 0
    try:
        os.makedirs(root, exist_ok=True)
    except OSError as exc:
        return refuse(f"the lane's root {root} could not be made: {exc}")
    _ACTIVE["add"] = (lane, path)

    def take_back(what: str) -> int:
        """Undo what this act made - the tree's registration and directory, and
        the branch where this act made it - after `what` went wrong. 2 where
        nothing is left, 1 where something is, with the commands printed."""
        left = []
        regs = git(checkout, "worktree", "list", "--porcelain")
        registered = None
        if regs.returncode == 0:
            registered = any(real(line[len("worktree "):]) == real(path)
                             for line in regs.stdout.splitlines() if line.startswith("worktree "))
        if registered:
            if git(checkout, "worktree", "remove", path).returncode != 0:
                left.append(f"the tree {path} (git -C {checkout} worktree remove {path})")
        elif registered is None:
            left.append(f"whatever git registered at {path} (its registrations could not be read)")
        elif os.path.lexists(path):
            # MADE AND NOT REGISTERED: nothing here proves it is git's alone,
            # so it is named and never deleted blind.
            left.append(f"the directory {path}, which git does not register")
        if not exists:
            tip = git_out(checkout, "rev-parse", "-q", "--verify", f"refs/heads/{branch}")
            if tip and tip != start_oid:
                # IT MOVED PAST WHERE THIS ACT STARTED IT: the commits on it are
                # somebody's, and a branch is their only name.
                left.append(f"branch {branch}, which this act made at {short(start_oid)} and "
                            f"which now holds {short(tip)}: kept, so that commit stays reachable")
            elif tip and (left or git(checkout, "update-ref", "-d", f"refs/heads/{branch}",
                                      start_oid).returncode != 0):
                left.append(f"branch {branch} at {short(tip)}, which this act made")
        _ACTIVE.pop("add", None)
        if not left:
            note(f"REFUSED, nothing was made: {what}; the fresh tree {path} was removed "
                 "again" + ("" if exists else f" and branch {branch} with it"))
            return 2
        note(f"{what}, and this is left of what this act made: {'; '.join(left)}. Record a "
             f"tree that is wanted with: lanes-edit.sh set-lane-tree {lane} {path} "
             f"--checkout {record_co}")
        return 1

    made = git(checkout, *argv)
    if made.returncode != 0:
        # A FAILED `worktree add` MAY HAVE MADE THE TREE ALREADY: a
        # `post-checkout` hook runs after the checkout, and git returns its
        # status. Whatever it left is taken back like a record that failed.
        return take_back(f"git worktree add failed: {_first_line(made.stderr)}")
    head = git_out(path, "rev-parse", "HEAD") or ""
    caller = os.environ.get("LANES_SESSION") or os.environ.get("CLAUDE_CODE_SESSION_ID") or ""
    record = ["set-lane-tree", lane, path, "--checkout", record_co]
    if UUID_RE.match(caller):
        record += ["--writer", caller]
    rproc = helper.call(*record)
    if rproc.returncode == 0:
        note(f"made {path} on branch {branch} at {short(head)} in {checkout}{leg_line}; recorded "
             f"in lane {lane}'s inventory as {rproc.stdout.strip() or '?'}"
             + (f" with {record_co} as its checkout" if record_co != checkout else ""))
        if assembly_hint:
            note(assembly_hint)
        _ACTIVE.pop("add", None)
        say(path)
        return 0
    # THE RECORD FAILED: the fresh tree and the branch this act made are taken
    # back out, so a tree nothing records is not left behind by the act whose
    # whole point is that there are none. `worktree remove` without --force:
    # nothing has been written in it yet, and git still refuses if it was.
    return take_back(f"the tree was made and could NOT be recorded (`lanes-edit.sh "
                     f"{' '.join(record)}` exited {rproc.returncode}: "
                     f"{_first_line(rproc.stderr)})")


# ==================================================================== main

class _Parser(argparse.ArgumentParser):
    def error(self, message):
        raise Usage(message)


def parse(argv: list):
    parser = _Parser(prog=PROG, add_help=False)
    parser.add_argument("verb", nargs="?")
    parser.add_argument("lane", nargs="?")
    parser.add_argument("slice", nargs="?")          # add's
    parser.add_argument("--branch")                  # add's
    parser.add_argument("--from", dest="from_ref")   # add's
    parser.add_argument("--checkout")                # add's
    mode = parser.add_mutually_exclusive_group()
    mode.add_argument("--dry-run", action="store_true")
    mode.add_argument("--yes", action="store_true")
    parser.add_argument("--porcelain", action="store_true")
    parser.add_argument("--bundle", action="store_true")
    parser.add_argument("--branches", action="store_true")
    parser.add_argument("--links", action="store_true")
    parser.add_argument("--include-scratch", action="store_true")
    parser.add_argument("--include-caches", action="store_true")
    parser.add_argument("--include-sandboxes", action="store_true")
    parser.add_argument("--include-foreign", action="store_true")
    parser.add_argument("--word")
    parser.add_argument("--live", action="append")
    parser.add_argument("--fetch", action="store_true")
    parser.add_argument("--push-untracked", action="store_true")
    parser.add_argument("--estate")
    parser.add_argument("--expire", action="store_true")
    parser.add_argument("--all", action="store_true")
    parser.add_argument("--report", action="store_true")
    parser.add_argument("--post")
    parser.add_argument("-h", "--help", action="store_true")
    parser.add_argument("--version", action="store_true")
    return parser.parse_args(argv)


_ACTIVE: dict = {}


def main(argv: list) -> int:
    """The exit contract, whatever happens inside: an error no read caught is
    exit 2 on a dry run (nothing was changed), and under `--yes` the archive's
    DISPOSITION.md is written first and the exit is 1 (B2)."""
    for stream in (sys.stdout, sys.stderr):
        if hasattr(stream, "reconfigure"):
            stream.reconfigure(errors="surrogateescape")
    try:
        return _main(argv)
    except Exception as exc:  # pylint: disable=broad-except
        import traceback
        frames = [f for f in traceback.extract_tb(exc.__traceback__)
                  if os.path.basename(f.filename) == os.path.basename(__file__)]
        where = f" at {PROG} line {frames[-1].lineno}" if frames else ""
        detail = f"{type(exc).__name__}: {exc}{where}"
        added = _ACTIVE.get("add")
        if added:
            # `add` MADE A TREE BEFORE THE ERROR: "nothing was changed" would
            # be false, and the tree may be one nothing records.
            note(f"INTERNAL ERROR after the tree {added[1]} was made: {detail}; whether it is "
                 f"recorded is in: lanes-edit.sh lane-trees {added[0]}")
            return 1
        sweep = _ACTIVE.get("sweep")
        acted = bool(sweep is not None and sweep.acting)
        if sweep is not None and sweep.opts.porcelain:
            prow("refused", "internal error, outside the table: " + detail)
        if acted:
            try:
                if sweep.archive:
                    sweep._write_disposition()
            except Exception:  # pylint: disable=broad-except
                pass
            note(f"INTERNAL ERROR part-way through --yes: {detail}; what was done is in "
                 f"{tilde(sweep.archive) if sweep.archive else 'the table above'}")
            return 1
        note(f"INTERNAL ERROR, nothing was changed: {detail}")
        return 2


def _main(argv: list) -> int:
    try:
        ns = parse(argv)
    except Usage as exc:
        note(f"usage: {exc} (see {PROG} --help)")
        return 64
    if ns.help:
        sys.stdout.write(__doc__)
        return 0
    if ns.version:
        say(f"{PROG} {VERSION}")
        return 0
    if ns.verb == "add":
        # THE SWEEP'S WORDS ARE NOT ADD'S: a flag a verb does not read is a
        # line that does not do what it says.
        theirs = [flag for flag, given in (
            ("--yes", ns.yes), ("--porcelain", ns.porcelain), ("--bundle", ns.bundle),
            ("--branches", ns.branches), ("--links", ns.links),
            ("--push-untracked", ns.push_untracked),
            ("--include-scratch", ns.include_scratch), ("--include-caches", ns.include_caches),
            ("--include-sandboxes", ns.include_sandboxes),
            ("--include-foreign", ns.include_foreign), ("--word", ns.word is not None),
            ("--live", bool(ns.live)), ("--fetch", ns.fetch), ("--estate", ns.estate is not None),
            ("--expire", ns.expire), ("--all", ns.all), ("--report", ns.report),
            ("--post", ns.post is not None)) if given]
        if theirs:
            note(f"usage: {', '.join(theirs)} belong to sweep, not to add")
            return 64
        return add_tree(ns)
    if ns.verb != "sweep":
        note("usage: lane-worktrees sweep <lane> [--dry-run | --yes] ... | lane-worktrees "
             "add <lane> <slice> ... (see --help)")
        return 64
    if ns.slice is not None or ns.branch is not None or ns.from_ref is not None \
            or ns.checkout is not None:
        note("usage: a slice, --branch, --from and --checkout are add's, and sweep takes "
             "none of them")
        return 64
    if ns.all or ns.report or ns.post is not None:
        if not (ns.all and ns.report) or ns.lane or ns.yes or ns.expire:
            note("usage: the estate-wide sweep only reports: lane-worktrees sweep --all "
                 "--dry-run --report [--post <path|owner/repo#n>]")
            return 64
        return report_all(ns)
    if ns.expire:
        if ns.lane:
            note("usage: sweep --expire takes no lane: it reads every lane's archives")
            return 64
        return expire(ns)
    if not ns.lane:
        note("usage: lane-worktrees sweep <lane> [--dry-run | --yes] ... (see --help)")
        return 64
    if not re.match(r"^[A-Za-z0-9][A-Za-z0-9._-]*$", ns.lane):
        note(f"usage: '{ns.lane}' is not a lane name")
        return 64
    if ns.word is not None and not ns.include_foreign:
        note("usage: --word is the word --include-foreign acts on, and nothing else takes it")
        return 64
    opts = Options(ns)
    sweep = Sweep(ns.lane, opts)
    _ACTIVE["sweep"] = sweep
    sweep.gather()
    if sweep.usage:
        note(f"usage: {sweep.usage}")
        return 64
    if sweep.refusal and not sweep.trees:
        sweep.decide()
        (sweep.porcelain if opts.porcelain else sweep.render)()
        if not opts.porcelain:
            note("REFUSED: " + sweep.refusal)
        return 2
    sweep.decide()
    if opts.yes and not sweep.refusal:
        sweep.act()
    (sweep.porcelain if opts.porcelain else sweep.render)()
    if sweep.refusal or sweep.refused_check:
        return 2
    if opts.yes:
        return 1 if sweep.failed else 0
    return 3 if sweep.retire_count() else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
