Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/LEDGER-GATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,14 @@ reaches `docs/adr/README.md`, so the ADR becomes invisible. Three had already be
pwsh -NoProfile -File scripts\coord\alloc.ps1 -Kind adr -Title "Worktree gate"
pwsh -NoProfile -File scripts\coord\alloc.ps1 -Kind backlog -Title "Ledger allocator"
pwsh -NoProfile -File scripts\coord\alloc.ps1 -List
pwsh -NoProfile -File scripts\coord\alloc.ps1 -Kind backlog -ShowFloor # read-only: allocates nothing
```

`-ShowFloor` prints the computed floor, **the paths it swept**, the sub-partition maximum and the number
it would issue next — without claiming anything. Use it to answer "what can the floor see" instead of
spending a number on the question: allocation is a one-way door, so before this existed the floor's own
correctness was the one property nobody re-tested.

It claims a number by **exclusively creating** `<git-common-dir>/mefor-coord/alloc/<kind>/<number>.json`.
That create is atomic on NTFS: if a sibling session got there first it throws, and we move to the next
number. It is a **test-and-set**, never a read-modify-write on a shared list — PowerShell was measured
Expand All @@ -67,6 +73,34 @@ ever computed is stored at `<git-common-dir>/mefor-coord/alloc/<kind>/.floor-hig
never goes below it; a computed floor beneath the mark prints a loud NOTE rather than quietly handing
out a used number. The mark can only rise.

**Two maximums, not one — and conflating them bricked the allocator on 2026-08-03.** The public backlog
sequence is partitioned from the maintainer-internal one at `PUBLIC_BACKLOG_FLOOR` (`#1000`), so the
allocator needs two different numbers:

| Measurement | Question it answers | Must include public numbers? |
|---|---|---|
| **Floor** — max over everything swept | *What must I not re-issue?* | **Yes** |
| **Sub-floor max** — max below the partition | *How much runway does the internal sequence have?* | **No** |

The residual detector read `Floor`. So the first legitimate item filed in the public sequence — `BACKLOG
#1000` — made every backlog allocation in the repository throw `REFUSING TO ALLOCATE … has reached the
public floor`. The guard was not detecting a breach; it was detecting the partition being used exactly as
designed, and it fired on correct input.

**That detector can now only WARN, and the limit is the data, not the implementation.** Once an internal
item is allocated at or above the boundary it is indistinguishable, in the published files, from a
legitimate public item at the same number — both are just `## N.` with N ≥ the floor. A refusal arm would
have to fire on correct input or never fire at all, so it was **removed** rather than made unreachable: a
branch that cannot fire reads as protection and is worse than none. Detecting a real breach needs an
internal-side input this repository does not have. What remains is a warning at 90 % of the boundary,
measured on the sub-floor band, where public numbers cannot distort it.

*(The sweep does reach internal numbers, which is worth stating because the opposite was suspected:
measured 2026-08-03, 489 of 490 vault-ish remote-tracking refs carry `docs/BACKLOG.md`, and 67 item
numbers live only there — including `#240`–`#247`, the numbers the Ledger erratum records as re-issued
over cited work. Seeing them is what makes the floor trustworthy; it is telling an internal `#1001` from
a public `#1001` that is impossible.)*

Two consequences worth knowing before you tidy refs:

- **`git fetch origin --prune` is safe** — it prunes only `refs/remotes/origin/*`, which is not where the
Expand Down
139 changes: 116 additions & 23 deletions scripts/coord/alloc.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,26 @@ function Get-Floor {
}
$floor = [Math]::Max($computed, $previous)
if ($floor -gt $previous -and -not $Peek) { Set-Content -Path $watermark -Value $floor -Encoding ASCII }
# Measure-Object hands back a [double]; the 'D4' format specifier is integer-only and throws on one.
[int]$floor

# TWO NUMBERS, NOT ONE -- and conflating them is what bricked this script on 2026-08-03.
#
# `Floor` is the whole observed set's maximum. It answers "what must I not re-issue", so it MUST
# include public numbers.
#
# `SubFloorMax` is the maximum BELOW the partition. It answers a different question -- "how much
# runway does the maintainer-internal sequence have left" -- and it must EXCLUDE public numbers,
# because a public item at or above the boundary is the design working, not a breach.
#
# Returning one number for both is not a style problem. The residual detector below read `Floor`,
# so the first legitimate public item filed at #1000 made the guard throw on every subsequent
# backlog allocation, repo-wide, until it was patched. The guard fired on correct input.
#
# `[int]` on both: Measure-Object hands back a [double], and the 'D4' format specifier is
# integer-only and throws on one.
[pscustomobject]@{
Floor = [int]$floor
SubFloorMax = [int](($seen | Where-Object { $_ -lt $PublicBacklogFloor } | Measure-Object -Maximum).Maximum)
}
}

