code-index://docs/reason-codes is at 3,979 of its 4,000-token cap, so the next reason code this project mints cannot be documented #184

Closed
opened 2026-09-06 09:54:11 +02:00 by buildagent · 2 comments
Member

Found by dogfooding during the 2026-09-06 triage session that closed 22 issues, while verifying #155 — whose fix mints a new reason code and lands the resource within 21 tokens of its ceiling.

Measured

code-index://docs/reason-codes    3,979 tokens used of a 4,000-token resource cap

21 tokens of headroom. A reason code plus its one-line semantics does not fit in 21 tokens. So the next code this project declares will either go undocumented, or force whoever declares it to trim unrelated prose in the same change.

Why this is a correctness problem, not housekeeping

Reason codes are wire contract here — #80 says so explicitly ("Reason values are wire contract. Renames require compatibility normalization."), and the whole disclosure discipline rests on an agent being able to look one up and learn what it means. A catalogue that cannot grow is a contract that cannot grow.

The immediate pressure is real and already queued:

  • #155 added index_reconciling to the closed coverage family (crates/indexer/src/coverage.rs:190, catalogued at crates/mcp-server/src/docs/reason-codes.md:61,97,103) — that is the change that consumed the margin.
  • #124 leaves PackageSet::refused() / RefusedPackage reaching no coverage code at all. Closing it means minting codes.
  • #128 explicitly defers a decision that needs "either a new code (a migration, because generation_failure_reasons is seeded from FAILURE_REASONS) or a producer for UNCLASSIFIED".
  • #86 gap 3 and #153 both point at reason-code surface.

So this is not a distant ceiling; it is in the path of at least three open issues.

The failure mode to avoid, stated because this repo has the scar

The tempting response is to raise the cap. That is the move this project has repeatedly refused elsewhere and should refuse here: 73ef473 trimmed prose rather than raise a ceiling ("The first draft was 26 tokens over and prose was cut, not the ceiling"), and #98's option 3 — "just raise it" — is explicitly rejected in that issue's own text.

Note also that the resource cap is not the only ceiling under pressure: #160 measures the startup payload at 16,530 of 16,555 (25 tokens), #98/#133 put overview_payload_budget_e2e at 25 tokens of 4,300, and #137 adds ~106 uncapped tokens per linked project. Four ceilings, all within ~25 tokens, is a pattern rather than four coincidences — and raising any one of them individually is how the pattern gets hidden.

