mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-14 12:27:18 +01:00
train S3: merge #3698 at 395df002ec v1.92.64.0
This commit is contained in:
commit
6ee3272f58
11 files changed
+204
-18
No files matched your search
+124
-2
@@ -182,7 +182,7 @@ function host(dir: string, env: NodeJS.ProcessEnv, o: { output?: (title: string)
|
||||
if (mode === 'after') throw new Error('create result lost');
|
||||
return { tasks: [{ index, title: t.title }], ...(rejected ? { rejected: [{ index, error: 'cap' }] } : {}) };
|
||||
},
|
||||
start: async ({ indices }: { indices: number[] }) => { starts.push(indices); for (const i of indices) subs.get(String(i))!.status = 'working'; return { started: indices.map(index => ({ index })), rejected: [] }; },
|
||||
start: async ({ indices, model }: { indices: number[]; model?: unknown }) => { starts.push(indices); for (const i of indices) { const s = subs.get(String(i))!; s.status = 'working'; if (model) s.model = model; } return { started: indices.map(index => ({ index })), rejected: [] }; },
|
||||
list: async () => { advance(); return { tasks: [...subs].map(([p, s]) => ({ path: p, title: s.title, status: s.status })), total: subs.size }; },
|
||||
get: async ({ path: p }: { path: string }) => ({ path: p, status: subs.get(p)!.status, summary: subs.get(p)!.summary }),
|
||||
},
|
||||
@@ -338,7 +338,7 @@ describe('Capy pass program (fake host, real bins)', () => {
|
||||
const h = host(dir, env);
|
||||
const base = { base: 'main', out, pass: 'x7p1', planFlags: ['--depth-floor', 'full'] };
|
||||
expect(await runPass(h, base)).toContain('NEXT: judge (Phase 4)');
|
||||
expect(await runPass(h, { ...base, stage: 'voices' })).toContain("STOP: the outside voice needs input.models['outside-voice']");
|
||||
expect(await runPass(h, { ...base, stage: 'voices', laneModel: undefined })).toContain("STOP: the outside voice needs input.models['outside-voice']");
|
||||
expect(h.creates.some(c => c.title.startsWith('ship x7p1 ') && /adversarial|outside/.test(c.title))).toBe(false);
|
||||
const models = { 'outside-voice': { model_id: 'openai/gpt-6.1-sol' }, 'adversarial-native': { model_id: 'anthropic/claude-opus-5-5', reasoning_mode: 'high' } };
|
||||
expect(await runPass(h, { ...base, stage: 'voices', models })).toContain('NEXT: judge the voices');
|
||||
@@ -346,6 +346,128 @@ describe('Capy pass program (fake host, real bins)', () => {
|
||||
expect(h.creates.find(c => c.title === 'ship x7p1 adversarial-native')!.model).toEqual(models['adversarial-native']);
|
||||
}, 240_000);
|
||||
|
||||
// Value: protects=Step 11's outside voice is never the native voice's or the lane's model family; fails_when=the STOP checks only that some model was given (lib/capy-review-pass.js before GSTA-167 PR B), so a Claude outside voice beside a Claude native voice runs as "outside";
|
||||
// why_new=GSTA-167 plan R2 (autoplan-20261031135232); seam=none; no_claim=which family pairs find more
|
||||
test('a same-family explicit outside voice STOPs before either voice is created', async () => {
|
||||
const { dir, env } = repo();
|
||||
const h = host(dir, env);
|
||||
const base = { base: 'main', out: path.join(dir, '.state/run/p1'), pass: 'x14p1', planFlags: ['--depth-floor', 'full'] };
|
||||
expect(await runPass(h, base)).toContain('NEXT: judge (Phase 4)');
|
||||
expect(await runPass(h, { ...base, stage: 'voices', models: { 'outside-voice': { model_id: 'anthropic/claude-sonnet-5-5', reasoning_mode: 'medium' } } }))
|
||||
.toContain("STOP: the outside voice anthropic/claude-sonnet-5-5 is the anthropic family, the same as the lane model; Step 11 needs input.models['outside-voice'] from another family");
|
||||
expect(await runPass(h, { ...base, stage: 'voices', models: { 'outside-voice': { model_id: 'openai/gpt-6.1-sol', reasoning_mode: 'medium' }, 'adversarial-native': { model_id: 'openai/gpt-6-astra', reasoning_mode: 'high' } } }))
|
||||
.toContain('STOP: the outside voice openai/gpt-6.1-sol is the openai family, the same as the native voice');
|
||||
expect(h.creates.some(c => /^ship x14p1 (adversarial-native|outside-voice)$/.test(c.title))).toBe(false);
|
||||
}, 240_000);
|
||||
|
||||
// Value: protects=a Step 11 rerun on one manifest keeps refusing a same-family outside voice, including one queued at the subagent cap that tools.subagent.start would pick up; fails_when=the family check runs only on the first render, so a resumed or queued voice starts unchecked;
|
||||
// why_new=GSTA-167 plan R2; seam=none; no_claim=the host's cap timing
|
||||
test('a Step 11 rerun on one manifest still refuses a same-family outside voice, rendered or queued', async () => {
|
||||
const same = { 'outside-voice': { model_id: 'anthropic/claude-sonnet-5-5', reasoning_mode: 'medium' } };
|
||||
const refused = /STOP: the outside voice anthropic\/claude-sonnet-5-5 is the anthropic family/;
|
||||
const { dir, env } = repo();
|
||||
const h = host(dir, env);
|
||||
const base = { base: 'main', out: path.join(dir, '.state/run/p1'), pass: 'x15p1', planFlags: ['--depth-floor', 'full'] };
|
||||
expect(await runPass(h, base)).toContain('NEXT: judge (Phase 4)');
|
||||
expect(await runPass(h, { ...base, stage: 'voices', models: same })).toMatch(refused);
|
||||
expect(await runPass(h, { ...base, stage: 'voices', models: same })).toMatch(refused);
|
||||
expect(h.creates.some(c => /^ship x15p1 (adversarial-native|outside-voice)$/.test(c.title))).toBe(false);
|
||||
|
||||
const b = repo();
|
||||
const h2 = host(b.dir, b.env, { reject: t => t.endsWith('outside-voice') });
|
||||
const base2 = { ...base, out: path.join(b.dir, '.state/run/p1'), pass: 'x18p1' };
|
||||
expect(await runPass(h2, base2)).toContain('NEXT: judge (Phase 4)');
|
||||
expect(await runPass(h2, { ...base2, stage: 'voices', budgetMs: 1 })).toMatch(/DISPATCHED: outside-voice path=\d+ queued/);
|
||||
const starts = h2.starts.length;
|
||||
const recorded = path.join(base2.out, 'voice-models.json');
|
||||
const kept = fs.readFileSync(recorded, 'utf8');
|
||||
fs.rmSync(recorded);
|
||||
expect(await runPass(h2, { ...base2, stage: 'voices', models: same })).toMatch(refused);
|
||||
expect(h2.starts.length).toBe(starts);
|
||||
fs.writeFileSync(recorded, kept);
|
||||
const resumed = await runPass(h2, { ...base2, stage: 'voices', models: same });
|
||||
expect(resumed).toContain('OUTSIDE_VOICE: openai/gpt-6.1-sol reasoning=medium as dispatched, started from the queue (certified configuration)');
|
||||
expect(h2.creates.find(c => c.title === 'ship x18p1 outside-voice')!.model).toEqual({ model_id: 'openai/gpt-6.1-sol', reasoning_mode: 'medium' });
|
||||
}, 300_000);
|
||||
|
||||
// Value: protects=a queued outside voice resumes on the models recorded when the voices were dispatched, so changed rerun inputs cannot pair it with a native voice of its own family; fails_when=the rerun's input decides the queued voice's model and the native family (outside-voice finding on #3698: two OpenAI voices started);
|
||||
// why_new=/ship Step 11 review of #3698; seam=none; no_claim=inputs that change between reruns on purpose
|
||||
test('a queued outside voice resumes on its dispatched model, checked against the dispatched native family', async () => {
|
||||
const { dir, env } = repo();
|
||||
const h = host(dir, env, { reject: t => t.endsWith('outside-voice') });
|
||||
const base = { base: 'main', out: path.join(dir, '.state/run/p1'), pass: 'x19p1', planFlags: ['--depth-floor', 'full'] };
|
||||
expect(await runPass(h, base)).toContain('NEXT: judge (Phase 4)');
|
||||
const first = { 'adversarial-native': { model_id: 'openai/gpt-6-astra', reasoning_mode: 'high' }, 'outside-voice': { model_id: 'google/gemini-4-argon', reasoning_mode: 'medium' } };
|
||||
expect(await runPass(h, { ...base, stage: 'voices', models: first, budgetMs: 1 })).toMatch(/DISPATCHED: outside-voice path=\d+ queued/);
|
||||
const res = await runPass(h, { ...base, stage: 'voices' });
|
||||
expect(res).not.toContain('STOP:');
|
||||
expect(res).toContain('OUTSIDE_VOICE: google/gemini-4-argon reasoning=medium as dispatched, started from the queue; outside the certified configuration');
|
||||
expect(h.creates.find(c => c.title === 'ship x19p1 outside-voice')!.model).toEqual(first['outside-voice']);
|
||||
expect(h.creates.find(c => c.title === 'ship x19p1 adversarial-native')!.model).toEqual(first['adversarial-native']);
|
||||
}, 300_000);
|
||||
|
||||
// Value: protects=a lane whose family is unknown or not Anthropic gets no default outside voice, and an explicit one cannot skip the family check; fails_when=the pin applies to a lane it was not certified for, or an outside voice runs with nothing to compare its family to;
|
||||
// why_new=GSTA-167 plan R2; seam=none; no_claim=an OpenAI lane's pairing, which is uncertified
|
||||
test('a missing lane identity STOPs the outside voice: none given, or given with nothing to compare its family to', async () => {
|
||||
const { dir, env } = repo();
|
||||
const h = host(dir, env);
|
||||
const base = { base: 'main', out: path.join(dir, '.state/run/p1'), pass: 'x16p1', planFlags: ['--depth-floor', 'full'] };
|
||||
expect(await runPass(h, base)).toContain('NEXT: judge (Phase 4)');
|
||||
const none = "STOP: the outside voice needs input.models['outside-voice'] (a model of another family); none was given";
|
||||
expect(await runPass(h, { ...base, stage: 'voices', laneModel: undefined })).toContain(none);
|
||||
expect(await runPass(h, { ...base, stage: 'voices', laneModel: 'openai/gpt-6.1-sol' })).toContain(none);
|
||||
expect(await runPass(h, { ...base, stage: 'voices', laneModel: undefined, models: { 'outside-voice': { model_id: 'openai/gpt-6.1-sol', reasoning_mode: 'medium' } } }))
|
||||
.toContain('STOP: the outside voice openai/gpt-6.1-sol cannot be checked against another family: neither input.laneModel nor a native voice model was given');
|
||||
expect(await runPass(h, { ...base, stage: 'voices', models: { 'outside-voice': { model_id: 'claude-sonnet-5-5' } } }))
|
||||
.toContain('STOP: the outside voice claude-sonnet-5-5 has no provider prefix, so its family cannot be checked');
|
||||
expect(await runPass(h, { ...base, stage: 'voices', models: { 'outside-voice': { model_id: 'Anthropic/claude-sonnet-5-5' } } }))
|
||||
.toContain('STOP: the outside voice Anthropic/claude-sonnet-5-5 is the anthropic family, the same as the lane model');
|
||||
expect(await runPass(h, { ...base, stage: 'voices', laneModel: 'claude-opus-5-5', models: { 'outside-voice': { model_id: 'openai/gpt-6.1-sol' } } }))
|
||||
.toContain('STOP: the outside voice openai/gpt-6.1-sol cannot be checked against another family: the lane model claude-opus-5-5 has no provider prefix and no native voice model was given');
|
||||
expect(h.creates.some(c => /^ship x16p1 (adversarial-native|outside-voice)$/.test(c.title))).toBe(false);
|
||||
}, 240_000);
|
||||
|
||||
const certified = { model_id: 'openai/gpt-6.1-sol', reasoning_mode: 'medium' };
|
||||
async function voicesRun(dir: string, env: NodeJS.ProcessEnv, sub: string, models?: Record<string, unknown>) {
|
||||
const out = path.join(dir, '.state/run', sub);
|
||||
const h = host(dir, env);
|
||||
const base = { base: 'main', out, pass: 'x17p1', planFlags: ['--depth-floor', 'full'] };
|
||||
expect(await runPass(h, base)).toContain('NEXT: judge (Phase 4)');
|
||||
const res = await runPass(h, { ...base, stage: 'voices', ...(models ? { models } : {}) });
|
||||
const m = JSON.parse(fs.readFileSync(path.join(out, 'pass-manifest.json'), 'utf8')) as Manifest;
|
||||
for (const r of m.readers) expect(h.creates.find(c => c.title === r.title)!.prompt).toBe(fs.readFileSync(r.file!, 'utf8'));
|
||||
const shas = Object.fromEntries(h.creates.map(c => [c.title, promptHash(c.prompt.split(out).join('<out>'))]));
|
||||
return { res, shas, models: Object.fromEntries(h.creates.map(c => [c.title, c.model])) };
|
||||
}
|
||||
|
||||
// Value: protects=an Anthropic lane's Step 11 gets the certified outside voice (openai/gpt-6.1-sol at medium, as rounds 3-6 and A3668 ran) without naming it, and every brief goes out byte for byte as it does with that model named; fails_when=the lane must pick the outside voice (gpt-6-astra in 6c/7e/8g), or the pin changes any reader's brief or another reader's model;
|
||||
// why_new=GSTA-167 plan R2; seam=none; no_claim=the certified pair's recall, which arm-L6 measured
|
||||
test('an anthropic lane defaults the outside voice to the certified model, with every brief byte-identical to naming it', async () => {
|
||||
const { dir, env } = repo();
|
||||
const pinned = await voicesRun(dir, env, 'pin');
|
||||
expect(pinned.res).toContain('OUTSIDE_VOICE: openai/gpt-6.1-sol reasoning=medium default for an anthropic lane (certified configuration)');
|
||||
expect(pinned.res).toContain('NEXT: judge the voices');
|
||||
expect(pinned.models['ship x17p1 outside-voice']).toEqual(certified);
|
||||
const named = await voicesRun(dir, env, 'named', { 'outside-voice': certified });
|
||||
expect(named.res).toContain('OUTSIDE_VOICE: openai/gpt-6.1-sol reasoning=medium given (certified configuration)');
|
||||
expect(named.shas).toEqual(pinned.shas);
|
||||
expect(named.models).toEqual(pinned.models);
|
||||
}, 300_000);
|
||||
|
||||
// Value: protects=an explicit cross-family override is accepted but its pass output says it is outside the certified configuration; fails_when=a changed outside-voice model or reasoning passes silently as if certified;
|
||||
// why_new=GSTA-167 plan R2; seam=none; no_claim=the uncertified configuration's recall
|
||||
test('an explicit outside-voice override with changed reasoning runs and is recorded as uncertified, other briefs unchanged', async () => {
|
||||
const { dir, env } = repo();
|
||||
const high = { model_id: 'openai/gpt-6.1-sol', reasoning_mode: 'high' };
|
||||
const over = await voicesRun(dir, env, 'over', { 'outside-voice': high });
|
||||
expect(over.res).toContain('OUTSIDE_VOICE: openai/gpt-6.1-sol reasoning=high given; outside the certified configuration (openai/gpt-6.1-sol at medium beside an anthropic lane)');
|
||||
expect(over.res).toContain('NEXT: judge the voices');
|
||||
expect(over.models['ship x17p1 outside-voice']).toEqual(high);
|
||||
const named = await voicesRun(dir, env, 'named', { 'outside-voice': certified });
|
||||
expect(over.shas).toEqual(named.shas);
|
||||
for (const t of Object.keys(named.models)) if (t !== 'ship x17p1 outside-voice') expect(over.models[t]).toEqual(named.models[t]);
|
||||
}, 300_000);
|
||||
|
||||
test('an audit reader marked first settles before any specialist starts; its output is returned, never merged', async () => {
|
||||
const { dir, env } = repo();
|
||||
const out = path.join(dir, '.state/run/p1');
|
||||
|
||||
@@ -222,11 +222,11 @@ describe('variant G: the call plan, observe budget, waits and audit readers (GST
|
||||
// Value: protects=audits that cannot block (Steps 7, 8, 14.5) run on a small model at low effort with their sections' own briefs, and skip their reader when --audit decides them; fails_when=an audit reader inherits the lane's model and effort (#36: the coverage audit alone was 2.45M at Opus), or an audit brief is paraphrased;
|
||||
// why_new=audit model routing is new; seam=none; no_claim=the audits' recall
|
||||
test('audit readers run on a small model at low effort, named for Capy, and Phase 3 dispatches only what READER names', () => {
|
||||
expect(flat(doc)).toContain('Audit readers (Steps 7, 8, 14.5) cannot block, so each runs on a small model at low effort; their findings go to the PR body');
|
||||
expect(flat(doc)).toContain('Audit readers (Steps 7, 8, 14.5) cannot block, so each runs at low effort on a small model, Step 14.5 once on Sonnet 5.5; findings go to the PR body');
|
||||
expect(flat(doc)).toContain('`Coverage:` and Step 8\'s line come from `--audit` when no reader ran');
|
||||
const capy = read('capy/skill/gstack/SKILL.md');
|
||||
expect(capy).toContain('model: { model_id: "anthropic/claude-sonnet-5-5", reasoning_mode: "low" }');
|
||||
expect(capy).toContain('`anthropic/claude-haiku-5-5` also fits');
|
||||
expect(capy).toContain('(14.5 once)');
|
||||
});
|
||||
|
||||
// Value: protects=Step 9.2.1's report-only probes run in the parent's own Phase 4 program, as the section says, with its Reads in Phase 3, and
|
||||
|
||||
Reference in new issue
Block a user