# THE FLOOR IS DEFINED ONCE, IN THE GATE, AND READ HERE.
Expand All @@ -207,7 +225,36 @@ if (Test-Path $gateFile) {
if ($m.Success) { $PublicBacklogFloor = [int]$m.Groups[1].Value }
}

$observed = Get-Floor -Peek:$ShowFloor
$measured = Get-Floor -Peek:$ShowFloor
$observed = $measured.Floor
$subFloorMax = $measured.SubFloorMax

# Both checks are evaluated ONCE, here, so -ShowFloor and a real allocation cannot disagree. They did:
# -ShowFloor returned 19 lines before the guard, so it printed a next number while every real
# allocation threw. An inspector that does not run the checks it previews reports a number the tool
# will refuse to issue -- it answers the adjacent question, which is the failure CLAUDE.md §11 names.
$warnAt = if ($null -ne $PublicBacklogFloor) { [int]($PublicBacklogFloor * 0.9) } else { 0 }
$residualWarning = ($Kind -eq "backlog") -and ($null -ne $PublicBacklogFloor) -and ($subFloorMax -ge $warnAt)

# THE BOUNDARY RATCHET -- the one refusal this data can actually justify.
#
# PUBLIC_BACKLOG_FLOOR is a constant in a source file, so it can be LOWERED: a bad revert, a merge
# resolved the wrong way, a tidy-up. Lower it to 900 and ledger_check.py cheerfully accepts a new
# public #900 sitting on top of an internal #900 -- with a GREEN pre-commit and a GREEN CI, because a
# runner has no memory of yesterday's value and the constant is the only thing either consults.
#
# A ratchet OUTSIDE the constant is the only instrument that can see this, and unlike the boundary
# check it replaces, it is genuinely reachable: it triggers on an observable local fact (the value
# moved down) rather than on an integer whose provenance cannot be recovered.
#
# THREE QUANTITIES, THREE PURPOSES -- keep them strictly separate:
# $observed (union max) -> $start / the next number, ONLY
# $subFloorMax (below boundary) -> the WARNING, ONLY
# $boundarySeen (highest floor) -> the REFUSAL, ONLY
$boundaryMark = Join-Path $alloc ".boundary-highwater"
$boundarySeen = 0
if (Test-Path $boundaryMark) { [void][int]::TryParse((Get-Content $boundaryMark -Raw).Trim(), [ref]$boundarySeen) }
$boundaryLowered = ($Kind -eq "backlog") -and ($null -ne $PublicBacklogFloor) -and ($PublicBacklogFloor -lt $boundarySeen)

