backlog: invert BACKLOG #1269 off a file that cannot answer it, and file #1349 with its own severity already retracted - #554
Merged
Conversation
wshallwshall
force-pushed
the
ledger/1269-inversion-and-1349-rescue-refs
branch
from
August 23, 2026 19:22
1bf2442 to
f5606fb
Compare
wshallwshall
force-pushed
the
ledger/1269-inversion-and-1349-rescue-refs
branch
from
August 23, 2026 19:24
f5606fb to
9c0bd81
Compare
wshallwshall
force-pushed
the
ledger/1269-inversion-and-1349-rescue-refs
branch
2 times, most recently
from
August 23, 2026 19:44
5b0009f to
429f80e
Compare
…fect it was filed to fix The row directed the alarm to read seat-tick.last. seat-tick.ps1 states verbatim, at its state-file declaration, that this file cannot answer "when did THIS seat last actually get a tick", because a run reporting COLD for a seat overwrites the run in which that seat was SENT -- and that the throttle, needing the same question, was given seat-tick.state.json for it. #1269 is titled "the obvious implementation reads healthy at the moment it should fire". An alarm reading seat-tick.last goes healthy on the overwrite. The row specified its own subject. Inverted: state.json, keyed by absolute worktree path with unix-second values, answers WHEN and WHO. seat-tick.last answers only WHY NOT, for the exclusion decision. The tick-identity dedupe, exclusion rule, pinned absolute path, throttle-age consistency check and the discriminating test are unchanged and still required -- the file assignment moved, not the job. Also recorded: the seat name is NOT unique in seat-tick.last. Measured today, steward, lander and dispatcher each appear twice in one live line, once STALE(no-live-session) and once SENT:<id>, so a first-match scan gets STALE for all three. That is the same first-match trap as the fleet roster, in the file this alarm was told to parse, and it is the third instance of one shape in one day. The emitted vocabulary grew after the row was written -- STALE(no-live-session) at :654 and the (roster-blind) suffix at :767 are absent from its exclusion list, which is incomplete rather than wrong. And the pin-the-path rule is right with stale evidence: its two named decoys are gone, three exist today under a live lane's scratchpad, one in a directory called ticktest, so newest-wins would land in a live lane's test fixture. Measured by the building lane, ruled by the dispatcher, authored here under the 2026-08-13 ruling that builders do not write ledger content. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…be checked where it matters P1. A rescue ref whose name contains a branch name can dereference to an ancestor of that branch with nothing reporting it. Confirmed instance, re-derived independently by two seats to the file and the insertion: one dated rescue tag is a strict ancestor of the branch it names, 75 commits short, 167 files and 23410 insertions, holding zero commits that branch lacks. That branch is gone from every worktree, so the ref is the only thing between that work and nothing. Capturing the tip is plainly the contract: 374 of 730 tag-scheme refs hold it exactly, so the short ones are broken and the remedy is in whatever writes them. Measured defect count is 22. A further 32 read as diverged and are recorded UNVERIFIED, because that matcher pairs on the longest branch name in the tag name and may mis-pair. The 302 naming no local branch are not a defect -- a deleted branch is the case rescue refs exist for. A second scheme was censused separately and does not cover the first: of 248 auto-scheme refs, 114 name a live branch, 103 hold the tip, 10 diverged, 0 purely stale. That zero must not be read as refuting the tag-scheme finding. The structural finding is worse than any count. A rescue ref can only be verified against a branch that still exists, and 436 refs name a branch that is gone -- precisely the ones a rescue ref is for. The measurement is possible exactly where it does not matter and impossible exactly where it does, so no census of this kind can bound the risk. Ref names are deliberately not quoted. The forbidden-content gate blocked the first attempt on three internal worktree slugs and it was right: those do not belong in a public ledger, and the finding is the shape and the counts rather than the identifiers. Not allowlisted, removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… must not be generalised together
Two seats measured opposite objects and both said "rescue tags". Dated tags are true one-time
snapshots with nothing re-taking them, so staleness is permanent. Auto refs are updated by
push -- one ref's reflog shows six updates -- so staleness there is transient lag.
A seat reported an auto ref 13 commits behind. That reading was correct and transient: it sits
at that ref's reflog @{4} with four pushes after it, and the mechanism caught up. The confirmed
instance in this row is a DATED tag, which is the half where staleness is permanent and
therefore the half worth alerting on.
I had written the amendment as "every ref is a snapshot and nothing re-takes it", citing the
auto-ref reading as evidence. That is false for the auto scheme and I reverted it before
committing. The correction arrived while the file was still dirty.
The structural problem is worse than the row first stated: all 730 dated tags are uncomparable
by name, because the leaf is a date-scoped label rather than a branch name. Zero of them can be
censused the way the auto scheme was, and the confirmed instance was checkable only because the
branch it belonged to was already known.
Instrument note: these are annotated tags, 4 of the 730. rev-parse returns the tag object, not
the commit; use rev-parse ^{commit} or %(*objectname). diff, merge-base and rev-list dereference
silently, so ancestry and diffstat were unaffected, but a tag-object sha published into a log
will not resolve. One such sha was published and corrected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…gree one time in seven For one branch, one clone holds both a refs/tags/rescue/auto/... tag 13 commits behind and a refs/remotes/private/rescuetags/auto/... ref holding the head exactly. Both readings correct. The names differ by namespace alone. Over 110 branches carrying both and still alive: the tag holds the current head 86 times, the remote ref 99 times, and the two disagree with each other 16 times. So "check your rescue tag" returns opposite answers about one time in seven, decided only by which ref the reader reaches for. The tooling hides each from the other. git tag -l cannot see a remotes ref; a reflog showing "update by push" cannot exist on a tag. Two seats each verified a real object with an instrument structurally blind to the other's, then read the other's result as a refutation. That is why this row has now been amended three times, and each amendment added a measurement rather than an opinion. The remedy is a naming rule rather than a repair: quote the full refname, never "the rescue tag". The counts in the row -- 730, 393, 752, 248 -- are slices of different namespaces and are deliberately NOT reconciled, because forcing them into one figure would manufacture the false precision this row is about. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t is withdrawn Filed P1 on "unrecoverable loss of committed work". Measured since, on the one examined case: the branch exists as a ref at b2149fcc and the push-updated namespace holds that tip exactly. Two dated tags are behind it. Nothing is unrecoverable. I was told the branch was gone from every WORKTREE and read that as gone. A branch can exist as a ref without being checked out anywhere, and I built a P1 severity on the difference without running the one command that separates them. Same shape as reading four RUNNING roster rows as four live sessions this morning: an absence in one instrument read as an absence. The writer-defect argument is withdrawn by its author and is now recorded in the row only so it is not re-derived. It ran: 374 of 730 tags hold the tip, therefore capturing a tip is intended, therefore the short ones are broken. But 374 is also exactly what snapshots of mostly-dormant branches look like, so that measurement cannot distinguish "tags track heads" from "most branches have not moved since their snapshot". A correlation read as intent. Corrected: tags are snapshots by design and the push-updated namespace is the live tracking mechanism. Severity is a wrong recovery decision -- reaching for a short ref, or concluding work is gone when another namespace holds it -- not data loss. P1 to P2, cluster from data safety to naming. Surviving as measured fact: 22 tags short of their branch tip, now labelled a fact rather than a defect; the 167-file 23410-insertion gap on the named instance; the 15 percent disagreement between namespaces over 110 branches; and that all 730 dated tags are uncomparable by name. The remedy was already stated correctly in the third amendment and is unchanged: quote the full refname, never "the rescue tag". Corrected before landing rather than after. The row is with the lander and held on request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… no longer resolves #1236: a brief I sent a lane said admin-unlock shipped in 3ab2775. It did not. That commit's SUBJECT says sole-admin lockout recovery; its CONTENT adds no locked_until line and no unlock affordance exists anywhere in messagefoundry/, with a 7-hit control proving the search fires. A commit subject is not a content check -- and the brief came from a verification pass whose own agent instructions said not to decide from commit messages. The lane caught it by going to the code before building on it. Scope was also wrong in the narrowing direction: the brief named service.py:800 alone, and the refusal is emitted at :797, :2243 and :2608, so a duration fix at one leaves two inconsistent. Two findings from the lane are recorded because they remove work rather than add it. The enumeration tension is already spent -- "account locked" is distinct from "invalid credentials" today, so a duration-bearing refusal refines a shipped signal rather than opening a disclosure class, and no owner ruling is needed. And set_password clears the lock at store.py:7897, so recovery exists mechanically; what is missing is an out-of-band affordance, since a locked-out sole admin cannot authenticate to reach it and 38 subcommands offer no unlock path. Scope ruled: build the on-demand recovery, do not re-scope to self-expiry. Waiting is a workaround, not recovery, for an item whose premise is that nobody else exists to unlock you. The predecessor lane's own note set the boundary -- a re-scope routes back, confirming the original scope does not -- so this needed no owner. #1214: the row cited test_documented_bounds_match_the_live_constants, renamed to test_transcribed_bounds_match_the_live_constants in a5d5a9a. The old citation no longer resolves. Both names recorded so a reader of either can follow it. Authored here under the 2026-08-13 ruling; measured by the lanes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n was the error The row briefly said admin-unlock did not ship in 3ab2775. That correction was wrong and is replaced. Verified repo-wide with both controls firing: __main__.py:603 registers the admin-unlock subcommand, :3962 clears the lock via record_login_failure(failed_attempts=0, locked_until=None), :3964 audits auth.admin_unlocked, and 3ab2775 added 92 lines to that file. So the on-demand affordance already exists and needs no build. What remains is narrower: a duration-bearing refusal is absent at all three sites, and lock cycles are uncapped because _register_failure has no counter. My earlier scope ruling of "build the on-demand recovery" is therefore satisfied already; the remaining limbs are the duration and the cycle cap. The row now records how the wrong correction was produced, because the control is the lesson. Three layers failed in the same direction: a search scoped to auth/service.py returned a zero that was TRUE FOR THAT FILE and was published as repo-wide, since admin-unlock lives in __main__.py; the backstop was both filtered and truncated with head -8 so it never reached the right file; and a control was printed and did pass. The control confirmed the instrument worked on the file that was searched, not on the claim that was made. A control that certifies your scope instead of your claim is not a control. A scoped search whose scope is wrong produces a true zero and a false conclusion, and a control drawn from the same scope cannot see it. The #1214 citation fix in the previous commit is unaffected and stands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The row said __main__.py:603 registers admin-unlock and named three sites. An independent repo-wide re-derivation measures :604, and a FOURTH site at :5091 -- the dispatch-table entry "admin-unlock": _admin_unlock, which is the line that makes the subcommand reachable at all. Neither changes the conclusion; both are corrected because a line number in a ledger row gets followed. Verified here before amending: :604 and :5091 both confirmed by git grep on origin/main. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ion limb needs an owner This row said twice that a duration-bearing refusal needs no owner ruling. Wrong both times. The distinct strings live only in logs and audit. Verified at the boundary with the control firing: service.py:147's LoginOutcome contract says the error field is for logs/audit and must never leak the reason to clients; auth_routes.py:259 raises HTTPException(401, "invalid credentials") as a fixed string whenever not outcome.ok, regardless of outcome.error; and outcome.error appears zero times in auth_routes.py against a control of 7 for "outcome.". So a client sees "invalid credentials" whether the account is locked, the password is wrong, or the user does not exist. There is no enumeration leak today, which inverts the conclusion: a duration reaching the locked-out operator is a NEW disclosure to an unauthenticated caller -- that the account exists, is locked, and for how long. Owner-level, and routed. A duration added only to the error field would help nobody, since the operator never sees it. And MFA and WebAuthn return a bare False with no error channel, so a duration there needs a different mechanism rather than the same edit three times. The cycle cap is unaffected and remains buildable. Root cause of both wrong versions, from the lane that measured and retracted them: error strings were compared INSIDE service.py and a conclusion published about what a CLIENT sees. Both times the local measurement was correct and the LAYER was wrong -- the same shape as searching one file and concluding about the repo, one level up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wshallwshall
force-pushed
the
ledger/1269-inversion-and-1349-rescue-refs
branch
from
August 23, 2026 19:52
429f80e to
e6820e6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Five ledger commits from the Dispatcher, cherry-picked onto current main with authorship preserved.
docs/BACKLOG.mdonly.BACKLOG #1269 prescribed the defect it was filed to fix
The row said to read
seat-tick.last.seat-tick.ps1states in its own source that that file cannot answer "when did THIS seat last get a tick", because a later run overwrites it. It answers WHY NOT, never WHEN or WHO.state.jsonanswers both.Builder 2 built the code half against the same inversion, independently, and it is PR 557. Neither was derived from the other.
BACKLOG #1349 is filed, amended three times, and the last amendment retracts its own severity
The row now lands already corrected rather than landing and being corrected on main. That sequence is deliberate, and it is why this PR was held and force-pushed twice while it was open.
What the last commit changes: P1 to P2, cluster from data-safety to naming, severity from "unrecoverable loss of committed work" to "a wrong recovery decision", the writer-defect argument recorded as withdrawn with its reason, and the 22 short tags relabelled measured fact rather than measured defect.
Two premises fell, and neither was an instrument failure
The first was mine. I told the author a branch was "gone from every worktree, so the tag is the only thing standing between that work and nothing". The first clause is true; the second is false, and my own output two lines earlier had printed the branch alive:
A branch can exist as a ref while being checked out nowhere. The tool answered correctly, printed the answer, and the prose said the opposite. No control catches that; only re-reading your own output against your own sentence does.
The second is sharper and belongs to the cleanup seat. They had argued that 374 of 730 tags holding their tip shows tags are meant to track heads. They withdrew it: 374 is also exactly what snapshots of mostly-dormant branches look like. The measurement cannot distinguish "tags track heads" from "most branches have not moved since their snapshot" — a correlation read as intent.
What survives as measured fact
22 tags short of their branch tip. The 167-file, 23,410-insertion gap on the named instance, re-derived independently. The 15 percent disagreement between the two namespaces across 110 branches. And that all 730 dated tags are uncomparable by name, because the leaf is a date-scoped label rather than a branch name.
The remedy in the row is a naming rule, not a repair: quote the full refname, never "the rescue tag".
git tag -lcannot see a remotes ref, and anupdate by pushreflog entry cannot exist on a tag. Each instrument is structurally blind to the other's object, which is how two seats produced confident, correct, mutually contradictory reports within an hour.Counts are deliberately not reconciled
Several namespace slices appear without being forced into one figure. Collapsing them would manufacture exactly the false precision the item is about.
Ledger check
parse_itemsagainst main as control: 356/260 to 357/261, 0 duplicate banner fields on both sides. Added 1349. Lost none.Co-Authored-By: Claude Opus 5 noreply@anthropic.com