From f1239e0fa6508048378789c1faf19a4a76d68154 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 30 Jul 2026 17:45:21 -0500 Subject: [PATCH] fix(coord): the backlog floor ignored every ref but origin/main 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 --- docs/BACKLOG.md | 31 +++++++++++++++++++++++++++++++ scripts/coord/alloc.ps1 | 35 +++++++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 82ca2622..a4c2e535 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -8,6 +8,37 @@ cited report. Several of those reports — the `docs/reviews/` and `docs/securit maintainer-internal and will not resolve here; [`SECURITY-DOCS-POLICY.md`](SECURITY-DOCS-POLICY.md) states the rule that decides what is withheld and what you can request. +### Ledger erratum (2026-07-30) — read this before citing or allocating a number + +**`#242`–`#246` as written in the ADRs are not indices into this file.** +[ADR 0115](adr/0115-asvs-l3-drive-to-pass-secure-by-default-flips-and-residual-closure.md) partitioned +the ASVS L3 drive-to-Pass programme across five work packages and writes them `BACKLOG #242`–`#246`; +ADRs 0004, 0014, 0018, 0019, 0068, 0077, 0080 and 0105 cite the same numbers as `WP #243`–`WP #246`. +Those items were filed in the maintainer-internal ledger that **this file is a published baseline of**, +and the published baseline stops at **#231** — as the #185 banner and the `#313` reference further down +already say. They were never published here, and they are not back-filled: their per-cell scope is +defined only in the `docs/security/` remediation plan, which +[`SECURITY-DOCS-POLICY.md`](SECURITY-DOCS-POLICY.md) withholds. Read those citations the way the +`docs/reviews/` and `docs/security/` paths above are read — **provenance into the internal ledger, not a +pointer into this file.** The same applies to every ASVS-programme number above #231; that programme +continued well past #246. Whether any of it is republished here is an owner decision. + +**Consequently the numbers in this file above #231 are a second, independent sequence**, and items +#232–#239 and #248–#251 do not correspond to the internal items sharing those numbers. This is recorded, +not repaired: renumbering would rewrite ratified ADRs, and republishing would cross the policy above. + +**#240–#247 are permanent holes — do not file there.** They were allocated on 2026-07-30 by repeated +runs for the same four titles; only the last run's numbers (#248–#251) were filed. #240–#243 are held by +a worktree that no longer exists, and `alloc.ps1` has no release verb by design ("holes are free, +collisions are not"), so those claims stand permanently and the ledger gate will refuse a commit that +files there. **#315** is a deliberate probe allocation used to verify the floor fix below; it is also a +hole. Always allocate with `scripts/coord/alloc.ps1`; never pick a number by reading this file. + +The root cause is fixed: the backlog floor in `alloc.ps1` now sweeps **every** local and remote ref, as +its own header comment always promised and as the ADR path already did. Before the fix it read only +`origin/main` + `HEAD`, so numbers living on refs this branch does not carry were invisible and were +handed out as free — which is exactly how #240–#247 were issued over cited numbers. + --- ## Shipped — v0.1.0 (enterprise / HA milestone) diff --git a/scripts/coord/alloc.ps1 b/scripts/coord/alloc.ps1 index 2c37670a..cb25d3d8 100644 --- a/scripts/coord/alloc.ps1 +++ b/scripts/coord/alloc.ps1 @@ -81,15 +81,34 @@ function Get-Floor { } } } else { - # BACKLOG.md is one big file: read it from origin/main and from this worktree's HEAD + index. - $texts = @( - (& git show "origin/main:docs/BACKLOG.md" 2>$null) -join "`n" - (& git show "HEAD:docs/BACKLOG.md" 2>$null) -join "`n" - ) + # BACKLOG.md is ONE BIG FILE, so the floor needs its CONTENT, not a filename listing -- but it + # still needs EVERY ref, exactly like the adr branch above and exactly as this function's own + # header comment promises. Reading only origin/main + HEAD is what re-issued #240-#247 on + # 2026-07-30 over numbers ADR 0115 and seven amended ADRs already cite: the items holding + # those numbers live on refs the published branch does not carry, so they were invisible here + # and the allocator handed the numbers out as free. A number that exists on ANY ref is taken. + # + # Batched deliberately: ~550 refs share ~190 distinct BACKLOG.md blobs, and a `git show` per + # ref costs ~34s on Windows (one process each). Two `git cat-file` processes do it in ~3s. + $refs = @("origin/main", "HEAD") + @(& git for-each-ref --format='%(refname)' refs/heads refs/remotes) + $specs = ($refs | Select-Object -Unique | ForEach-Object { "${_}:docs/BACKLOG.md" }) + + $oids = [System.Collections.Generic.HashSet[string]]::new() + foreach ($line in ($specs -join "`n" | & git cat-file --batch-check='%(objectname) %(objecttype)' 2>$null)) { + $p = "$line".Split(' ') + if ($p.Count -ge 2 -and $p[1] -eq 'blob') { [void]$oids.Add($p[0]) } + } + + $rx = [regex]'^#{2,3} (\d+)\.' + if ($oids.Count -gt 0) { + foreach ($line in (($oids -join "`n") | & git cat-file --batch 2>$null)) { + $m = $rx.Match("$line") + if ($m.Success) { $seen.Add([int]$m.Groups[1].Value) } + } + } $wip = Join-Path $repo "docs/BACKLOG.md" - if (Test-Path $wip) { $texts += (Get-Content $wip -Raw) } - foreach ($t in $texts) { - foreach ($m in [regex]::Matches($t, '(?m)^#{2,3} (\d+)\.')) { $seen.Add([int]$m.Groups[1].Value) } + if (Test-Path $wip) { + foreach ($m in $rx.Matches((Get-Content $wip -Raw))) { $seen.Add([int]$m.Groups[1].Value) } } }