if ($ShowFloor) {
# Name the SOURCES, not just the number. "Which files did this sweep actually read" is the
Expand All @@ -217,12 +264,22 @@ if ($ShowFloor) {
Write-Host "floor : $observed"
if ($Kind -eq "backlog") {
Write-Host "paths : docs/BACKLOG.md, docs/archive/backlog/BACKLOG-CLOSED.md"
Write-Host "sub-floor: $subFloorMax (highest number BELOW the #$PublicBacklogFloor boundary; over-states the internal high-water)"
Write-Host "boundary : $PublicBacklogFloor (highest ever seen on this clone: $boundarySeen)"
Write-Host "next : $([Math]::Max($observed, $PublicBacklogFloor - 1) + 1) (clamped to >= $PublicBacklogFloor)"
} else {
Write-Host "paths : docs/adr/NNNN-*.md (filenames, all refs)"
Write-Host "next : $($observed + 1)"
}
Write-Host "watermark: $(Join-Path $alloc '.floor-highwater')"
if ($boundaryLowered) {
Write-Host ""
Write-Host "WOULD REFUSE: PUBLIC_BACKLOG_FLOOR is $PublicBacklogFloor but this clone has allocated against $boundarySeen." -ForegroundColor Red
}
if ($residualWarning) {
Write-Host ""
Write-Host "WOULD WARN: highest sub-boundary number $subFloorMax has reached 90% of #$PublicBacklogFloor." -ForegroundColor Yellow
}
Write-Host ""
Write-Host "Read-only: nothing was allocated." -ForegroundColor DarkGray
return
Expand All @@ -233,34 +290,70 @@ if ($Kind -eq "backlog") {
throw "Could not read PUBLIC_BACKLOG_FLOOR from $gateFile. Refusing to allocate a backlog number rather than guess a floor the gate will not honour."
}

# THE RESIDUAL DETECTOR, ON APPROACH RATHER THAN ARRIVAL.
# WHY THE OLD "INTERNAL REACHED THE BOUNDARY" REFUSAL IS GONE.
#
# It compared the WHOLE-SET maximum against the floor, so the first legitimate public item filed at
# #1000 (BACKLOG #1000, 2026-08-03) made every subsequent backlog allocation throw, repo-wide. It
# was not detecting a breach; it was detecting the partition being used exactly as designed.
#
# It is NOT that this clone cannot see internal numbers -- that was suspected and is false.
# Measured 2026-08-03: 490 vault-ish remote-tracking refs are present, 489 carry docs/BACKLOG.md,
# and 67 item numbers live ONLY there, including the #242-#246 band ADR 0115 cites. The sweep does
# reach them, and that is exactly why the floor is trustworthy.
#
# The partition binds only the PUBLIC side; nothing can stop the maintainer-internal sequence
# allocating past the boundary, and CI cannot see it -- a public runner checks out origin only. But
# THIS machine can: Get-Floor already swept every ref, internal ones included. So the one place the
# breach is observable is here, at allocation time.
# The premise fails for four other reasons, any ONE of them fatal:
# (a) NO PROVENANCE. An integer does not say which sequence issued it. "Internal reached the
# boundary" and "public was legitimately allocated at the boundary" are the SAME observation
# -- which is why #1000, on origin/main and holding a registry claim, read as a breach.
# (b) FOSSIL. The newest vault-ish ref here is 2026-07-26 and the only configured refspec is
# +refs/heads/*:refs/remotes/origin/*, so nothing can advance them. The partition landed
# eight days later. (Measured: these refs say 314 while the real vault is at 315 -- the
# fossil is already stale by one item.)
# (c) CLONE-LOCAL. A fresh public clone has zero vault refs, so the term is absent entirely.
# (d) MASKED. Internal 314 < public 353, so the internal term does not even determine the
# sub-boundary maximum today.
#
# Warning only on ARRIVAL would fire exactly when it is too late -- at that point the next internal
# allocation already collides and there is no room to move. A check that fires only on collision has
# the same practical value as no check for every moment until the collision. So: warn at 90% of the
# boundary, with hundreds of numbers of runway left, and REFUSE at the boundary itself.
$warnAt = [int]($PublicBacklogFloor * 0.9)
if ($observed -ge $PublicBacklogFloor) {
# So the refusal moved to a trigger that IS observable and IS reachable -- the boundary ratchet
# above, which fires when PUBLIC_BACKLOG_FLOOR is lowered beneath a value this clone has already
# allocated against. What remains here is a warning only.
#
# $subFloorMax is "the highest number below the boundary", NOT "the internal maximum". It includes
# public pre-partition numbers, so it deliberately OVER-states the internal high-water: it warns
# early rather than late, which is the safe direction for a runway indicator.
if ($boundaryLowered) {
throw @"
REFUSING TO ALLOCATE. The all-refs backlog maximum ($observed) has reached the public floor ($PublicBacklogFloor).
The partition assumes the maintainer-internal sequence stays BELOW that boundary, and it no longer does
-- so the next number this would hand out is not safe to use. Raise PUBLIC_BACKLOG_FLOOR in
scripts/hooks/ledger_check.py (the allocator reads it from there), and say so in the PR.
REFUSING TO ALLOCATE. PUBLIC_BACKLOG_FLOOR is $PublicBacklogFloor, but this clone has already
allocated against a boundary of $boundarySeen. The constant was LOWERED beneath numbers that were
issued under the higher value, so the next number handed out could collide with the maintainer-internal
sequence -- and neither the pre-commit gate nor CI can see it, because both read only the current value
of the constant and have no memory of the previous one.

Restore PUBLIC_BACKLOG_FLOOR in scripts/hooks/ledger_check.py to at least $boundarySeen. If the
reduction is deliberate, delete $boundaryMark and say why in the PR.
"@
}
elseif ($observed -ge $warnAt) {
if ($residualWarning) {
Write-Host ""
Write-Host "WARNING: the all-refs backlog maximum ($observed) is approaching the public floor ($PublicBacklogFloor)." -ForegroundColor Yellow
Write-Host " Still safe -- but the partition's headroom is running out, and at the boundary" -ForegroundColor Yellow
Write-Host " this script will refuse to allocate. Plan to raise PUBLIC_BACKLOG_FLOOR in" -ForegroundColor Yellow
Write-Host " scripts/hooks/ledger_check.py before that happens, not after." -ForegroundColor Yellow
Write-Host "WARNING: the highest sub-partition number ($subFloorMax) has reached 90% of the #$PublicBacklogFloor boundary." -ForegroundColor Yellow
Write-Host " The maintainer-internal sequence is running out of room below the partition." -ForegroundColor Yellow
Write-Host " Raise PUBLIC_BACKLOG_FLOOR in scripts/hooks/ledger_check.py (this script reads" -ForegroundColor Yellow
Write-Host " it from there) BEFORE the two sequences meet, and say so in the PR. Once they" -ForegroundColor Yellow
Write-Host " meet, nothing in this repository can tell the two apart." -ForegroundColor Yellow
Write-Host ""
}
# Record the boundary we are about to allocate under. Only rises; only on a real allocation.
if ($PublicBacklogFloor -gt $boundarySeen) {
Set-Content -Path $boundaryMark -Value $PublicBacklogFloor -Encoding ASCII
}

# $observed IS THE UNION MAXIMUM HERE, DELIBERATELY, AND MUST STAY THAT WAY.
#
# The tempting "fix" for the #1000 brick is to repoint $observed at the sub-boundary maximum, since
# that is what the guard should have read. Do not: $start would become max(353, 999) + 1 = 1000 --
# a number already merged on origin/main -- and in a FRESH clone, whose registry is empty, the
# atomic CreateNew has no claim file to collide with and would NOT catch the re-issue. The union
# maximum is what makes "never hand out a number that exists anywhere" true; the sub-boundary
# maximum answers a different question and belongs only to the warning above.
$start = [Math]::Max($observed, $PublicBacklogFloor - 1) + 1
}
else {
Expand Down
24 changes: 19 additions & 5 deletions scripts/hooks/ledger_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,25 @@
# of check_backlog() computing a set and discarding it, i.e. unable to fail at all. A floor needs no
# registry, no worktree, and no sight of the internal ledger (CI checks out origin only).
#
# KNOWN RESIDUAL, and where it is detected: this binds only the public side. Nothing here can stop the
# internal ledger allocating past #1000. CI cannot see that -- but alloc.ps1 can, on any machine
# holding those refs, and it warns at allocation time if the all-refs maximum ever reaches this
# boundary. Raising this number is a one-line reviewable source change, deliberately not an allowlist
# file that would rot out of sight.
# KNOWN RESIDUAL, and it is NOT detected anywhere: this binds only the public side, and nothing in this
# repository can stop -- or observe -- the maintainer-internal ledger allocating past #1000.
#
# This comment used to claim alloc.ps1 "warns at allocation time if the all-refs maximum ever reaches
# this boundary". That was the wrong instrument twice over, and it was the defect written down:
# - The all-refs maximum has NO PROVENANCE. A public item legitimately allocated at the boundary and
# an internal breach are the same observation. That guard fired on BACKLOG #1000 -- correct input --
# and bricked every backlog allocation in the repo until 2026-08-03.
# - It claimed a liveness the ref store does not have. The vault-ish remote-tracking refs it would
# read are a FOSSIL: no configured refspec advances them, the newest is older than the partition
# itself, and a fresh clone has none at all.
# alloc.ps1 now warns only on the highest number BELOW the boundary (which over-states the internal
# high-water, so it warns early), and refuses only on a lowered boundary, which is locally observable.
#
# Raising this number is a one-line reviewable source change, deliberately not an allowlist file that
# would rot out of sight. LOWERING it is the dangerous direction and is the one thing neither this gate
# nor CI can catch -- both read only the current value and have no memory of the previous one -- so
# alloc.ps1 keeps a `.boundary-highwater` ratchet beside its registry and refuses when the constant
# drops beneath a value that clone has already allocated against.
#
# THIS LINE IS PARSED, not imported: scripts/coord/alloc.ps1 regex-matches it so the floor is defined
# exactly once and the allocator can never emit a number this gate refuses. Keep the name and the
Expand Down
Loading
Loading