Files
gstack/test/ship-budget-batch.test.ts
T
Garry Tanandcapy-ai[bot] dcd62c389d v1.91.325.0 fix(ship-budget): thresholds above the host call floor; children hand off instead of stopping; batches admit a prefix (GSTA-167) (#3620)
* fix(ship-budget): calibrate projections from the run's own meter, settle dispatch reservations when reviewers return (GST-101)

The /ship budget projected every step and dispatch from fixed constants
(7.43M per dispatch, 2.25M per default step) plus a fixed 4.48M reserve, so
real caps refused work that cost 3-20x less (GSTA-152 PR B, GSTA-2). Each
settled reservation is now measured on the next reading, and the run projects
with its own factor (max actual/prior of its last three measurements x 1.5),
the prior only before the first measurement. The default tail reserve scales
with the step factor; the stop overhead keeps its fit.

Reservations of reviewers admitted one at a time no longer pile up: check
--returned settles named dispatches, --finish settles the pass's dispatches of
that step, and a step change settles consumed dispatches. DRIFT and
BUDGET_DRIFT lines print projected against actual.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (GST-101)

Unseen charges (settle --unknown, a Claude-metered outside voice) are booked
at their reservation and never measured, so they cannot shrink the dispatch
factor. The handoff holds back the same calibrated reserve as check, so a
delegate verdict is one the handoff admits.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(ship-budget): handoff sub-cap subtracts unknown charges and the calibrated reserve

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: changelog (Unreleased) for GST-101 ship-budget calibration

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(ship-budget): give the GSTA-152 PR B replay an explicit 120s timeout

It spawns about 100 CLI processes; on windows-latest it ran past the
shard runner's 30s per-test timeout (windows-free-shard 5, run
40917695070), and on 2 cores it sat at bare bun test's 5s default.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ship-budget): thresholds above the host call floor; a child hands off instead of stopping; parallel batches admit a prefix (GSTA-167)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ship-budget): a child returns incomplete while a grandchild is unreconciled or an ADMIT remainder was never re-checked; join a child's meter only after its handoff succeeds (GSTA-167)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(troubleshooting): BUDGET_CAP_REACHED admits a fitting prefix of a reviewer batch (GSTA-167)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: changelog (Unreleased) for GSTA-167 handoff floor

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: move GSTA-167 changelog entry into [Unreleased]

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.325.0 stamp

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-30 01:01:20 +00:00

94 lines
5.6 KiB
TypeScript

