From 7fb87575931c0ab947e9b5269074d6f030463bca Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Sun, 23 Aug 2026 13:52:32 -0500 Subject: [PATCH 1/9] backlog: #1269 prescribed a mechanism that would have produced the defect 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:, 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 --- docs/BACKLOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index df48914c..c20c5717 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -13001,6 +13001,20 @@ measurement from this row's subject and it is named here rather than performed.* > Verdict: build > Closing-act: code +***AMENDED 2026-08-23 -- THE MECHANISM THIS ROW PRESCRIBED IS BUILT ON A FILE THAT CANNOT ANSWER THE QUESTION, AND `seat-tick.ps1` SAYS SO IN ITS OWN COMMENT.*** + +*The row directed the alarm to read `seat-tick.last`. The tool's comment at the state-file declaration states verbatim that it* **"cannot answer 'when did THIS seat last actually get a tick', because a run that reported COLD for a seat OVERWRITES the run in which that seat was SENT"** -- *and that the throttle, needing the same second question, was therefore given `seat-tick.state.json`.* + +***BUILDING THIS ROW AS WRITTEN WOULD HAVE PRODUCED THE DEFECT IT WAS FILED TO FIX.*** *The title is "the obvious implementation reads healthy at the moment it should fire"; **an alarm reading that file goes healthy on the overwrite.*** **The row specified its own subject.** + +**INVERTED SCOPE, RULED 2026-08-23:** ***`seat-tick.state.json` -- keyed by ABSOLUTE WORKTREE PATH, values unix seconds -- answers WHEN and WHO. `seat-tick.last` answers only WHY NOT, for the exclusion decision alone.*** *Unchanged and still required: the tick-identity dedupe, the exclusion rule, the pinned absolute path, the throttle-age consistency check, and a discriminating test that actually fires.* **The alarm's job did not shrink; only the file assignment moved.** + +***AND THE SEAT NAME IS NOT UNIQUE IN `seat-tick.last`.*** *Measured 2026-08-23: `steward`, `lander` and `dispatcher` each appear **TWICE** in one live line -- once `STALE(no-live-session)`, once `SENT:`.* **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*** -- *a third instance of one shape in one day.* + +**SCOPE NOTE, NOT A SEPARATE ITEM:** *the emitted vocabulary grew after this row was written.* **`STALE(no-live-session)` at `seat-tick.ps1:654` and the `(roster-blind)` suffix at `:767` are absent from the exclusion list here.** *The list is INCOMPLETE rather than wrong; build against the vocabulary as it stands and say so.* + +***THE PIN-THE-PATH RULE IS RIGHT AND ITS EVIDENCE IS STALE, WHICH STRENGTHENS IT.*** *The two decoys this row names are gone; **three exist today, all under a live lane's scratchpad, one in a directory called `ticktest`.*** **Newest-wins would today land in a live lane's test fixture.** *Keep the rule exactly as stated.* + **Cluster:** Fleet coordination / observability. **Priority:** P2. **Verdict:** build. **Severity:** no deployment axis (§0) -- this is fleet tooling, not engine code. The cost is that the mechanism which keeps every seat alive has no independent observer, so its death is silent by construction, and the first implementation anyone reaches for is green at precisely the moment it should be red. From 942b63175d290468aac1bc7f659719c7af843d05 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Sun, 23 Aug 2026 13:58:41 -0500 Subject: [PATCH 2/9] backlog: file #1349 -- a rescue ref can hold an ancestor, and cannot 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 --- docs/BACKLOG.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index c20c5717..ec30da7d 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -15663,3 +15663,60 @@ cannot tell those apart has never demonstrated it is testing occupancy at all.* **Expiry:** this stops being right if `claim.ps1` gains a liveness probe, or if worktree directories are removed with their sessions. + +## 1349. a rescue ref can silently hold an ancestor instead of the tip, and the population where that matters is the population where it cannot be checked + +> 🔢 **Filed 2026-08-23 - not started.** ***A rescue ref whose NAME contains a branch name can dereference to an ANCESTOR of that branch rather than its tip, 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 its name contains -- 75 commits short, `167 files changed, 23410 insertions(+), 1578 deletions(-)`, holding ZERO commits that branch lacks.** ***That branch is gone from every worktree, so the tag is the only thing standing between that work and nothing.*** +> Verdict: build +> Research: none +> Closing-act: code + +**Cluster:** coordination tooling / data safety. **Priority:** P1. **Verdict:** build. +**Severity:** no product axis (sec. 0). **The cost is unrecoverable loss of committed work, discovered at +the moment recovery is attempted.** + +***CAPTURING THE TIP IS PLAINLY THE INTENDED BEHAVIOUR, WHICH IS WHAT MAKES THE SHORT ONES A DEFECT +RATHER THAN A DOCUMENTATION GAP.*** *Of 730 refs under `refs/tags/rescue`, **374 hold the tip exactly**. +If holding the tip were not the contract that number would be near zero.* + +| tag-scheme census, 730 refs | count | reading | +|---|---|---| +| **hold the tip exactly** | ***374*** | *the contract, working* | +| ***ANCESTOR -- short of the tip*** | ***22*** | ***MEASURED DEFECT*** | +| diverged from the named branch | *32* | ***UNVERIFIED*** -- *the matcher takes the longest branch name in the tag name, so a pairing may be spurious* | +| name no local branch | *302* | **NOT a defect** -- *a deleted branch is the case rescue refs exist for* | + +***AT LEAST 22 OF 730. The `32` is deliberately NOT added to that figure.*** + +**A second scheme was censused separately and its numbers do NOT cover the first.** *Of 248 refs under +`private/rescuetags/auto/`, 114 name a branch that still exists: **103 hold the tip, 10 DIVERGED, 1 +ahead, 0 purely stale**.* ***The `0 purely stale` must not be read as refuting the tag-scheme finding -- +different population, different scheme, and it is evidence about neither.*** + +**FOUR SHORT REFS WERE IDENTIFIED, AND TWO OF THEM NAME THE SAME BRANCH BY DIFFERENT AMOUNTS -- `75` +and `106` behind, one in each naming scheme.** *The other two are `55` and `13` behind, and **the +`55`-behind ref names a branch an independent triage had classified LANDABLE***. *Ref names are held +outside this file: internal worktree slugs do not belong in a public ledger, and the finding is the +shape and the counts, not the identifiers.* + +## ***THE STRUCTURAL FINDING, AND IT IS WORSE THAN ANY COUNT*** + +***A RESCUE REF CAN ONLY BE VERIFIED AGAINST A BRANCH THAT STILL EXISTS. 436 REFS NAME A BRANCH THAT IS +GONE -- AND THOSE ARE PRECISELY THE ONES A RESCUE REF IS FOR.*** + +> **THE MEASUREMENT IS POSSIBLE EXACTLY WHERE IT DOES NOT MATTER AND IMPOSSIBLE EXACTLY WHERE IT DOES.** +> ***No census of this kind can bound the risk.*** *The confirmed instance was findable ONLY because its +> branch happened to survive alongside the tag; had the branch been pruned, the ref would have looked +> like any of the 302.* + +**This is also the one instrument whose failure surfaces only when it is too late to fix** -- *a rescue +ref is consulted once, in the moment the original is already gone.* ***Every other wrong reading recorded +today was survivable because something else could still be measured.*** + +**Two controls, and the second is the hard one:** a ref written for a branch that is then advanced must +FAIL a tip check, and **a ref must be verifiable without the branch it names** -- *by recording the sha +it captured at write time, or by refusing to write a ref that is not the tip.* ***A rescue mechanism that +can only be audited while the thing it rescues still exists has never been shown to work.*** + +**Expiry:** this stops being right if rescue refs are written with a recorded target sha, or if the +writer refuses any ref that is not its branch's tip. From 063d1a45efd9289b78ab2fd53e46ee03d04f8f27 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Sun, 23 Aug 2026 14:00:11 -0500 Subject: [PATCH 3/9] backlog: #1349 -- the two rescue schemes are different mechanisms and 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 --- docs/BACKLOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index ec30da7d..a5778234 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -15718,5 +15718,29 @@ FAIL a tip check, and **a ref must be verifiable without the branch it names** - it captured at write time, or by refusing to write a ref that is not the tip.* ***A rescue mechanism that can only be audited while the thing it rescues still exists has never been shown to work.*** +***AMENDED SAME DAY -- THE TWO SCHEMES ARE DIFFERENT MECHANISMS AND MUST NOT BE GENERALISED TOGETHER. +TWO SEATS MEASURED OPPOSITE OBJECTS AND BOTH SAID "rescue tags".*** + +| scheme | mechanism | staleness | +|---|---|---| +| ***dated tags, 730 refs*** | ***TRUE ONE-TIME SNAPSHOT. Nothing re-takes it.*** | ***PERMANENT*** | +| auto refs, 248 refs | ***UPDATED BY PUSH*** -- *one ref's reflog shows six updates* | *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.* ***An auto ref that +looks stale may simply be lagging. A DATED TAG NEVER CATCHES UP, because there is no mechanism behind +it.*** **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.** + +***AND THE STRUCTURAL PROBLEM IS WORSE THAN THIS ROW FIRST STATED: ALL 730 DATED TAGS ARE UNCOMPARABLE BY +NAME.*** *Their leaf is a **date-scoped label**, not a branch name, so **zero** of them can be censused +the way the auto scheme was.* **The confirmed instance was checkable only because the branch it belonged +to was already known** -- *not because anything in the ref said so.* + +**INSTRUMENT NOTE, MEASURED: these are ANNOTATED tags -- 4 of the 730.** *`rev-parse` returns the **tag +object**, not the commit; `rev-parse ^{commit}` or `%(*objectname)` returns the commit.* ***`diff`, +`merge-base` and `rev-list` dereference silently, so ancestry and diffstat are unaffected -- but a tag- +object sha published into a log will not resolve.*** *One such sha was published and corrected here.* + **Expiry:** this stops being right if rescue refs are written with a recorded target sha, or if the writer refuses any ref that is not its branch's tip. From 6678a40577258721382e9dd90959a6416e25b235 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Sun, 23 Aug 2026 14:02:18 -0500 Subject: [PATCH 4/9] backlog: #1349 -- two rescue namespaces name the same branch and disagree 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 --- docs/BACKLOG.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index a5778234..ac545f1c 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -15742,5 +15742,35 @@ object**, not the commit; `rev-parse ^{commit}` or `%(*objectname)` returns the `merge-base` and `rev-list` dereference silently, so ancestry and diffstat are unaffected -- but a tag- object sha published into a log will not resolve.*** *One such sha was published and corrected here.* +***AMENDED A THIRD TIME, AND THIS IS THE ACTIONABLE FORM: TWO NAMESPACES CARRY NEAR-IDENTICAL NAMES FOR +THE SAME BRANCH, AND THEY DISAGREE ABOUT ONE TIME IN SEVEN.*** + +*For one branch, both of these exist in one clone right now:* **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 | holds the current head | +|---|---| +| the **tag** | *86 / 110 -- **78 pct*** | +| the **remote-tracking ref** | *99 / 110 -- **90 pct*** | +| ***the two DISAGREE WITH EACH OTHER*** | ***16 / 110 -- 15 pct*** | + +***SO "CHECK YOUR RESCUE TAG" RETURNS OPPOSITE ANSWERS ABOUT ONE TIME IN SEVEN, DECIDED ONLY BY WHICH REF +THE READER HAPPENS TO REACH FOR.*** + +**AND THE TOOLING HIDES THE OTHER ONE FROM YOU.** *`git tag -l 'rescue/*'` **cannot see** the 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.*** + +***THE REMEDY IS A NAMING RULE, NOT A REPAIR: QUOTE THE FULL REFNAME. NEVER WRITE "THE RESCUE TAG".*** +*Every count in this row is a slice of a different namespace and **the slices are NOT reconciled here** -- +`730`, `393`, `752`, `248` were each measured over a different set, and forcing them into one figure +would manufacture exactly the false precision this row is about.* + +**WHAT SURVIVES ALL THREE AMENDMENTS:** *the snapshot is least reliable for the **MOST ACTIVE** branch -- +**a tag because it never moves, a remote ref because an active branch outruns the push*** -- *and you +read it once, in the moment the original is already gone.* + **Expiry:** this stops being right if rescue refs are written with a recorded target sha, or if the writer refuses any ref that is not its branch's tip. From 03d57d438d512d938355da89b7633831737675bf Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Sun, 23 Aug 2026 14:21:55 -0500 Subject: [PATCH 5/9] backlog: #1349 -- the work is not lost, and the writer-defect argument 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 --- docs/BACKLOG.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index ac545f1c..14f8dff3 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -15666,23 +15666,27 @@ removed with their sessions. ## 1349. a rescue ref can silently hold an ancestor instead of the tip, and the population where that matters is the population where it cannot be checked -> 🔢 **Filed 2026-08-23 - not started.** ***A rescue ref whose NAME contains a branch name can dereference to an ANCESTOR of that branch rather than its tip, 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 its name contains -- 75 commits short, `167 files changed, 23410 insertions(+), 1578 deletions(-)`, holding ZERO commits that branch lacks.** ***That branch is gone from every worktree, so the tag is the only thing standing between that work and nothing.*** +> 🔢 **Filed 2026-08-23 - not started.** ***A rescue ref whose NAME contains a branch name can dereference to an ANCESTOR of that branch rather than its tip, 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 its name contains -- 75 commits short, `167 files changed, 23410 insertions(+), 1578 deletions(-)`, holding ZERO commits that branch lacks.** ***THE WORK IS NOT AT RISK AND THE FIRST FILING SAID IT WAS.*** *That branch is gone from every WORKTREE, which was read as gone. **It exists as a ref, and the push-updated namespace holds its tip exactly.** The hazard is a reader reaching for the wrong ref and concluding work is lost, or recovering 75 commits short.* > Verdict: build > Research: none > Closing-act: code -**Cluster:** coordination tooling / data safety. **Priority:** P1. **Verdict:** build. -**Severity:** no product axis (sec. 0). **The cost is unrecoverable loss of committed work, discovered at -the moment recovery is attempted.** +**Cluster:** coordination tooling / naming. **Priority:** P2. **Verdict:** build. +**Severity:** no product axis (sec. 0). ***The cost is a WRONG RECOVERY DECISION -- reaching for a ref that +is short, or concluding work is gone when another namespace holds it.*** *NOT data loss: no instance of +actual loss has been demonstrated, and the one examined case is fully held elsewhere.* -***CAPTURING THE TIP IS PLAINLY THE INTENDED BEHAVIOUR, WHICH IS WHAT MAKES THE SHORT ONES A DEFECT -RATHER THAN A DOCUMENTATION GAP.*** *Of 730 refs under `refs/tags/rescue`, **374 hold the tip exactly**. -If holding the tip were not the contract that number would be near zero.* +***THE ARGUMENT THAT THIS IS A WRITER DEFECT WAS WITHDRAWN BY ITS AUTHOR AND IS RECORDED HERE 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.* ***374 IS ALSO EXACTLY WHAT SNAPSHOTS OF MOSTLY-DORMANT BRANCHES +LOOK LIKE.*** **That measurement cannot distinguish "tags track heads" from "most branches have not moved +since their snapshot" -- a correlation read as intent.** *Tags in this repository are **snapshots by +design**; the live tracking mechanism is the push-updated namespace.* | tag-scheme census, 730 refs | count | reading | |---|---|---| | **hold the tip exactly** | ***374*** | *the contract, working* | -| ***ANCESTOR -- short of the tip*** | ***22*** | ***MEASURED DEFECT*** | +| ***ANCESTOR -- short of the tip*** | ***22*** | ***MEASURED FACT. Not a defect -- a snapshot older than its branch.*** | | diverged from the named branch | *32* | ***UNVERIFIED*** -- *the matcher takes the longest branch name in the tag name, so a pairing may be spurious* | | name no local branch | *302* | **NOT a defect** -- *a deleted branch is the case rescue refs exist for* | From 39aec3dc825734fd088e17431e86d3a310a0ee13 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Sun, 23 Aug 2026 14:36:08 -0500 Subject: [PATCH 6/9] backlog: correct a dispatch brief on #1236, and #1214's citation that no longer resolves #1236: a brief I sent a lane said admin-unlock shipped in 3ab27755. 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 a5d5a9ab. 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 --- docs/BACKLOG.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 14f8dff3..6ffec2db 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -10829,7 +10829,7 @@ gate is the wrong shape, validation of the walk is the right one. > 🔢 **Re-scored 2026-08-20 -> P3.** Value **2/10** · Difficulty **2/10** · _fill-in_. The published defect, a failure message naming an artefact the check never opened, is fixed in place with its reasoning in the docstring, and two guards were added past the filing. The remainder is a one-identifier rename or the document-binding alternative the Scope explicitly makes optional, which ships nothing runnable either way, so value 2 and a small edit on an existing test module. _(was 5/10 · 3/10.)_ > -> **Filed 2026-08-11 -- found by Session C, MEASURED not argued.** `tests/test_threat_model_doc_drift.py::test_documented_bounds_match_the_live_constants` compares each live constant to a **hardcoded literal in the test file**, then reports a mismatch as `"doc says {expected!r}, code says {actual!r}"`. **"doc says" is not true of any value it compares** -- the literal is a human transcription, and nothing binds the transcription to the document. All of the roughly 60 rows in the `checks` list share the shape, so the gate catches *code drifting from the transcription* and can never catch *the transcription drifting from the document*. +> **Filed 2026-08-11 -- found by Session C, MEASURED not argued.** `tests/test_threat_model_doc_drift.py::test_transcribed_bounds_match_the_live_constants` (**renamed 2026-08-23 in `a5d5a9ab`; this row previously cited `test_documented_bounds_...`, which no longer resolves**) compares each live constant to a **hardcoded literal in the test file**, then reports a mismatch as `"doc says {expected!r}, code says {actual!r}"`. **"doc says" is not true of any value it compares** -- the literal is a human transcription, and nothing binds the transcription to the document. All of the roughly 60 rows in the `checks` list share the shape, so the gate catches *code drifting from the transcription* and can never catch *the transcription drifting from the document*. > **The proof it does not work is that it PASSES when fully wired.** Session C set `MEFOR_THREAT_MODEL_DOC` and `MEFOR_REQUIRE_THREAT_MODEL_DOC` and ran it against the real vault document: **90 passed, 11 skipped**. A gate that passes against the artefact it claims to compare against is measuring something else. > **This is SDS-3.8 -- the instrument answering the adjacent question -- inside the very module built to prevent that class.** It is the sibling of `#1043`, which made this module's doc-absent skip LOUD: the skip is now honest, and the comparison underneath it is still not the one the name promises. Fixing the skip did not fix the measurement. > **Scope:** bind the comparison to the DOCUMENT -- parse the value out of the named document at run time and compare that to the live constant -- or rename the check and its failure message to say what it actually asserts. Either is honest; the present state is not. @@ -11394,6 +11394,17 @@ only check that reads the CODE, and it is the one that decides startability.*** > **The step-up gate on the sole remedy is a CONSTRAINT here, not a mitigation** (`auth_routes.py:757`, `require_step_up(Permission.USERS_MANAGE)`). It is correct on its own axis and it makes this case harder, which is precisely the shape #1131's amendment first got backwards. > **How to prove a fix:** lock the sole administrator, then recover **without** editing the database by hand and **without** an already-authenticated second admin -- and assert the recovery path is itself gated, since an unlock affordance is a control an attacker wants. A fix that only lengthens the docs, or that opens an ungated reset, fails on opposite sides. +***CORRECTED 2026-08-23. A DISPATCH BRIEF STATED THAT `admin-unlock` SHIPPED IN `3ab27755`. IT DID NOT.*** +*That commit's **SUBJECT** says sole-admin lockout recovery; its **CONTENT** adds no `locked_until` line to `auth/service.py`, and no account-unlock affordance exists anywhere in `messagefoundry/`.* **Control: `locked_until` appears 7 times in that file, so the search fires.** ***A COMMIT SUBJECT IS NOT A CONTENT CHECK*** -- *the brief was produced by a verification pass whose own instructions forbade deciding from commit messages.* + +**AND THE SCOPE IS THREE SITES, NOT ONE.** *The brief named `service.py:800` alone.* ***The refusal is emitted at `:797` (password), `:2243` (MFA) and `:2608` (WebAuthn), so a duration fix at one leaves two inconsistent*** -- *an error in the narrowing direction, which is the one that yields a half-fix that reads as complete.* + +***THE ENUMERATION TENSION IS ALREADY SPENT AND NEEDS NO OWNER RULING.*** *`"account locked"` at `:797` is **already distinct** from `"invalid credentials"` at `:795`, `:819` and `:856`, so a duration-bearing refusal **refines a signal that already ships** rather than opening a new disclosure class.* + +**RECOVERY EXISTS MECHANICALLY AND IS UNREACHABLE.** *`set_password` clears the lock at `store.py:7897`* -- ***but a sole administrator who is locked out cannot authenticate to call it, and 38 argparse subcommands contain no user, passwd or unlock path.*** **The gap is an out-of-band, admin-facing affordance.** + +***SCOPE RULED 2026-08-23: BUILD THE ON-DEMAND RECOVERY, DO NOT RE-SCOPE TO SELF-EXPIRY.*** *Waiting `lockout_minutes` is a workaround, not recovery, for an item whose premise is that **nobody else exists to unlock you**.* **A re-scope to declare self-expiry acceptable routes to the owner; confirming the original scope did not.** + > **AMENDED 2026-08-21 -- THE TEST ABOVE PASSES TODAY, BY WAITING, so it cannot discriminate a fixed system from the shipped one.** The lock is time-bounded and clears itself, by default in 15 minutes (`auth/service.py:851-853`, `auth/policy.py:103`), and simply waiting recovers the account **without** hand-editing the database and **without** a second authenticated admin -- satisfying the criterion above exactly as written. **An acceptance test that a defect-free system and the defective system both pass is not an acceptance test.** A usable criterion has to exclude the passage of time: recovery must be reachable **on demand**, gated, and **faster than `lockout_minutes`**, or the item must say plainly that self-expiry is the accepted recovery and re-scope to what is actually owed. **Measured while amending this, and it settles a limb the premise correction had flagged unmeasured:** an ACTIVE lock cannot be extended -- `service.py:739` returns at `:742` before `_register_failure` at `:746` -- but the NUMBER of lock cycles is unbounded, which `docs/SECURITY.md` already words as bounding the lock rather than the campaign. **Amendment only; no status, score or tier changed by this edit.** > Verdict: build > Closing-act: code From 4d52978eda04bbd663f35b3bc28002afe1f01a91 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Sun, 23 Aug 2026 14:42:59 -0500 Subject: [PATCH 7/9] backlog: re-correct #1236 -- admin-unlock does ship, and my correction was the error The row briefly said admin-unlock did not ship in 3ab27755. 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 3ab27755 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 --- docs/BACKLOG.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 6ffec2db..deed0242 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -11394,16 +11394,32 @@ only check that reads the CODE, and it is the one that decides startability.*** > **The step-up gate on the sole remedy is a CONSTRAINT here, not a mitigation** (`auth_routes.py:757`, `require_step_up(Permission.USERS_MANAGE)`). It is correct on its own axis and it makes this case harder, which is precisely the shape #1131's amendment first got backwards. > **How to prove a fix:** lock the sole administrator, then recover **without** editing the database by hand and **without** an already-authenticated second admin -- and assert the recovery path is itself gated, since an unlock affordance is a control an attacker wants. A fix that only lengthens the docs, or that opens an ungated reset, fails on opposite sides. -***CORRECTED 2026-08-23. A DISPATCH BRIEF STATED THAT `admin-unlock` SHIPPED IN `3ab27755`. IT DID NOT.*** -*That commit's **SUBJECT** says sole-admin lockout recovery; its **CONTENT** adds no `locked_until` line to `auth/service.py`, and no account-unlock affordance exists anywhere in `messagefoundry/`.* **Control: `locked_until` appears 7 times in that file, so the search fires.** ***A COMMIT SUBJECT IS NOT A CONTENT CHECK*** -- *the brief was produced by a verification pass whose own instructions forbade deciding from commit messages.* +***RE-CORRECTED 2026-08-23, SAME HOUR. `admin-unlock` DOES SHIP, AND AN EARLIER CORRECTION IN THIS ROW +CLAIMING OTHERWISE WAS ITSELF WRONG.*** *Verified repo-wide with both controls firing:* **`__main__.py:603` +registers the `admin-unlock` subcommand with `--username`, `--db` and `--json`; `:3962` clears the lock via +`record_login_failure(user.id, failed_attempts=0, locked_until=None)`; `:3964` audits `auth.admin_unlocked`.** +*`3ab27755` added 92 lines to `__main__.py`.* -**AND THE SCOPE IS THREE SITES, NOT ONE.** *The brief named `service.py:800` alone.* ***The refusal is emitted at `:797` (password), `:2243` (MFA) and `:2608` (WebAuthn), so a duration fix at one leaves two inconsistent*** -- *an error in the narrowing direction, which is the one that yields a half-fix that reads as complete.* +***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 -- `:797` password, `:2243` MFA, `:2608` +WebAuthn -- and **lock CYCLES are still uncapped**, since `_register_failure` has no counter.* -***THE ENUMERATION TENSION IS ALREADY SPENT AND NEEDS NO OWNER RULING.*** *`"account locked"` at `:797` is **already distinct** from `"invalid credentials"` at `:795`, `:819` and `:856`, so a duration-bearing refusal **refines a signal that already ships** rather than opening a new disclosure class.* +**THE ENUMERATION TENSION IS SPENT AND NEEDS NO OWNER RULING:** *`"account locked"` is **already +distinct** from `"invalid credentials"` at `:795`, `:819` and `:856`, so a duration-bearing refusal +refines a signal that already ships.* -**RECOVERY EXISTS MECHANICALLY AND IS UNREACHABLE.** *`set_password` clears the lock at `store.py:7897`* -- ***but a sole administrator who is locked out cannot authenticate to call it, and 38 argparse subcommands contain no user, passwd or unlock path.*** **The gap is an out-of-band, admin-facing affordance.** +## ***HOW THE WRONG CORRECTION WAS PRODUCED, BECAUSE THE CONTROL IS THE LESSON*** -***SCOPE RULED 2026-08-23: BUILD THE ON-DEMAND RECOVERY, DO NOT RE-SCOPE TO SELF-EXPIRY.*** *Waiting `lockout_minutes` is a workaround, not recovery, for an item whose premise is that **nobody else exists to unlock you**.* **A re-scope to declare self-expiry acceptable routes to the owner; confirming the original scope did not.** +*Three layers failed in the same direction:* **a `git show -- auth/service.py` search returned a +zero that was TRUE FOR THAT FILE and was published as repo-wide** -- *`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 claim was repo-wide; every instrument was file-wide.* > **AMENDED 2026-08-21 -- THE TEST ABOVE PASSES TODAY, BY WAITING, so it cannot discriminate a fixed system from the shipped one.** The lock is time-bounded and clears itself, by default in 15 minutes (`auth/service.py:851-853`, `auth/policy.py:103`), and simply waiting recovers the account **without** hand-editing the database and **without** a second authenticated admin -- satisfying the criterion above exactly as written. **An acceptance test that a defect-free system and the defective system both pass is not an acceptance test.** A usable criterion has to exclude the passage of time: recovery must be reachable **on demand**, gated, and **faster than `lockout_minutes`**, or the item must say plainly that self-expiry is the accepted recovery and re-scope to what is actually owed. **Measured while amending this, and it settles a limb the premise correction had flagged unmeasured:** an ACTIVE lock cannot be extended -- `service.py:739` returns at `:742` before `_register_failure` at `:746` -- but the NUMBER of lock cycles is unbounded, which `docs/SECURITY.md` already words as bounding the lock rather than the campaign. **Amendment only; no status, score or tier changed by this edit.** > Verdict: build From 7ef064deb8fc0eba2f865ca4e7e0ee69c5397207 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Sun, 23 Aug 2026 14:45:46 -0500 Subject: [PATCH 8/9] backlog: #1236 -- correct an off-by-one and a missing fourth site 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 --- docs/BACKLOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index deed0242..8dc3ee57 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -11395,9 +11395,13 @@ only check that reads the CODE, and it is the one that decides startability.*** > **How to prove a fix:** lock the sole administrator, then recover **without** editing the database by hand and **without** an already-authenticated second admin -- and assert the recovery path is itself gated, since an unlock affordance is a control an attacker wants. A fix that only lengthens the docs, or that opens an ungated reset, fails on opposite sides. ***RE-CORRECTED 2026-08-23, SAME HOUR. `admin-unlock` DOES SHIP, AND AN EARLIER CORRECTION IN THIS ROW -CLAIMING OTHERWISE WAS ITSELF WRONG.*** *Verified repo-wide with both controls firing:* **`__main__.py:603` +CLAIMING OTHERWISE WAS ITSELF WRONG.*** *Verified repo-wide with both controls firing:* **`__main__.py:604` registers the `admin-unlock` subcommand with `--username`, `--db` and `--json`; `:3962` clears the lock via -`record_login_failure(user.id, failed_attempts=0, locked_until=None)`; `:3964` audits `auth.admin_unlocked`.** +`record_login_failure(user.id, failed_attempts=0, locked_until=None)`; `:3964` audits `auth.admin_unlocked`; +and `:5091` is the DISPATCH-TABLE ENTRY `"admin-unlock": _admin_unlock` -- **the line that makes the +subcommand reachable at all**.** *Four sites, not three; an earlier draft of this paragraph said `:603` +and named three. **Both were corrected by an independent repo-wide re-derivation, because a line number +in a ledger row gets followed.*** *`3ab27755` added 92 lines to `__main__.py`.* ***SO THE ON-DEMAND AFFORDANCE ALREADY EXISTS AND NEEDS NO BUILD.*** **What remains is narrower:** *a From e6820e663b3a6dbe5628624e46ba164c25ef624f Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Sun, 23 Aug 2026 14:48:03 -0500 Subject: [PATCH 9/9] backlog: #1236 -- the enumeration tension is NOT spent, and the duration 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 --- docs/BACKLOG.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 8dc3ee57..1cbb707f 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -11406,11 +11406,29 @@ in a ledger row gets followed.*** ***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 -- `:797` password, `:2243` MFA, `:2608` -WebAuthn -- and **lock CYCLES are still uncapped**, since `_register_failure` has no counter.* +WebAuthn -- and **lock CYCLES are still uncapped**, since `_register_failure` has no counter.* ***THE +DURATION LIMB IS BLOCKED ON AN OWNER RULING (see below). THE CYCLE CAP IS NOT.*** -**THE ENUMERATION TENSION IS SPENT AND NEEDS NO OWNER RULING:** *`"account locked"` is **already -distinct** from `"invalid credentials"` at `:795`, `:819` and `:856`, so a duration-bearing refusal -refines a signal that already ships.* +***THE ENUMERATION TENSION IS NOT SPENT. THIS ROW SAID TWICE THAT IT WAS, AND THAT WAS WRONG BOTH +TIMES.*** **The distinct strings live ONLY in logs and audit. Verified at the BOUNDARY, with the control +firing:** + +- *`auth/service.py:147`, the `LoginOutcome` contract, verbatim:* **"``error`` is for logs/audit -- never + leak the reason to clients"** +- *`api/auth_routes.py:259` raises* **`HTTPException(401, "invalid credentials")` -- a FIXED STRING**, + *whenever `not outcome.ok`, regardless of `outcome.error`* +- ***`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.*** + +**THAT INVERTS THE CONCLUSION.** *A duration that reaches the locked-out operator is a **NEW +DISCLOSURE**: it tells an **unauthenticated** caller that the account exists, is locked, and for how +long.* ***OWNER-LEVEL, AND ROUTED.*** *And a duration added only to the `error` field would help nobody, +because the operator never sees that field.* + +**MFA AND WEBAUTHN RETURN A BARE `False` WITH NO ERROR CHANNEL AT ALL**, *so a duration there needs a +different mechanism -- **not the same edit three times**, which is what a three-site brief implied.* ## ***HOW THE WRONG CORRECTION WAS PRODUCED, BECAUSE THE CONTROL IS THE LESSON***