mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 06:24:41 +01:00
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>
This commit is contained in:
1 parent
f30c33f7d5
commit
00aba4e13c
3 files changed
+52
-8
No files matched your search
+7
-5
@@ -51,7 +51,7 @@ export const METER_CONTRACTS: Record<MeterKind, MeterContract> = {
|
||||
export interface Reservation {
|
||||
id: string; kind: 'step' | 'dispatch' | 'child'; step: string; next: string; roles?: string[]; tokens: number; at: string;
|
||||
state: 'open' | 'consumed' | 'settled' | 'released'; token?: string; started_at?: string; settled_at?: string;
|
||||
prior?: number; reading_at?: number | null; epoch?: number; actual?: number;
|
||||
prior?: number; reading_at?: number | null; epoch?: number; actual?: number; unseen?: boolean;
|
||||
}
|
||||
export interface Sample { id: string; kind: 'step' | 'dispatch'; epoch: number; prior: number; projected: number; actual: number }
|
||||
export interface Handoff {
|
||||
@@ -172,12 +172,14 @@ export function projector(l: Ledger, cal: Calibration = loadCalibration(l.calibr
|
||||
/**
|
||||
* Measure settled reservations against the current (fresh) reading: a step
|
||||
* from its reading to now; settled dispatches as one group from the earliest
|
||||
* of their readings, split by role. Returns one DRIFT line per measurement.
|
||||
* of their readings, split by role. A charge the meter cannot see (settled
|
||||
* --unknown, or a Claude-metered outside voice) is never measured. Returns one
|
||||
* DRIFT line per measurement.
|
||||
*/
|
||||
export function measureSettled(l: Ledger): string[] {
|
||||
const last = l.meter.last;
|
||||
if (last === null) return [];
|
||||
const pending = l.reservations.filter(r => r.state === 'settled' && r.kind !== 'child' && r.actual === undefined
|
||||
const pending = l.reservations.filter(r => r.state === 'settled' && r.kind !== 'child' && r.actual === undefined && !r.unseen
|
||||
&& typeof r.reading_at === 'number' && r.epoch === l.meter.epoch && last >= r.reading_at);
|
||||
const dispatches = pending.filter(r => r.kind === 'dispatch');
|
||||
const from = Math.min(...dispatches.map(r => r.reading_at!));
|
||||
@@ -277,10 +279,10 @@ export function settleIn(l: Ledger, r: Reservation, opts: { unknown?: boolean; c
|
||||
if (r.state === 'settled' || r.state === 'released') return;
|
||||
r.state = 'settled';
|
||||
r.settled_at = new Date().toISOString();
|
||||
if (opts.unknown) { l.unknown += r.tokens; return; }
|
||||
if (opts.unknown) { l.unknown += r.tokens; r.unseen = true; return; }
|
||||
if (l.meter.contract.meter === 'estimate') { l.estimate_used += r.tokens; return; }
|
||||
const unseen = l.meter.contract.meter === 'claude' ? (r.roles ?? []).filter(x => x === 'outside-voice').length : 0;
|
||||
if (unseen && r.roles?.length) l.unknown += Math.round((r.tokens * unseen) / r.roles.length);
|
||||
if (unseen && r.roles?.length) { l.unknown += Math.round((r.tokens * unseen) / r.roles.length); r.unseen = true; }
|
||||
}
|
||||
|
||||
/** Validate and store one meter reading against the run's contract. */
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ import { wtreeFingerprint } from './script-invocation';
|
||||
import { resultLine } from './result-codes';
|
||||
import {
|
||||
SHIP_STEPS, budgetError, budgetLine, ledgerFileFor, loadCalibration, mintRunId, mutateLedger, parseStep, pointMarker,
|
||||
readLedger, reserveIn, runDirFor, segmentEstimate, tally, usageError, writeLedger, type Handoff, type Ledger,
|
||||
projector, readLedger, reserveIn, runDirFor, segmentEstimate, tally, usageError, writeLedger, type Handoff, type Ledger,
|
||||
} from './ship-budget';
|
||||
import { loadMeterState, saveMeterState } from './claude-usage';
|
||||
import { parseArgs, readMeter, stopLines, str, tokensOpt, writeReturnFile, type Ctx, type Outcome } from './ship-budget-cli';
|
||||
@@ -92,7 +92,7 @@ export function cmdHandoff(argv: string[], ctx: Ctx): Outcome {
|
||||
const call = readMeter(l, o, true);
|
||||
const cal = loadCalibration(l.calibration);
|
||||
const t = tally(l);
|
||||
const room = l.cap === null ? Infinity : l.cap - t.used - t.reserved - t.unknown - l.tail - l.stop_overhead;
|
||||
const room = l.cap === null ? Infinity : l.cap - t.used - t.reserved - t.unknown - projector(l, cal).reserve;
|
||||
const subCap = tokensOpt(o, 'sub-cap') ?? Math.min(room, segmentEstimate(cal, l.meter.contract.unit, from, to));
|
||||
if (subCap <= 0 || subCap > room) return { stdout: stopLines(l, { call, next: from, step: from, projected: subCap, reason: 'budget_exhausted' }), exit: 3 };
|
||||
const pins = pinsNow(ctx.cwd, base, ctx.env);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* ship-budget-projection-GSTA-152-prb.md in the GStack Minder drive).
|
||||
*/
|
||||
import { afterEach, describe, expect, test } from 'bun:test';
|
||||
import { loadCalibration, SHIP_STEPS, unitConstants } from '../lib/ship-budget';
|
||||
import { loadCalibration, measureSettled, settleIn, SHIP_STEPS, unitConstants } from '../lib/ship-budget';
|
||||
import { budgetHarness, type Harness } from './helpers/ship-budget-harness';
|
||||
|
||||
const cal = loadCalibration();
|
||||
@@ -197,3 +197,45 @@ describe('dispatch reservations settle when the reviewer returns (GST-101)', ()
|
||||
expect(close.out).toMatch(/^BUDGET_DRIFT: steps=2 step_projected=\d+ step_actual=700000 dispatches=0 dispatch_projected=0 dispatch_actual=0$/m);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the gate fixes from /ship review (GST-101)', () => {
|
||||
test('a dispatch settled --unknown is never measured, so the dispatch factor stays at the prior', () => {
|
||||
const h = fresh();
|
||||
const s = started(h, '40M');
|
||||
const id = h.field(h.sb(['check', '--run', s.run, '--step', '11', '--next', 'dispatch:outside-voice', '--reading', '0']).out, 'RESERVATION', 'id');
|
||||
expect(h.reviewLog(['--start', 'adversarial-review', '--reservation', id]).code).toBe(0);
|
||||
const settled = h.sb(['settle', '--run', s.run, '--reservation', id, '--unknown', '--reading', '100000']);
|
||||
expect(settled.code, settled.out + settled.err).toBe(0);
|
||||
expect(settled.out).not.toContain('DRIFT:');
|
||||
const next = h.sb(['check', '--run', s.run, '--step', '11', '--next', 'dispatch:outside-voice', '--reading', '200000']);
|
||||
expect(h.field(next.out, 'BUDGET', 'calibration')).toEndWith('dispatch:prior');
|
||||
expect(Number(h.field(next.out, 'BUDGET', 'projected'))).toBe(K.per_dispatch);
|
||||
expect(Number(h.field(next.out, 'BUDGET', 'unknown'))).toBe(K.per_dispatch);
|
||||
});
|
||||
|
||||
test('a Claude-metered outside voice is booked unknown and never measured', () => {
|
||||
const r = { id: 'r1', kind: 'dispatch', step: '11', next: 'dispatch:outside-voice', roles: ['outside-voice'], tokens: K.per_dispatch, at: '', state: 'consumed', reading_at: 0, epoch: 1 };
|
||||
const l: any = { meter: { contract: { meter: 'claude' }, epoch: 1, last: 100_000 }, reservations: [r], unknown: 0, samples: [] };
|
||||
settleIn(l, r as any);
|
||||
expect(l.unknown).toBe(K.per_dispatch);
|
||||
expect(measureSettled(l)).toEqual([]);
|
||||
expect(l.samples).toEqual([]);
|
||||
});
|
||||
|
||||
test('a calibrated delegate verdict is one the handoff admits: both hold back the same reserve', () => {
|
||||
const h = fresh();
|
||||
const cap = 12_000_000;
|
||||
const s = started(h, '12M');
|
||||
const sb = (args: string[]) => { const c = h.sb(['check', '--run', s.run, ...args]); expect(c.code, c.out + c.err).toBe(0); return c; };
|
||||
for (const [i, step] of ['1', '2', '3'].entries()) sb(['--step', step, '--next', String(i + 2), '--reading', String(300_000 * (i + 1)), '--prior-reading', String(300_000 * i + 200_000)]);
|
||||
const d = h.field(sb(['--step', '4', '--next', 'dispatch:specialist', '--reading', '1200000', '--prior-reading', '1100000']).out, 'RESERVATION', 'id');
|
||||
expect(h.reviewLog(['--start', 'review', '--reservation', d]).code).toBe(0);
|
||||
const c = sb(['--step', '4', '--next', '12', '--reading', '8000000', '--returned', d]);
|
||||
expect(h.field(c.out, 'BUDGET', 'verdict')).toBe('delegate');
|
||||
const tail = Number(h.field(c.out, 'BUDGET', 'tail'));
|
||||
expect(cap - 8_000_000).toBeLessThan(2_000_000 + K.stop_overhead);
|
||||
const ho = h.sb(['handoff', '--run', s.run, '--from', '12', '--to', '22', '--base', 'origin/main', '--reading', '8000000']);
|
||||
expect(ho.code, ho.out + ho.err).toBe(0);
|
||||
expect(Number(ho.out.match(/^HANDOFF: \S+ sha256=\S+ sub_cap=(\d+)$/m)![1])).toBe(cap - 8_000_000 - tail);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user