/**
* GSTA-167 (gstack-evals #35, gate class): full-depth Step 9 launched 7 Opus
* readers in parallel at about 0.76M each, and the shared counter went 13.2M ->
* 18.5M before the parent's poll saw it. A dispatch batch now prices each reader
* at max(its projection, the run's measured mean for that role), admits the
* largest prefix that fits near the cap (ADMIT: k of n), and keeps the rest as
* remaining until a later check requests them; close flags readers never re-checked.
*/
import { afterEach, describe, expect, test } from 'bun:test';
import { loadCalibration, unitConstants } from '../lib/ship-budget';
import { budgetHarness, type Harness } from './helpers/ship-budget-harness';
const cal = loadCalibration();
const K = unitConstants(cal, 'capy-tokens');
const harnesses: Harness[] = [];
const fresh = () => { const h = budgetHarness(); harnesses.push(h); return h; };
afterEach(() => { while (harnesses.length) harnesses.pop()!.cleanup(); });
const READERS = ['specialist', 'specialist', 'specialist', 'specialist', 'specialist', 'red-team', 'adversarial-native'];
const AT_35 = 13_200_000;
/** Room for exactly three readers at the prior above the #35 reading, with 1M to spare. */
const CAP_35 = AT_35 + 3 * K.per_dispatch + K.stop_overhead + 1_000_000;
function start(h: Harness, cap: number) {
const r = h.sb(['start', '--record', h.record(), '--token-cap', String(cap), '--reading', '0', '--reserve', '0']);
expect(r.code, r.out + r.err).toBe(0);
return { run: h.field(r.out, 'BUDGET_RUN', 'id'), ledger: h.field(r.out, 'BUDGET_RUN', 'ledger') };
}
const batch = (h: Harness, run: string, roles: string[], reading: number, extra: string[] = []) =>
h.sb(['check', '--run', run, '--step', '9', '--next', 'dispatch:' + roles.join(','), '--reading', String(reading), ...extra]);
describe('parallel dispatch admission near the cap', () => {
test('#35 replay: a 7-reader batch near the cap admits the largest prefix and names the rest', () => {
const h = fresh();
const s = start(h, CAP_35);
const c = batch(h, s.run, READERS, AT_35);
expect(c.code, c.out + c.err).toBe(0);
expect(c.out).toMatch(/^ADMIT: 3 of 7 \(remaining=specialist,specialist,red-team,adversarial-native\)/m);
expect(h.field(c.out, 'BUDGET', 'verdict')).toBe('continue');
expect(h.field(c.out, 'RESERVATION', 'next')).toBe('dispatch:specialist,specialist,specialist');
expect(Number(h.field(c.out, 'RESERVATION', 'tokens'))).toBe(3 * K.per_dispatch);
const r = h.ledger(s.ledger).reservations.find((x: any) => x.kind === 'dispatch');
expect(r).toMatchObject({ roles: ['specialist', 'specialist', 'specialist'], remaining: ['specialist', 'specialist', 'red-team', 'adversarial-native'] });
});
test('a role\'s measured mean overrides a lower projection', () => {
const h = fresh();
const s = start(h, 200_000_000);
let id = h.field(batch(h, s.run, ['red-team'], 0).out, 'RESERVATION', 'id');
let reading = 3_000_000;
for (let i = 0; i < 4; i++, reading += 500_000) id = h.field(batch(h, s.run, ['specialist'], reading, ['--returned', id]).out, 'RESERVATION', 'id');
const c = batch(h, s.run, ['red-team', 'specialist'], reading, ['--returned', id]);
expect(c.code, c.out + c.err).toBe(0);
const f = (500_000 / K.per_dispatch) * cal.headroom;
const specialist = Math.ceil((K.per_dispatch * f) / cal.round_to) * cal.round_to;
expect(specialist).toBeLessThan(3_000_000);
expect(Number(h.field(c.out, 'BUDGET', 'projected'))).toBe(3_000_000 + specialist);
});
test('when not even the first reader fits, the batch stops as before', () => {
const h = fresh();
const s = start(h, CAP_35);
const c = batch(h, s.run, READERS, CAP_35 - K.stop_overhead - K.per_dispatch + 1);
expect(c.code).toBe(3);
expect(c.out).toContain('STOP: reason=budget_exhausted');
expect(c.out).not.toContain('ADMIT:');
expect(Number(h.field(c.out, 'BUDGET', 'projected'))).toBe(READERS.length * K.per_dispatch);
expect(h.ledger(s.ledger).reservations.filter((x: any) => x.kind === 'dispatch')).toHaveLength(0);
});
test('the remainder re-check admits after the first readers settle; close flags readers never re-checked', () => {
const h = fresh();
const s = start(h, CAP_35);
const first = h.field(batch(h, s.run, READERS, AT_35).out, 'RESERVATION', 'id');
expect(h.sb(['settle', '--run', s.run, '--reservation', first, '--reading', String(AT_35 + 3 * 760_000)]).code).toBe(0);
const rest = batch(h, s.run, READERS.slice(3), AT_35 + 3 * 760_000);
expect(rest.code, rest.out + rest.err).toBe(0);
expect(rest.out).not.toContain('ADMIT:');
expect(h.field(rest.out, 'RESERVATION', 'next')).toBe('dispatch:' + READERS.slice(3).join(','));
expect(h.ledger(s.ledger).reservations.find((x: any) => x.id === first).remaining).toEqual([]);
expect(h.sb(['close', '--run', s.run, '--reading', String(AT_35 + 4_000_000)]).code).toBe(0);
const d = start(h, CAP_35);
expect(batch(h, d.run, READERS, AT_35).out).toContain('ADMIT: 3 of 7');
const close = h.sb(['close', '--run', d.run, '--reading', String(AT_35 + 3_000_000)]);
expect(close.code).toBe(3);
expect(close.out).toMatch(/^DROPPED: step=9 reservation=\S+ roles=specialist,specialist,red-team,adversarial-native/m);
expect(close.out.trim().split('\n').at(-1)).toMatch(/^GSTACK_RESULT: skill=ship status=incomplete run=/);
const accepted = h.sb(['close', '--run', d.run, '--reading', String(AT_35 + 3_000_000), '--approved-by', 'Garry', '--words', 'three readers are enough']);
expect(accepted.code).toBe(0);
expect(accepted.out).toContain('ACCEPTED: 0 unreserved dispatches and 1 unfinished batches by Garry');
});
});