mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-11 03:00:26 +01:00
* test(judge-calibration): preregistered cookie-workflow corpus, blind second review and adjudication; calibration config (GSTA-91) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(judge-calibration): price the cookie-workflow calibration before dispatch (GSTA-91) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(judge-calibration): record the cookie-workflow comparison-1 run (held: one new false pass) (GSTA-91) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(changelog): cookie-workflow calibration recorded as held (GSTA-91) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (GSTA-91) /ship Step 9 Fix-First on the cookie-workflow calibration record: - README: scope the "no sample errored" sentence to the 2026-10-03 configurations, move the W2 pilot and W2 spend sentences back to the W2 paragraph, record the cookie UC3 prose-step trigger and that its hand-written manifest entry must survive manifest regeneration. - TODOS: the cookie configuration now has a corpus and a held comparison-1; add its triggered prose step to the deferred prose-step entry. - test: pin the cookie pass rule's actionability floor and validate() rejection, and guard that the cookie registration sends only COOKIE_WORKFLOW_JUDGE's keys so the calibrated request stays the request production sends. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes, pass 2 (GSTA-91) /ship Step 9 Fix-First, second pass: - README: the blind-review sentence and results heading now fit the cookie corpus (labels predate any request with the schema; the row carries its own date and model), and the cookie paragraph records that both valid old-a a07 samples also scored 4/4/4. - CHANGELOG: same a07 fact. - TODOS: today's cookie request false-passes a07 and a08; the deferred prose step targets those. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(judge-calibration): pin the cookie calibration's prompt and recorded result (GSTA-91) /ship Step 7 coverage audit: - the cookie-workflow calibration builds exactly the prompt the cookie eval sends for the same bundle; - analyze() reproduces the committed comparison-1 result from its labels, samples, stop record and request options (request_hash covers model and options only, so prompt text and bundle bytes are not pinned). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(changelog): describe the new cookie calibration tests (GSTA-91) 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>
341 lines
23 KiB
TypeScript
341 lines
23 KiB
TypeScript
import { describe, expect, spyOn, test } from 'bun:test';
|
|
import Anthropic from '@anthropic-ai/sdk';
|
|
import * as fs from 'node:fs';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
import { callJudge, JudgeRefusalError, type CallJudgeOptions, type JudgeResponseMeta } from './helpers/llm-judge';
|
|
import { buildCookieWorkflowJudgeInput, COOKIE_WORKFLOW_JUDGE } from './helpers/cookie-workflow-judge-input';
|
|
import {
|
|
analyze, classifyError, loadCorpus, undispatched, PANEL_SAMPLES, PHASES, planCalls, priceManifest, runCalls, runSample,
|
|
type CalibrationConfig, type CorpusItem, type JudgeCall, type SampleRecord,
|
|
} from '../scripts/judge-calibration';
|
|
|
|
const item = (id: string, expected: 'pass' | 'fail', split: 'dev' | 'heldout' = 'dev'): CorpusItem =>
|
|
({ id, category: expected, expected, split, rationale: 'fixture', inputs: { text: `doc ${id}` } });
|
|
|
|
const config: CalibrationConfig = {
|
|
id: 'stub', priority: 1, cases: ['a', 'b'], model: 'claude-fable-5-1',
|
|
build: inputs => `grade: ${inputs.text}`,
|
|
oldOptions: {},
|
|
newOptions: { jsonSchema: { type: 'object' } },
|
|
validate: value => { if (typeof (value as { score?: unknown })?.score !== 'number') throw new Error('score missing'); },
|
|
verdict: samples => samples.filter(sample => (sample as { score: number }).score >= 4).length * 2 > samples.length ? 'pass' : 'fail',
|
|
estimatedOutputTokens: 1000,
|
|
};
|
|
|
|
/** Stub provider keyed by prompt and phase; usage is reported through the real onResponse seam. */
|
|
function stub(score: (prompt: string, opts: CallJudgeOptions, n: number) => unknown): { call: JudgeCall; calls: Array<{ prompt: string; opts: CallJudgeOptions }> } {
|
|
const calls: Array<{ prompt: string; opts: CallJudgeOptions }> = [];
|
|
return {
|
|
calls,
|
|
call: async (prompt, _model, opts) => {
|
|
calls.push({ prompt, opts });
|
|
opts.onResponse?.({ id: 'msg', model: 'claude-fable-5-1', stop_reason: 'end_turn', usage: { input_tokens: 1000, output_tokens: 1000 } });
|
|
const value = score(prompt, opts, calls.length);
|
|
if (value instanceof Error) throw value;
|
|
return value;
|
|
},
|
|
};
|
|
}
|
|
|
|
const records = async (items: CorpusItem[], call: JudgeCall) => {
|
|
const out: SampleRecord[] = [];
|
|
for (const split of ['dev', 'heldout'] as const) out.push(...(await runCalls(config, planCalls(items, split), { budgetUsd: 1000, call })).records);
|
|
return out;
|
|
};
|
|
|
|
describe('judge calibration harness (free, stubbed provider)', () => {
|
|
test('every item gets two independent old panels and one new panel, each sample a direct call', async () => {
|
|
const items = [item('p1', 'pass'), item('f1', 'fail'), item('h1', 'pass', 'heldout')];
|
|
const provider = stub(() => ({ score: 5 }));
|
|
const out = await records(items, provider.call);
|
|
expect(provider.calls).toHaveLength(items.length * PHASES.length * PANEL_SAMPLES);
|
|
for (const entry of items) {
|
|
for (const phase of PHASES) expect(out.filter(record => record.item === entry.id && record.phase === phase)).toHaveLength(PANEL_SAMPLES);
|
|
}
|
|
expect(provider.calls.filter(call => call.opts.jsonSchema)).toHaveLength(items.length * PANEL_SAMPLES);
|
|
expect(new Set(provider.calls.map(call => call.prompt))).toEqual(new Set(items.map(entry => `grade: ${entry.inputs.text}`)));
|
|
// Held-out samples are dispatched only after every development sample.
|
|
const firstHeldout = out.findIndex(record => record.split === 'heldout');
|
|
expect(out.slice(firstHeldout).every(record => record.split === 'heldout')).toBe(true);
|
|
});
|
|
|
|
test('the harness never routes through judgePanel or the workflow passing-panel cache', () => {
|
|
const scripts = path.join(import.meta.dir, '..', 'scripts');
|
|
for (const file of ['judge-calibration.ts', 'judge-calibration-configs.ts'].filter(name => fs.existsSync(path.join(scripts, name)))) {
|
|
const source = fs.readFileSync(path.join(scripts, file), 'utf8');
|
|
const imports = source.match(/^import[\s\S]*?from '[^']+';$/gm) ?? [];
|
|
expect(imports.length).toBeGreaterThan(0);
|
|
expect(imports.join('\n')).not.toMatch(/\bjudgePanel\b|workflow-judge-cache|eval-input-cache/);
|
|
expect(source.replace(/^\s*(?:\/\*\*|\*|\/\/).*$/gm, '')).not.toMatch(/\bjudgePanel\(|prepareWorkflowJudgeCache|lookupEvalInputCache/);
|
|
}
|
|
});
|
|
|
|
test('a second old panel is sampled, not replayed: old-vs-old flips are counted', async () => {
|
|
const items = [item('p1', 'pass')];
|
|
const provider = stub((_prompt, opts, n) => ({ score: opts.jsonSchema ? 5 : n <= 3 ? 5 : 2 }));
|
|
// Force the old panels apart: the first three old calls pass, the next three fail.
|
|
let oldCalls = 0;
|
|
const call: JudgeCall = (prompt, model, opts) => provider.call(prompt, model, opts).then(value =>
|
|
opts.jsonSchema ? value : ({ score: ++oldCalls <= 3 ? 5 : 2 }));
|
|
const result = analyze(config, items, await records(items, call));
|
|
expect(result.all.old_flips).toEqual({ flips: 1, compared: 1 });
|
|
});
|
|
|
|
test('error kinds are recorded per sample and never count as verdicts', async () => {
|
|
expect(classifyError(new JudgeRefusalError({ content: [] }))).toBe('refusal');
|
|
expect(classifyError(new Error('Judge response truncated at max_tokens=8192 (model=m)'))).toBe('truncation');
|
|
expect(classifyError(new SyntaxError('Unexpected token'))).toBe('parse');
|
|
expect(classifyError(new Error('Judge returned non-JSON: hi'))).toBe('parse');
|
|
expect(classifyError(new Error('Structured judge did not complete: stop_reason=pause_turn'))).toBe('parse');
|
|
expect(classifyError(Object.assign(new Error('Overloaded'), { status: 529 }))).toBe('transport');
|
|
|
|
const planned = { item: item('p1', 'pass'), phase: 'new' as const, sample: 0 };
|
|
const schema = await runSample(config, planned, stub(() => ({ verdict: 'yes' })).call);
|
|
expect(schema).toMatchObject({ status: 'error', error_kind: 'schema', cost_usd: 0.06 });
|
|
expect(schema).not.toHaveProperty('value');
|
|
const refusal = await runSample(config, planned, stub(() => new JudgeRefusalError({ content: [] })).call);
|
|
expect(refusal).toMatchObject({ status: 'error', error_kind: 'refusal' });
|
|
|
|
// One errored new sample leaves that panel incomplete: excluded from flips, counted as an error.
|
|
const items = [item('p1', 'pass'), item('p2', 'pass')];
|
|
let newCalls = 0;
|
|
const result = analyze(config, items, await records(items, stub((_prompt, opts) =>
|
|
opts.jsonSchema && ++newCalls === 1 ? new SyntaxError('bad json') : { score: 5 }).call));
|
|
expect(result.errors.new).toMatchObject({ parse: 1, samples: 6 });
|
|
expect(result.errors.old).toMatchObject({ parse: 0, samples: 12, rate: 0 });
|
|
expect(result.all.new_flips.compared).toBe(1);
|
|
expect(result.all.old_flips.compared).toBe(2);
|
|
expect(result.prose_step_triggered.triggered).toBe(true);
|
|
expect(result.prose_step_triggered.reasons).toContain('new prompt has errored samples');
|
|
});
|
|
|
|
test('latency is measured per sample and the p95 gate holds the schema back', async () => {
|
|
let now = 0;
|
|
const clock = () => now;
|
|
const slow: JudgeCall = async (_prompt, _model, opts) => { now += opts.jsonSchema ? 100_000 : 10_000; return { score: 5 }; };
|
|
const items = [item('p1', 'pass')];
|
|
const out: SampleRecord[] = (await runCalls(config, planCalls(items, 'dev'), { budgetUsd: 1000, call: slow, clock, concurrency: 1 })).records;
|
|
expect(out.filter(record => record.phase === 'new').every(record => record.latency_ms === 100_000)).toBe(true);
|
|
const result = analyze(config, items, out);
|
|
expect(result.latency_p95_ms).toEqual({ old: 10_000, new: 100_000 });
|
|
expect(result.landing.lands).toBe(false);
|
|
expect(result.landing.reasons.join(' ')).toContain('p95 latency');
|
|
});
|
|
|
|
test('landing: flips at most old+1, agreement does not drop, zero new false passes', async () => {
|
|
const items = [item('p1', 'pass'), item('f1', 'fail'), item('f2', 'fail')];
|
|
const same = analyze(config, items, await records(items, stub(prompt => ({ score: prompt.includes('p1') ? 5 : 1 })).call));
|
|
expect(same.landing).toEqual({ lands: true, reasons: [], strict_no_flip_increase: true });
|
|
expect(same.all.agreement).toEqual({ 'old-a': 3, 'old-b': 3, new: 3 });
|
|
|
|
const leaky = analyze(config, items, await records(items, stub((prompt, opts) =>
|
|
({ score: prompt.includes('p1') || (opts.jsonSchema && prompt.includes('f1')) ? 5 : 1 })).call));
|
|
expect(leaky.all.new_false_passes).toEqual(['f1']);
|
|
expect(leaky.landing.lands).toBe(false);
|
|
expect(leaky.landing.reasons.join(' ')).toContain('new false passes: f1');
|
|
expect(leaky.landing.reasons.join(' ')).toContain('agreement dropped');
|
|
});
|
|
|
|
test('two consecutive transport failures stop the study; the budget stops dispatch before it is passed', async () => {
|
|
const items = [item('p1', 'pass'), item('p2', 'pass')];
|
|
const down = await runCalls(config, planCalls(items, 'dev'), { budgetUsd: 1000, concurrency: 1,
|
|
call: stub(() => Object.assign(new Error('socket hang up'), { status: 500 })).call });
|
|
expect(down.records).toHaveLength(2);
|
|
expect(down.stopped).toContain('infrastructure');
|
|
|
|
const capped = await runCalls(config, planCalls(items, 'dev'), { budgetUsd: 0.5, concurrency: 1, call: stub(() => ({ score: 5 })).call });
|
|
expect(capped.stopped).toContain('budget');
|
|
expect(capped.records.reduce((sum, record) => sum + record.cost_usd, 0)).toBeLessThanOrEqual(0.5);
|
|
const result = analyze(config, items, capped.records, capped.stopped);
|
|
expect(result.complete).toBe(false);
|
|
expect(result.landing.lands).toBe(false);
|
|
});
|
|
|
|
test('resume completes only never-dispatched planned calls and never redraws a recorded sample', async () => {
|
|
const items = [item('p1', 'pass'), item('p2', 'pass')];
|
|
const planned = planCalls(items, 'dev');
|
|
const first = await runCalls(config, planned.slice(0, 7), { budgetUsd: 1000, call: stub(() => ({ score: 5 })).call });
|
|
const rest = undispatched(planned, first.records);
|
|
expect(rest).toEqual(planned.slice(7));
|
|
const provider = stub(() => ({ score: 5 }));
|
|
const second = await runCalls(config, rest, { budgetUsd: 1000, call: provider.call });
|
|
expect(provider.calls).toHaveLength(planned.length - 7);
|
|
expect(undispatched(planned, [...first.records, ...second.records])).toEqual([]);
|
|
expect(analyze(config, items, [...first.records, ...second.records]).complete).toBe(true);
|
|
});
|
|
|
|
test('callJudge reports usage and stop reason through onResponse without sending it', async () => {
|
|
const key = process.env.ANTHROPIC_API_KEY;
|
|
process.env.ANTHROPIC_API_KEY = 'test-only-key';
|
|
const create = spyOn(Anthropic.Messages.prototype, 'create').mockResolvedValue({ id: 'msg_1', model: 'claude-fable-5-1',
|
|
stop_reason: 'end_turn', content: [{ type: 'text', text: '{"score":4}' }], usage: { input_tokens: 12, output_tokens: 34 } } as never);
|
|
try {
|
|
const seen: JudgeResponseMeta[] = [];
|
|
expect(await callJudge<{ score: number }>('grade', 'claude-fable-5-1', { onResponse: meta => seen.push(meta) })).toEqual({ score: 4 });
|
|
expect(seen).toEqual([{ id: 'msg_1', model: 'claude-fable-5-1', stop_reason: 'end_turn', usage: { input_tokens: 12, output_tokens: 34 } }]);
|
|
expect(create.mock.calls[0]![0]).toEqual({ model: 'claude-fable-5-1', max_tokens: 8192, messages: [{ role: 'user', content: 'grade' }] });
|
|
} finally {
|
|
create.mockRestore();
|
|
if (key === undefined) delete process.env.ANTHROPIC_API_KEY; else process.env.ANTHROPIC_API_KEY = key;
|
|
}
|
|
});
|
|
|
|
test('the manifest prices every call in priority order from a committed corpus', async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'judge-cal-'));
|
|
try {
|
|
for (const id of ['second', 'first']) {
|
|
fs.mkdirSync(path.join(root, id, 'inputs'), { recursive: true });
|
|
fs.writeFileSync(path.join(root, id, 'inputs', 'p1.text.md'), 'from file');
|
|
fs.writeFileSync(path.join(root, id, 'corpus.json'), JSON.stringify({ items: [
|
|
{ ...item('p1', 'pass'), inputs: { text: { file: 'inputs/p1.text.md' } } }, item('f1', 'fail', 'heldout')] }));
|
|
}
|
|
expect(loadCorpus('first', root)[0]!.inputs.text).toBe('from file');
|
|
const entries = await priceManifest([{ ...config, id: 'second', priority: 2 }, { ...config, id: 'first', priority: 1 }, { ...config, id: 'absent', priority: 0 }],
|
|
async () => 2000, root);
|
|
expect(entries.map(entry => entry.config)).toEqual(['first', 'second']);
|
|
expect(entries[0]).toMatchObject({ items: 2, calls: 18, input_tokens: 36_000, estimated_cost_usd: 1.26 });
|
|
} finally { fs.rmSync(root, { recursive: true, force: true }); }
|
|
});
|
|
});
|
|
|
|
describe('committed calibration configurations and corpora', () => {
|
|
test('comparison (1) changes only the JSON schema; prompts come from the eval builders', async () => {
|
|
const { CALIBRATION_CONFIGS } = await import('../scripts/judge-calibration-configs');
|
|
expect(CALIBRATION_CONFIGS.map(config => config.id)).toEqual(['arm', 'qa-workflow', 'qa-health-rubric', 'qa-anti-refusal', 'cross-skill', 'voice', 'workflow-default', 'cookie-workflow']);
|
|
expect(CALIBRATION_CONFIGS.map(config => config.priority)).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
|
|
for (const config of CALIBRATION_CONFIGS) {
|
|
const { jsonSchema, ...rest } = config.newOptions;
|
|
expect(jsonSchema).toBeTruthy();
|
|
expect(rest).toEqual(config.oldOptions);
|
|
expect(config.oldOptions).not.toHaveProperty('jsonSchema');
|
|
}
|
|
});
|
|
|
|
test('every corpus is 24 preregistered, second-reviewed items with a stratified held-out third', async () => {
|
|
const { CALIBRATION_CONFIGS } = await import('../scripts/judge-calibration-configs');
|
|
for (const config of CALIBRATION_CONFIGS) {
|
|
const raw = JSON.parse(fs.readFileSync(path.join(import.meta.dir, 'fixtures', 'judge-calibration', config.id, 'corpus.json'), 'utf8'));
|
|
const items = loadCorpus(config.id);
|
|
expect(items).toHaveLength(24);
|
|
for (const category of ['pass', 'fail', 'adjacent'] as const) expect(items.filter(entry => entry.category === category)).toHaveLength(8);
|
|
expect(items.filter(entry => entry.split === 'heldout').map(entry => entry.id).sort()).toEqual(['a07', 'a08', 'f06', 'f07', 'f08', 'p06', 'p07', 'p08']);
|
|
for (const entry of raw.items) {
|
|
expect(['pass', 'fail']).toContain(entry.expected);
|
|
if (entry.category !== 'adjacent') expect(entry.expected).toBe(entry.category);
|
|
expect(['pass', 'fail']).toContain(entry.second_review?.verdict);
|
|
if (entry.second_review.verdict !== (entry.author_expected ?? entry.expected)) expect(entry.adjudication).toBeTruthy();
|
|
}
|
|
for (const entry of items) {
|
|
const prompt = config.build(entry.inputs);
|
|
for (const value of Object.values(entry.inputs)) expect(prompt).toContain(value.trim().slice(0, 200));
|
|
}
|
|
}
|
|
});
|
|
|
|
// /ship Step 7 coverage audit (GSTA-91).
|
|
// Value: protects=the cookie-workflow calibration sends the eval's exact prompt for the same bundle;
|
|
// fails_when=its build drops COOKIE_WORKFLOW_JUDGE context/goal or adds a capability the eval does not send;
|
|
// why_new=the corpus test only checks the bundle is contained in the prompt; seam=none;
|
|
// no_claim=provider behavior (no request is sent; both prompts come from the live SKILL.md and BROWSER.md)
|
|
test('the cookie-workflow calibration builds the prompt the cookie eval sends', async () => {
|
|
const { CALIBRATION_CONFIGS } = await import('../scripts/judge-calibration-configs');
|
|
const cookie = CALIBRATION_CONFIGS.find(config => config.id === 'cookie-workflow')!;
|
|
const production = buildCookieWorkflowJudgeInput(path.join(import.meta.dir, '..'));
|
|
expect(cookie.build({ bundle: production.text })).toBe(production.prompt);
|
|
});
|
|
|
|
test('panel verdicts restate each eval pass rule', async () => {
|
|
const { CALIBRATION_CONFIGS } = await import('../scripts/judge-calibration-configs');
|
|
const byId = Object.fromEntries(CALIBRATION_CONFIGS.map(config => [config.id, config]));
|
|
const qa = (clarity: number, completeness: number, actionability: number) => ({ clarity, completeness, actionability, reasoning: '' });
|
|
for (const id of ['qa-workflow', 'qa-health-rubric', 'workflow-default']) {
|
|
expect(byId[id]!.verdict([qa(3, 3, 4), qa(3, 3, 4), qa(3, 3, 4)])).toBe('pass');
|
|
expect(byId[id]!.verdict([qa(5, 5, 4), qa(5, 5, 4), qa(5, 5, 3)])).toBe('pass');
|
|
expect(byId[id]!.verdict([qa(5, 5, 4), qa(5, 5, 3), qa(5, 5, 3)])).toBe('fail');
|
|
expect(() => byId[id]!.validate({ ...qa(3, 3, 4), clarity: '3' })).toThrow();
|
|
}
|
|
expect(byId['cookie-workflow']!.verdict([qa(4, 3, 4), qa(4, 3, 4), qa(4, 3, 4)])).toBe('pass');
|
|
expect(byId['cookie-workflow']!.verdict([qa(5, 5, 4), qa(5, 5, 4), qa(3, 2, 3)])).toBe('pass');
|
|
expect(byId['cookie-workflow']!.verdict([qa(4, 3, 4), qa(3, 3, 4), qa(3, 3, 4)])).toBe('fail');
|
|
expect(byId['cookie-workflow']!.verdict([qa(5, 2, 5), qa(5, 2, 5), qa(5, 3, 5)])).toBe('fail');
|
|
expect(byId['cookie-workflow']!.verdict([qa(5, 5, 4), qa(5, 5, 3), qa(5, 5, 3)])).toBe('fail');
|
|
expect(() => byId['cookie-workflow']!.validate({ ...qa(4, 3, 4), clarity: '4' })).toThrow();
|
|
const arm = (over_engineering: number) => ({ over_engineering, construct: over_engineering ? 'helper' : 'none', reasoning: '' });
|
|
expect(byId.arm!.verdict([arm(0), arm(1), arm(3)])).toBe('pass');
|
|
expect(byId.arm!.verdict([arm(1), arm(2), arm(2)])).toBe('fail');
|
|
expect(() => byId.arm!.validate({ over_engineering: 0, construct: 'helper', reasoning: '' })).toThrow();
|
|
const browse = (would_browse: boolean, confidence: number) => ({ would_browse, confidence, fallback_behavior: '', reasoning: '' });
|
|
expect(byId['qa-anti-refusal']!.verdict([browse(true, 4), browse(true, 4), browse(false, 5)])).toBe('pass');
|
|
expect(byId['qa-anti-refusal']!.verdict([browse(true, 4), browse(true, 3), browse(true, 4)])).toBe('pass');
|
|
expect(byId['qa-anti-refusal']!.verdict([browse(true, 4), browse(true, 3), browse(true, 3)])).toBe('fail');
|
|
const voice = (low: number) => ({ directness: 5, concreteness: 5, avoids_corporate: 5, avoids_ai_vocabulary: low, connects_user_outcomes: 5, reasoning: '' });
|
|
expect(byId.voice!.verdict([voice(4), voice(4), voice(4)])).toBe('pass');
|
|
expect(byId.voice!.verdict([voice(4), voice(4), voice(3)])).toBe('pass');
|
|
expect(byId.voice!.verdict([voice(4), voice(3), voice(3)])).toBe('fail');
|
|
});
|
|
});
|
|
|
|
describe('landed schemas match the recorded calibration', () => {
|
|
const evalSource = fs.readFileSync(path.join(import.meta.dir, 'skill-llm-eval.test.ts'), 'utf8');
|
|
const judgeSource = fs.readFileSync(path.join(import.meta.dir, 'helpers', 'llm-judge.ts'), 'utf8');
|
|
const result = (id: string) => JSON.parse(fs.readFileSync(path.join(import.meta.dir, 'fixtures', 'judge-calibration', id, 'runs', 'comparison-1.result.json'), 'utf8'));
|
|
const wiring: Array<[string, string, string]> = [
|
|
['qa-workflow', 'buildQaWorkflowJudgePrompt(section)', 'JUDGE_SCORE_SCHEMA'],
|
|
['qa-health-rubric', 'buildQaHealthRubricJudgePrompt(section)', 'JUDGE_SCORE_SCHEMA'],
|
|
['qa-anti-refusal', 'buildQaAntiRefusalJudgePrompt(diffAwareSection, rulesSection)', 'QA_ANTI_REFUSAL_JUDGE_SCHEMA'],
|
|
['cross-skill', 'buildCrossSkillConsistencyJudgePrompt(collected)', 'CROSS_SKILL_CONSISTENCY_JUDGE_SCHEMA'],
|
|
['voice', 'buildVoiceDirectiveJudgePrompt(voiceSection)', 'VOICE_DIRECTIVE_JUDGE_SCHEMA'],
|
|
];
|
|
|
|
test.each(wiring)('%s sends its schema only when its complete calibration landed', (id, call, schema) => {
|
|
const recorded = result(id);
|
|
expect(recorded.complete).toBe(true);
|
|
const sent = evalSource.includes(`${call}, undefined, { jsonSchema: ${schema} })`);
|
|
expect(evalSource.includes(`${call})`) || sent).toBe(true);
|
|
expect(sent).toBe(recorded.landing.lands);
|
|
});
|
|
|
|
// /ship Step 7 coverage audit (GSTA-91).
|
|
// Value: protects=the held cookie-workflow result is what analyze() derives from its committed labels, samples, stop record and request options;
|
|
// fails_when=result.json is hand-edited, a corpus label moves after the run, a sample is altered, or the config's model/options drift;
|
|
// why_new=no test reads samples.jsonl or stop.json, and the wiring test trusts result.json's landing verbatim; seam=none;
|
|
// no_claim=that the recorded samples are genuine provider output (the committed JSONL stands in for the paid run), or that prompt
|
|
// text and bundle bytes are unchanged since the run (request_hash covers model and options only)
|
|
test('the recorded cookie-workflow result is reproducible from its committed labels, samples, stop record and request options', async () => {
|
|
const { CALIBRATION_CONFIGS } = await import('../scripts/judge-calibration-configs');
|
|
const cookie = CALIBRATION_CONFIGS.find(config => config.id === 'cookie-workflow')!;
|
|
const runs = path.join(import.meta.dir, 'fixtures', 'judge-calibration', 'cookie-workflow', 'runs');
|
|
const samples: SampleRecord[] = fs.readFileSync(path.join(runs, 'comparison-1.samples.jsonl'), 'utf8').trim().split('\n').map(line => JSON.parse(line));
|
|
const { stopped } = JSON.parse(fs.readFileSync(path.join(runs, 'comparison-1.stop.json'), 'utf8'));
|
|
expect(JSON.parse(JSON.stringify(analyze(cookie, loadCorpus('cookie-workflow'), samples, stopped)))).toEqual(result('cookie-workflow'));
|
|
});
|
|
|
|
test('armJudge sends ARM_JUDGE_SCHEMA because its calibration landed', () => {
|
|
expect(result('arm').landing.lands).toBe(true);
|
|
expect(judgeSource).toContain('buildArmJudgePrompt(task, diff), ARM_JUDGE_MODEL, { jsonSchema: ARM_JUDGE_SCHEMA })');
|
|
});
|
|
|
|
test('workflow transport follows the workflow-default and cookie-workflow calibrations; uncalibrated configurations stay on today\'s request', () => {
|
|
const recorded = result('workflow-default');
|
|
expect(recorded.complete).toBe(true);
|
|
const cookie = result('cookie-workflow');
|
|
expect(cookie.complete).toBe(true);
|
|
const registrations = [...evalSource.matchAll(/await runWorkflowJudge\(\{([\s\S]*?)\n \}\);/g)].map(match => match[1]!);
|
|
expect(registrations.length).toBeGreaterThan(14);
|
|
for (const body of registrations) {
|
|
const testName = /testName: '([^']+)'/.exec(body)![1];
|
|
const defaultConfig = !/^\s+(?:agentCapability|model|readInput|thresholds|maxTokens|stream|effort):|\.\.\.COOKIE_WORKFLOW_JUDGE/m.test(body);
|
|
const expected = testName === 'ship/SKILL.md workflow' || (defaultConfig && recorded.landing.lands)
|
|
|| (testName === 'setup-browser-cookies/SKILL.md workflow' && cookie.landing.lands);
|
|
expect(body.includes('schemaTransport: true'), testName).toBe(expected);
|
|
expect(body.includes('compactReasoning: true'), testName).toBe(testName === 'ship/SKILL.md workflow');
|
|
}
|
|
expect(registrations.filter(body => body.includes('schemaTransport: true'))).toHaveLength(cookie.landing.lands ? 16 : 15);
|
|
const cookieBody = registrations.find(body => body.includes("testName: 'setup-browser-cookies/SKILL.md workflow'"))!;
|
|
expect(cookieBody).toContain('...COOKIE_WORKFLOW_JUDGE');
|
|
expect(cookieBody).not.toMatch(/^\s+(?:agentCapability|model|thresholds|maxTokens|stream|effort):/m);
|
|
expect(Object.keys(COOKIE_WORKFLOW_JUDGE).sort()).toEqual(['judgeContext', 'judgeGoal', 'model', 'thresholds']);
|
|
});
|
|
});
|