fix(explore): pay every admitted file on every render path (CG-26)

The invariant this closes: every admitted file receives at least its
reservation before any file draws on carry-forward slack. CG-30 bounded an
oversize cluster member and CG-31 gave the cluster path a displacement guard;
three holes were left, and each one starved a file that had been admitted,
reserved and — in the worst case — rendered.

1. The whole-file arms had no displacement guard. BUY's fit test read
   `renderCeiling - totalChars` (everyone's room) while its source-space
   sibling refused the same trade, and GRACE was not fit-tested at all.
   okhttp's CallServerInterceptor.kt shipped 8,499 chars on a 5,964 funded
   ceiling and the rank-6 file below it delivered nothing. Both arms now test
   the render they actually produce against `fundedHeadroom`, and a whole
   render that does not fit falls through to clustering instead of skipping
   the file.

2. Every section was charged a flat 200 chars while a real header runs
   300-500. The loop believed it had room it did not have — okhttp allocated
   26,601 against a 24,400 ceiling — so the final truncation threw a
   fully-rendered section away. Sections are charged their real cost now, the
   owed-below arithmetic uses a per-file overhead estimated from the file's own
   symbols, and a marginal overrun trims the weakest cluster (or windows the
   last one into the room that is left) rather than skipping the file over a
   rounding difference.

3. `owedPayableBelow` held all-or-nothing. When the last admitted file's FULL
   reservation no longer fit, nothing was held for it: on the precise-query
   fixture the rank-5 file took 4,134 chars against a 2,948 reservation while
   rank 6 — admitted, reserved 2,539 — was left 4 chars and skipped. It now
   holds the remainder while that remainder is still worth a section
   (MIN_CHARS).

And the epilogue is budgeted instead of discarded. The flat 600-char margin was
neither the epilogue's size (1,064 gin, 1,788 django, 2,231 excalidraw) nor a
bound on it, so four of six suite repos shipped with no pointer list and no
reminders at all. The loop now reserves the epilogue's FLOOR — the one line
that says an uncovered area exists, plus a pointer for every file whose bytes
were deliberately withheld (CG-12) — and the rest is fitted to the room that
actually remains, in priority order, entry by entry. Sized from the real
strings; no constant was swept against the suite.

Deterministic, same clean-rebuilt indexes, baseline = CG-31 tip:

  repo         base source   new source   files      ceiling
  django            20,791       20,878   6 -> 6     was discarding its epilogue
  tokio             21,521       21,607   5 -> 5     was discarding its epilogue
  okhttp            19,034       18,870   5 -> 6     +1 file delivered
  excalidraw        20,204       19,652   8 -> 8     keeps its pointer list
  gin               10,776       10,776   4 -> 4     byte-identical
  alamofire         11,662       11,662   2 -> 2     byte-identical

No repo truncates any more and none loses a file. okhttp and excalidraw trade
164 and 552 source chars on their LAST-ranked file for the pointer list naming
what the response could not cover — bytes the CG-31 tip only had because it
over-filled a ceiling it mis-measured and then discarded the epilogue whole.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Colby McHenry
2026-08-06 03:45:28 -05:00
co-authored by Claude Opus 5
parent c54e0080c2
commit 7cbde95ce2
5 changed files with 796 additions and 135 deletions
@@ -0,0 +1,265 @@
/**
* Regression fixture for CG-26 — the end-to-end reservation invariant.
*
* Every admitted file receives at least its reservation before any file draws
* on carry-forward slack.
*
* CG-30 bounded how far an oversize cluster member may overshoot and CG-31 gave
* the cluster path a displacement guard. This pins the invariant they jointly
* satisfy across EVERY render path — cluster, whole-file grace, whole-file BUY —
* and in BOTH directions: the top-ranked file when the files below it overspend,
* and an admitted lower-ranked file when the top one does.
*
* Two things CG-26 fixed are pinned here because nothing else can see them:
*
* - The whole-file arms were fit-tested against raw room before the ceiling,
* never against what was still owed below. A grace-sized file could take a
* pending file's reservation on its way to the ceiling; okhttp's
* `CallServerInterceptor.kt` shipped 8,499 chars on a 5,964 funded ceiling
* and the rank-6 file below it delivered nothing.
* - Every section was charged a flat 200 chars of overhead while a real header
* runs 300500. The loop believed it had room it did not have (okhttp
* rendered 26,601 chars against a 24,400 ceiling), so the final truncation
* threw a fully-rendered section away — the same starvation, arriving after
* the guard had done its work.
*
* Shares the `displacement-ts` fixture: four pipeline stages competing for one
* envelope, the first a single ~20K function, padded past 500 indexed files so
* the response sits on the 24K tier where reservations genuinely saturate the
* ceiling.
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import CodeGraph from '../src/index';
import { ToolHandler } from '../src/mcp/tools';
import { attributeSourceBytes } from '../src/mcp/explore-diagnostics';
import type { ExploreDiagnosticReport, ExploreDiagnosticFile } from '../src/mcp/explore-diagnostics';
const FIXTURE_SRC = path.join(__dirname, 'fixtures', 'displacement-ts');
const FILLER_FILES = 520;
/** The giant: one ~20K function. Ranks #1 under the spread query. */
const GIANT = 'src/pipeline/ingest.ts';
/**
* Three shapes, so the invariant is tested from both sides:
* spread — every stage named; the giant ranks #1 and overspends downwards.
* tail — the stages BELOW the giant named; something small ranks #1 while
* the giant competes from underneath. This is the direction CG-31's
* fixture could not reach.
* precise — one symbol. The concentration case the guard must not flatten.
*/
const QUERIES = {
spread: 'ingestRecords normalizeRecords enrichRecords publishRecords',
tail: 'publishRecords sinkRecord PipelineRecord ingestRecords',
precise: 'ingestRecords',
} as const;
type Shape = keyof typeof QUERIES;
interface Probe {
response: string;
report: ExploreDiagnosticReport;
bytes: Map<string, number>;
}
describe('CG-26 — no admitted file is starved, on any render path', () => {
let testDir: string;
let cg: CodeGraph;
const probes = {} as Record<Shape, Probe>;
/** Admitted = the allocator reserved bytes for it. */
const admitted = (probe: Probe): ExploreDiagnosticFile[] =>
probe.report.files.filter((f) => (f.allowance ?? 0) > 0);
const all = (): Probe[] => Object.values(probes);
beforeAll(async () => {
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cg26-'));
fs.cpSync(FIXTURE_SRC, testDir, { recursive: true });
fs.rmSync(path.join(testDir, '.codegraph'), { recursive: true, force: true });
const filler = path.join(testDir, 'src', 'generated');
fs.mkdirSync(filler, { recursive: true });
for (let i = 0; i < FILLER_FILES; i++) {
fs.writeFileSync(
path.join(filler, `unit${i}.ts`),
`export const seed${i} = ${i};\n`
+ `export function widget${i}(n: number): number {\n return n * ${i + 1} + seed${i};\n}\n`,
);
}
cg = CodeGraph.initSync(testDir);
await cg.indexAll();
const sidecar = path.join(testDir, 'explore-diag.jsonl');
const previous = process.env.CODEGRAPH_EXPLORE_DEBUG;
process.env.CODEGRAPH_EXPLORE_DEBUG = sidecar;
try {
const handler = new ToolHandler(cg);
for (const [shape, query] of Object.entries(QUERIES) as [Shape, string][]) {
const result = await handler.execute('codegraph_explore', { query });
const response = result.content?.[0]?.text ?? '';
const written = fs.readFileSync(sidecar, 'utf-8').trim().split('\n').filter(Boolean);
probes[shape] = {
response,
report: JSON.parse(written[written.length - 1]!) as ExploreDiagnosticReport,
bytes: attributeSourceBytes(response),
};
}
} finally {
if (previous === undefined) delete process.env.CODEGRAPH_EXPLORE_DEBUG;
else process.env.CODEGRAPH_EXPLORE_DEBUG = previous;
}
}, 180_000);
afterAll(() => {
if (cg) cg.destroy();
if (testDir && fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true, force: true });
});
// ── Fixture shape — if these rot, the gates below mean nothing ─────────────
describe('fixture shape', () => {
it('sits on the 24K tier, where the reservations saturate the ceiling', () => {
expect(cg.getStats().fileCount).toBeGreaterThanOrEqual(500);
for (const probe of all()) expect(probe.report.budget.maxOutputChars).toBe(24000);
});
it('exercises both directions — the giant ranks #1 in one shape and lower in another', () => {
// Which shape puts it where is the ranker's business and may move; that
// it lands on BOTH sides across the three is what makes the gates below
// test the invariant rather than one arrangement of it.
const ranks = all().map((p) => p.report.files.find((f) => f.path === GIANT)?.rank ?? -1);
expect(ranks).toContain(1);
expect(ranks.some((r) => r > 1)).toBe(true);
});
it('exercises both render paths — something ships whole, something clusters', () => {
const modes = new Set(all().flatMap((p) => p.report.files.map((f) => f.render)));
expect(modes).toContain('clusters');
expect(modes).toContain('whole');
});
});
// ── The invariant ─────────────────────────────────────────────────────────
describe('the reservation invariant', () => {
it('CG-26 GATE: no file on ANY render path emits past what was still free', () => {
// CG-31 pinned this for `clusters` only. The whole-file arms were fit-
// tested against `renderCeiling - totalChars`, which is everyone's room,
// not this file's — so a whole render could spend a reservation the loop
// had already promised further down.
for (const [shape, probe] of Object.entries(probes) as [Shape, Probe][]) {
const over = probe.report.files
.filter((f) => f.render !== null && f.render !== 'dropped' && f.funded !== null)
// +1 for the render loop's own rounding on a windowed cut.
.filter((f) => f.emittedChars > f.funded! + 1)
.map((f) => `${shape}/${f.path}: ${f.emittedChars} emitted of ${f.funded} funded (${f.render})`);
expect(over).toEqual([]);
}
});
it('CG-26 GATE: every admitted file is delivered, whatever its rank', () => {
for (const [shape, probe] of Object.entries(probes) as [Shape, Probe][]) {
for (const rec of admitted(probe)) {
expect(rec.skipped, `${shape}/${rec.path} skipped`).toBeNull();
expect(probe.bytes.get(rec.path) ?? 0, `${shape}/${rec.path} bytes`).toBeGreaterThan(0);
}
}
});
it('CG-26 GATE: the rank-#1 file gets its reservation even when a file below overspends', () => {
// The direction CG-31's fixture could not reach: under `tail` the giant
// ranks below a small file and draws far past its own reservation from
// carry-forward slack. Rank #1 must still receive what it was promised
// (or its whole file, if that is less).
for (const [shape, probe] of Object.entries(probes) as [Shape, Probe][]) {
const top = admitted(probe).sort((a, b) => a.rank - b.rank)[0];
if (!top) continue;
const onDisk = fs.statSync(path.join(testDir, top.path)).size;
expect(probe.bytes.get(top.path) ?? 0, `${shape}/${top.path}`)
.toBeGreaterThanOrEqual(Math.min(top.allowance!, onDisk) * 0.9);
}
});
it('and the gate above is not vacuous — a lower-ranked file does overspend', () => {
const overspenders = (probe: Probe) => admitted(probe)
.filter((f) => f.rank > 1 && f.emittedChars > f.allowance!);
expect(overspenders(probes.tail).length).toBeGreaterThan(0);
});
});
// ── What the ceiling must no longer do ────────────────────────────────────
describe('the hard ceiling never throws a rendered section away', () => {
it('the render loop spends what it counts — nothing is allocated past the ceiling', () => {
// Sections used to be charged a flat 200 chars against a header that runs
// 300500, so the loop over-filled and the final truncation dropped whole
// sections. `allocatedChars` is the pre-truncation length: it staying
// under the ceiling IS the accounting being exact.
for (const [shape, probe] of Object.entries(probes) as [Shape, Probe][]) {
expect(probe.report.envelope.allocatedChars, shape)
.toBeLessThanOrEqual(probe.report.budget.hardCeiling);
expect(probe.report.envelope.truncated, shape).toBe(false);
}
});
it('no file is rendered and then dropped', () => {
for (const probe of all()) {
expect(probe.report.files.filter((f) => f.render === 'dropped')).toEqual([]);
}
});
it('keeps the response inside the hard ceiling', () => {
for (const probe of all()) {
expect(probe.report.envelope.chars).toBeLessThanOrEqual(probe.report.budget.hardCeiling);
}
});
});
// ── The epilogue is budgeted, not discarded ───────────────────────────────
describe('the epilogue the loop budgeted for is the epilogue it emits', () => {
it('a response that withheld files still says so, and says to explore not Read', () => {
// The flat 600-char margin was neither the epilogue's size nor a bound on
// it, so a saturated response shipped with no pointer list and no
// reminders at all. Whatever else is traded away, the agent must be told
// an uncovered area exists and that another explore reaches it.
for (const [shape, probe] of Object.entries(probes) as [Shape, Probe][]) {
const withheld = probe.report.files.some(
(f) => f.render === null || (probe.bytes.get(f.path) ?? 0) === 0);
if (!withheld) continue;
expect(
/Not shown above|omitted for size|codegraph_explore/.test(probe.response),
`${shape} withheld files without saying where to look`,
).toBe(true);
}
});
it('never steers the agent to Read', () => {
for (const probe of all()) {
expect(/use (the )?Read|fall back to Read(?!ing those files)/i.test(probe.response)).toBe(false);
}
});
});
// ── The thing the invariant must NOT become ───────────────────────────────
describe('concentration survives', () => {
it('a precise symbol query still puts the most source in the named file', () => {
const mine = probes.precise.bytes.get(GIANT) ?? 0;
expect(mine).toBeGreaterThan(0);
for (const [p, n] of probes.precise.bytes) {
if (p === GIANT) continue;
expect(mine, `${GIANT} vs ${p}`).toBeGreaterThan(n);
}
});
it('is not an even split — the named file outspends its equal share', () => {
const rec = probes.precise.report.files.find((f) => f.path === GIANT)!;
const even = probes.precise.report.budget.maxOutputChars / admitted(probes.precise).length;
expect(rec.emittedChars).toBeGreaterThan(even);
});
});
});