# FINDING: repo_lint.py L5 "next-free identifiers" is unreliable -- do not use it for numbering

**Status:** DOCFIX candidate. Read-only analysis by the main-chat stream at repo HEAD
`59a7c73` (2026-07-06). No code changed. Code assigns the DOCFIX number when actioning
(re-run `ledger-scan` for next-free first; main-chat consumed none of these).

**One-line:** `scripts/repo_lint.py` prints an `[info] L5 next-free identifiers` line that
is wrong on all three counters and disagrees with `ledger-scan`. Numbers must be taken
from `ledger-scan`, never from repo_lint. This is a live collision risk while the jumphost
stream is consuming DOCFIX numbers rapidly.

## Evidence

At this HEAD the two tools disagree:

| counter | repo_lint L5 says | ledger-scan says (authoritative) | ground truth |
|---|---|---|---|
| D | next-free `D-075` | next-free `D-074` | highest header `D-073`; `D-074` exists only as a PROPOSAL mention |
| DOCFIX | next-free `DOCFIX-100` | next-free `DOCFIX-106` | highest consumed `DOCFIX-105`; `DOCFIX-106` is a next-free pointer |
| BUNDLEFIX | next-free `BUNDLEFIX-051` | next-free `BUNDLEFIX-012` | highest consumed `BUNDLEFIX-011`; `051` traces to a test fixture (below) |

`ledger-scan` is correct on all three. Each repo_lint number is wrong via a DIFFERENT
defect, which is why this is worth fixing rather than tweaking.

## Root cause -- three independent defects in the L5 next-free block

The block (`repo_lint.py`, L5 identifier numbering) computes next-free as `max(seen)+1`
over `re.finditer(r"\b(D|DOCFIX|BUNDLEFIX)-(0\d{2})\b", ...)` across `all_text()`:

1. **Band-limited regex `0\d{2}`.** It only matches identifiers `000`-`099`. It is blind to
   `DOCFIX-100..106` (the current live range), so it reports `DOCFIX-100` when true next-free
   is `106`. This is the same defect `ledger-scan` already carried and Code already fixed
   (its regex is now `[0-9]{3}`); repo_lint still has the old band-limited form.
2. **Mention-counting, no header-authority, no pointer-exclusion.** It counts every
   identifier OCCURRENCE, not definition-headers, and does not skip "Next-free:" pointer
   lines. So a PROPOSAL reference inflates it -- the main-chat CIDR plan mentions `D-074`,
   which is exactly why repo_lint reports `D-075`. `ledger-scan` uses design-decisions
   headers for D and excludes `next-free` pointer lines.
3. **Scans the test tree.** `all_text()` includes `tests/`. `tests/ledger-scan/run-tests.sh`
   contains a deliberately-high fixture line
   (`Next-free: D-071, DOCFIX-099, BUNDLEFIX-050 ... must NOT inflate`) written to PROVE
   `ledger-scan` ignores pointer lines. repo_lint ingests that fixture's `BUNDLEFIX-050`
   and reports `051` -- the precise trap the fixture was authored to catch.

Correction of the record: an earlier main-chat note called `BUNDLEFIX-051` "spurious." It is
not -- it is `max+1` of the fixture token in defect 3. The imprecision is corrected here.

## Fix options (Code's lane; not implemented here)

**Recommended -- remove the next-free print from repo_lint entirely.** repo_lint's actual L5
job is the duplicate-definition-heading collision guard; keep that. Next-free is
`ledger-scan`'s job, and having two tools derive it by different regexes is how they drifted
apart. Single source of truth (the same principle already floated for the machine-derived
ledger cache). Lowest risk, removes the drift surface.

**Fallback -- if repo_lint must keep printing next-free, make it match `ledger-scan`:**
widen the regex to `[0-9]{3,}`; use design-decisions headers for D; exclude `next-free`
pointer lines; and exclude `tests/` from `all_text()` for identifier scanning. More code,
same answer -- prefer the removal.

## Interim guidance (both streams, until fixed)

Take D / DOCFIX / BUNDLEFIX next-free from `bash scripts/ledger-scan.sh` ONLY. Treat
repo_lint's `[info] L5 next-free` line as advisory-and-currently-wrong; it does not gate
(it is `[info]`, 0-fail), so it is safe to ignore for numbering.
