fix(coord): ratchet the allocation floor so it cannot silently regress - #101
Merged
Conversation
wshallwshall
enabled auto-merge (squash)
July 31, 2026 13:23
wshallwshall
force-pushed
the
alloc-floor-highwater
branch
2 times, most recently
from
July 31, 2026 14:53
fbc5ded to
1fe3e9a
Compare
wshallwshall
force-pushed
the
alloc-floor-highwater
branch
from
July 31, 2026 15:26
1fe3e9a to
9f27adb
Compare
wshallwshall
added a commit
that referenced
this pull request
Jul 31, 2026
…the claims, keep the guidance (#99) docs/AOAG-DEPLOYMENT.md tells operators that two features do not exist. Both shipped three weeks ago. Neither correction changes any operational instruction — the DNS-side listener configuration in 4.5, including its planned listener restart, stands exactly as written. 1. 4.5 said the store connection "has no AG-aware keywords at all" and that an opt-in MultiSubnetFailover setting was merely "assessed", closing with "until it ships, configure the listener DNS-side as below". It shipped: BACKLOG #100, 2026-07-10. `[store].multi_subnet_failover` emits ODBC `MultiSubnetFailover=Yes` (config/settings.py; store/sqlserver.py's connection_string) and is already documented in CONFIGURATION.md — so this file contradicted its own repo. The section now says the keyword exists, defaults false, and should be turned on for any multi-subnet AG — while keeping the DNS workaround as the path you can currently rely on, for two stated reasons: the reconnect is unit-tested but has NEVER been exercised against a live cross-subnet AG (#100's validation needs the SQL AG rig), and the keyword covers the STORE connection only — the db_lookup connector builds its own connection string and emits nothing, verified by grep. Deliberately NOT done: demoting or removing the workaround. Telling a DBA they can skip an outage on the strength of an unvalidated setting is worse than stale text. That edit waits on the rig. 2. 5.1 said leader-site preference is "not yet built", and the failover table said a DR engine may win the lease "since leader-site preference is unbuilt". BACKLOG #101 shipped 2026-07-12 (ADR 0096): `[cluster].acquire_delay_seconds` and `promotable = false`, surfaced in GET /cluster/nodes. Setting `promotable = false` on the DR engine is the configured answer to the cross-WAN-active-engine hazard both passages describe at length, so operators were being pointed at a manual runbook for something they can configure away. What remains genuinely unbuilt is AUTOMATIC geographic fail-back; the text now says that instead. 3. One cross-reference: 4.5 pointed the cross-DC connect drill at 5.4, which is the Inbound MLLP VIP / LB section. The engine-to-DB reconnect drill is 5.3. The other 5.4 references in the file are about repointing senders and are correct; they are untouched. Verified: every link target in the file resolves (including the ADR 0096 filename, which is "...-preference-and-non-promotable-..." and was wrong on first write); backlog-status, ledger, runbook and doc-ref suites 49 passed. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
wshallwshall
force-pushed
the
alloc-floor-highwater
branch
from
July 31, 2026 15:56
9f27adb to
c23694d
Compare
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>
wshallwshall
force-pushed
the
alloc-floor-highwater
branch
from
July 31, 2026 20:35
c23694d to
d99da66
Compare
wshallwshall
added a commit
that referenced
this pull request
Aug 2, 2026
…ide (#144) Follow-on to #143, which merged before this measurement existed. Adds the one argument #340 was missing, and it is a better one than the cycle-count case. Measured 2026-08-02T13:41Z, re-derived here rather than relayed: open=14 armed=9 armed_and_inert=6 armed_and_CLEAN=0 #142 BEHIND #139 BEHIND #128 BEHIND #101 BEHIND #96 BEHIND #71 DIRTY Two-thirds of the armed PRs in this repo cannot land, and NOT ONE armed PR was CLEAN. #71 is armed and DIRTY, so it can never land at all. Why this belongs in the item: everything else in #340 is an efficiency argument, and an efficiency argument has a "then be patient" answer. This one does not. Every session here reads autoMergeRequest != null as "this will land" -- I said exactly that about my own PR an hour before measuring this -- when for six of nine it means "this waits until a human runs gh pr update-branch", with nothing reporting the difference. That is the ADR 0158 defect class (a green signal that means nothing) caught live rather than in retrospect. ADR 0158 is referenced by number, not linked: it is not on main yet. The measurement came out of the sandbox-codec session's queue claim, checked by the announce-hook session, and the connection to 0158's class is sandbox-codec's. Both routed it to me rather than writing it, since #340 is claimed here. Re-derived independently before writing; their figures and mine agree exactly.
wshallwshall
added a commit
that referenced
this pull request
Aug 4, 2026
…ones (#163) * docs(backlog): close BACKLOG #226 — the estate Hybrid-layout sweep is done, off-repo The per-feed Hybrid split (connections.toml / <INBOUND>_router.py / <INBOUND>_handler.py / _<feed>_transforms.py) landed across the ported estate in the maintainer-internal migration repository. Owner-attested; nothing in this repository changes, which is also why leaving the item open could never have closed it. Both "Also" clauses are recorded as NOT delivered, with the reason each is not a residual of this item: - "align the IDE Corepoint-import / scaffold path to emit the Hybrid layout" — there is no Corepoint-import path in ide/ to align. That tooling is #105, still open, so the clause is a constraint on #105's design rather than work #226 can perform. The scaffold half is misaddressed too: Insert Element (#48) drops per-file idioms into the current buffer (ide/src/insertElement.ts:1-5) and emits no multi-file feed layout. - "consider a recursive-glob / folder-per-feed loader enhancement" — filed as a consider, and not taken: load_config still globs *.py non-recursively (config/wiring.py:4162), the flat-merge behaviour the Hybrid layout is built around. Follows the #227 precedent: close the primary, state the off-repo/misaddressed residuals explicitly so the item is not re-opened for them. backlog_status_check.py: OK — 277 items, each declaring exactly one status. * fix(ledger): teach the number-space gates to span an archive, and fix two holes found proving it Prerequisite for moving the 185 closed BACKLOG items into docs/archive/backlog/. No item has moved yet; this only makes the guards able to see one when it does. The item namespace will span two paths, so every guard now reads their UNION: - backlog_status_check.py: scan() takes (label, text) pairs and parses them as ONE namespace. A number re-used across BACKLOG.md and the archive was structurally undetectable before -- `seen` was per-parse -- which is the erratum's own shape. - ledger_check.py: triggers on any backlog-bearing path, not the one literal, and builds head/base as the union. Reading the union on both sides also removes a false positive: the move relocates 185 items, so head-union == base-union and `head - base` stays empty, where a per-file view would report 185 vanished numbers with a remedy that renumbers cited items. - alloc.ps1: sweeps both paths in the all-refs term and the working-tree term. - backlog-hygiene.yml: accepts a banner updated in either location. Two pre-existing defects surfaced only because the gates were made to fail on purpose first, neither of which is about the archive: 1. alloc.ps1's working-tree term has NEVER worked. `[regex]'^...'` anchors at the start of the STRING; the term feeds it `Get-Content -Raw`, one string starting "# Backlog". Measured: 0 of 277 headings matched without Multiline, 277 with. The all-refs term hid it by covering every number committed somewhere -- i.e. every case except the uncommitted one this term exists for. 2. backlog-hygiene.yml diffed BASE_SHA..HEAD_SHA (two-dot), which credits a PR for main-side changes to paths it never touched. One main-side edit to BACKLOG.md -- the move being a large one -- would let every PR with an older base pass the "must update BACKLOG.md" required check while enforcing nothing. Now three-dot, matching ci.yml's form for the same question. Anti-narrowing, because a green gate over a shrunken corpus is the failure mode: - `--min-items N` fails when fewer items are found than required, and CI pins 277. Without it, 277 -> 92 fails nothing. - The scanned files are always printed with the count; a bare integer cannot distinguish "items closed" from "a file stopped being read". - A liveness receipt in the test suite asserts the same floor. - An explicitly-named --backlog path that does not exist is an error, not a skip. alloc.ps1 gains `-ShowFloor`: print the floor and the swept paths, allocate nothing. Allocation is a one-way door, so before this the only way to ask what the floor could see was to spend a number on the question -- which is how it ran a whole release reading two refs while its header promised all of them. Get-Floor takes -Peek so the inspection cannot advance the high-water ratchet; the first -ShowFloor run against a planted number moved this clone's watermark 316 -> 990 before that was fixed. Proofs run, each observed failing BEFORE the fix: - archive-only unallocated #1007 staged: old gate rc=0, new gate BLOCKED. - #990 planted in the archive: old sweep floor 353 (blind), new sweep 990. - cross-file duplicate #118: detected, naming the other file. - banner violations inside the archive only: detected. - --min-items over a narrowed corpus: rc=1 with the scanned-file list. - -ShowFloor twice against a plant: watermark unchanged at 316. ruff + mypy --strict clean; 43 gate tests pass. * docs(backlog): move the 185 closed items into docs/archive/backlog/BACKLOG-CLOSED.md docs/BACKLOG.md becomes the ~92 items someone can act on: 8,742 -> 3,648 lines. The closed items are not deleted, summarised, or rewritten -- they are relocated verbatim, so the file that gets opened, grepped and edited daily is the open set. MOVED, NOT REWRITTEN. Every relocated block is byte-identical to the one that left BACKLOG.md, headings included. Verified mechanically against a pre-move copy: - 277 items before = 92 after + 185 archived, no overlap, union identical - every OPEN block byte-identical to its source - every ARCHIVED block byte-identical to its source - all non-item prose in BACKLOG.md preserved verbatim Byte-identical headings are load-bearing, not tidiness: GitHub derives anchor slugs from heading text, so all 64 archived->archived cross-references keep resolving with no edit at all. That is the whole argument for one archive file rather than a split by status, year, or cluster -- #52 alone receives 99 of the 110 in-file anchors, and its citers span #65 to #184, so no cut isolates them. Cutting item blocks at the next '## ' heading of EITHER kind, not the next numbered item: 4 blocks in this file are followed by a section header, which a naive cut would have dragged into the archive along with the prose beneath it. Anchors, all 127 re-resolved against real headings after the edit: - 44 rewritten in BACKLOG.md -> archive/backlog/BACKLOG-CLOSED.md#<same-slug> - 1 rewritten in the archive -> ../../BACKLOG.md#<same-slug> (#226 -> #105) - 3 cross-file links repointed: AOAG-DEPLOYMENT.md (#100, #101), ADR 0026 (#30) - 64 archived->archived untouched, by design 13 anchors still do not resolve, and ALL 13 WERE ALREADY DEAD BEFORE THIS COMMIT -- confirmed by running the same check over the pre-move file, which returns the identical multiset (11 bare-number self-anchors: #40 x4, #323 x3, #28, #29, #329, #333; plus 2 links to #13 in COUNSEL-ENGAGEMENT-BRIEF.md, a number this sequence never had). They are left dead and documented in the archive header rather than repointed at a plausible neighbour: a citation resolving to the WRONG item is the erratum's failure mode, and unlike a dead link it looks like success. The archive carries its retirement banner inline rather than in a sibling README -- docs/archive/throughput/ needs a README because it indexes five documents; one file does not, and two documents that must agree is a drift surface. It states the rules that keep the namespace honest: never renumber, re-open by moving the block back (never by copying, which creates the cross-file duplicate the status check now fails), and add any future archive file to alloc.ps1's $backlogPaths AND backlog_status_check.py's DEFAULT_SOURCES in the same commit -- a file named in neither is policed by nothing. Gates verified post-move: - backlog_status_check.py --min-items 277: OK, 277 items, and it now PRINTS "scanned: docs/BACKLOG.md (92), docs/archive/backlog/BACKLOG-CLOSED.md (185)" - ledger_check.py on the staged move: rc=0 (relocation adds no numbers, because head-union == base-union -- the exact false positive the union view removes) - alloc.ps1 -ShowFloor: floor 353 across both paths, next 1000 - 43 gate tests pass Note the floor is unchanged at 353 because the highest item (#353) is open and stays in BACKLOG.md. The archive-sweep fix is therefore PROSPECTIVE, not a save: it starts mattering the first time a top-of-range item closes and moves. * docs(backlog): re-score all 92 open items on the ten-level scale (2026-08-03) Every open item now carries a current value x difficulty score. Before this, 23 had none at all and the other 69 were from the frozen 2026-07-10 pass, which predates the 2026-07-28 reconcile that closed 31 items -- and a stale score reads exactly like a fresh one. Method, unchanged from the pass it supersedes: scored from each item's own Scope / Why / Trigger / Nearest-existing-mechanism text rather than rescaled from the old number, then adversarially verified against the code -- a second reader per batch attacking build state first, then verdict/tier, then value and difficulty. 26 of 92 scores were overturned by that pass and carry the refuter's number. The banner is the live record and the table is a view of it; both are written here and a mechanical check confirms 92 banners and 92 rows agree on every triple. THE RATIONALE IS REPLACED, NOT JUST THE NUMBERS. Carrying an old justification under a new score is how a banner comes to argue against itself: - #114's surviving "clean workaround via the on-demand test probe" is a claim PR #162 explicitly retracted -- both destinations' test_connection CREATE the target dir, so the probe cannot answer the question the toggle asks. That is what lifts it off the parity-with-a-workaround band to 6/3. Its replacement rationale was ALSO stale (it described the silent-ignore #162 had just fixed) and is hand-corrected. - #105's "large greenfield 71-action mapper needing its own ADR" describes an importer that has since shipped under ADR 0086. Scheduling barely moved, which is the reassuring result: only TWO tiers changed -- #64 DEMAND-GATE -> P3 (an index over levers that live in #62/#63/#47/#34, so it ships nothing runnable of its own) and #105 P3 -> DEMAND-GATE. Neither contradicts an explicit demand-gate/on-trigger ruling in its own body; that was checked for all 51 items carrying a prior tier. Distribution is RECOMPUTED with the table rather than carried forward, and all four lines sum to 92. The superseded table keeps its own frozen lines and now says so. Tiers: P1 4, P2 19, P3 17, DEMAND-GATE 52 Quadrants: quick win 22, big bet 5, fill-in 56, money pit 9 The four P1s: #341 (9/3, a handler returning a tuple/set of Sends delivers nothing silently -- an accept-and-drop CLAUDE.md §12 forbids), #324 (7/2), #325 (6/2), #327 (6/2). NOT in this commit: 24 items were found to misdescribe their own build state -- prose asserting a gap that has since shipped, or citing messagefoundry/console/, a package retired with #103. Those are banner corrections and land separately; the scores here already price the remainder rather than the original scope. Two mechanical faults were caught by reading the output rather than trusting the run: the quadrant regex omitted the hyphen in "fill-in", so 57 of 69 items took the fallback branch and got a SECOND score inserted beside the first; and the synthesizer's own distribution lines did not follow from its own table (11 quadrant mismatches, 8 ordering violations, difficulty summing to 95 of 92). The script now refuses to write when any line carries two score spans or the scored count is not 92. backlog_status_check.py --min-items 277: OK, 277 items across both files. * docs(backlog): correct 10 items whose own prose misdescribed build state The 2026-08-03 re-score flagged 24 open items as misdescribing what the code does. Re-verified each against the tree as it stands -- after the archive move and after PR #162, both of which post-date the findings -- and 10 survived. The other 14 did not, and are recorded here rather than silently dropped: #84 #95 #99 #105 #114 #124 #125 #127 #133 #137 #167 #169 #214 #228 Most of those already carry an amendment that covers the stale sentence (#95, #99, #105, #114, #124, #125, #127, #133, #228), and stacking a second ruling saying the same thing is noise. The rest did not survive verification: the finding was itself wrong or overstated, and a wrong correction in a ledger is worse than a stale one. CORRECTIONS ARE ADDED AS DATED AMENDMENTS, NOT PROSE REWRITES. This file's convention is to leave the original claim standing and rule against it, so the record shows what was believed and what replaced it. Silently editing the stale sentence would destroy the evidence that makes the correction checkable. Applied to #62 #64 #131 #166 #179 #182 #237 #321 #329 #336. Representative: - #329 "Five MEFOR_ALLOW_INSECURE_TLS cells": the census is FOUR. #323 landed and routed transports/direct.py through the clamp; it now holds no call to the raw predicate at all (:63, :197, :215). - #321 "no test asserts the detectors can see a site code": false -- tests/test_scan_forbidden.py has per-class hit tests for at least the site code (:126), a customer name (:83), a case-sensitive code (:91) and a routable IP (:107). The detector-coverage half of its Proposed 2 is already in the tree. - #62 plans a dual-read over "existing mfenc:v1 rows", but cell-bound mfenc:v2 is the default writer (settings.py:383 -> base.py:1841; crypto.py:36), and v2 folds (table, column, pk) into the GCM tag -- so a body landing under a different column must be RE-ENCRYPTED, not merely re-encoded. That tightens the catch. - #64's ordered plan still reads live ("Nothing builds before it"), but the measure-first phase completed 2026-07-12 (ADR 0051) and its step-2 lever is refused outright (ADR 0055 withdrawn; ADR 0107 "Do not build F2 or F3"). The refuters removed two overclaims before they landed: #62's draft asserted a live store holds both mfenc markers (a fresh store under the shipped default holds only v2 -- the defensible claim is that a MIGRATION must expect both), and #64's asserted the multi-DB log split still remains, which could not be verified against ADR 0098 and would have been a fresh false claim. No item closes here: in every case the correction narrows the remainder rather than discharging it, and the 2026-08-03 scores already price the remainder. backlog_status_check.py --min-items 277: OK, 277 items, one status banner each. * docs(backlog): file BACKLOG #1000 — prove each required merge context can fail Escalated by the coordinator on the ground that it outlives the PR that fixed it. Deliberately NOT filed as "fix the two-dot diff": that instance already landed in 39b62bf, and filing shipped work is the rot the hygiene gate exists to prevent. The item is the CLASS. `.github/required-contexts.txt` names 13 contexts that block merge, and not one of them is proven able to go red. The deliverable is a negative control per context -- a fixture carrying the exact violation that context exists to catch -- plus a CI job that fails when a required context has none, so the coverage cannot silently decay as contexts are added. Scoped narrower than "test the gates" on purpose: it does not re-test what each gate checks, since the gates' own suites do that. It asserts one property per context -- this gate is capable of failing. The argument is that the class has now fired at least four times here, each found by hand and none by CI: #334 semgrep, required and blocking, scans a two-directory allow-list #327 six .gitignore rules are the sole control over maintainer-internal docs, and nothing asserts they still match anything #321 the forbidden-content gate exited 0 on a real site code and partner product #325 the same gate's home-path detector misses 1 of 4 spellings of a Windows path Each is correctly filed as its own defect. None of them establishes the property that would have caught all four before they shipped, and that property is a different artifact from any of the individual fixes. Value 7 / Difficulty 3, quick win, P1 -- not demand-gated; the trigger fired four times. Ranked table and all four distribution lines recomputed to 93 open items; a mechanical check confirms 93 banners and 93 rows agree on every triple. Number allocated atomically via scripts/coord/alloc.ps1 (#1000 -- the first in the post-partition public sequence, clamped to >= PUBLIC_BACKLOG_FLOOR), never grepped. backlog_status_check.py --min-items 277: OK, 278 items across both files. The floor is a floor, so growth passes it; it is there to catch shrinkage.
wshallwshall
added a commit
that referenced
this pull request
Aug 4, 2026
…allocator in tests
Revision after an independent design pass. The mechanism from the previous commit
holds; four things about the patch did not.
1. THE REFUSAL NOW HAS A REAL TRIGGER instead of being removed. Refusing nothing was
the safe correction, not the right one. PUBLIC_BACKLOG_FLOOR is a constant in a
source file, so it can be LOWERED -- a bad revert, a merge resolved wrongly, a
tidy-up -- and NEITHER the pre-commit gate NOR CI can catch that: both read only
the current value and have no memory of the previous one. Drop it to 900 and a new
public #900 lands on top of an internal #900, green all the way. A ratchet outside
the constant is the only instrument that can see it, so alloc.ps1 now keeps
.boundary-highwater beside its registry and refuses when the constant falls beneath
a value this clone already allocated against. Locally observable, genuinely
reachable.
Three quantities, three purposes, never crossed:
$observed (union max) -> the next number, ONLY
$subFloorMax (below boundary) -> the warning, ONLY
$boundarySeen (highest floor) -> the refusal, ONLY
2. "THIS CLONE CANNOT SEE THE INTERNAL LEDGER" IS FALSE AND IS NOT THE ARGUMENT.
Measured: 490 vault-ish remote-tracking refs, 489 carrying docs/BACKLOG.md, 67
numbers living only there including the #242-#246 band ADR 0115 cites. The premise
fails for four other reasons, any one fatal, and they are now written down: an
integer has NO PROVENANCE (a legitimate public item at the boundary and an internal
breach are the same observation -- which is exactly how #1000 read as a breach);
the refs are a FOSSIL (newest 2026-07-26, no refspec advances them, and they say
314 while the real vault is at 315); they are CLONE-LOCAL (a fresh clone has none);
and they are MASKED (internal 314 < public 353).
3. $observed STAYS THE UNION MAXIMUM, with the trap recorded. Repointing it at the
sub-boundary max -- the tempting one-line fix -- makes $start = max(353, 999) + 1 =
1000, a number already merged, and in a fresh clone with an empty registry the
atomic CreateNew has no claim to collide with and would NOT catch the re-issue.
4. NAMING: $subFloorMax is "the highest number below the boundary", never "the
internal maximum". It includes public pre-partition numbers, so it deliberately
OVER-states the internal high-water and warns early rather than late. Relatedly,
ledger_check.py's high-water 314 is NOT refreshed to 353: 314 is the vault-refs
maximum, 353 is a public number on origin/main, and relabelling one as the other
would plant a fresh false premise inside the comment being fixed.
ledger_check.py's own claim -- that alloc.ps1 "warns if the all-refs maximum reaches
this boundary" -- was the defect written down: wrong instrument, and it asserted a
liveness the fossil ref store does not have. Replaced, not softened.
THE TESTS NOW RUN THE ALLOCATOR. Nothing in tests/ ever had: the two existing
references are read_text() assertions and stayed green through the entire brick. The
seam is the process working directory -- alloc.ps1 has no -Repo switch and reads no
env var, so a throwaway repo gets its own registry AND supplies its own
ledger_check.py, making the boundary injectable. FLOOR=100, not 10: at 10 the warn
tier and the highest sub-boundary number collide and every tier test passes for the
wrong reason. The isolation check runs FIRST, because every later test spends real
numbers if it is false.
PROVEN TO FAIL: the same rig against the PRE-FIX alloc.ps1, with an item at the
boundary, returns rc=1 and "REFUSING TO ALLOCATE ... has reached the public floor
(100)" -- the production brick reproduced at 1/10 scale. Against the fix it allocates
#101. Real registry confirmed untouched by the suite: 62 claims before and after,
.floor-highwater still 1000.
34 tests pass; ruff clean.
wshallwshall
added a commit
that referenced
this pull request
Aug 4, 2026
…red the wrong maximum (#166) * fix(coord): unbrick the allocator — the partition guard measured the wrong maximum Filing BACKLOG #1000, the first legitimate item in the post-partition public sequence, made EVERY backlog allocation in the repository throw: REFUSING TO ALLOCATE. The all-refs backlog maximum (1000) has reached the public floor (1000). I filed #1000 and I caused this. It is not a latent bug that #1000 revealed -- the guard's premise held right up until a public number legitimately reached the floor, and mine was the first. ONE MEASUREMENT WAS SERVING TWO INCOMPATIBLE PURPOSES. Get-Floor returned a single maximum, used for: - the emit start, which wants the max over EVERYTHING so a number is never re-issued. Correct as written. - the residual detector, which wants the maintainer-internal sequence's high-water to see it running out of room below the partition. It read the union instead. Public items are SUPPOSED to be >= 1000 -- that is the whole design -- so the detector fired on the partition being used correctly. Get-Floor now returns both `Floor` and `SubFloorMax`; the emit start keeps the union, the detector reads only the sub-floor band (353 on this tree, so it is quiet). THE REFUSAL IS REMOVED, NOT REWRITTEN TO BE UNREACHABLE. 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 `## N.` with N >= floor. A refusal arm would have to fire on correct input or never fire at all, and a branch that cannot fire reads as protection while providing none. Detecting a real breach needs an internal-side input this repository does not have. What remains is an honest warning at 90% of the boundary. THE COMMENT'S LOAD-BEARING CLAIM WAS CHALLENGED AND SURVIVED. It justified the guard with "Get-Floor already swept every ref, internal ones included", and that was suspected of being false. Measured instead of repeated: 490 vault-ish remote-tracking refs exist, 489 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. The claim is TRUE and the sweep is why the floor is trustworthy. What is false is the inference that seeing internal numbers means being able to detect the breach. The comment now says which is which. SECOND DEFECT, ALSO MINE: -ShowFloor lied. I added it earlier today and it returned 19 lines BEFORE the guard, so it printed `next: 1001` while every real allocation threw. A peer session listed it among six verifications, got a green answer, and recorded it as fact. The residual warning is now computed ONCE, above the -ShowFloor block, and both paths read that one expression -- they cannot drift apart. -ShowFloor also prints the sub-floor maximum, because a bare floor cannot answer the question the partition raises. Negative controls, in the existing source-text seam (tests/test_ledger_check.py) -- executing the allocator to test it would either spend a real number or write to .git/mefor-coord/alloc/**, and a test that mutates the ledger registry to check the ledger registry is its own hazard: - the sub-floor measurement must exist, and Floor must survive for the emit start - `$observed -ge $PublicBacklogFloor` must never reappear (the regression verbatim) - $residualWarning must be computed before the -ShowFloor block and read by both PROVEN TO FAIL: re-planting the exact regression makes the second test red; removing the plant makes it green. 29 tests pass. DISCLOSURE -- I spent a real number proving this. The regression probe allocated BACKLOG #1001, which is now a permanent hole; claims are never released, and the precedent for exactly this is #315/#317, the probe allocations made verifying the previous floor fix. Next backlog number is #1002. Verified after the fix: a real allocation succeeds, the ADR path still works (next 0160), -List works, and -ShowFloor leaves the high-water ratchet untouched. No BACKLOG number is filed for this work: the allocator is the thing that was broken. "A control nobody has watched fail is an assumption wearing a green tick" -- this one had never been watched fail on LEGITIMATE input, which is the harder half. That is BACKLOG #1000's own thesis, and #1000 is the item that broke it. ruff clean; docs/LEDGER-GATE.md documents both maximums and why the refusal is gone. * fix(coord): move the refusal to a reachable trigger, and execute the allocator in tests Revision after an independent design pass. The mechanism from the previous commit holds; four things about the patch did not. 1. THE REFUSAL NOW HAS A REAL TRIGGER instead of being removed. Refusing nothing was the safe correction, not the right one. PUBLIC_BACKLOG_FLOOR is a constant in a source file, so it can be LOWERED -- a bad revert, a merge resolved wrongly, a tidy-up -- and NEITHER the pre-commit gate NOR CI can catch that: both read only the current value and have no memory of the previous one. Drop it to 900 and a new public #900 lands on top of an internal #900, green all the way. A ratchet outside the constant is the only instrument that can see it, so alloc.ps1 now keeps .boundary-highwater beside its registry and refuses when the constant falls beneath a value this clone already allocated against. Locally observable, genuinely reachable. Three quantities, three purposes, never crossed: $observed (union max) -> the next number, ONLY $subFloorMax (below boundary) -> the warning, ONLY $boundarySeen (highest floor) -> the refusal, ONLY 2. "THIS CLONE CANNOT SEE THE INTERNAL LEDGER" IS FALSE AND IS NOT THE ARGUMENT. Measured: 490 vault-ish remote-tracking refs, 489 carrying docs/BACKLOG.md, 67 numbers living only there including the #242-#246 band ADR 0115 cites. The premise fails for four other reasons, any one fatal, and they are now written down: an integer has NO PROVENANCE (a legitimate public item at the boundary and an internal breach are the same observation -- which is exactly how #1000 read as a breach); the refs are a FOSSIL (newest 2026-07-26, no refspec advances them, and they say 314 while the real vault is at 315); they are CLONE-LOCAL (a fresh clone has none); and they are MASKED (internal 314 < public 353). 3. $observed STAYS THE UNION MAXIMUM, with the trap recorded. Repointing it at the sub-boundary max -- the tempting one-line fix -- makes $start = max(353, 999) + 1 = 1000, a number already merged, and in a fresh clone with an empty registry the atomic CreateNew has no claim to collide with and would NOT catch the re-issue. 4. NAMING: $subFloorMax is "the highest number below the boundary", never "the internal maximum". It includes public pre-partition numbers, so it deliberately OVER-states the internal high-water and warns early rather than late. Relatedly, ledger_check.py's high-water 314 is NOT refreshed to 353: 314 is the vault-refs maximum, 353 is a public number on origin/main, and relabelling one as the other would plant a fresh false premise inside the comment being fixed. ledger_check.py's own claim -- that alloc.ps1 "warns if the all-refs maximum reaches this boundary" -- was the defect written down: wrong instrument, and it asserted a liveness the fossil ref store does not have. Replaced, not softened. THE TESTS NOW RUN THE ALLOCATOR. Nothing in tests/ ever had: the two existing references are read_text() assertions and stayed green through the entire brick. The seam is the process working directory -- alloc.ps1 has no -Repo switch and reads no env var, so a throwaway repo gets its own registry AND supplies its own ledger_check.py, making the boundary injectable. FLOOR=100, not 10: at 10 the warn tier and the highest sub-boundary number collide and every tier test passes for the wrong reason. The isolation check runs FIRST, because every later test spends real numbers if it is false. PROVEN TO FAIL: the same rig against the PRE-FIX alloc.ps1, with an item at the boundary, returns rc=1 and "REFUSING TO ALLOCATE ... has reached the public floor (100)" -- the production brick reproduced at 1/10 scale. Against the fix it allocates #101. Real registry confirmed untouched by the suite: 62 claims before and after, .floor-highwater still 1000. 34 tests pass; ruff clean.
wshallwshall
added a commit
that referenced
this pull request
Aug 7, 2026
…251 #252 #257 #260 #273) (#274) * feat(asvs): prove an absence claim bites, not just that its pattern matches (BACKLOG #1006) `check_absences` admits an ASVS absence claim on `re.search(a.pattern, a.mutation)` -- one TOML field matched against another. That proves the mutation is well-formed; it never proves the mutation BITES. A reintroduction raised into a swallowing handler, written to a field nobody reads, or behind a flag nobody branches on satisfies every failure mode `check_absences` has and changes nothing observable. A green gate that is not evidence. Add an opt-in `--prove-absences` mode (`scripts/asvs/scorecard.py`) that executes the claim rather than grepping it: - Two optional `Absence` fields, `mutation_path` and `observable` (a pytest node id). When both are set the mode copies the tree to a scratch dir, runs the observable (baseline must be green), appends the mutation, and requires the observable to go RED -- and to fail as a test failure (exit 1). It fails closed on every other code: an already-red baseline, an uncollectable node, or a mutation that only breaks import is a PROVE-ERROR, never a proof. A claim that reddens nothing is UNPROVEN and fails the mode. - A coarse same-file static backstop screens claims carrying `mutation_path` but no `observable`: a `raise` landing in a file whose every handler swallows. It is a screen, not a proof (it cannot see a swallow in a caller), documented as such. - The whole pass runs in a TemporaryDirectory scratch copy, so it never mutates the tracked tree and never trips the committed-tree scan on itself. Both fields default empty and load without being refused: the vault's ~81 existing absence claims carry neither and must stay loadable (ADR 0156 §7). Absent means "not yet proven by execution", surfaced by the mode, never "proven vacuous". Review hardening carried in this change (the mode's own helpers): - `_scratch_ignore` refuses `.env*`, `*.db` (+ WAL sidecars) and `docs/security` when copying the tree. The vault runs this module against the REAL tree (ADR 0156 §7); a scratch copy carrying those would spill secrets / the local store / vault posture data into a world-default temp dir, which CLAUDE.md §9 forbids. The public-repo path never sees them; this is defence for the eventual vault run. - `_is_within_tree` refuses a `mutation_path` that is absolute or contains `..` before anything is applied, so an authored path cannot escape the scratch copy. Tests (tests/test_asvs_scorecard.py): eight fixture tests drive `prove_absences` directly (proved / UNPROVEN / already-red baseline / collection-error / two static backstop arms including a re-raise reach control / root-untouched / load round-trip), plus three that drive the CLI contract CI depends on -- `main([..., "--prove-absences"])` exit 0 on a biting fixture and 1 on a non-biting one, and `main([...])` without `--corpus` exit 2 -- plus the secrets-exclusion and path-traversal guards. Every new test was falsified (broken on purpose, watched red, restored). MessageFoundry is a not-deployed beta: the mode is opt-in, the default `verify` path is byte-unchanged, and no authored claim carries an `observable` yet, so nothing new is blocked by this alone today. Wiring the mode over the vault claims and backfilling their observables is the owner's follow-up. * docs(backlog): flip #1006 to shipped -- the absence gate can now prove behaviour (BACKLOG #1006) Flip the #1006 banner from filed to shipped. It is written as a capability claim, not a closure claim: the `--prove-absences` mode CAN catch a well-formed-but-vacuous reintroduction once a claim carries an `observable`, but the default `verify` path is byte-unchanged and no authored claim carries one yet, so nothing new is blocked by this alone today -- the honest present-tense state for a not-deployed beta. Banner lines of #1006 ONLY. The ranked table, the four census distribution lines, and every other item's banner are untouched. The status census was NOT recomputed. * test(connscale): dynamic contiguous inbound-port allocation, drop the flaky marker (BACKLOG #1014) The connscale SQLite smoke test hard-coded base_port=41000 and needs 24 contiguous inbound ports, so two worktrees running the suite at once contended for the same fixed block; a @pytest.mark.flaky(reruns=2) marker retried past the collision, relabelling a determinate resource conflict as CI noise. On the first parallel run it would keep masking exactly this class. Replace the fixed block with _free_contiguous_ports(), which anchors an n-wide block at a RANDOM base inside a bounded window, probes each port with a no-REUSEADDR bind, and returns the range only when all n bind. The random anchor over a wide window de-correlates concurrent worktrees; a genuine future collision now surfaces as a red, not a masked retry. Contiguity is asserted at the acquisition site and the allocator fails loudly -- never a silent fixed fallback -- via two branches: an up-front width guard when the block cannot fit the window, and a post-loop raise when no free block is found after `tries` attempts. The window is [20000,30000): the lower bound sits ABOVE the sibling MLLP fixed-port band (other tests bind fixed inbound ports in the 11xxx-19xxx range, e.g. 15099/19601), and the upper bound stays BELOW the OS ephemeral floors (Linux 32768+, Windows/macOS 49152+) so a kernel-assigned ephemeral port -- the sink/API ports, or any unrelated connection -- can never land in the block after it is probed. Drop the @pytest.mark.flaky marker: the collision was the cause, so keeping it would re-hide the class this removes. Add three helper tests -- contiguity and in-window, post-loop exhaustion (tries=0), and the width guard -- each pinned to its branch (match=) and falsified by mutation. Test-only change; no product code is touched. * backlog: close #1014 -- dynamic connscale port allocation ships, flaky marker dropped Flip #1014's status banner from open (filed) to shipped: the dynamic contiguous inbound-port allocation and the flaky-marker removal land in the same branch (commit 3450c3f). This edits the #1014 banner line ONLY. The ranked table and the four census distribution lines are untouched, and the census was NOT recomputed. * feat(anon): structural PHI-shape detectors + coverage report + token-floor signal on the leak-check (BACKLOG #331) The fail-closed leak-check verified only that MAPPED fields were pseudonymized; PHI sitting in a field no rule mapped would pass the check clean on first deployment (a real MRN is not a denylisted string). Scoped to the fields anonymize did NOT rewrite, add: - high-precision structural detectors over unmapped fields: dashed SSN, punctuated NANP phone, and CX MR/MRN-typed identifier. Deliberately narrow, to avoid the mass false-positives a broad digit-run search produces on HL7 bodies dense with dates/order-numbers/set-ids (ADR 0030 section 5). - LeakReport: an unmapped-field coverage report (addresses only, never a value) plus token_tables_live / token_floor_reason. Reasons name the shape + field ADDRESS only, so a raised LeakError / log line never carries PHI. - token_floor_failure() folded into the fail-closed decision under the require_live_denylist opt-IN lever (default off, so token-less CI/OSS/fork runs stay green with the structural detectors as the live backstop). The whole structural block is mirrored byte-identical into tee/anon/leak.py; a new engine/tee leak_report parity test pins it. Each detector was falsified (removed it, watched the unmapped-PHI dataset slip through, restored); a false-positive guard proves a benign unmapped field (14-digit EVN timestamp, order number, coded observation id) does not trip. Docs are written to the shipped DEFAULT behaviour, not an overclaim: the coverage report and token-floor reason are RECORDED and surfaced on a refusal or via the on_report hook (not an unconditional clean-path catch-all), and the strict refusal is opt-IN. anon/__init__.py (both copies) necessarily changed to export LeakReport/leak_report/coverage_clause and wire the lever. ADR 0030 section 5 / section 7 / Consequences amended (the "deferred" phrasing was stale against the shipped code). NOT-DEPLOYED beta: worded as "would let unmapped PHI through on first deployment", no present-tense exposure claim. * docs(backlog): flip #331 banner to SHIPPED, worded to the default behaviour (BACKLOG #331) Banner-only flip of #331 to SHIPPED. Worded to the shipped DEFAULT behaviour, not an overclaim: the coverage report and token_floor_reason are RECORDED and surfaced on a refusal or via the on_report hook (not an unconditional clean-path catch-all), and require_live_denylist is the strict opt-IN lever (default off). Census NOT recomputed: only the #331 banner line changed. The ranked table, the four census distribution lines, and every other item's banner are untouched. * test(sandbox): a static ast guard pins the codec+worker import boundary (BACKLOG #346) The sandbox's import boundary (DEFAULT_FORBIDDEN_MODULES in pipeline/sandbox.py -- socket/ssl/asyncio, the I/O- and secret-bearing messagefoundry.* subpackages, cryptography) is enforced only at RUNTIME and only inside the off-by-default [sandbox].mode=subprocess child. Nothing statically pins that the two modules which run inside that boundary -- _sandbox_codec.py and _sandbox_worker.py -- do not themselves import a forbidden module. Both are clean today; a future edit reintroducing a forbidden import would make mode=subprocess DOA on first deployment while the default-mode suite stayed green -- the failure inverts, hitting the most security-conscious installs hardest and quietest. This is defence-in-depth test coverage, not a code change: neither sandbox.py nor the codec is touched. tests/test_sandbox_import_boundary.py walks the two files' own ast import nodes (ast.Import/ast.ImportFrom, including nested/function-level and relative imports resolved to absolute) and asserts none resolves under a DEFAULT_FORBIDDEN_MODULES prefix. The forbidden set is imported from the runtime constant, never copied, so the guard tracks whatever the sandbox forbids. It ships with a positive control (each static import form the walker handles is seen, including the load-bearing from-parent alias-append) and a negative control (benign messagefoundry.* imports raise zero flags). Scope is the two files' DIRECT imports, deliberately not a transitive walk: importing the codec pulls asyncio/cryptography/store/transports/auth into sys.modules, so a transitive walker would red on clean shipped code and prove nothing. sandbox.py is out of scope per BACKLOG #346 even though the worker child imports it; the docstring records that residual for the owner. Falsified: planting `import socket` into the real _sandbox_codec.py reddens the live guard naming it; removing the walker's alias-append reddens only the alias-append positive-control case; an over-broad matcher reddens the negative control. All plants restored before commit. * backlog: flip #346 to SHIPPED -- the static ast import-boundary guard landed (BACKLOG #346) The #346 banner alone: OPEN -> SHIPPED, pointing at tests/test_sandbox_import_boundary.py (the static ast guard added in the preceding commit). The completeness wording is softened from "every forbidden import form is seen" to "each static import form the walker handles" -- a static walker cannot see dynamic importlib/__import__ forms, and CLAUDE.md section 11 prefers a bounded claim to an enumeration. Only the #346 banner line changed; the ranked table, the four census distribution lines, and every other item's banner are untouched. The census was NOT recomputed. * fix(tls): route four insecure-TLS escape cells through the ADR-0092 clamp (BACKLOG #329) LDAPS (auth/ldap.py), SFTP host-key (transports/remotefile.py), the webhook sink (pipeline/alert_sinks.py) and the AI-broker (transports/ai_broker.py) read the raw MEFOR_ALLOW_INSECURE_TLS escape directly; on an enforcing-PHI instance each would otherwise honour the env var on first deployment. Each now routes through the ADR-0092 weakened_tls_escape helper: SFTP is built in-gate so it uses _here(); the other three are built outside the hop scope, so the instance posture is threaded explicitly through AuthService / notifier_from_settings / ai_broker_from_settings (additive, default None = byte-identical for existing callers). The fifth cell the item names (direct.py) was already clamped in #323, so this converts the remaining four. Docs (CONNECTIONS/DEPLOYMENT/ PHI) corrected from 'not clamped'/'unclamped' to clamped. * docs(backlog): flip #329 to shipped -- four insecure-TLS cells clamped (BACKLOG #329) Banner line only (leaves the 2026-08-03 amendment note); census not recomputed. * fix(dev): anchor setup-leak-gate.ps1 to its own checkout, not the cwd (BACKLOG #1063) `$repo` came from `git rev-parse --show-toplevel`, which resolves against the CURRENT directory rather than the path the script was handed. Invoked by absolute `-File` path from another worktree -- the ordinary shape on a clone carrying dozens of them -- it armed the CALLER's checkout and printed CONFIGURED about that one, while the checkout the operator named kept no token source and went on failing closed. An absolute `-File` invocation is naming the checkout to act on; it must not then consult a different one. Now `Split-Path -Parent (Split-Path -Parent $PSScriptRoot)`, the form postgres.ps1:37 and sqlserver.ps1:56 in the same directory already use, plus an assert that the derived root actually carries scripts/security/ -- a wrong root should say so where it is derived rather than surface later as a confusing scanner failure. Tested by the DIVERGENCE, which is the only shape that can fail: two temp checkouts that both carry scripts/security/, the script invoked by absolute path while the shell stands in the other one. A test run from inside the target passes with the bug still in, because cwd and script root are then the same directory. Reverted to the old line, the same test reports "the named checkout was not armed" -- the negative control was run, not assumed. The fixture copies only the three files the script reaches for, never the whole of scripts/security/: a maintainer running this suite has the real token list sitting in that directory, and a copytree would sweep it into a temp dir. Also corrects this item's own prose. It called alloc.ps1:51 "byte-equivalent"; it is not -- alloc.ps1 carries --path-format=absolute and this script does not. The defect is identical, the bytes are not, and "byte-equivalent" is the kind of claim a later reader greps for and then trusts. * fix(coord): anchor alloc.ps1 and claim.ps1 to their own checkout (BACKLOG #1060) Both took `$repo` from an unanchored `git rev-parse --show-toplevel`, which resolves against the CURRENT directory rather than the path the script was handed. Invoked by absolute `-File` path from worktree A while intending to commit from worktree B, the claim was recorded to A; the ledger gate then refused B's commit -- correctly, it fails closed -- but far from the cause and with a message about the wrong thing, and it cost a number. `git -C $PSScriptRoot`, not `Split-Path`. The recorded `worktree` value has THREE readers: ledger_check.py:227, this script's own `-List`, and prune-merged.ps1's orphan-claim release, whose comment at :787 names the producing command -- "records `worktree = $repo` from `git rev-parse --path-format=absolute`" -- and matches on the full normalised path because a false positive there hands a live session's key to someone else. All three fold separators, so `Split-Path` would not have broken anything; it would have silently falsified that comment, in a destructive tool, for no gain. THE FILING NAMED ONE OF FOUR CWD-DERIVED READS, and the other three are measured in the negative control below: * the `branch` recorded with the claim was the CALLER's branch; * the floor's boundary was parsed from the CALLER's scripts/hooks/ledger_check.py; * the floor's WORKING-TREE term read the CALLER's docs/BACKLOG.md. The third is not friction and the item's severity paragraph is corrected in the same commit. That term exists to catch a number written but committed NOWHERE. Reading the caller's tree makes a number drafted in the target worktree invisible, so the allocator hands it out as free and two items share it -- both owned by that worktree, so owns() passes and the ledger gate never fires. The silent collision the docstring says this script exists to prevent, reached through the script. Narrow, since anything committed on any ref is still caught by the all-refs term, but a correctness hole rather than friction. claim.ps1:54 carried the same construct and was never filed -- found by inspection here, fixed in the same commit. Its enforcing hook, claim_check.py, reads the repo from cwd and is RIGHT to: a commit hook's cwd IS the committing worktree. Hook right, tool wrong, and only the tool can be invoked from somewhere else. Both scripts now print a NOTE when the shell is standing somewhere else. Anchoring is correct but surprising, and the item's other half -- showing the recorded worktree -- was already built (`claimed by:` / `by :`); what was missing is saying so when it diverges, instead of leaving it to surface as a refused commit later. Silent on the ordinary same-tree invocation, so it stays worth reading. THE FIX TURNED TWO SANDBOXED TEST FILES INTO WRITERS ON THE LIVE REGISTRY, which is worse than the red suite it also caused, and is the reason those fixtures changed here. test_coord_claim_{refresh,liveness}.py ran the REAL scripts/coord/claim.ps1 with cwd set to a temp repo -- scoped to a throwaway registry purely by ambient cwd, and one of them said so ("it scopes itself to the cwd's repo"). Once the script stopped consulting cwd, the passing half of the run wrote real claims into this clone's shared registry: two strays, `k` and a date-shaped key, were created and removed by hand. Both fixtures now stage and COMMIT a copy of the script inside the temp repo, so the sandbox is structural rather than ambient, and a linked worktree of the fixture carries its own copy -- which is how the peer-holds-the-key tests still produce a claim recorded against the peer. test_ledger_check.py already did exactly this for alloc.ps1, which is why it was the one that did not break. Tested by the DIVERGENCE, with -ShowFloor so no numbers are burned: allocation is a one-way door and a test that allocated would leave permanent holes in the shared registry for every worktree of this clone. Two temp checkouts draft different numbers and carry different PUBLIC_BACKLOG_FLOOR stubs; the caller's number is deliberately HIGHER, because the floor is a maximum and an equal or lower one would pass with the bug in. Reverted to the old lines, the same test reports floor 7777, boundary 1900 and a watermark under Caller/.git -- three independent signals, all pointing at the wrong tree. * docs(backlog): record the test-isolation trap #1060's fix walked into (BACKLOG #1060) A cwd-dependence that reads as a defect in the tool can be load-bearing ISOLATION in its tests. Both claim test files were scoped to a throwaway registry purely by ambient cwd -- one said so in a docstring -- so anchoring the script turned the passing half of the run into a writer on this clone's shared registry before the rest of it went red. Recorded in the item rather than only in the commit message, because #1057 and #1059 are the remaining instances of the same class and will hit the same trap: check what a test is isolated BY before changing what the code reads. * docs(CONNECTIONS): repoint the serial/ASTM decline at the archive; #27 is closed The connector-parity row for Serial (RS-232) / ASTM E1381/E1394/E1318 cited the decline as ([BACKLOG.md](BACKLOG.md) #27). Item 27 is closed and lives at docs/archive/backlog/BACKLOG-CLOSED.md:994; it is not in the live ledger. The pointer sent a reader to the wrong file. This is the second half of a designated two-marker pair. The archived item's own banner names both markers -- "marker landed in PR #411 (CLAUDE.md section 12 + docs/CONNECTIONS.md Serial row)" -- and commit 8a14602 repointed the CLAUDE.md half while logging this one as still carrying the decline, because that commit was scoped to section 12. The two halves disagreed about where #27 lives until now. Form: an anchored link, matching the sibling convention already used in this same directory for this same target (docs/AOAG-DEPLOYMENT.md:389 and :476). The cell already opens with "declined-by-design (v0.2+)", so the citation's only job is to resolve; restating "closed" in the cell would duplicate a fact the cell asserts two clauses earlier. Relative path: (archive/backlog/BACKLOG-CLOSED.md), NOT (docs/archive/...). The link is repo-relative from inside docs/. CLAUDE.md is at the repo root and correctly uses the docs/-prefixed form; copying that form here would resolve to docs/docs/archive/... and 404. Verified, not assumed: - The anchor slug was derived by a rule first replayed against three anchors already committed in the repo (#100, #101, #52) -- 3 of 3 exact -- then applied to #27's heading, then confirmed to match exactly one real "## " heading in the target file. A bogus anchor was run through the same check and found nothing, so the check can report a miss. - Item locations come from parse_items imported from scripts/docs/backlog_status_check.py, per CLAUDE.md section 11 -- not a hand-rolled scan of the banner alphabet. - backlog_status_check.py still reports 363 items, unchanged. - Read from origin/main throughout; the primary checkout runs behind. NOT changed, deliberately, with the reason: - docs/BACKLOG.md:574 -- bare number inside the section headed "Value & priority analysis (recorded 2026-06-19) - superseded". A superseded snapshot is a historical record; it has no path to rot. - docs/testing/FEATURE-COVERAGE-PLAN.md:41 -- names the features in prose and carries no number or path at all. Nothing to rot; adding a pointer would be new scope, not a repair. - docs/testing/master-test-plan/00-strategy-and-governance.md:699 -- bare #26/#27 that resolve to nothing rather than to wrong content. Repairing one link here would leave a single correct relative link among 28 broken root-relative ones in the same file; it belongs in the doc-set-wide sweep that class needs. - docs/BACKLOG.md:906 -- a real defect, but larger than a pointer repair and in a file several sessions are editing. Reported separately for a decision. A wider scan (127 path-bearing BACKLOG citations) found roughly 90 more naming the live ledger for an archived item. Not touched here: the staleness is currently uniform, and repointing a subset would assert by contrast that the untouched siblings are live. That class needs one pass, not a trickle.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #88, which fixed the backlog floor to read every ref. That was correct — and it left the fix depending on those refs continuing to exist.
The exposure
Measured on the maintainer clone:
refs/remotes/origin+ local headsThe 62-number difference lives on remote-tracking refs for a remote
git remote -vno longer lists. Remove them and every term in the floor collapses together, the floor reverts to its pre-#88 value, and the allocator resumes issuing numbers that are already in use — no error, no signal, the same bug #88 was opened for.The fix
Every term in the floor is derived from something removable, so derive one that isn't. The highest floor ever computed is persisted at
alloc/<kind>/.floor-highwater, and the floor never drops below it. A computed floor beneath the mark prints a loudNOTE:instead of quietly handing out a used number. The mark can only rise.It's a backstop, not a substitute: the ratchet keeps the number space correct, but if those refs go, the history behind them is still gone. The PR says so rather than implying the risk is retired.
A correction a peer earned
A peer session ran a prune, then checked whether it had destroyed anything. It hadn't — and they were right to push back on my warning:
git fetch origin --pruneis SAFE. It prunes onlyrefs/remotes/origin/*, which is not where the high numbers live — and it's what you should run before allocating.originremote, deleting its refs, or agc/reflog expirethat drops unreachable objects.An undifferentiated "don't prune" is worse than no warning, because it scares people off the fetch the allocation flow depends on.
LEDGER-GATE.mdnow draws that line explicitly.Verification
Real path, against the live registry:
Ratchet branch exercised in isolation against a throwaway watermark — deliberately not by deleting real refs:
Also confirmed the mark can't be mistaken for an allocation:
$allocis kind-scoped, the registry scan filters*.json, andledger_check.pyreads a specific<kind>/<number>.jsonrather than globbing.#317 is a probe allocation and a permanent hole, like #315.
🤖 Generated with Claude Code