0058 — The row-lease lapse sweep runs on a schedule, and observes only

The problem: a detector that works, and that nothing invokes

A row on a phase board is claimed under a 3-hour lease (expires_in_seconds 10800). When a lease lapses on a still-open row, that row can be neither worked (the holder's generation is stale, so its writes are refused) nor adopted (another lane's claim is refused because the row is still held). It is stuck, and silently.

smith-row-lease sweep is the detector for exactly that condition, and it works. Run by hand against this dock's 75 rows it returned rc=0, rows_asked 75, rows_read 75, counts {held: 35, parked: 40}, 0 lapsed, 0 foreign holders, recorded: false.

It has never swept a real board.

$ smith-row-lease sweep-status --board portfolio-architecture-phases
rc=3  {"status": "never", … "the detector has never run on this board, so a
       lapse here has never been looked for, let alone found"}

The liveness log holds exactly one line, and that line is itself the positive control — the format works, the writer works, and a real lapsed count has rendered once:

2026-09-10 23:50:46 row-sweep rollout-x: 3 rows — held 1, lapsed 1, never_claimed 1

rollout-x is a test board. One sweep, of a test board, thirty-five days ago, and none since on anything.

And nothing schedules it. fleet-daemon --status lists 67 labels, of which 8 are out-of-scope (not fleet jobs — Google updaters and the like), leaving 59 in scope: 43 daemon, 12 deferred, 2 agent, 2 disabled. Four of those are sweeps (ccswitch-walled-sweep, decider-sweep, decision-block-sweep, inspo-sweep). No row-lease or row-sweep daemon at any of those grains. Paired control: doctrine-pull is in that listing (grep count 1) while the target greps 0, so the registry is complete and greppable and the absence is about this job.

⚠️ A grain correction, recorded because the count is the kind of number this fleet gets wrong. The row that filed this said "66 registered daemons". Read live for this record, the listing is 67 labels / 59 in scope — 66 is not the count at any grain the tool reports. The finding is unchanged, because the finding never rested on the total: it rests on the paired control.

What it has already cost. Earlier the same day a hand sweep found fifteen of this dock's rows lapsed — thirteen of them still open — known only because a driver happened to look. ⚠️ Attributed, not re-measured here: that is the dock driver's own hand sweep as recorded on the row. This record did not re-run it, because re-running it now would read a board whose leases have since moved, and a fresh count would answer a different question than the one the row asked.

⚠️ A method warning this row paid for, recorded because it is the reusable part. The first attempt to prove "nothing schedules it" grepped ~/Library/LaunchAgents and found nothing — and its control, a grep for doctrine-pull, also found nothing there, while doctrine-pull demonstrably runs. The zero was about the scope, not the world: these jobs live in the system domain through fleet-daemon. A control that fails alongside the thing it is controlling has not certified an absence; it has reported that you are looking in the wrong place.

The decision

The sweep runs on a schedule, as a system-domain daemon registered via fleet-daemon, against the live boards, passing --record so the evidence line exists. It gets its own SYSTEMS.md entry with liveness signals.

The hosting half — that a fleet background job belongs in the system domain, and that anything asking "is job X loaded" must ask both domains — is not decided here. It is the subject of PR #227 ("fleet background jobs run in the system domain"), whose own number is contested on main and which a ruling on that PR has renumbered to 0057. ⚠️ It has not merged, and ADR-0055 records what that has cost. This ADR depends on that hosting rule but does not restate it as settled.

The two reasons that bind the shape, not just the outcome

Both were verified at source, not taken from the ruling.

(a) A driver may not sweep its own board, because a driver does not poll. The option that looks conservative — "make each driver sweep its own board on every wake" — is a driver polling, and it reverses a standing ruling: ADR-0017 is the driver is fed, it does not poll. Coverage built that way would also be a function of who happened to wake, which is the accident this row is about.

(b) The liveness line belongs to the scheduled sweeper and to nothing else. row_lease_cli.py's _sweep docstring says it verbatim:

⚠️ IT DOES NOT WRITE THE LIVENESS LOG unless --record is passed. The line is the evidence that the SCHEDULED sweeper ran; a hand-run that wrote one would make a board nobody armed read as watched — this row's own defect, one layer down.

So an attended driver sweep could never legitimately record. Combined with (a), an attended-only world is one where sweep-status reads "never" forever while coverage silently depended on whoever happened to look. The author had already named this row's defect in a docstring.

What the sweep may NOT do — observation only

The scheduled sweep never owner-starts, never acquires, and never heartbeats. It reads row ownership, classifies it, and appends one evidence line. That is a constraint on anything built under this ADR, and it is also already true of the code: _sweep calls only read_sweep_evidence, sweep_rows, an optional write_sweep_evidence under --record, and _emit. It acquires nothing, mints no owner and mutates no lease. Negative control: a grep for acquire over that range finds it only inside a comment.

This matters because the row that produced this ADR was filed on the opposite premise — that a sweep is an attended act because smith-row-lease owner-start mints a one-hour attended owner. That conflated two separate subcommands and was false (row_lease_cli.py dispatches sweep and owner-start at different lines, and declares owner-start elsewhere again). A sweep is a read. Writing the constraint down is what keeps a later implementation from quietly making it an act, which would put the scheduled job in the path of the very leases it is meant to observe.

Why the liveness signal reads sweep-status's own answer

sweep-status already returns an honest three-valued answer, and the catalog needs nothing more inventive than to read the same thing:

sweep-status means catalog signal renders
rc 0, status "recorded" a sweep of this board is on record ok / stale by age
rc 3, status "never" log read, this board not in it — a definite no down
rc 1 the log could not be read — not the same as 3 unknown

The catalog is read-only by construction — its evaluators stat, read files and run launchctl list, and there is deliberately no signal type that shells out to a command. So the signal cannot literally invoke sweep-status. It does not need to: sweep-status's needle is " row-sweep <board>: " against ~/Library/Logs/smith-row-sweep.log, and a log-match-age signal declaring that same file and that same needle performs the identical read, with evalLogMatchAge already separating unreadable (unknown) from read, no line (down) from found (ok/ stale). No new file to watch, and the three-valued distinction holds by construction rather than by a convention someone must maintain.

⚠️ The needle must be board-scoped. A bare row-sweep pattern would match the 2026-09-10 rollout-x test-board line and render ok for a production board nothing has ever swept — this row's own defect, one layer down again. The pattern carries the board id and the colon, exactly as sweep-status does.

staleAfterS is 10800 because that is the lease TTL: a lapse takes three hours to form, so a sweep older than three hours has not seen the current set.

What this ADR deliberately does not do

Residual — stated so it is not mistaken for checked