fix(coord): the backlog number floor ignored every ref but origin/main - #88
Conversation
11b9276 to
323dfa1
Compare
Get-Floor's own header comment says "FLOOR = max over (origin/main) U (every local + remote ref) U (existing allocations)", and LEDGER-GATE.md repeats it. The adr branch does that. The backlog branch did not -- it read origin/main, HEAD and the working file, and nothing else. That is not cosmetic. docs/BACKLOG.md in this repo is a published baseline of a larger internal ledger, and numbers above the baseline live on refs this branch does not carry. Invisible to the floor, they were handed out as free: on 2026-07-30 the allocator issued #240-#247 over numbers ADR 0115 and seven amended ADRs already cite as ASVS work packages, and nobody noticed until a session cross-read the citations. Measured here: the old floor returns 251, the corrected one 314. Batched rather than looped. ~550 refs share ~190 distinct BACKLOG.md blobs; a `git show` per ref costs ~34s on Windows because each is its own process. Resolving all specs through one `git cat-file --batch-check` and streaming the distinct blobs through one `git cat-file --batch` gives the same answer in ~3s, which is the "about a second, once per ADR" the docstring budgets for. Verified end to end: an allocation after this change lands at #315, where before it would have taken #252 and walked straight back into the internal namespace. Caveat worth stating: the floor is now as complete as the local ref set. A clone without those refs computes a lower floor -- but that is exactly the property the adr path has always had, and a lower floor was never the safe direction. The erratum records what cannot be repaired by tooling: #242-#246 in the ADRs are provenance into the internal ledger, not pointers into this file; the numbers here above #231 are a second independent sequence; #240-#247 and #315 are permanent holes. Renumbering would rewrite ratified ADRs and republishing would cross SECURITY-DOCS-POLICY, so both are left as owner calls rather than taken. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
323dfa1 to
f1239e0
Compare
Owner ruled 2026-07-30 that helper descent is too risky. ADR 0076 Amendment B goes PROPOSED -> DECLINED, and Phase D is not to be built. Recorded in four places, because a decline that only exists where the decline was argued is a decline nobody finds. The load-bearing one is ADR 0089 itself: a session planning the next lens phase reads 0089's phase list, not an amendment to a different ADR, and 0089's own text sells Phase D as "the largest structural lever" against "265 delegating call-sites". That paragraph is left intact with the ruling above it, plus the correction that 265 is a statement-scan count and not a yield estimate -- descent makes nothing editable by itself. Decline-by-design, not deferral. The specification stays so the reasoning is auditable and so the next reader who thinks helper delegation is the biggest remaining win finds the work already done. Reopening takes a new amendment, and must first answer the aliasing question (two call sites of one helper produce identical child spans; expect_src matches both, so an edit to one silently rewrites the other) and produce a real per-helper measurement rather than the 218/522 heuristic superset. Promotes the alternative from precondition to recommendation: teaching Phase A the ast.Assign-to-ast.Subscript form (msg["X"] = v) widens what is editable without touching the row shape, and is on no phase list. Also fixes a stale cross-reference this session introduced: #239's banner cited "#240 (comment-only rows)" from before that item was renumbered to #248. #240 is now a permanent hole, so the citation pointed at nothing -- the exact failure the #88 erratum describes. The same banner's "~70% of the opaque mass" argument is corrected: with Phase D declined it rests on comment-only rows alone. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner ruled 2026-07-30 that helper descent is too risky. ADR 0076 Amendment B goes PROPOSED -> DECLINED, and Phase D is not to be built. Recorded in four places, because a decline that only exists where the decline was argued is a decline nobody finds. The load-bearing one is ADR 0089 itself: a session planning the next lens phase reads 0089's phase list, not an amendment to a different ADR, and 0089's own text sells Phase D as "the largest structural lever" against "265 delegating call-sites". That paragraph is left intact with the ruling above it, plus the correction that 265 is a statement-scan count and not a yield estimate -- descent makes nothing editable by itself. Decline-by-design, not deferral. The specification stays so the reasoning is auditable and so the next reader who thinks helper delegation is the biggest remaining win finds the work already done. Reopening takes a new amendment, and must first answer the aliasing question (two call sites of one helper produce identical child spans; expect_src matches both, so an edit to one silently rewrites the other) and produce a real per-helper measurement rather than the 218/522 heuristic superset. Promotes the alternative from precondition to recommendation: teaching Phase A the ast.Assign-to-ast.Subscript form (msg["X"] = v) widens what is editable without touching the row shape, and is on no phase list. Also fixes a stale cross-reference this session introduced: #239's banner cited "#240 (comment-only rows)" from before that item was renumbered to #248. #240 is now a permanent hole, so the citation pointed at nothing -- the exact failure the #88 erratum describes. The same banner's "~70% of the opaque mass" argument is corrected: with Phase D declined it rests on comment-only rows alone. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Correction to this PR's description — I overstated the CI gapThis PR's body says:
The first sentence is accurate. The second is wrong, and the framing "defect" is wrong. Correcting it here because acting on it would break CI for everyone.
A CI backstop does exist — it just lives in another script. The failure that actually matters (a number reused against the published file) surfaces as a duplicate What genuinely has no CI coverage — and neither case is fixable in CI:
So the asymmetry with the ADR path is real (ADRs get a CI-safe reuse check and a local ownership check; backlog gets the ownership check plus duplicate detection elsewhere), but it is not an unguarded hole, and there is no change to make here. The |
BACKLOG #252 was allocated BEFORE #88 fixed alloc.ps1's floor, which read only origin/main + HEAD despite a docstring promising every local and remote ref. docs/BACKLOG.md is a published baseline of a larger internal ledger, so numbers above #231 exist on refs this branch does not carry — and 252 was one of them: refs/remotes/vaultall/claude/throughput-thursday-4debf6 ## 252. Per-row ingress-stage raw checksum (integrity anchor) So the DICOM peer-control item was a duplicate of an unrelated integrity-anchor item. Re-allocated with the fixed floor, which returns 316 — confirming the jump the fix predicts. Verified 316 is free on every vault ref before taking it. Renumbered in the entry, the gate comment, the refusal message, the test docstring, docs/CONNECTIONS.md, the docs/SECURITY.md decision row and the CHANGELOG. The claim moved with it: 316 taken, 252 released. Deliberately a targeted replacement of "BACKLOG #252" and the "## 252." heading rather than a global 252 -> 316 sweep. CHANGELOG.md mentions cp1252 twice, and a naive replace turns it into cp1316 — a silent corruption in a file nobody re-reads. Both occurrences verified intact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR #88 made the backlog floor read every ref, which is correct but leaves the fix dependent on those refs continuing to exist. Measured on the maintainer clone: the floor is 314 counting all refs and 252 counting only refs/remotes/origin plus local heads. The 62-number difference lives on remote-tracking refs for a remote that `git remote -v` no longer lists, so a routine cleanup would revert the floor to its pre-fix value and the allocator would resume issuing numbers that are already in use -- no error, no signal, the same bug again. Every term in the floor is derived from something removable, so derive one that is not: persist the highest floor ever computed at alloc/<kind>/.floor-highwater and never go below it. A computed floor under the mark prints a loud NOTE instead of quietly handing out a used number. The mark only rises. It is a backstop, not a substitute: the ratchet keeps the number space correct, but if those refs go the history behind them is still gone. Also documents a distinction a peer session flagged after running a prune and worrying: `git fetch origin --prune` is SAFE -- it touches only refs/remotes/origin/*, and it is what you should run before allocating. The dangerous operations are removing a non-origin remote, deleting its refs, or a gc/reflog expire that drops unreachable objects. An undifferentiated "do not prune" warning is worse than none, because it scares people off the fetch the allocation flow depends on. Verified: allocation writes the mark (316) and lands #317; the ratchet branch exercised in isolation over six cases -- collapse (252 vs mark 314) holds at 314 and warns, a rising floor advances the mark, first run with no mark, an equal mark, and empty/non-numeric mark files, which must neither crash nor zero the floor. The collapse case was tested against a throwaway watermark rather than by deleting real refs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…#252) (#94) * fix(dicom): an AE-title list is not a peer control on its own (BACKLOG #252) The fail-closed peer-control gate refuses a non-loopback C-STORE SCP that has no peer control, and it accepted any ONE of three: calling_ae_allowlist, source_ip_allowlist, or mTLS. It COUNTED them rather than weighing them. A Calling AE Title is a string the caller asserts about ITSELF in the association request — no key, no signature, nothing to verify — and AE Titles are published in conformance statements and visible in any capture. So an SCP whose only peer control was an AE-title list was reachable by anyone who could route to it and knew one string, while passing a check named "fail-closed peer controls". Server TLS does not close it either: without tls_ca_file there is no client certificate, so the cleartext bind guard (confidentiality) and this gate (authentication) are orthogonal. Option (a) of the three recorded on #252, chosen by the owner: PAIR, do not remove. Off-loopback the gate now requires source_ip_allowlist or mTLS. calling_ae_allowlist is KEPT and still enforced at association time — it catches a misrouted sender and pins intent, which is real value — it simply cannot stand alone. Measured, off-loopback: AE-title-only STARTS -> REFUSED; AE-title PAIRED with an IP allowlist starts; IP-only and mTLS-only unchanged; every loopback bind unchanged (the common dev/single-box case does not regress). Full unfiltered suite: 9518 passed, 0 failed. The refusal detects the unpaired case specifically and explains why an AE Title cannot be verified, and tells the operator to KEEP it and add a control — otherwise the message reads as "but I already set an allowlist" and the obvious wrong move is to delete the AE list. BREAKING for a site whose non-loopback SCP relies on AE-title-alone. The fix is one line (source_ip_allowlist on the inbound(...) call, which for a DICOM SCP is the only authoring surface) and the refusal names it. Recorded in CHANGELOG. A note on the citation. The code and both docs credited this rule to "ADR 0025 §9". ADR 0025 has no section 9 — its decision sections run 1-8, and the "§9" in that file refers to CLAUDE.md §9 — and calling_ae_allowlist appears nowhere in it. The normative statement lives in docs/CONNECTIONS.md and the docs/SECURITY.md decision table, which is what this commit amends. No ADR amendment is possible because there is no ADR text to amend; the new code and tests cite BACKLOG #252, which is real, rather than repeating a pointer that sends a reader somewhere they cannot go. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ledger): renumber this item 252 -> 316, it collided (BACKLOG #316) BACKLOG #252 was allocated BEFORE #88 fixed alloc.ps1's floor, which read only origin/main + HEAD despite a docstring promising every local and remote ref. docs/BACKLOG.md is a published baseline of a larger internal ledger, so numbers above #231 exist on refs this branch does not carry — and 252 was one of them: refs/remotes/vaultall/claude/throughput-thursday-4debf6 ## 252. Per-row ingress-stage raw checksum (integrity anchor) So the DICOM peer-control item was a duplicate of an unrelated integrity-anchor item. Re-allocated with the fixed floor, which returns 316 — confirming the jump the fix predicts. Verified 316 is free on every vault ref before taking it. Renumbered in the entry, the gate comment, the refusal message, the test docstring, docs/CONNECTIONS.md, the docs/SECURITY.md decision row and the CHANGELOG. The claim moved with it: 316 taken, 252 released. Deliberately a targeted replacement of "BACKLOG #252" and the "## 252." heading rather than a global 252 -> 316 sweep. CHANGELOG.md mentions cp1252 twice, and a naive replace turns it into cp1316 — a silent corruption in a file nobody re-reads. Both occurrences verified intact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
PR #88 made the backlog floor read every ref, which is correct but leaves the fix dependent on those refs continuing to exist. Measured on the maintainer clone: the floor is 314 counting all refs and 252 counting only refs/remotes/origin plus local heads. The 62-number difference lives on remote-tracking refs for a remote that `git remote -v` no longer lists, so a routine cleanup would revert the floor to its pre-fix value and the allocator would resume issuing numbers that are already in use -- no error, no signal, the same bug again. Every term in the floor is derived from something removable, so derive one that is not: persist the highest floor ever computed at alloc/<kind>/.floor-highwater and never go below it. A computed floor under the mark prints a loud NOTE instead of quietly handing out a used number. The mark only rises. It is a backstop, not a substitute: the ratchet keeps the number space correct, but if those refs go the history behind them is still gone. Also documents a distinction a peer session flagged after running a prune and worrying: `git fetch origin --prune` is SAFE -- it touches only refs/remotes/origin/*, and it is what you should run before allocating. The dangerous operations are removing a non-origin remote, deleting its refs, or a gc/reflog expire that drops unreachable objects. An undifferentiated "do not prune" warning is worse than none, because it scares people off the fetch the allocation flow depends on. Verified: allocation writes the mark (316) and lands #317; the ratchet branch exercised in isolation over six cases -- collapse (252 vs mark 314) holds at 314 and warns, a rising floor advances the mark, first run with no mark, an equal mark, and empty/non-numeric mark files, which must neither crash nor zero the floor. The collapse case was tested against a throwaway watermark rather than by deleting real refs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR #88 made the backlog floor read every ref, which is correct but leaves the fix dependent on those refs continuing to exist. Measured on the maintainer clone: the floor is 314 counting all refs and 252 counting only refs/remotes/origin plus local heads. The 62-number difference lives on remote-tracking refs for a remote that `git remote -v` no longer lists, so a routine cleanup would revert the floor to its pre-fix value and the allocator would resume issuing numbers that are already in use -- no error, no signal, the same bug again. Every term in the floor is derived from something removable, so derive one that is not: persist the highest floor ever computed at alloc/<kind>/.floor-highwater and never go below it. A computed floor under the mark prints a loud NOTE instead of quietly handing out a used number. The mark only rises. It is a backstop, not a substitute: the ratchet keeps the number space correct, but if those refs go the history behind them is still gone. Also documents a distinction a peer session flagged after running a prune and worrying: `git fetch origin --prune` is SAFE -- it touches only refs/remotes/origin/*, and it is what you should run before allocating. The dangerous operations are removing a non-origin remote, deleting its refs, or a gc/reflog expire that drops unreachable objects. An undifferentiated "do not prune" warning is worse than none, because it scares people off the fetch the allocation flow depends on. Verified: allocation writes the mark (316) and lands #317; the ratchet branch exercised in isolation over six cases -- collapse (252 vs mark 314) holds at 314 and warns, a rising floor advances the mark, first run with no mark, an equal mark, and empty/non-numeric mark files, which must neither crash nor zero the floor. The collapse case was tested against a throwaway watermark rather than by deleting real refs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR #88 made the backlog floor read every ref, which is correct but leaves the fix dependent on those refs continuing to exist. Measured on the maintainer clone: the floor is 314 counting all refs and 252 counting only refs/remotes/origin plus local heads. The 62-number difference lives on remote-tracking refs for a remote that `git remote -v` no longer lists, so a routine cleanup would revert the floor to its pre-fix value and the allocator would resume issuing numbers that are already in use -- no error, no signal, the same bug again. Every term in the floor is derived from something removable, so derive one that is not: persist the highest floor ever computed at alloc/<kind>/.floor-highwater and never go below it. A computed floor under the mark prints a loud NOTE instead of quietly handing out a used number. The mark only rises. It is a backstop, not a substitute: the ratchet keeps the number space correct, but if those refs go the history behind them is still gone. Also documents a distinction a peer session flagged after running a prune and worrying: `git fetch origin --prune` is SAFE -- it touches only refs/remotes/origin/*, and it is what you should run before allocating. The dangerous operations are removing a non-origin remote, deleting its refs, or a gc/reflog expire that drops unreachable objects. An undifferentiated "do not prune" warning is worse than none, because it scares people off the fetch the allocation flow depends on. Verified: allocation writes the mark (316) and lands #317; the ratchet branch exercised in isolation over six cases -- collapse (252 vs mark 314) holds at 314 and warns, a rising floor advances the mark, first run with no mark, an equal mark, and empty/non-numeric mark files, which must neither crash nor zero the floor. The collapse case was tested against a throwaway watermark rather than by deleting real refs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR #88 made the backlog floor read every ref, which is correct but leaves the fix dependent on those refs continuing to exist. Measured on the maintainer clone: the floor is 314 counting all refs and 252 counting only refs/remotes/origin plus local heads. The 62-number difference lives on remote-tracking refs for a remote that `git remote -v` no longer lists, so a routine cleanup would revert the floor to its pre-fix value and the allocator would resume issuing numbers that are already in use -- no error, no signal, the same bug again. Every term in the floor is derived from something removable, so derive one that is not: persist the highest floor ever computed at alloc/<kind>/.floor-highwater and never go below it. A computed floor under the mark prints a loud NOTE instead of quietly handing out a used number. The mark only rises. It is a backstop, not a substitute: the ratchet keeps the number space correct, but if those refs go the history behind them is still gone. Also documents a distinction a peer session flagged after running a prune and worrying: `git fetch origin --prune` is SAFE -- it touches only refs/remotes/origin/*, and it is what you should run before allocating. The dangerous operations are removing a non-origin remote, deleting its refs, or a gc/reflog expire that drops unreachable objects. An undifferentiated "do not prune" warning is worse than none, because it scares people off the fetch the allocation flow depends on. Verified: allocation writes the mark (316) and lands #317; the ratchet branch exercised in isolation over six cases -- collapse (252 vs mark 314) holds at 314 and warns, a rising floor advances the mark, first run with no mark, an equal mark, and empty/non-numeric mark files, which must neither crash nor zero the floor. The collapse case was tested against a throwaway watermark rather than by deleting real refs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#101) PR #88 made the backlog floor read every ref, which is correct but leaves the fix dependent on those refs continuing to exist. Measured on the maintainer clone: the floor is 314 counting all refs and 252 counting only refs/remotes/origin plus local heads. The 62-number difference lives on remote-tracking refs for a remote that `git remote -v` no longer lists, so a routine cleanup would revert the floor to its pre-fix value and the allocator would resume issuing numbers that are already in use -- no error, no signal, the same bug again. Every term in the floor is derived from something removable, so derive one that is not: persist the highest floor ever computed at alloc/<kind>/.floor-highwater and never go below it. A computed floor under the mark prints a loud NOTE instead of quietly handing out a used number. The mark only rises. It is a backstop, not a substitute: the ratchet keeps the number space correct, but if those refs go the history behind them is still gone. Also documents a distinction a peer session flagged after running a prune and worrying: `git fetch origin --prune` is SAFE -- it touches only refs/remotes/origin/*, and it is what you should run before allocating. The dangerous operations are removing a non-origin remote, deleting its refs, or a gc/reflog expire that drops unreachable objects. An undifferentiated "do not prune" warning is worse than none, because it scares people off the fetch the allocation flow depends on. Verified: allocation writes the mark (316) and lands #317; the ratchet branch exercised in isolation over six cases -- collapse (252 vs mark 314) holds at 314 and warns, a rising floor advances the mark, first run with no mark, an equal mark, and empty/non-numeric mark files, which must neither crash nor zero the floor. The collapse case was tested against a throwaway watermark rather than by deleting real refs. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The bug
Get-Floor's own header comment inscripts/coord/alloc.ps1says:docs/LEDGER-GATE.md:56-58repeats it. Theadrbranch does that. Thebacklogbranch did not — it readorigin/main,HEAD, and the working file, and nothing else.That is not cosmetic.
docs/BACKLOG.mdhere is a published baseline of a larger internal ledger — the file says so itself (the #185 banner, and the#313reference). Numbers above the baseline live on refs this branch does not carry. Invisible to the floor, they were handed out as free.On 2026-07-30 the allocator issued #240–#247 over numbers that ADR 0115 and seven amended ADRs already cite as ASVS work packages (
WP #243–WP #246). Nobody noticed until a session cross-read the citations while filing unrelated items. Taking#244would have made ADR 0014'sWP #244silently resolve to a Steps-view item.Measured: old floor returns 251, corrected floor returns 314.
The fix
Sweep every local and remote ref, as promised — batched, not looped. ~550 refs share ~190 distinct
BACKLOG.mdblobs, and agit showper ref costs ~34s on Windows because each is its own process. Resolving all specs through onegit cat-file --batch-checkand streaming distinct blobs through onegit cat-file --batchgives the same answer in ~3s, inside the "about a second, once per ADR" the docstring budgets.Verified end to end, not just by inspection:
Before this change that call returns #252 — straight back into the internal namespace.
Caveat, stated plainly: the floor is now as complete as the local ref set; a clone without those refs computes a lower floor. That is exactly the property the
adrpath has always had, and a lower floor was never the safe direction.The erratum
docs/BACKLOG.mdgains a preamble note recording what tooling cannot repair:#242–#246in the ADRs are provenance into the internal ledger, not pointers into this file — read like thedocs/reviews/anddocs/security/paths already are.alloc.ps1has no release verb by design ("holes are free, collisions are not"), so the gate will refuse a commit that files there.Written as a
###heading with no leading digit so it matches neitherbacklog_status_check.py's^## (\d+)\.(which would demand a status banner) nor the allocator's own floor regex (which would silently move the floor).Deliberately NOT done — both are owner calls
SECURITY-DOCS-POLICY.md, which withholds that material.ledger_check.pyguards its only backlog rule withif not self.ci, socheck_backlog()can never fail in CI. The CI backstop advertised inLEDGER-GATE.md:99-103does not exist for BACKLOG numbers — a--no-verifycommit squatting cited numbers would land green. Flagging rather than changing enforcement in a fix PR.Verification
🤖 Generated with Claude Code