# Changelog 2026-07-10 -- ledger-scan.sh: self-inflicted next-free false positive (DOCFIX-174)

No live infrastructure touched. Found while preparing to assign the next real
DOCFIX number for follow-up work: `bash scripts/ledger-scan.sh` reported
`DOCFIX: highest=1004  next-free=1005` -- clearly wrong, since the repo is at
DOCFIX-173 (confirmed via `git log`).

## Root cause

Ironic: DOCFIX-173's OWN changelog
(`docs/changelog-20260710-sweep-script-fixes.md`) narrated a bug reproduction
for the `{3}` -> `{3,}` regex widening it was making, and used a literal
illustrative example (this repo's real prefix, followed by the digits one-
zero-zero-four) to demonstrate the OLD bug's truncation behavior.
`nextfree_mentions()` in `scripts/ledger-scan.sh` greps ALL prose under
`docs/` and `runbooks/` for the prefix-plus-3-or-more-digits pattern
(excluding only lines that look like "next-free" pointers) and takes the
highest number found. The just-widened regex now matched that illustrative
example as if it were a real assignment, inflating next-free from 174 to
1005. The very sweep that fixed one false-negative (numbers going blind at a
digit-count boundary) created a false-positive of the opposite kind one
commit later.

Confirmed via reproduction: scanning `docs/` and `runbooks/` for the real
prefix-plus-digits pattern (excluding "next-free" pointer lines) lists
DOCFIX-173 as the true highest real mention, plus one spurious high-looking
match traced directly to the changelog's own illustrative prose described
above.

## Fix

1. Reworded the illustrative example in
   `docs/changelog-20260710-sweep-script-fixes.md` to describe the
   reproduction in words instead of spelling out a literal
   `DOCFIX-<digits>`-shaped string, removing the false match at its source.
2. Made the identical edit to `scripts/ledger-scan.sh`'s own comment (which
   had the same literal string in a code comment -- not itself scanned since
   `scripts/` isn't a scanned directory, but worth fixing for hygiene so it
   isn't copy-pasted into a doc later).
3. Hardened `nextfree_mentions()` itself: consolidated the (accidentally
   duplicated, see Verification below) function definition into one, and
   added a second exclusion alongside the existing "next-free" pointer-line
   filter: lines matching `reproduc` (case-insensitive) are now also
   excluded from the mention scan. This is defense-in-depth, not a
   substitute for #1/#2 -- this repo's own delivery discipline narrates bug
   reproductions constantly ("Reproduced:", "confirmed-via-reproduction"),
   so a future sweep changelog doing exactly what DOCFIX-173's did is a
   real, recurring risk, not a one-off.
4. Added a regression fixture to `tests/ledger-scan/run-tests.sh`: a
   `docs/repro-example.md` file containing a "Reproduced: ... DOCFIX-1004
   ..." line, asserting the DOCFIX next-free assertion (`next-free=086`)
   still holds with that fixture present -- so this exact class can't
   regress silently again.

**Process note on how this was caught:** not by review -- by simply running
`bash scripts/ledger-scan.sh` before assigning the next real DOCFIX number for
unrelated follow-up work, and noticing the output was implausible (a jump from
173 to 1004) rather than trusting it. Same "verify before trusting a tool's
own output" discipline this session used repeatedly elsewhere (the
`expect_ok` dead-code bug, the `phase-06-capi-stack.sh` `PIPESTATUS` fix).

**It recurred immediately, within this same delivery, before commit.** This
changelog's own first draft, and its mirrored bullet in
`docs/session-ledger.md`, ALSO quoted the illustrative example as a literal
string while narrating the incident -- reinflating next-free right back to
1005 on the next `ledger-scan.sh` run, caught by re-running the scan again
before assigning DOCFIX-175/176 rather than assuming the fix from this same
delivery was airtight. Fixed by rewording both (spelled-out digits, same
convention `scripts/ledger-scan.sh`'s own comment now documents) rather than
relying solely on the `reproduc`-line exclusion, which only catches the
word appearing on the SAME line as the example -- a narrower guarantee than
it first appears, worth remembering next time this bug class comes up.

## Verification

`bash tests/ledger-scan/run-tests.sh`: 37/37 PASS (up from 33/33 -- one new
regression fixture + its assertion). `bash scripts/ledger-scan.sh`: now
correctly reports `DOCFIX: highest=173  next-free=174`. `bash
scripts/repo-lint.sh`: 0 fail, 1 documented legacy warn. Full gauntlet
(`bash scripts/run-tests-all.sh`): re-run after this fix, same pre-existing
environment gaps as every prior check tonight, zero regressions (see this
delivery's companion session-ledger entry for the exact count).

While editing, also caught and fixed my own mistake mid-fix: the first pass
at hardening `nextfree_mentions()` left TWO definitions of the function in
the file (the original, unedited one at its original location, plus a new
one with the fix inlined just before the call sites) -- a duplicate that
would still have worked (bash uses the last definition) but was sloppy and
would have confused the next reader. Caught by re-reading the file before
running the harness, consolidated to one definition in place.

REVERT: `git checkout HEAD~ -- scripts/ledger-scan.sh
tests/ledger-scan/run-tests.sh
docs/changelog-20260710-sweep-script-fixes.md` (safe -- no live infrastructure
depends on this script's output; it is a reporting/reconciliation tool only).

## Next actionable step

None required by this fix alone. The true next-free numbers going into any
follow-up work tonight are: D-111, DOCFIX-175, BUNDLEFIX-012 (this delivery
consumes DOCFIX-174).