What must NOT be done

  • Do not raise the 4,000-token cap as the fix. If it is raised, it must be as a deliberate, measured re-budget of the resource family — with the number and the reason recorded — not as the cheapest way to land the next code.
  • Do not drop codes from the catalogue to make room. An undocumented shipped code is worse than a documented one: an agent that meets it has no way to learn what it means, which is precisely the "empty means nothing qualifies this" trap #124 was filed about.
  • Do not merge codes to save bytes. Collapsing two causes into one name is the silent-loss shape this repo has closed four times (#115, #123, #128, #157).
  • Do not leave the next code undocumented and file a follow-up. That is how the catalogue and the vocabulary drift apart, and #72's registry already caught Resp::stage's schema doc listing three of the five values the source stamps — stale for two releases. The same drift, one layer out.

Shape of a real fix

Two honest options, both of which need someone to choose:

  1. Split the topic. One resource per family (package/install, runtime, fact-validation, activation, coverage) rather than one document holding all of them. Each gets its own budget and its own room to grow. Costs a resource-registry change and an entry in #107's URI grader.
  2. Re-budget the resource family deliberately, measuring what the reason-code catalogue actually needs at the rate codes are being minted, and recording the number the way startup_payload_budget_e2e records its two-sided bound.

Option 1 is more in keeping with how this tree has solved the same pressure elsewhere. Either way the decision should be taken before the next code is minted, not during.

Verification note

There should be a gate. Today the cap is enforced but nothing warns as it is approached, so this was found by a human-readable measurement during unrelated work rather than by CI. A floor-and-ceiling pair — as startup_payload_budget_e2e uses (MAX 16_555 / MIN 10_000) — would make both "it grew past" and "it silently collapsed" visible.

🤖 Filed by the triage lane, 2026-09-06, found while verifying #155.

Found by dogfooding during the 2026-09-06 triage session that closed 22 issues, while verifying #155 — whose fix mints a new reason code and lands the resource within **21 tokens** of its ceiling. ## Measured ``` code-index://docs/reason-codes 3,979 tokens used of a 4,000-token resource cap ``` **21 tokens of headroom.** A reason code plus its one-line semantics does not fit in 21 tokens. So the next code this project declares will either go undocumented, or force whoever declares it to trim unrelated prose in the same change. ## Why this is a correctness problem, not housekeeping Reason codes are **wire contract** here — #80 says so explicitly (*"Reason values are wire contract. Renames require compatibility normalization."*), and the whole disclosure discipline rests on an agent being able to look one up and learn what it means. A catalogue that cannot grow is a contract that cannot grow. The immediate pressure is real and already queued: - **#155** added `index_reconciling` to the closed coverage family (`crates/indexer/src/coverage.rs:190`, catalogued at `crates/mcp-server/src/docs/reason-codes.md:61,97,103`) — that is the change that consumed the margin. - **#124** leaves `PackageSet::refused()` / `RefusedPackage` reaching **no** coverage code at all. Closing it means minting codes. - **#128** explicitly defers a decision that needs *"either a new code (a migration, because `generation_failure_reasons` is seeded from `FAILURE_REASONS`) or a producer for `UNCLASSIFIED`"*. - **#86** gap 3 and **#153** both point at reason-code surface. So this is not a distant ceiling; it is in the path of at least three open issues. ## The failure mode to avoid, stated because this repo has the scar The tempting response is to **raise the cap**. That is the move this project has repeatedly refused elsewhere and should refuse here: `73ef473` trimmed prose rather than raise a ceiling (*"The first draft was 26 tokens over and prose was cut, not the ceiling"*), and #98's option 3 — "just raise it" — is explicitly rejected in that issue's own text. Note also that the resource cap is not the only ceiling under pressure: **#160** measures the startup payload at 16,530 of 16,555 (25 tokens), **#98/#133** put `overview_payload_budget_e2e` at 25 tokens of 4,300, and **#137** adds ~106 uncapped tokens per linked project. Four ceilings, all within ~25 tokens, is a pattern rather than four coincidences — and raising any one of them individually is how the pattern gets hidden. ## What must NOT be done - **Do not raise the 4,000-token cap as the fix.** If it is raised, it must be as a deliberate, measured re-budget of the resource family — with the number and the reason recorded — not as the cheapest way to land the next code. - **Do not drop codes from the catalogue to make room.** An undocumented shipped code is worse than a documented one: an agent that meets it has no way to learn what it means, which is precisely the "empty means nothing qualifies this" trap #124 was filed about. - **Do not merge codes to save bytes.** Collapsing two causes into one name is the silent-loss shape this repo has closed four times (#115, #123, #128, #157). - **Do not leave the next code undocumented and file a follow-up.** That is how the catalogue and the vocabulary drift apart, and #72's registry already caught `Resp::stage`'s schema doc listing three of the five values the source stamps — **stale for two releases**. The same drift, one layer out. ## Shape of a real fix Two honest options, both of which need someone to choose: 1. **Split the topic.** One resource per family (package/install, runtime, fact-validation, activation, coverage) rather than one document holding all of them. Each gets its own budget and its own room to grow. Costs a resource-registry change and an entry in `#107`'s URI grader. 2. **Re-budget the resource family deliberately**, measuring what the reason-code catalogue actually needs at the rate codes are being minted, and recording the number the way `startup_payload_budget_e2e` records its two-sided bound. Option 1 is more in keeping with how this tree has solved the same pressure elsewhere. Either way the decision should be taken **before** the next code is minted, not during. ## Verification note There should be a gate. Today the cap is enforced but nothing warns as it is approached, so this was found by a human-readable measurement during unrelated work rather than by CI. A floor-and-ceiling pair — as `startup_payload_budget_e2e` uses (`MAX 16_555` / `MIN 10_000`) — would make both "it grew past" and "it silently collapsed" visible. 🤖 Filed by the triage lane, 2026-09-06, found while verifying #155.
Author
Member

CONFIRMED — and it was worse than filed, in a way that proves your own point about stale prose. Fixed on lane/provenance (worktree /tmp/cosi-lane-provenance, based on fc329a8).

Measured, and the finding you missed

topic tokens of 4,000 headroom
reason-codes 3,976 24
refusal-codes 3,990 10

You measured reason-codes at 3,979 and it was the one you filed. refusal-codes was tighter. And the tree's own comment about it, written at the #80 S21 split, says:

refusal-codes is now the half that grows — every new reason code is a refusal — and it sits at ~2,634 tokens with ~1,366 of headroom instead of 21.

Stale by 1,356 tokens. That is exactly the drift you cite #72 for, one layer out: the growth half had 10 tokens, not 1,366, and the note designating it as the growth half was the reason nobody looked.

What shipped — option 1, split by family

Six catalogue topics instead of two, plus one new standalone page:

topic tokens headroom
reason-codes (hub: how to read it, the five vocabularies, Coverage) 2,008 1,992
refusal-codes (package/install, signatures, publisher trust) 1,749 2,251
runtime-codes (runtime, operator confirmation, conformance) 1,703 2,297
extraction-codes (what an extractor may say, fact validation) 1,375 2,625
activation-offers (+ the Activation refusal family) 1,336 2,664
code-inventories (owed, retired, declared-unwritten) 1,137 2,863
answer-provenance (#181/#182 — new) 1,151 2,849

The cap was not raised. No code was dropped or merged. Verified: every ## section of the two old files survives the split — the only heading that disappears is ## The other half of this catalogue, replaced by a map of all seven. reason_code_registry is green (12 tests), including every_declared_code_is_in_the_catalogue and both inventory cross-checks.

That answer-provenance could be minted at all is the demonstration you asked for: the next code this project declares now fits.

The gate you asked for

Your verification note: "Today the cap is enforced but nothing warns as it is approached … A floor-and-ceiling pair would make both 'it grew past' and 'it silently collapsed' visible."

every_doc_topic_keeps_room_to_grow requires 1,400 tokens of headroom on every topic — sized against the largest section the split moved (Coverage, ~1,004 tokens), so a topic can absorb another family-sized block before delivery has to be reconsidered — plus a 100-token floor sized against the shortest real topic this server publishes (cursors, 161) rather than against the catalogue files, which would have been a floor no small topic could satisfy. It prints the widest topic and its headroom on every run.

The old every_doc_topic_fits_the_resource_budget_untruncated stays: it catches the state where a document is already being served truncated, which is the one this cannot see.

Mutations, run:

  • append 40 table rows (~4,700 chars) to reason-codes.md → RED: "at 3178 of 4000 tokens, leaving 822 of the 1400 tokens of headroom this catalogue is required to keep". Note the cap itself was not breached — the old gate would still have been green there, which is the whole point.
  • replace code-inventories.md with a one-line stub → RED on the floor.

The drift you predicted, closed structurally

reason_code_registry held a hand-copied list of three catalogue files. A split into six would have left it grading two thirds of the catalogue while every gate reported green — the #72 shape again. It now derives the population twice over:

  1. doc_markdown_files() reads what DOC_TOPICS publishes, out of server.rs;
  2. a topic is part of the catalogue iff it says so in its own prose — the shared ONE CATALOGUE sentence every catalogue page carries and answer-provenance does not. The classification is a fact a reader can see, not metadata beside it.

every_catalogue_file_carries_codes_of_its_own then asserts both directions: a page claiming membership must carry ≥5 codes, and the set of pages not claiming it must equal the declared standalone list exactly. A seventh topic is a decision, not an omission, and a stale declaration fails rather than reads as a comment.

Mutations, run: stub runtime-codes.md → RED per file (the union of six still cleared the global floor of 60, which is why the check is per file); add "runtime-codes" to the standalone list → RED, because the page declares itself; hardcode catalogue_files() back to two entries → RED across six registry tests.

One thing your issue prompts that I did not do

You note index_coverage points at catalogues containing none of its 17 reason codes. That is a real gap and the split does not close it — the codes still have no home. It needs its own decision about which family they belong to, so it is left for that rather than folded in here.

**CONFIRMED — and it was worse than filed, in a way that proves your own point about stale prose.** Fixed on `lane/provenance` (worktree `/tmp/cosi-lane-provenance`, based on `fc329a8`). ## Measured, and the finding you missed | topic | tokens of 4,000 | headroom | |---|---|---| | `reason-codes` | 3,976 | 24 | | **`refusal-codes`** | **3,990** | **10** | You measured `reason-codes` at 3,979 and it was the one you filed. **`refusal-codes` was tighter.** And the tree's own comment about it, written at the #80 S21 split, says: > `refusal-codes` is now the half that grows — every new reason code is a refusal — and it sits at ~2,634 tokens with ~1,366 of headroom instead of 21. **Stale by 1,356 tokens.** That is exactly the drift you cite #72 for, one layer out: the growth half had 10 tokens, not 1,366, and the note designating it as the growth half was the reason nobody looked. ## What shipped — option 1, split by family Six catalogue topics instead of two, plus one new standalone page: | topic | tokens | headroom | |---|---|---| | `reason-codes` (hub: how to read it, the five vocabularies, Coverage) | 2,008 | 1,992 | | `refusal-codes` (package/install, signatures, publisher trust) | 1,749 | 2,251 | | `runtime-codes` (runtime, operator confirmation, conformance) | 1,703 | 2,297 | | `extraction-codes` (what an extractor may say, fact validation) | 1,375 | 2,625 | | `activation-offers` (+ the Activation refusal family) | 1,336 | 2,664 | | `code-inventories` (owed, retired, declared-unwritten) | 1,137 | 2,863 | | `answer-provenance` (#181/#182 — new) | 1,151 | 2,849 | **The cap was not raised. No code was dropped or merged.** Verified: every `## ` section of the two old files survives the split — the only heading that disappears is `## The other half of this catalogue`, replaced by a map of all seven. `reason_code_registry` is green (12 tests), including `every_declared_code_is_in_the_catalogue` and both inventory cross-checks. That `answer-provenance` could be minted at all is the demonstration you asked for: **the next code this project declares now fits.** ## The gate you asked for Your verification note: *"Today the cap is enforced but nothing warns as it is approached … A floor-and-ceiling pair would make both 'it grew past' and 'it silently collapsed' visible."* `every_doc_topic_keeps_room_to_grow` requires **1,400 tokens of headroom** on every topic — sized against the largest section the split moved (`Coverage`, ~1,004 tokens), so a topic can absorb another family-sized block before delivery has to be reconsidered — plus a 100-token floor sized against the shortest real topic this server publishes (`cursors`, 161) rather than against the catalogue files, which would have been a floor no small topic could satisfy. It prints the widest topic and its headroom on every run. The old `every_doc_topic_fits_the_resource_budget_untruncated` stays: it catches the state where a document is *already* being served truncated, which is the one this cannot see. **Mutations, run:** - append 40 table rows (~4,700 chars) to `reason-codes.md` → **RED**: *"at 3178 of 4000 tokens, leaving 822 of the 1400 tokens of headroom this catalogue is required to keep"*. Note the cap itself was **not** breached — the old gate would still have been green there, which is the whole point. - replace `code-inventories.md` with a one-line stub → **RED** on the floor. ## The drift you predicted, closed structurally `reason_code_registry` held a **hand-copied list of three catalogue files**. A split into six would have left it grading two thirds of the catalogue while every gate reported green — the #72 shape again. It now derives the population twice over: 1. `doc_markdown_files()` reads what `DOC_TOPICS` publishes, out of `server.rs`; 2. a topic is part of the catalogue iff **it says so in its own prose** — the shared `ONE CATALOGUE` sentence every catalogue page carries and `answer-provenance` does not. The classification is a fact a reader can see, not metadata beside it. `every_catalogue_file_carries_codes_of_its_own` then asserts both directions: a page claiming membership must carry ≥5 codes, and the set of pages *not* claiming it must equal the declared standalone list exactly. **A seventh topic is a decision, not an omission**, and a stale declaration fails rather than reads as a comment. **Mutations, run:** stub `runtime-codes.md` → RED per file (the union of six still cleared the global floor of 60, which is why the check is per file); add `"runtime-codes"` to the standalone list → RED, because the page declares itself; hardcode `catalogue_files()` back to two entries → RED across six registry tests. ## One thing your issue prompts that I did not do You note `index_coverage` points at catalogues containing none of its 17 reason codes. That is a real gap and the split does not close it — the codes still have no home. It needs its own decision about which family they belong to, so it is left for that rather than folded in here.
Author
Member

Already fixed on master — this issue is stale, and it also measured the wrong file as the tightest.

Verified against the tree rather than the comment history. The split shipped on lane/provenance and is on master.

The shape chosen: split by family (option 1), cap unchanged

Measured, current:

topic tokens / 4,000 headroom
reason-codes 2,349 1,651
answer-provenance 1,947 2,053
refusal-codes 1,749 2,251
runtime-codes 1,703 2,297
extraction-codes 1,565 2,435
activation-offers 1,336 2,664
code-inventories 1,192 2,808

The cap was not raised, and no code was dropped or merged — which is the outcome this issue asked for. Trimming unrelated prose to buy headroom was the failure mode it named, and that is not what happened.

The correction to this issue's own measurement

This was filed about reason-codes at 3,979. At that revision refusal-codes was at 3,990 — tighter, and unmentioned. The issue identified the right defect through the wrong instance, which does not change the conclusion but is worth recording: a ceiling problem found by looking at one file will generally not be found at its worst point.

The gate, and its mutation

every_doc_topic_keeps_room_to_grow (crates/mcp-server/src/server.rs:21435) requires 1,400 tokens of headroom plus a 100-token floor — so the failure fires while a new code still fits, rather than after it no longer does.

Mutation (run): appended 60 catalogue rows to reason-codes.md →

RED: `code-index://docs/reason-codes` is at 3992 of 4000 tokens, leaving 8 of
     the 1400 tokens of headroom this catalogue is required to keep.

The old cap-only gate stayed green at 3,992 — which is precisely the gap this issue was filed about. A cap that only fires once the catalogue is already full cannot protect the next code; a headroom floor can.

One thing to watch

The catalogue has already consumed 341 of its new headroom (reason-codes 2,008 → 2,349 since the split). That is normal growth and well inside the floor, but it means the 1,651 figure above is a snapshot, not a resting state. The gate is what makes that safe to ignore day to day.

Closing.

**Already fixed on `master` — this issue is stale, and it also measured the wrong file as the tightest.** Verified against the tree rather than the comment history. The split shipped on `lane/provenance` and is on master. ## The shape chosen: split by family (option 1), cap unchanged Measured, current: | topic | tokens / 4,000 | headroom | |---|---|---| | `reason-codes` | **2,349** | 1,651 | | `answer-provenance` | 1,947 | 2,053 | | `refusal-codes` | 1,749 | 2,251 | | `runtime-codes` | 1,703 | 2,297 | | `extraction-codes` | 1,565 | 2,435 | | `activation-offers` | 1,336 | 2,664 | | `code-inventories` | 1,192 | 2,808 | **The cap was not raised, and no code was dropped or merged** — which is the outcome this issue asked for. Trimming unrelated prose to buy headroom was the failure mode it named, and that is not what happened. ## The correction to this issue's own measurement This was filed about `reason-codes` at 3,979. At that revision **`refusal-codes` was at 3,990** — tighter, and unmentioned. The issue identified the right defect through the wrong instance, which does not change the conclusion but is worth recording: a ceiling problem found by looking at one file will generally not be found at its worst point. ## The gate, and its mutation `every_doc_topic_keeps_room_to_grow` (`crates/mcp-server/src/server.rs:21435`) requires **1,400 tokens of headroom** plus a 100-token floor — so the failure fires while a new code still fits, rather than after it no longer does. **Mutation (run):** appended 60 catalogue rows to `reason-codes.md` → ``` RED: `code-index://docs/reason-codes` is at 3992 of 4000 tokens, leaving 8 of the 1400 tokens of headroom this catalogue is required to keep. ``` The **old cap-only gate stayed green at 3,992** — which is precisely the gap this issue was filed about. A cap that only fires once the catalogue is already full cannot protect the next code; a headroom floor can. ## One thing to watch The catalogue has already consumed **341** of its new headroom (`reason-codes` 2,008 → 2,349 since the split). That is normal growth and well inside the floor, but it means the 1,651 figure above is a snapshot, not a resting state. The gate is what makes that safe to ignore day to day. Closing.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
h-dv/code-index#184
No description provided.