A file whose proportional reservation lands below its own size stopped
rendering whole, and the fallback cluster render could leave most of that
reservation unspent — the bytes were neither delivered nor redistributed.
Found by CG-15's agent A/B on the express control: `lib/utils.js`, the
top-ranked file, was reserved 3,870 chars and spent 583. The whole-file
grace bound (reservation + a sliver) sat just under the file's 5,293
bytes, so the whole render was declined and three matched symbols became a
stub. The source envelope fell 13,849 -> 9,241 against an UNCHANGED
budget, and the agent Read the file back four times in 1 run of 3.
Two levers, per the task's candidate fixes:
- WHOLE_FILE_BUY_FRACTION: a reservation that already covers 60% of a
file buys the whole file. Funded from ONE shared overshoot pool sized
at 15% of the envelope, spent in rank order. Per-file funding is the
version that fails, and it fails the same way the bug does — the merit
test is a ratio, so several files qualify at once and N independent
overshoots push the last section past the render ceiling. Measured on
the payroll fixture: three files bought whole and `payslip_builder.go`
was dropped entirely. A dropped section is strictly worse than a
clustered one.
- Reservation carry-forward: what a file cannot spend goes to the next
file down, bounded by MAX_SHARE. Tracked as two running totals rather
than a `spent` variable threaded through the render loop's dozen exit
paths, so no path can forget to account, and symmetric — a buy that
overshoots suppresses slack until a later under-spend covers it.
Express reproducer: `lib/utils.js` 583 -> 6,268 whole, envelope 9,241 ->
14,505 on the same 13,000 budget. The `memory-budget.ts` exception CG-14
documented is RESOLVED rather than re-justified: it ships whole again at
5,672 (27.3%) while `src/mcp/tools.ts` rises to 52.6% — so the answer
file wins the envelope AND no previously-unclipped file is clipped, which
is CG-12's own acceptance criterion finally holding.
Two hermetic fixtures added, one per lever, because nothing in the suite
had this shape — which is how it shipped. Both mutation-tested: removing
the buy arm reddens 3, removing the carry-forward reddens 2, and removing
the funding guard reddens 4 (including payroll's dropped
`payslip_builder.go`). Their `fixture shape` blocks are load-bearing: the
gates pass vacuously if a target ever drifts inside the grace bound, so
the window is asserted directly.
Full suite green (2,868 passed); both #1500 regression fixtures pass.
Coverage for the CG-12 allocator, built around "would this go red if the
lever were removed" rather than line coverage — every way this regresses
is silent, ending in an agent falling back to Read.
Unit (`explore-proportional-allocation.test.ts`, 18 -> 38): calibration
pins, envelope safety across every tier and 30 candidate shapes, the
cliff boundary, spine weighting/trim survival, the diffuse control, and
the degenerate inputs — identical scores, a lone file, a runaway top
scorer, zero results, maxFiles 0, a non-finite score.
End-to-end (`explore-allocation-e2e.test.ts`, new): CG-6's second
regression fixture as a deterministic synthetic mirror — a large relevant
file, a small helper that used to win by shipping whole, and an
incidental `explore`/`BUDGET` collision — asserting per-file budget
share, not file presence. Plus degenerate result sets and a survey-style
diffuse control through the real render loop. The live self-query arm
stays in probe-allocation.mjs, where drift is a number to re-baseline
rather than a red suite.
Reverting the render loop to the pre-CG-12 rules reproduces #1500 on the
mirror exactly and takes 5 e2e + 2 payroll gates red:
file score pre-CG-12 CG-12
src/mcp/allocator.ts 77.5 4,843 (39.7%) 9,335 (80.1%)
src/util/budget-math.ts 36.0 6,079 (49.8%) 1,037 ( 8.9%)
Two defects the invariants surfaced, both fixed in tools.ts:
- rounded shares could sum past `pool`, so "reservations fit the
envelope" was approximate rather than exact; both terms now floor
- a non-finite score made every share Infinity/Infinity, handing the
render loop a NaN allowance; `weightOf` now fails safe to 0
Also adds a hard-ceiling gate to the payroll fixture — at 19.3K against
a 19.5K ceiling it is the only fixture that stresses the ~25K inline cap
— and exports EXPLORE_ALLOCATION so invariant tests read the constants
while one test pins the literals.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>