feat(ship): variant E orchestration: one program per review pass and Step 14.5 audit, the review child starts at Step 9, a metered dispatch:docs role, and the driver's record headings (GSTA-167)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-30 15:28:39 +00:00
1 parent 8a1e5bb675
commit 80e5625cfc
18 files changed
+267 -123

No files matched your search

+68 -34
View File
@@ -61,7 +61,7 @@ describe('per-call thresholds above the host floor', () => {
test('at a segment boundary, 150k above the floor delegates and less continues; an unknown charge still delegates', () => {
const h = fresh();
const s = start(h, ['--token-cap', '80M', '--reading', '1000000']);
const low = check(h, s.run, '6', '7', ['--reading', '1230000', '--prior-reading', '1000000']);
const low = check(h, s.run, '8', '9', ['--reading', '1230000', '--prior-reading', '1000000']);
expect(low.code, low.out + low.err).toBe(0);
expect(h.field(low.out, 'BUDGET', 'above_floor')).toBe('80000');
expect(h.field(low.out, 'BUDGET', 'verdict')).toBe('continue');
@@ -76,7 +76,7 @@ describe('per-call thresholds above the host floor', () => {
test('--delegate measured (ship/DRIVER.md): an unknown charge continues at a segment boundary; 150k above the floor still delegates', () => {
const h = fresh();
const s = start(h, ['--token-cap', '80M', '--reading', '1000000', '--delegate', 'measured']);
const unknown = check(h, s.run, '6', '7', ['--reading', '1100000']);
const unknown = check(h, s.run, '8', '9', ['--reading', '1100000']);
expect(unknown.code, unknown.out + unknown.err).toBe(0);
expect(h.field(unknown.out, 'BUDGET', 'above_floor')).toBe('unknown');
expect(h.field(unknown.out, 'BUDGET', 'verdict')).toBe('continue');
@@ -105,7 +105,7 @@ describe('a child never stops on the context rule', () => {
test('#36 replay: a child at its first boundary with zero completed steps continues with one CONTEXT_HIGH note', () => {
const h = fresh();
const s = start(h, ['--token-cap', '400M', '--reading', '98000']);
for (const [from, to, step, at] of [['7', '11.5', '6', 1_000_000], ['12', '22', '11.5', 2_000_000]] as const) {
for (const [from, to, step, at] of [['9', '11.5', '8', 1_000_000], ['12', '22', '11.5', 2_000_000]] as const) {
const c = handOff(h, s.run, from, to, at);
const r = check(h, c.run, step, from, ['--reading', String(at + 200_000)]);
expect(r.code, r.out + r.err).toBe(0);
@@ -126,18 +126,18 @@ describe('a child never stops on the context rule', () => {
const child = handOff(h, s.run, '1', '22', 200_000);
expect(h.ledger(child.ledger).depth).toBe(1);
expect(check(h, child.run, '1', '2', ['--reading', '600000']).code).toBe(0);
const d = check(h, child.run, '6', '7', ['--reading', '1500000']);
const d = check(h, child.run, '8', '9', ['--reading', '1500000']);
expect(d.code, d.out + d.err).toBe(0);
expect(h.field(d.out, 'BUDGET', 'verdict')).toBe('delegate');
expect(d.out).toContain(`gstack-ship-budget handoff --run ${child.run} --from 7 --to 22`);
expect(d.out).toContain(`gstack-ship-budget handoff --run ${child.run} --from 9 --to 22`);
expect(d.out).not.toContain('RESERVATION');
const room = ['admission_cap', 'used', 'reserved', 'unknown', 'tail'].map(k => Number(h.field(d.out, 'BUDGET', k)));
const grand = handOff(h, child.run, '7', '22', 1_500_000);
const grand = handOff(h, child.run, '9', '22', 1_500_000);
expect(grand.subCap).toBe(room[0]! - room[1]! - room[2]! - room[3]! - room[4]!);
expect(h.field(grand.v.out, 'BUDGET', 'admission_cap')).toBe(String(grand.subCap));
const g = h.ledger(grand.ledger);
expect(g).toMatchObject({ role: 'child', depth: 2, parent: { run: child.run, ledger: child.ledger, from: '7', to: '22' } });
expect(h.ledger(child.ledger).handoffs[0]).toMatchObject({ depth: 2, from: '7', to: '22' });
expect(g).toMatchObject({ role: 'child', depth: 2, parent: { run: child.run, ledger: child.ledger, from: '9', to: '22' } });
expect(h.ledger(child.ledger).handoffs[0]).toMatchObject({ depth: 2, from: '9', to: '22' });
const text = fs.readFileSync(grand.brief, 'utf8');
expect(text).toContain('"depth": 2');
expect(text).not.toContain('a delegate rule stops you');
@@ -157,11 +157,11 @@ describe('a child never stops on the context rule', () => {
chain.push(c.ledger);
}
expect(chain.map(f => h.ledger(f).depth)).toEqual([1, 2, 3]);
const r = check(h, run, '6', '7', ['--reading', String((reading += 100_000))]);
const r = check(h, run, '8', '9', ['--reading', String((reading += 100_000))]);
expect(r.code, r.out + r.err).toBe(0);
expect(h.field(r.out, 'BUDGET', 'verdict')).toBe('continue');
expect(r.out).toMatch(/^CONTEXT_HIGH: .*chain depth 3 is the maximum/m);
const refused = h.sb(['handoff', '--run', run, '--from', '7', '--to', '22', '--base', 'origin/main', '--reading', String(reading)]);
const refused = h.sb(['handoff', '--run', run, '--from', '9', '--to', '22', '--base', 'origin/main', '--reading', String(reading)]);
expect(refused.code).toBe(2);
expect(refused.err).toContain('chain depth 3');
});
@@ -174,12 +174,12 @@ describe('a child never stops on the context rule', () => {
const returnFile = ho.out.match(/^RETURN_FILE: (\S+)$/m)![1]!;
const child = h.field(h.sb(['verify-handoff', '--in', brief, '--reading', '110000']).out, 'BUDGET_RUN', 'id');
expect(check(h, child, '1', '2', ['--reading', '120000']).code).toBe(0);
const stop = check(h, child, '6', '7', ['--reading', '2120000']);
const stop = check(h, child, '8', '9', ['--reading', '2120000']);
expect(stop.code).toBe(3);
expect(stop.out).toContain('STOP: reason=budget_exhausted');
expect(stop.out).toContain('(BUDGET_CAP_REACHED)');
expect(stop.out).not.toContain('context_threshold');
expect(fs.readFileSync(returnFile, 'utf8')).toMatch(/^RETURN: handoff=[0-9a-f]{64} status=incomplete steps=1-22 next=7/);
expect(fs.readFileSync(returnFile, 'utf8')).toMatch(/^RETURN: handoff=[0-9a-f]{64} status=incomplete steps=1-22 next=9/);
});
});
@@ -189,9 +189,9 @@ describe('a chained return is charged once', () => {
const s = start(h, ['--token-cap', '200M', '--reading', '1000000']);
const child = handOff(h, s.run, '1', '22', 1_100_000, ['--reading', '1200000']);
expect(check(h, child.run, '1', '2', ['--reading', '1500000']).code).toBe(0);
expect(h.field(check(h, child.run, '6', '7', ['--reading', '2000000']).out, 'BUDGET', 'verdict')).toBe('delegate');
const grand = handOff(h, child.run, '7', '22', 2_100_000, ['--reading', '2200000']);
expect(check(h, grand.run, '7', '8', ['--reading', '3000000']).code).toBe(0);
expect(h.field(check(h, child.run, '8', '9', ['--reading', '2000000']).out, 'BUDGET', 'verdict')).toBe('delegate');
const grand = handOff(h, child.run, '9', '22', 2_100_000, ['--reading', '2200000']);
expect(check(h, grand.run, '9', '10', ['--reading', '3000000']).code).toBe(0);
expect(h.sb(['return', '--run', grand.run, '--to', '22', '--changed', 'Reviews', '--reading', '6000000']).code).toBe(0);
const mid = h.sb(['reconcile', '--run', child.run, '--return', grand.returnFile, '--reading', '6100000']);
expect(mid.code, mid.out + mid.err).toBe(0);
@@ -239,13 +239,13 @@ describe('a chained return is charged once', () => {
const s = start(h, ['--token-cap', '400M', '--meter', 'estimate']);
const child = handOff(h, s.run, '1', '22', null);
expect(check(h, child.run, '1', '2').code).toBe(0);
expect(h.field(check(h, child.run, '6', '7').out, 'BUDGET', 'verdict')).toBe('delegate');
const grand = handOff(h, child.run, '7', '22', null);
expect(check(h, grand.run, '7', '8').code).toBe(0);
expect(check(h, grand.run, '8', '9').code).toBe(0);
expect(h.field(check(h, child.run, '8', '9').out, 'BUDGET', 'verdict')).toBe('delegate');
const grand = handOff(h, child.run, '9', '22', null);
expect(check(h, grand.run, '9', '10').code).toBe(0);
expect(check(h, grand.run, '10', '11').code).toBe(0);
const gret = h.sb(['return', '--run', grand.run, '--to', '22', '--changed', 'Reviews']);
const grandUsed = Number(h.field(gret.out, 'BUDGET', 'used'));
expect(grandUsed).toBe(stepProjection(cal, 'estimate-tokens', '8'));
expect(grandUsed).toBe(stepProjection(cal, 'estimate-tokens', '10'));
const childOwn = stepProjection(cal, 'estimate-tokens', '2');
for (let i = 0; i < 2; i++) expect(h.sb(['reconcile', '--run', child.run, '--return', grand.returnFile]).code).toBe(0);
const cret = h.sb(['return', '--run', child.run, '--to', '22', '--changed', 'Reviews']);
@@ -264,8 +264,8 @@ describe('a chained return is charged once', () => {
const s = start(h, ['--token-cap', '400M', '--session', FIXTURE.sessions.parent]);
const child = handOff(h, s.run, '1', '22', null, ['--session', 'child-session-1']);
expect(check(h, child.run, '1', '2').code).toBe(0);
expect(h.field(check(h, child.run, '6', '7').out, 'BUDGET', 'verdict')).toBe('delegate');
const ho = h.sb(['handoff', '--run', child.run, '--from', '7', '--to', '22', '--base', 'origin/main']);
expect(h.field(check(h, child.run, '8', '9').out, 'BUDGET', 'verdict')).toBe('delegate');
const ho = h.sb(['handoff', '--run', child.run, '--from', '9', '--to', '22', '--base', 'origin/main']);
const brief = ho.out.match(/^HANDOFF: (\S+)/m)![1]!;
const fake = path.join(h.root, 'fake-claude');
fs.writeFileSync(fake, '#!/bin/sh\nexit 0\n', { mode: 0o755 });
@@ -282,7 +282,7 @@ describe('a reconcile that stops the parent stops every check (replay 2, #36)',
test('after an incomplete child, check refuses every step until resume; a regenerated child that returns complete also clears it', () => {
const h = fresh();
const s = start(h, ['--token-cap', '200M', '--reading', '0', '--reserve', '0']);
const child = handOff(h, s.run, '7', '11.5', 1_000_000);
const child = handOff(h, s.run, '9', '11.5', 1_000_000);
const ret = h.sb(['return', '--run', child.run, '--to', '9', '--status', 'incomplete', '--reading', '5000000']);
expect(ret.code, ret.out + ret.err).toBe(0);
const rec = h.sb(['reconcile', '--run', s.run, '--return', child.returnFile, '--reading', '5100000']);
@@ -292,19 +292,19 @@ describe('a reconcile that stops the parent stops every check (replay 2, #36)',
for (const next of ['9', '7', 'dispatch:red-team']) {
const c = check(h, s.run, '8', next, ['--reading', '5200000']);
expect(c.code, next).toBe(3);
expect(c.out).toMatch(/^STOP: reason=child_incomplete at Step 7; resume first: /m);
expect(c.out).toMatch(/^STOP: reason=child_incomplete at Step 9; resume first: /m);
expect(c.out).not.toContain('RESERVATION:');
}
const resumed = h.sb(['resume', '--run', s.run, '--reading', '5300000']);
expect(resumed.code, resumed.out + resumed.err).toBe(0);
expect(resumed.out).toContain('RESUMED: from=Step 7 (child_incomplete)');
expect(resumed.out).toContain('RESUMED: from=Step 9 (child_incomplete)');
expect(check(h, s.run, '8', '9', ['--reading', '5400000']).code).toBe(0);
const t = start(h, ['--token-cap', '200M', '--reading', '0', '--reserve', '0']);
const first = handOff(h, t.run, '7', '11.5', 1_000_000);
const first = handOff(h, t.run, '9', '11.5', 1_000_000);
h.sb(['return', '--run', first.run, '--to', '9', '--status', 'incomplete', '--reading', '2000000']);
expect(h.sb(['reconcile', '--run', t.run, '--return', first.returnFile, '--reading', '2000000']).code).toBe(3);
const second = handOff(h, t.run, '7', '11.5', 2_100_000);
const second = handOff(h, t.run, '9', '11.5', 2_100_000);
expect(h.sb(['return', '--run', second.run, '--to', '11.5', '--reading', '3000000']).code).toBe(0);
const done = h.sb(['reconcile', '--run', t.run, '--return', second.returnFile, '--reading', '3100000']);
expect(done.code, done.out).toBe(0);
@@ -318,12 +318,12 @@ describe('a child returns complete only when its own work is finished', () => {
const s = start(h, ['--token-cap', '200M', '--reading', '98000']);
const child = handOff(h, s.run, '1', '22', 200_000);
expect(check(h, child.run, '1', '2', ['--reading', '600000']).code).toBe(0);
expect(h.field(check(h, child.run, '6', '7', ['--reading', '1500000']).out, 'BUDGET', 'verdict')).toBe('delegate');
const grand = handOff(h, child.run, '7', '22', 1_500_000);
expect(h.field(check(h, child.run, '8', '9', ['--reading', '1500000']).out, 'BUDGET', 'verdict')).toBe('delegate');
const grand = handOff(h, child.run, '9', '22', 1_500_000);
const ret = h.sb(['return', '--run', child.run, '--to', '22', '--changed', 'Reviews', '--reading', '1600000']);
expect(ret.code, ret.out + ret.err).toBe(0);
expect(ret.out).toMatch(/^RETURN: handoff=[0-9a-f]{64} status=incomplete steps=1-22/);
expect(ret.out).toContain(`UNRECONCILED: handoff=${h.ledger(child.ledger).handoffs[0].sha} steps=7-22`);
expect(ret.out).toContain(`UNRECONCILED: handoff=${h.ledger(child.ledger).handoffs[0].sha} steps=9-22`);
expect(ret.out).toContain(grand.returnFile);
const top = h.sb(['reconcile', '--run', s.run, '--return', child.returnFile, '--reading', '1700000']);
expect(top.code).toBe(3);
@@ -334,14 +334,14 @@ describe('a child returns complete only when its own work is finished', () => {
const h = fresh();
const s = start(h, ['--token-cap', '900M', '--reading', '0', '--reserve', '0']);
const subCap = K.stop_overhead + Math.floor(1.5 * K.per_dispatch);
const ho = h.sb(['handoff', '--run', s.run, '--from', '7', '--to', '11.5', '--base', 'origin/main', '--reading', '100000', '--sub-cap', String(subCap)]);
const ho = h.sb(['handoff', '--run', s.run, '--from', '9', '--to', '11.5', '--base', 'origin/main', '--reading', '100000', '--sub-cap', String(subCap)]);
expect(ho.code, ho.out + ho.err).toBe(0);
const child = h.field(h.sb(['verify-handoff', '--in', ho.out.match(/^HANDOFF: (\S+)/m)![1]!, '--reading', '100000']).out, 'BUDGET_RUN', 'id');
const b = check(h, child, '9', 'dispatch:specialist,specialist,red-team', ['--reading', '100000']);
expect(b.code, b.out + b.err).toBe(0);
expect(b.out).toMatch(/^ADMIT: 1 of 3 \(remaining=specialist,red-team\)/m);
const ret = h.sb(['return', '--run', child, '--to', '11.5', '--reading', '100000']);
expect(ret.out).toMatch(/^RETURN: handoff=[0-9a-f]{64} status=incomplete steps=7-11.5/);
expect(ret.out).toMatch(/^RETURN: handoff=[0-9a-f]{64} status=incomplete steps=9-11.5/);
expect(ret.out).toMatch(/^DROPPED: step=9 reservation=\S+ roles=specialist,red-team/m);
expect(ret.out.trim().split('\n').at(-1)).toMatch(/^GSTACK_RESULT: skill=ship status=incomplete run=/);
});
@@ -349,12 +349,46 @@ describe('a child returns complete only when its own work is finished', () => {
test("a grandchild handoff outside the child's own Steps refuses and reserves nothing", () => {
const h = fresh();
const s = start(h, ['--token-cap', '200M', '--reading', '98000']);
const child = handOff(h, s.run, '7', '11.5', 200_000);
const child = handOff(h, s.run, '9', '11.5', 200_000);
for (const [from, to] of [['7', '22'], ['6', '11.5']]) {
const r = h.sb(['handoff', '--run', child.run, '--from', from!, '--to', to!, '--base', 'origin/main', '--reading', '300000']);
expect(r.code).toBe(2);
expect(r.err).toContain(`--from ${from} --to ${to} is outside this child's Steps 7-11.5`);
expect(r.err).toContain(`--from ${from} --to ${to} is outside this child's Steps 9-11.5`);
}
expect(h.ledger(child.ledger).reservations.filter((r: any) => r.kind === 'child')).toHaveLength(0);
});
});
describe('variant E: the review segment and the docs reader (GSTA-167 replay 5)', () => {
// Value: protects=Steps 7-8 stay with the parent and the fresh child starts at Step 9 with Steps 9-11.5, so the child reads only the review sections; fails_when=the Step 7 boundary delegates again or the Step 9 boundary hands off any other segment;
// why_new=the boundary tests above take whichever segment the code names; seam=none; no_claim=what a child costs (handoffs/GSTA-167/replay5-anatomy.md estimates ~3M on #34)
test('Step 7 is no boundary; Step 9 delegates Steps 9-11.5 and Step 12 delegates Steps 12-22', () => {
const h = fresh();
const high = (step: string, next: string, at: number) => check(h, start(h, ['--token-cap', '400M', '--reading', '1000000']).run, step, next, ['--reading', String(at + 400_000), '--prior-reading', String(at)]);
const seven = high('6', '7', 1_100_000);
expect(h.field(seven.out, 'BUDGET', 'verdict')).toBe('continue');
expect(seven.out).not.toContain('DELEGATE:');
const nine = high('8', '9', 1_100_000);
expect(h.field(nine.out, 'BUDGET', 'verdict')).toBe('delegate');
expect(nine.out).toMatch(/^DELEGATE: Steps 9-11\.5 go to a fresh child: gstack-ship-budget handoff --run \S+ --from 9 --to 11\.5 /m);
expect(high('11.5', '12', 1_100_000).out).toMatch(/^DELEGATE: Steps 12-22 go to a fresh child/m);
});
// Value: protects=the Step 14.5 documentation audit is a metered dispatch role: priced per reader, reserved, and measured from its own reading pair, so its reader stays out of Step 14.5's step sample; fails_when=dispatch:docs is refused, priced other than a reader, or settles UNMEASURED with a valid pair, or the step sample swallows the audit (#34 replay 5: projected 1.83M, actual 7.64M, calibration=step:5.092);
// why_new=no role covered the docs audit; seam=none; no_claim=the docs reader's real size
test('dispatch:docs is reserved at the reader price, settles with a DRIFT line, and Step 14.5 measures only the parent\'s own calls', () => {
const h = fresh();
const s = start(h, ['--token-cap', '400M', '--reading', '1000000']);
expect(check(h, s.run, '14', '14.5', ['--reading', '1100000']).code).toBe(0);
const c = check(h, s.run, '14.5', 'dispatch:docs', ['--reading', '1200000']);
expect(c.code, c.out + c.err).toBe(0);
expect(h.field(c.out, 'BUDGET', 'projected')).toBe(String(K.per_dispatch));
const id = h.field(c.out, 'RESERVATION', 'id');
const settled = h.sb(['settle', '--run', s.run, '--reservation', id, '--dispatched-at', '1200000', '--reading', '3200000', '--readers', '1']);
expect(settled.code, settled.out + settled.err).toBe(0);
expect(settled.out).toMatch(new RegExp(`^DRIFT: id=${id} kind=dispatch next=dispatch:docs projected=${K.per_dispatch} actual=2000000 `, 'm'));
const step = check(h, s.run, '14.5', '15', ['--reading', '3500000']);
expect(step.out).toMatch(/^DRIFT: id=\S+ kind=step next=14\.5 projected=\d+ actual=400000 /m);
expect(h.sb(['check', '--run', s.run, '--step', '15', '--next', 'dispatch:doc', '--reading', '3600000']).err).toContain('valid: specialist, red-team, adversarial-native, outside-voice, fix-check, docs');
});
});
+1 -1
View File
@@ -49,7 +49,7 @@ describe('the Capy meter is the agent\'s own jam counter', () => {
const h = fresh();
const s = h.sb(['start', '--record', h.record(), '--token-cap', '200M', '--reading', '1000000']);
const run = h.field(s.out, 'BUDGET_RUN', 'id');
const ho = h.sb(['handoff', '--run', run, '--from', '7', '--to', '11.5', '--base', 'origin/main', '--reading', '8000000']);
const ho = h.sb(['handoff', '--run', run, '--from', '9', '--to', '11.5', '--base', 'origin/main', '--reading', '8000000']);
expect(ho.code, ho.out + ho.err).toBe(0);
const brief = ho.out.match(/^HANDOFF: (\S+) /m)![1]!;
const returnFile = ho.out.match(/^RETURN_FILE: (\S+)$/m)![1]!;
+14 -12
View File
@@ -88,7 +88,7 @@ describe('a dispatch is measured to its return, never across the parent\'s own w
test('#34 replay: a Red Team settled by the step change after the child\'s review and QA is UNMEASURED, so the voices keep the per-reader factor', () => {
const h = fresh();
const s = start(h, 200_000_000);
check(h, s.run, '8', '9', 3_500_000);
check(h, s.run, '8', '9', 3_500_000, ['--prior-reading', '3400000']);
const specialists = h.field(check(h, s.run, '9', 'dispatch:' + SIX.join(','), 4_000_000).out, 'RESERVATION', 'id');
expect(h.reviewLog(['--start', 'review', '--reservation', specialists]).code).toBe(0);
let reading = 4_000_000 + 6 * READER;
@@ -109,7 +109,7 @@ describe('a dispatch is measured to its return, never across the parent\'s own w
test('settled with a reading at its return, the Red Team measures only its own interval; the parent\'s QA after it lands in Step 9\'s sample', () => {
const h = fresh();
const s = start(h, 200_000_000);
check(h, s.run, '8', '9', 3_500_000);
check(h, s.run, '8', '9', 3_500_000, ['--prior-reading', '3400000']);
const red = h.field(check(h, s.run, '9', 'dispatch:red-team', 4_000_000).out, 'RESERVATION', 'id');
expect(h.reviewLog(['--start', 'review', '--reservation', red]).code).toBe(0);
const settled = ok(h, ['settle', '--run', s.run, '--reservation', red, '--dispatched-at', '4000000', '--reading', String(4_000_000 + READER)]);
@@ -231,7 +231,7 @@ describe('the per-call charge takes a prior reading from the previous call only
test('#34 replay: a child passing the parent\'s handoff reading as its prior gets call=unknown, not 1068386', () => {
const h = fresh();
const s = start(h, 400_000_000, 2_110_477);
const ho = ok(h, ['handoff', '--run', s.run, '--from', '7', '--to', '11.5', '--base', 'origin/main', '--reading', '8094129']);
const ho = ok(h, ['handoff', '--run', s.run, '--from', '9', '--to', '11.5', '--base', 'origin/main', '--reading', '8094129']);
const brief = ho.out.match(/^HANDOFF: (\S+) /m)![1]!;
const v = ok(h, ['verify-handoff', '--in', brief, '--reading', '8800000']);
const child = h.field(v.out, 'BUDGET_RUN', 'id');
@@ -253,22 +253,24 @@ describe('the budget section states both contracts', () => {
// why_new=the template test above never read the Capy skill; seam=none; no_claim=how often an agent obeys the prose
test('the Capy skill reads the meter inside the budget command\'s program and names no meter-only call', () => {
const capy = fs.readFileSync(path.join(import.meta.dir, '..', 'capy', 'skill', 'gstack', 'SKILL.md'), 'utf8');
const caps = capy.slice(capy.indexOf('Token caps on /ship'), capy.indexOf('/ship reviewer batches on Capy'));
const caps = capy.slice(capy.indexOf('Token caps on /ship'), capy.indexOf('/ship reviewers on Capy'));
expect(caps.length).toBeGreaterThan(0);
expect(caps).not.toContain('returning only that number');
expect(caps).toMatch(/inside the same (?:execute )?program as the\s+`gstack-ship-budget` command/);
for (const text of [capy, tmpl]) expect(text).not.toMatch(/read it again in your next call|meter-only call(?!s? (?:is|are) never)/);
});
// Value: protects=each reviewer batch is one execute program in a fixed order, so the readers' reading pair brackets only them and no start or poll costs its own parent call, and each reviewer's brief is copied from its section; fails_when=the order breaks (a start after the readers are created, a settle before the poll) or the Capy paragraph stops copying the section's brief;
// why_new=no test reads the batch order; seam=none; no_claim=the model's adherence
test('a reviewer batch is one execute program: check --step --next, review-log --start, BEFORE, create, poll, AFTER, settle', () => {
// Value: protects=each review pass (and Step 11, and the Step 14.5 audit) is one execute program in a fixed order, so the readers' reading pair brackets only them and no plan, start, poll or later batch costs its own parent call, and each reviewer's brief is copied from its section; fails_when=the order breaks (a start after the readers are created, a settle before the poll, the plan after the dispatch) or the Capy paragraph goes back to one program per batch or stops copying the section's brief;
// why_new=no test reads the pass order; seam=none; no_claim=the model's adherence (GSTA-167 replay 5: arm D walked Steps 9-9.4 in 27 calls)
test('a review pass is one execute program: plan, check --next dispatch, review-log --start, BEFORE, create, poll, AFTER, settle, collect', () => {
const capy = fs.readFileSync(path.join(import.meta.dir, '..', 'capy', 'skill', 'gstack', 'SKILL.md'), 'utf8');
const pass = capy.slice(capy.indexOf('/ship reviewer batches on Capy'), capy.indexOf('## Plan, approve, ship'));
expect(pass).toMatch(/one execute program per batch/);
const pass = capy.slice(capy.indexOf('/ship reviewers on Capy'), capy.indexOf('## Plan, approve, ship'));
expect(pass).toMatch(/one execute program per review pass, per Step 11 and for the Step 14\.5 audit/);
expect(pass).not.toMatch(/one execute program per batch/);
expect(pass).toContain('its brief copied verbatim from the section that dispatches it');
const order = ['check --run <id> --step <n> --next dispatch:<roles>', 'gstack-review-log --start', 'tools.subagent.create', 'tools.subagent.get', 'AFTER', 'gstack-ship-budget settle'];
const block = tmpl.slice(tmpl.indexOf('**One program per dispatch batch.**'));
const tmplOrder = ['check --step <n> --next dispatch:<roles>', 'gstack-review-log --start', 'BEFORE', 'AFTER', 'gstack-ship-budget settle'];
expect(pass).toContain('never walks the dispatch steps call by call');
const order = ['gstack-review-plan --base <base>', 'check --run <id> --step <n> --next dispatch:<roles>', 'gstack-review-log --start', 'tools.subagent.create', 'tools.subagent.get', 'AFTER', 'gstack-ship-budget settle', 'return only each reader\'s final output'];
const block = tmpl.slice(tmpl.indexOf('**One program per review pass.**'));
const tmplOrder = ['gstack-review-plan', 'check --step <n> --next dispatch:<roles>', 'gstack-review-log --start', 'BEFORE', 'AFTER', 'gstack-ship-budget settle', 'the collect'];
for (const [text, want] of [[pass, order], [block, tmplOrder]] as const) {
const at = want.map(o => text.indexOf(o));
expect(at.every(i => i >= 0), want.filter((_, k) => at[k]! < 0).join(',')).toBe(true);
+2 -2
View File
@@ -226,7 +226,7 @@ describe('gstack-review-log --start under a token cap', () => {
test('close counts starts reserved in its handoff children; an unreserved row in the child window is still flagged', () => {
const h = fresh();
const root = capped(h, '200M');
const ho = h.sb(['handoff', '--run', root.run, '--from', '7', '--to', '11.5', '--base', 'origin/main', '--reading', '1000000']);
const ho = h.sb(['handoff', '--run', root.run, '--from', '9', '--to', '11.5', '--base', 'origin/main', '--reading', '1000000']);
expect(ho.code, ho.out + ho.err).toBe(0);
const v = h.sb(['verify-handoff', '--in', ho.out.match(/^HANDOFF: (\S+) /m)![1]!, '--reading', '100000']);
expect(v.code, v.out + v.err).toBe(0);
@@ -243,7 +243,7 @@ describe('gstack-review-log --start under a token cap', () => {
expect(clean.code, clean.out).toBe(0);
const h2 = fresh();
const root2 = capped(h2, '200M');
const ho2 = h2.sb(['handoff', '--run', root2.run, '--from', '7', '--to', '11.5', '--base', 'origin/main', '--reading', '1000000']);
const ho2 = h2.sb(['handoff', '--run', root2.run, '--from', '9', '--to', '11.5', '--base', 'origin/main', '--reading', '1000000']);
expect(h2.sb(['verify-handoff', '--in', ho2.out.match(/^HANDOFF: (\S+) /m)![1]!, '--reading', '100000']).code).toBe(0);
expect(h2.reviewLog([JSON.stringify({ ...row, timestamp: new Date(Date.now() + 1000).toISOString() })]).code).toBe(0);
const flagged = h2.sb(['close', '--run', root2.run, '--reading', '1500000']);
+4 -4
View File
@@ -44,15 +44,15 @@ describe('check --runtime-lines', () => {
const r = h.sb(['start', '--record', h.record(`r${cap}.md`), '--token-cap', cap, '--reading', '0', '--call-floor', '0']);
return h.field(r.out, 'BUDGET_RUN', 'id');
};
const small = h.sb(['check', '--run', start('80M'), '--step', '6', '--next', '7', '--reading', '1220000', '--prior-reading', '1000000', '--runtime-lines', '0']);
const small = h.sb(['check', '--run', start('80M'), '--step', '8', '--next', '9', '--reading', '1220000', '--prior-reading', '1000000', '--runtime-lines', '0']);
expect(small.code).toBe(0);
expect(h.field(small.out, 'BUDGET', 'verdict')).toBe('continue');
expect(small.out).toContain('SMALL_RUN: runtime_lines=0');
expect(small.out).not.toContain('DELEGATE:');
const large = h.sb(['check', '--run', start('81M'), '--step', '6', '--next', '7', '--reading', '1220000', '--prior-reading', '1000000', '--runtime-lines', '900']);
const large = h.sb(['check', '--run', start('81M'), '--step', '8', '--next', '9', '--reading', '1220000', '--prior-reading', '1000000', '--runtime-lines', '900']);
expect(h.field(large.out, 'BUDGET', 'verdict')).toBe('delegate');
const omitted = h.sb(['check', '--run', start('82M'), '--step', '6', '--next', '7', '--reading', '1220000', '--prior-reading', '1000000']);
const omitted = h.sb(['check', '--run', start('82M'), '--step', '8', '--next', '9', '--reading', '1220000', '--prior-reading', '1000000']);
expect(h.field(omitted.out, 'BUDGET', 'verdict')).toBe('delegate');
expect(h.sb(['check', '--run', start('83M'), '--step', '6', '--next', '7', '--reading', '5', '--runtime-lines', 'many']).code).toBe(2);
expect(h.sb(['check', '--run', start('83M'), '--step', '8', '--next', '9', '--reading', '5', '--runtime-lines', 'many']).code).toBe(2);
});
});
+5 -5
View File
@@ -189,11 +189,11 @@ describe('delegation', () => {
test('a segment boundary with an unknown per-call charge delegates; --delegate never stops instead', () => {
const h = fresh();
const a = started(h, ['--token-cap', '80M', '--reading', '0']);
const d = h.sb(['check', '--run', a.run, '--step', '6', '--next', '7', '--reading', '100']);
const d = h.sb(['check', '--run', a.run, '--step', '8', '--next', '9', '--reading', '100']);
expect(d.code).toBe(0);
expect(h.field(d.out, 'BUDGET', 'verdict')).toBe('delegate');
expect(d.out).not.toContain('RESERVATION');
const small = h.sb(['check', '--run', a.run, '--step', '6', '--next', '7', '--reading', '1100', '--prior-reading', '1000']);
const small = h.sb(['check', '--run', a.run, '--step', '8', '--next', '9', '--reading', '1100', '--prior-reading', '1000']);
expect(h.field(small.out, 'BUDGET', 'verdict')).toBe('continue');
const n = started(h, ['--token-cap', '80M', '--reading', '0', '--delegate', 'never']);
const stop = h.sb(['check', '--run', n.run, '--step', '11.5', '--next', '12', '--reading', '100']);
@@ -204,9 +204,9 @@ describe('delegation', () => {
test('full chain: delegate, handoff, verify, child checks, 40-line return, idempotent reconcile, close', () => {
const h = fresh();
const s = started(h, ['--token-cap', '80M', '--reading', '1000000']);
const d = h.sb(['check', '--run', s.run, '--step', '6', '--next', '7', '--reading', '1200000', '--prior-reading', '800000']);
const d = h.sb(['check', '--run', s.run, '--step', '8', '--next', '9', '--reading', '1200000', '--prior-reading', '800000']);
expect(h.field(d.out, 'BUDGET', 'verdict')).toBe('delegate');
const ho = h.sb(['handoff', '--run', s.run, '--from', '7', '--to', '11.5', '--base', 'origin/main', '--reading', '1300000']);
const ho = h.sb(['handoff', '--run', s.run, '--from', '9', '--to', '11.5', '--base', 'origin/main', '--reading', '1300000']);
expect(ho.code).toBe(0);
const brief = ho.out.match(/^HANDOFF: (\S+) sha256=([0-9a-f]{64}) sub_cap=(\d+)$/m)!;
expect(brief).toBeTruthy();
@@ -225,7 +225,7 @@ describe('delegation', () => {
expect(cc.code).toBe(0);
const past = h.sb(['check', '--run', child, '--step', '11', '--next', '12', '--reading', '6000000']);
expect(past.code).toBe(0);
expect(past.out).toMatch(/^CONTEXT_HIGH: .*Step 12 is past this child's Steps 7-11\.5/m);
expect(past.out).toMatch(/^CONTEXT_HIGH: .*Step 12 is past this child's Steps 9-11\.5/m);
const ret = h.sb(['return', '--run', child, '--to', '11.5', '--changed', 'Reviews,Checks', '--reading', '9000000']);
expect(ret.code).toBe(0);
expect(ret.out.trim().split('\n').length).toBeLessThanOrEqual(40);
+29
View File
@@ -122,6 +122,35 @@ describe('ship/DRIVER.md covers the skeleton', () => {
});
});
describe('one program per pass (GSTA-167 variant E)', () => {
const flat = (s: string) => s.replace(/\s+/g, ' ');
const phase = (h: string) => flat(doc.slice(doc.indexOf(h), doc.indexOf('\n## ', doc.indexOf(h) + 1)));
// Value: protects=a review pass, Step 11 and the Step 14.5 audit each run as one program in the fixed order plan, reserve and start, BEFORE, readers, poll, AFTER and settle, later batches, collect, so the parent spends no call inside a pass and the reading pair brackets only the readers; fails_when=a phase goes back to walking Steps 9-9.4 call by call (replay 5: arm D's 69 orchestrator calls in Steps 7-14.5 against round 4's 21), the order breaks, or a phase stops pointing at the shared program;
// why_new=the coverage tests above check step ids, codes and section names only; seam=none; no_claim=the parent's adherence or the call count it reaches
test('the shared program lists its acts in order and Phases 4, 5 and Step 14.5 run through it', () => {
const block = flat(doc.slice(doc.indexOf('**One program per pass.**'), doc.indexOf('\n## ', doc.indexOf('**One program per pass.**'))));
expect(block).toContain('Each review pass (Phase 4), Step 11 (Phase 5) and the Step 14.5 audit is one program, and the parent makes no other call while it runs');
const order = ['gstack-review-plan --base <base>', 'check --step <n> --next dispatch:<roles>', 'gstack-review-log --start <skill> --reservation <id>', '(3) the reading `BEFORE`',
'(4) one subagent per reader, its brief copied verbatim from the section', '(5) a poll until every reader settles', '(6) the reading `AFTER`', '--dispatched-at <BEFORE> --reading <AFTER> --readers <n>',
'a later batch of the pass (the Red Team', '(7) the collect: return only each reader\'s final output', 'it never walks the dispatch steps call by call'];
const at = order.map(o => block.indexOf(o));
expect(order.filter((_, k) => at[k]! < 0)).toEqual([]);
expect([...at].sort((a, b) => a - b)).toEqual(at);
expect(phase('## 4. Review')).toContain('Each pass is one program (above)');
expect(phase('## 5. Adversarial')).toContain('in one program (above)');
expect(phase('## 7. Release')).toContain('read `sections/documentation.md` and run its audit as written, as one program (above) with `--next dispatch:docs`');
});
// Value: protects=Steps 7-8 run in the parent and the fresh child starts at Step 9, reading only the review sections and the record; fails_when=the driver delegates the audits again or the child is told to read the audit sections;
// why_new=the budget tests pin the boundary, not the driver's phase text; seam=none; no_claim=the child's actual reads
test('Phase 3 keeps the audits here and closes with the Step 9 handoff to a review-only child', () => {
const p3 = phase('## 3. Audits');
for (const s of ['as they are written, here (never delegated)', 'write them under Checks', '`check --step 8 --next 9` closes the phase', 'Steps 9-11.5 go to a fresh child',
'reads only `sections/review-army.md`, `sections/adversarial.md` and the record']) expect(p3).toContain(s);
expect(read('ship/sections/budget-handoff.md')).toContain('--from 9 --to 11.5');
});
});
describe('STOP anchors', () => {
test('every STOP row is rendered and its anchor names a heading that exists', () => {
for (const s of SHIP_DRIVER_STOPS) {
+39 -9
View File
@@ -3,11 +3,13 @@
* and #36 the Steps 7-11.5 child booted at 1.29M by reading the whole ship
* skill, preamble.md and docs/ship-budget.md before its first step; the brief
* now names ship/SKILL.md's step list, the segment's sections and
* budget-handoff.md, and nothing else.
* budget-handoff.md, and nothing else. Variant E (replay 5): Steps 7-8 stay
* with the parent, so the review child (Steps 9-11.5) reads review-army.md and
* adversarial.md, and the record the driver keeps carries the six headings.
*/
import { afterEach, describe, expect, test } from 'bun:test';
import * as fs from 'node:fs';
import { segmentSections } from '../lib/ship-handoff';
import { RECORD_HEADINGS, segmentSections } from '../lib/ship-handoff';
import { budgetHarness, type Harness } from './helpers/ship-budget-harness';
const ROOT = `${import.meta.dir}/..`;
@@ -26,11 +28,11 @@ function briefFor(from: string, to: string): string {
const readSection = (text: string) => text.slice(text.indexOf('## Read'), text.indexOf('\n## ', text.indexOf('## Read') + 1));
describe('the handoff brief names only the segment\'s sections', () => {
test('Steps 7-11.5: the step list plus test-coverage, plan-completion, review-army and adversarial', () => {
const read = readSection(briefFor('7', '11.5'));
test('Steps 9-11.5: the step list plus review-army and adversarial only', () => {
const read = readSection(briefFor('9', '11.5'));
expect(read).toContain("ship/SKILL.md's step list");
for (const s of ['test-coverage.md', 'plan-completion.md', 'review-army.md', 'adversarial.md', 'budget-handoff.md']) expect(read).toContain(s);
for (const s of ['changelog.md', 'documentation.md', 'pr-body.md', 'capy-pr.md', 'tests.md']) expect(read).not.toContain(s);
for (const s of ['review-army.md', 'adversarial.md', 'budget-handoff.md']) expect(read).toContain(s);
for (const s of ['test-coverage.md', 'plan-completion.md', 'changelog.md', 'documentation.md', 'pr-body.md', 'capy-pr.md', 'tests.md']) expect(read).not.toContain(s);
expect(read).toMatch(/Never read ship\/sections\/preamble\.md in full or docs\/ship-budget\.md/);
});
@@ -54,14 +56,15 @@ describe('the handoff brief names only the segment\'s sections', () => {
const lineOf = (file: string, heading: string) => fs.readFileSync(`${ROOT}/${file}`, 'utf8').split('\n').findIndex(l => l.startsWith(heading)) + 1;
const inside = (rs: [number, number][], n: number) => n > 0 && rs.some(([a, b]) => a <= n && n <= b);
const total = (file: string) => fs.readFileSync(`${ROOT}/${file}`, 'utf8').replace(/\n$/, '').split('\n').length;
const seg = briefFor('7', '11.5');
const seg = briefFor('9', '11.5');
expect(seg).toMatch(/Read only the `READ:` ranges below/);
const r = ranges(seg);
const skill = r['ship/SKILL.md']!;
expect(skill[0]![0]).toBe(lineOf('ship/SKILL.md', '# Ship:'));
for (const h of ['## Step 11.5:', '## Important Rules', '### Ship control flow']) expect(inside(skill, lineOf('ship/SKILL.md', h)), h).toBe(true);
for (const h of ['## Preamble (run first)', '## Section index', '## Step 3:', '## Step 12:']) expect(inside(skill, lineOf('ship/SKILL.md', h)), h).toBe(false);
for (const s of ['review-army', 'test-coverage', 'plan-completion', 'adversarial', 'budget-handoff']) expect(r[`ship/sections/${s}.md`]).toEqual([[1, total(`ship/sections/${s}.md`)]]);
for (const h of ['## Preamble (run first)', '## Section index', '## Step 3:', '## Step 7:', '## Step 8:', '## Step 12:']) expect(inside(skill, lineOf('ship/SKILL.md', h)), h).toBe(false);
for (const s of ['review-army', 'adversarial', 'budget-handoff']) expect(r[`ship/sections/${s}.md`]).toEqual([[1, total(`ship/sections/${s}.md`)]]);
for (const s of ['test-coverage', 'plan-completion']) expect(r[`ship/sections/${s}.md`]).toBeUndefined();
expect(Object.keys(r).filter(k => k.includes('audit-') || k.includes('changelog'))).toEqual([]);
const late = ranges(briefFor('12', '22'))['ship/SKILL.md']!;
for (const h of ['## Step 12:', '## Step 14.5:', '## Step 17:', '## Step 22:', '## Important Rules']) expect(inside(late, lineOf('ship/SKILL.md', h)), h).toBe(true);
@@ -95,4 +98,31 @@ describe('the handoff brief names only the segment\'s sections', () => {
const ids = new Set(manifest.sections.map((s: any) => s.id));
for (const id of segmentSections('1', '22')) expect(ids.has(id)).toBe(true);
});
// Value: protects=the record ship/DRIVER.md tells the parent to keep is one gstack-ship-budget handoff accepts: the driver names each of the six headings with the phase that writes it, and a record holding them hands Steps 9-11.5 off while one missing a heading is refused by name; fails_when=the driver drops or renames a heading the handoff requires (GSTA-167 replay 5: "record ... lacks headings: Release, Decisions, Reviews, Checks, Documentation, Next steps"), or the refusal stops naming the missing heading;
// why_new=the harness record above is hand-written, never the driver's; seam=none; no_claim=that the parent writes each heading on time
test('the driver names each record heading with its phase, and a record built from it hands Steps 9-11.5 off', () => {
const driver = fs.readFileSync(`${ROOT}/ship/DRIVER.md`, 'utf8');
const named = [...driver.matchAll(/\*\*([A-Z][a-z]+(?: steps)?)\*\*\s+\((?:Phases? [\d-]+|Step [\d.]+|any\s+phase|every phase)/g)].map(m => m[1]);
expect(new Set(named)).toEqual(new Set(RECORD_HEADINGS));
expect(driver).toContain('write its six headings at Phase 1');
const h = fresh();
const record = (headings: readonly string[]) => {
const file = h.record(`driver-${headings.length}.md`);
fs.writeFileSync(file, ['# /ship invocation record', '', ...headings.flatMap(x => [`## ${x}`, x === 'Checks' ? '- Coverage: 89% value-weighted; Scope Check: CLEAN' : '', ''])].join('\n'));
return file;
};
const handoff = (file: string) => {
const s = h.sb(['start', '--record', file, '--token-cap', '400M', '--reading', '98000']);
expect(s.code, s.out + s.err).toBe(0);
return h.sb(['handoff', '--run', h.field(s.out, 'BUDGET_RUN', 'id'), '--from', '9', '--to', '11.5', '--base', 'origin/main', '--reading', '1000000']);
};
const ok = handoff(record(RECORD_HEADINGS));
expect(ok.code, ok.out + ok.err).toBe(0);
const read = readSection(fs.readFileSync(ok.out.match(/^HANDOFF: (\S+) /m)![1]!, 'utf8'));
expect(read).toContain('review-army.md, adversarial.md, budget-handoff.md');
const refused = handoff(record(RECORD_HEADINGS.filter(x => x !== 'Next steps')));
expect(refused.code).toBe(2);
expect(refused.err).toContain('lacks headings: Next steps');
});
});
+2 -2
View File
@@ -1,4 +1,4 @@
{
"bytes": 841,
"why": "GSTA-167 turns: Steps 1-3, 5, 12 and 15-17 name gstack-ship-preflight and gstack-ship-release; own-jam meter and dispatch-pair metering in budget-handoff"
"bytes": 1203,
"why": "GSTA-167: ship preflight/release bins and the compact driver (+841); variant E: budget-handoff.md runs one program per review pass and hands Steps 9-11.5 to the review child (+362)"
}