/** * SkillOpt loop E2E: happy path + 5 orchestrator-visible failure modes. * * Sibling to `skillopt-pglite.serial.test.ts`. That file pins the three * v1 paths (dry-run, all-reject, manual revertAllPending). THIS file * proves the full optimization loop can actually improve a skill end-to-end * AND that each failure mode the loop is supposed to catch actually does * the right thing. * * Stub strategy: install one composite chat transport via * `__setChatTransportForTests`. The stub branches on `chatOpts.system`: * * - If system starts with "You are SkillOpt's optimizer", the call is * a reflect call. Branches further on FAILURE vs SUCCESS prompt. * - Otherwise, the call is a target-agent rollout. The stub emits * deterministic markdown based on which sections appear in the skill, * so applied edits change the rollout output → change the score. * * Hermetic: no real LLM calls, no `DATABASE_URL`, no API keys. PGLite * in-memory engine + tempdir SKILL.md + tempdir benchmark JSONL. * * .serial.test.ts because the stub installs module-state (the chat * transport) and the orchestrator walks multi-epoch shared disk state. */ import { describe, expect, test, beforeAll, afterAll, beforeEach, afterEach } from 'bun:test'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import { PGLiteEngine } from '../../src/core/pglite-engine.ts'; import { resetPgliteState } from '../helpers/reset-pglite.ts'; import { withEnv } from '../helpers/with-env.ts'; import { __setChatTransportForTests, type ChatOpts, type ChatResult, } from '../../src/core/ai/gateway.ts'; import { runSkillOpt } from '../../src/core/skillopt/orchestrator.ts'; import { bestPath, loadHistory, proposedPath, skillPath, } from '../../src/core/skillopt/version-store.ts'; import { loadRejectedBuffer } from '../../src/core/skillopt/rejected-buffer.ts'; import type { EditOp } from '../../src/core/skillopt/types.ts'; let engine: PGLiteEngine; beforeAll(async () => { engine = new PGLiteEngine(); await engine.connect({}); await engine.initSchema(); }); afterAll(async () => { await engine.disconnect(); }); beforeEach(async () => { await resetPgliteState(engine); }); afterEach(() => { // Belt-and-suspenders: ensure no test leaks a stub transport into the // next test. Every test path also clears explicitly in its finally block, // but a failed assertion mid-stub would skip that; this catches the // skipped-cleanup case. __setChatTransportForTests(null); }); // ─── Fixture helpers ──────────────────────────────────────────────────────── const SKILL = 'e2e-loop-skill'; /** A skill with only `## People`. Half the benchmark fails at baseline. */ const SKILL_PEOPLE_ONLY = `--- name: e2e-loop-skill version: 0.1.0 description: Test skill for E2E SkillOpt loop. triggers: - "do the loop task" brain_first: exempt --- # E2E Loop Test Skill When asked, produce a structured output. ## People List people mentioned. `; /** A skill with both sections. Full benchmark passes at baseline. */ const SKILL_BOTH_SECTIONS = `--- name: e2e-loop-skill version: 0.1.0 description: Test skill for E2E SkillOpt loop. triggers: - "do the loop task" brain_first: exempt --- # E2E Loop Test Skill When asked, produce a structured output. ## People List people mentioned. ## Citations Cite sources. `; /** * 50 tasks alternating People/Citations rule checks. The benchmark's * deterministic structure makes baseline scores predictable: with a * People-only skill, only People-tasks pass → score = 0.5 on any sufficiently * mixed sample. Split [4,1,5] = 20 train / 5 sel / 25 test (satisfies D17 * floor). */ const SAMPLE_BENCHMARK = Array.from({ length: 50 }, (_, i) => { const n = String(i + 1).padStart(3, '0'); const op = i % 2 === 0 ? 'People' : 'Citations'; return { task_id: `e2e-${n}`, task: `Process task ${i + 1}`, judge: { kind: 'rule' as const, checks: [{ op: 'contains' as const, arg: op }] }, }; }); interface Fixture { skillsDir: string; benchmarkPath: string; cleanup: () => void; } /** 50 tasks all checking `contains: Citations` — baseline People-only fails them all. */ const CITATIONS_BENCHMARK = Array.from({ length: 50 }, (_, i) => { const n = String(i + 1).padStart(3, '0'); return { task_id: `cit-${n}`, task: `Process task ${i + 1}`, judge: { kind: 'rule' as const, checks: [{ op: 'contains' as const, arg: 'Citations' }] }, }; }); /** Held-out set checking `contains: People` — baseline passes, a People-dropping candidate fails. */ const PEOPLE_HELDOUT = Array.from({ length: 6 }, (_, i) => { const n = String(i + 1).padStart(3, '0'); return { task_id: `ho-${n}`, task: `Held-out task ${i + 1}`, judge: { kind: 'rule' as const, checks: [{ op: 'contains' as const, arg: 'People' }] }, }; }); function setupFixture( skillBody: string = SKILL_PEOPLE_ONLY, benchmark: ReadonlyArray<{ task_id: string; task: string; judge: unknown }> = SAMPLE_BENCHMARK, ): Fixture { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'skillopt-loop-e2e-')); const skillDir = path.join(tmp, SKILL); fs.mkdirSync(skillDir, { recursive: true }); fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skillBody); const benchmarkPath = path.join(skillDir, 'skillopt-benchmark.jsonl'); fs.writeFileSync( benchmarkPath, benchmark.map((t) => JSON.stringify(t)).join('\n') + '\n', ); return { skillsDir: tmp, benchmarkPath, cleanup: () => { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch { /* ignore */ } }, }; } /** Write a held-out JSONL into the fixture's skill dir; return its path. */ function writeHeldOut(fixture: Fixture, tasks: ReadonlyArray<{ task_id: string; task: string; judge: unknown }>): string { const p = path.join(fixture.skillsDir, SKILL, 'held-out.jsonl'); fs.writeFileSync(p, tasks.map((t) => JSON.stringify(t)).join('\n') + '\n'); return p; } // ─── Stub builder ─────────────────────────────────────────────────────────── interface StubOpts { /** Edit returned by the FAILURE reflect call. Null/undefined → empty edits. */ failureEdit?: EditOp | null; /** Edit returned by the SUCCESS reflect call. Null/undefined → empty edits. */ successEdit?: EditOp | null; /** * Raw text returned by the optimizer (overrides failureEdit + successEdit). * Used for the malformed-JSON test case. */ optimizerRaw?: string; /** * Target-agent text emitter. Defaults to "emit text based on which sections * exist in the skill". Override to simulate broken/idiosyncratic agents. */ targetText?: (skillText: string) => string; /** * Optional per-call usage override for the budget-exhaustion test. When * set, every chat call reports this usage (driving cumulative cost up * fast against a tight cap). */ perCallUsage?: { input: number; output: number }; /** Raw body returned for ONE-SHOT REWRITE optimizer calls (optimizerMode test). */ oneShotBody?: string; /** Counters incremented as the stub observes each call kind (ablation tests). */ stats?: { successReflectCalls: number; oneShotCalls: number }; } // No trailing period: matches the FAILURE/SUCCESS reflect systems ("...optimizer.") // AND the one-shot system ("...optimizer in ONE-SHOT REWRITE mode."). const REFLECT_OPTIMIZER_PREFIX = "You are SkillOpt's optimizer"; const FAILURE_REFLECT_MARKER = 'FAILURE TRAJECTORIES'; const ONE_SHOT_MARKER = 'ONE-SHOT REWRITE'; function defaultTargetText(skillText: string): string { // Faithful agent: read the skill's body, emit sections that exist there. // Rule-check `contains: 'People'` passes when the section header is present // in the rollout's final_text (since the header literal contains 'People'). const parts: string[] = []; if (skillText.includes('## People')) parts.push('## People\nAlice attended the meeting.'); if (skillText.includes('## Citations')) parts.push('## Citations\nSource: example.com'); return parts.join('\n\n') || 'No structured output produced.'; } function makeChatResult( text: string, model: string, usage: { input: number; output: number } = { input: 100, output: 20 }, ): ChatResult { return { text, blocks: [{ type: 'text', text }], stopReason: 'end', usage: { input_tokens: usage.input, output_tokens: usage.output, cache_read_tokens: 0, cache_creation_tokens: 0, }, model, providerId: 'anthropic', }; } function installStub(opts: StubOpts): void { const usage = opts.perCallUsage ?? { input: 100, output: 20 }; __setChatTransportForTests(async (chatOpts: ChatOpts): Promise => { const sys = chatOpts.system ?? ''; const isOptimizerCall = sys.startsWith(REFLECT_OPTIMIZER_PREFIX); if (isOptimizerCall) { const model = chatOpts.model ?? 'anthropic:claude-opus-4-7'; // ONE-SHOT REWRITE mode returns a raw body, not edits JSON. if (sys.includes(ONE_SHOT_MARKER)) { if (opts.stats) opts.stats.oneShotCalls += 1; return makeChatResult(opts.oneShotBody ?? '', model, usage); } if (opts.optimizerRaw !== undefined) { return makeChatResult(opts.optimizerRaw, model, usage); } const isFailureMode = sys.includes(FAILURE_REFLECT_MARKER); if (!isFailureMode && opts.stats) opts.stats.successReflectCalls += 1; const edit = isFailureMode ? opts.failureEdit : opts.successEdit; const text = JSON.stringify({ edits: edit ? [edit] : [] }); return makeChatResult(text, model, usage); } // Target-agent rollout. const model = chatOpts.model ?? 'anthropic:claude-sonnet-4-6'; const fn = opts.targetText ?? defaultTargetText; return makeChatResult(fn(sys), model, usage); }); } function uninstallStub(): void { __setChatTransportForTests(null); } // ─── Common runSkillOpt invocation ────────────────────────────────────────── interface RunOptsOverride { maxCostUsd?: number; epochs?: number; batchSize?: number; noMutate?: boolean; heldOutPath?: string; optimizerMode?: 'reflect' | 'one-shot-rewrite'; reflectMode?: 'both' | 'failure-only'; disableValidationGate?: boolean; maxRuntimeMin?: number; } async function runOnce(fixture: Fixture, over: RunOptsOverride = {}) { return runSkillOpt({ engine, skillName: SKILL, skillsDir: fixture.skillsDir, benchmarkPath: fixture.benchmarkPath, epochs: over.epochs ?? 1, batchSize: over.batchSize ?? 2, lr: 4, lrSchedule: 'constant', split: [4, 1, 5], optimizerModel: 'anthropic:claude-opus-4-7', targetModel: 'anthropic:claude-sonnet-4-6', judgeModel: 'anthropic:claude-sonnet-4-6', mode: 'patch', dryRun: false, noMutate: over.noMutate ?? false, allowMutateBundled: true, bootstrapReviewed: false, json: true, maxCostUsd: over.maxCostUsd ?? 100, maxRuntimeMin: over.maxRuntimeMin ?? 1, force: true, // bypass dirty-tree (tempdir isn't a git repo) ...(over.heldOutPath ? { heldOutPath: over.heldOutPath } : {}), ...(over.optimizerMode ? { optimizerMode: over.optimizerMode } : {}), ...(over.reflectMode ? { reflectMode: over.reflectMode } : {}), ...(over.disableValidationGate ? { disableValidationGate: over.disableValidationGate } : {}), }); } // ─── Cases ────────────────────────────────────────────────────────────────── describe('skillopt full-loop E2E (happy path + broken cases)', () => { test('happy path: optimizer proposes a real edit, gate accepts, SKILL.md mutated', async () => { const fixture = setupFixture(SKILL_PEOPLE_ONLY); try { // Optimizer (FAILURE mode) proposes adding a ## Citations section right // after the ## People heading. After apply, the agent emits both sections // → every rollout passes → sel score goes from 0.5 (baseline) to 1.0, // delta = 0.5 ≫ epsilon=0.05 → ACCEPT. installStub({ failureEdit: { op: 'add', anchor: 'People', content: '## Citations\nCite the source for every claim.', reason: 'agent failed Citations tasks because no Citations section exists', }, successEdit: null, // success-mode reflect produces no edits }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { const result = await runOnce(fixture); // Outcome contract: accepted + mutated. expect(result.outcome).toBe('accepted'); expect(result.mutatedSkillFile).toBe(true); // SKILL.md on disk now has BOTH sections. const finalSkill = fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8'); expect(finalSkill).toContain('## People'); expect(finalSkill).toContain('## Citations'); // Frontmatter preserved (D5: edits never touch frontmatter). expect(finalSkill).toContain('name: e2e-loop-skill'); expect(finalSkill).toContain('brain_first: exempt'); // best.md mirrors the on-disk SKILL.md content. expect(fs.readFileSync(bestPath(fixture.skillsDir, SKILL), 'utf8')).toBe(finalSkill); // History has exactly one committed row with the right shape. const history = loadHistory(fixture.skillsDir, SKILL); const committed = history.filter((r) => r.status === 'committed'); expect(committed).toHaveLength(1); expect(committed[0]!.version_n).toBe(1); expect(committed[0]!.delta).toBeGreaterThan(0.05); // > epsilon expect(committed[0]!.sel_score).toBeGreaterThan(committed[0]!.delta); // monotone // The committed row records the actual edit applied (not just an empty proposal). expect(committed[0]!.edits).toHaveLength(1); expect(committed[0]!.edits[0]).toMatchObject({ op: 'add', anchor: 'People' }); // Receipt sel_score reflects the accepted candidate's score. expect(result.receipt.best_sel_score).toBeGreaterThan(0.9); expect(result.receipt.outcome).toBe('accepted'); expect(result.receipt.epochs_completed).toBe(1); }); } finally { uninstallStub(); } } finally { fixture.cleanup(); } }); test('broken: below-baseline regression edit (gate rejects, SKILL.md unchanged)', async () => { // Start with a skill that already scores 1.0. The optimizer (in SUCCESS // mode — failures=[] since baseline is perfect) proposes a destructive // edit that removes the ## People section. The candidate's sel score // collapses to 0.5; the gate rejects with reason=below_baseline; the // on-disk SKILL.md MUST stay byte-identical to the baseline. const fixture = setupFixture(SKILL_BOTH_SECTIONS); try { installStub({ failureEdit: null, successEdit: { op: 'delete', target: '## People\nList people mentioned.\n', reason: 'mistakenly thinks the People section is redundant', }, }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { const result = await runOnce(fixture); // Outcome contract: no acceptance + no mutation. expect(result.outcome).toBe('no_improvement'); expect(result.mutatedSkillFile).toBe(false); // SKILL.md on disk is byte-identical to baseline. expect(fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8')) .toBe(SKILL_BOTH_SECTIONS); // History is empty (no committed rows; rejected edits don't enter history). const history = loadHistory(fixture.skillsDir, SKILL); expect(history.filter((r) => r.status === 'committed')).toHaveLength(0); // The destructive edit landed in the rejected-edits buffer for // anti-bias context on future runs (the optimizer learns). const rejected = loadRejectedBuffer(fixture.skillsDir, SKILL); expect(rejected.length).toBeGreaterThan(0); expect(rejected.some((e) => e.reason.startsWith('validation_gate'))).toBe(true); // The recorded edit shape matches the destructive proposal so the // optimizer's anti-bias prompt sees the actual edit, not a stub. expect(rejected.some((e) => e.edits.some((edit) => edit.op === 'delete' && (edit as { target: string }).target.includes('## People')), )).toBe(true); }); } finally { uninstallStub(); } } finally { fixture.cleanup(); } }); test('broken: malformed reflect JSON (no edits parsed, no acceptance)', async () => { // The optimizer returns syntactically broken JSON. The reflect module's // forgiving parser yields zero valid edits; applyEditBatch sees an empty // batch; the orchestrator hits the "no_edits_applied" branch; the sel // gate is never invoked. SKILL.md stays untouched. Critically: the run // does NOT crash on malformed optimizer output (graceful degradation). const fixture = setupFixture(SKILL_PEOPLE_ONLY); try { installStub({ // Adversarial: looks like JSON but isn't. Different broken shapes // hit different fallback paths in tryExtractEdits. optimizerRaw: '{"edits": [BROKEN, no quotes, trailing comma,]', }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { const result = await runOnce(fixture); expect(result.outcome).toBe('no_improvement'); expect(result.mutatedSkillFile).toBe(false); expect(fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8')) .toBe(SKILL_PEOPLE_ONLY); expect(loadHistory(fixture.skillsDir, SKILL).filter((r) => r.status === 'committed')) .toHaveLength(0); }); } finally { uninstallStub(); } } finally { fixture.cleanup(); } }); test('broken: anchor-not-found edit (apply rejects, sel gate skipped)', async () => { // The optimizer proposes a structurally valid edit pointing at a heading // that doesn't exist in the skill. applyEditBatch returns all-rejected; // the orchestrator's all-rejected branch fires (logs no_edits_applied, // pushes to rejected-buffer, skips the sel gate). The skill stays // unchanged AND the bogus anchor lands in the rejected-buffer with // reason 'apply_failed' (separate from gate-rejected entries). const fixture = setupFixture(SKILL_PEOPLE_ONLY); try { installStub({ failureEdit: { op: 'add', anchor: 'NonExistentHeading', content: 'Some content', reason: 'optimizer hallucinated a heading', }, }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { const result = await runOnce(fixture); expect(result.outcome).toBe('no_improvement'); expect(result.mutatedSkillFile).toBe(false); expect(fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8')) .toBe(SKILL_PEOPLE_ONLY); // Rejected-buffer should carry an apply_failed entry (the failed // anchor lookup is recorded so the optimizer doesn't re-propose). const rejected = loadRejectedBuffer(fixture.skillsDir, SKILL); expect(rejected.length).toBeGreaterThan(0); expect(rejected.some((e) => e.reason === 'apply_failed')).toBe(true); }); } finally { uninstallStub(); } } finally { fixture.cleanup(); } }); test('broken: budget exhausted mid-run (aborts cleanly, no half-committed state)', async () => { // Cap is just under the preflight estimate so the run starts (preflight // refusal would prevent us from observing BudgetExhausted mid-loop), then // trips on the cumulative spend during the loop. Outcome=aborted is the // contract; the load-bearing assertion is that NO pending or committed // history rows survive (the abort path must not leave the skill in a // half-mutated state). const fixture = setupFixture(SKILL_PEOPLE_ONLY); try { installStub({ failureEdit: { op: 'add', anchor: 'People', content: '## Citations\nCite.', reason: 'mid-budget edit', }, // Drive per-call cost up so the cumulative spend trips the cap fast. perCallUsage: { input: 50_000, output: 5_000 }, }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { // The exact cap is calibrated to (a) survive the preflight check // (preflight refuses with a CostCapExceeded error if its estimate // exceeds the cap), but (b) trip mid-loop when real per-call // usage from the stub accumulates. Preflight estimates assume // small per-call usage; the stub inflates per-call usage so we // exceed the cap before the loop completes. let result: Awaited>; try { result = await runOnce(fixture, { maxCostUsd: 5.0 }); } catch (err) { // Acceptable: preflight may refuse before the loop starts if its // estimator now overshoots. In that case the contract becomes // "no mutation happened" — assert that directly via filesystem. const msg = err instanceof Error ? err.message : String(err); expect(msg).toMatch(/cost|budget/i); expect(fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8')) .toBe(SKILL_PEOPLE_ONLY); return; } // Reaching the loop: budget exhausted is the expected outcome. Other // non-acceptance outcomes (no_improvement, errored) are also fine // — the load-bearing assertion is no half-committed state. expect(['aborted', 'no_improvement', 'errored']).toContain(result.outcome); // No PENDING rows: the v0.42 D8 two-phase commit insists every // pending row is either committed or reverted; an abort path that // leaves a pending row would corrupt resume. const history = loadHistory(fixture.skillsDir, SKILL); expect(history.filter((r) => r.status === 'pending')).toHaveLength(0); // If outcome is aborted, MUST NOT mutate. if (result.outcome === 'aborted') { expect(result.mutatedSkillFile).toBe(false); expect(fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8')) .toBe(SKILL_PEOPLE_ONLY); } }); } finally { uninstallStub(); } } finally { fixture.cleanup(); } }); test('converged skill: re-running on perfect baseline yields no_improvement (no double-commit)', async () => { // Start from an already-perfect skill (baseline score = 1.0). The forward // gate finds zero failures. The reflect failure-mode path is never // invoked (failures=[]); only success-mode runs. The success-mode stub // returns empty edits. The loop converges with outcome=no_improvement // and the on-disk skill stays byte-identical. This proves the optimizer // doesn't pointlessly mutate a converged skill — the v1 "convergence" // path that protects against thrash on a well-tuned starting point. const fixture = setupFixture(SKILL_BOTH_SECTIONS); try { installStub({ failureEdit: null, // wouldn't be called anyway — baseline has no failures successEdit: null, // success-mode stub returns empty edits }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { const result = await runOnce(fixture); expect(result.outcome).toBe('no_improvement'); expect(result.mutatedSkillFile).toBe(false); expect(fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8')) .toBe(SKILL_BOTH_SECTIONS); // Receipt baseline IS the best score (no improvement to report). expect(result.receipt.best_sel_score).toBeGreaterThan(0.9); // History is empty. expect(loadHistory(fixture.skillsDir, SKILL).filter((r) => r.status === 'committed')) .toHaveLength(0); }); } finally { uninstallStub(); } } finally { fixture.cleanup(); } }); test('idempotent re-run: accept once, run again, second run sees new baseline + does not re-mutate', async () => { // The cathedral test: drive the loop twice in sequence on the same // fixture. Run 1 accepts the add-Citations edit (skill improves from // People-only to both sections). Run 2 starts from the now-improved // skill, sees baseline=1.0, finds no failures, returns no_improvement. // SKILL.md stays at v1; history still has exactly one committed row. // This proves the optimizer is "stable at the fixed point" — the // critical property of an iterative optimizer. const fixture = setupFixture(SKILL_PEOPLE_ONLY); try { // Same stub config across both runs: failure-mode proposes the // add-Citations edit. After run 1 accepts it, run 2's forward gate // sees no failures → failure-reflect never fires → no edits proposed. const stubConfig = { failureEdit: { op: 'add' as const, anchor: 'People', content: '## Citations\nCite the source.', reason: 'baseline missing Citations section', }, successEdit: null, }; // Run 1: accept. installStub(stubConfig); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { const result1 = await runOnce(fixture); expect(result1.outcome).toBe('accepted'); expect(result1.mutatedSkillFile).toBe(true); }); } finally { uninstallStub(); } const afterRun1 = fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8'); const historyAfterRun1 = loadHistory(fixture.skillsDir, SKILL); expect(historyAfterRun1.filter((r) => r.status === 'committed')).toHaveLength(1); // Run 2: same stub, but loop should observe the improved baseline and // converge without further mutation. installStub(stubConfig); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { const result2 = await runOnce(fixture); expect(result2.outcome).toBe('no_improvement'); expect(result2.mutatedSkillFile).toBe(false); }); } finally { uninstallStub(); } // SKILL.md byte-identical to its state after run 1. expect(fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8')).toBe(afterRun1); // History still has exactly one committed row (no double-commit). const historyAfterRun2 = loadHistory(fixture.skillsDir, SKILL); expect(historyAfterRun2.filter((r) => r.status === 'committed')).toHaveLength(1); // version_n unchanged at 1. expect(historyAfterRun2.filter((r) => r.status === 'committed')[0]!.version_n).toBe(1); } finally { fixture.cleanup(); } }); }); // ─── T3: held-out gate + ablation opts + no-DB-pollution ───────────────────── describe('skillopt T3 — F11 held-out gate, ablation opts, no-DB-pollution', () => { test('F11 held-out BLOCKS: candidate passes D_sel but regresses held-out → no commit', async () => { // baseline People-only fails the Citations benchmark; the failure edit // REPLACES People with Citations → candidate passes D_sel (Citations) but // tanks the held-out (People) → held-out gate refuses the commit. const fixture = setupFixture(SKILL_PEOPLE_ONLY, CITATIONS_BENCHMARK); const heldOutPath = writeHeldOut(fixture, PEOPLE_HELDOUT); try { installStub({ failureEdit: { op: 'replace', target: '## People\nList people mentioned.', replacement: '## Citations\nCite the source.', reason: 'swap People for Citations to pass the benchmark', }, successEdit: null, }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { const result = await runOnce(fixture, { heldOutPath }); // Held-out gate blocked the promotion. expect(result.outcome).toBe('no_improvement'); // SKILL.md unchanged: still People-only, never swapped to Citations. const skill = fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8'); expect(skill).toContain('## People'); expect(skill).not.toContain('## Citations'); // No committed history row. expect(loadHistory(fixture.skillsDir, SKILL).filter((r) => r.status === 'committed')).toHaveLength(0); }); } finally { uninstallStub(); } } finally { fixture.cleanup(); } }); test('F11 held-out ALLOWS: candidate improves D_sel AND holds held-out → commit', async () => { // ADD Citations (keep People): passes D_sel (Citations) and keeps held-out // (People) at baseline → held-out gate allows → commit. const fixture = setupFixture(SKILL_PEOPLE_ONLY, CITATIONS_BENCHMARK); const heldOutPath = writeHeldOut(fixture, PEOPLE_HELDOUT); try { installStub({ failureEdit: { op: 'add', anchor: 'People', content: '## Citations\nCite the source.', reason: 'add Citations' }, successEdit: null, }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { const result = await runOnce(fixture, { heldOutPath }); expect(result.outcome).toBe('accepted'); const skill = fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8'); expect(skill).toContain('## People'); expect(skill).toContain('## Citations'); expect(loadHistory(fixture.skillsDir, SKILL).filter((r) => r.status === 'committed')).toHaveLength(1); }); } finally { uninstallStub(); } } finally { fixture.cleanup(); } }); test('--no-mutate writes proposed.md and best.md, leaves SKILL.md untouched', async () => { const fixture = setupFixture(SKILL_PEOPLE_ONLY, CITATIONS_BENCHMARK); try { installStub({ failureEdit: { op: 'add', anchor: 'People', content: '## Citations\nCite the source.', reason: 'add Citations' }, successEdit: null, }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { const result = await runOnce(fixture, { noMutate: true }); expect(result.outcome).toBe('accepted'); expect(result.mutatedSkillFile).toBe(false); expect(result.proposedPath).toBe(proposedPath(fixture.skillsDir, SKILL)); expect(fs.readFileSync(result.proposedPath!, 'utf8')).toContain('## Citations'); expect(fs.readFileSync(bestPath(fixture.skillsDir, SKILL), 'utf8')).toContain('## Citations'); // SKILL.md on disk is UNCHANGED (still People-only). const skill = fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8'); expect(skill).not.toContain('## Citations'); }); } finally { uninstallStub(); } } finally { fixture.cleanup(); } }); test('optimizerMode one-shot-rewrite: single rewrite, no epoch loop, receipt records mode', async () => { const fixture = setupFixture(SKILL_PEOPLE_ONLY, CITATIONS_BENCHMARK); const stats = { successReflectCalls: 0, oneShotCalls: 0 }; try { installStub({ stats, // Body-only rewrite (frontmatter re-attached by the orchestrator). oneShotBody: '# E2E Loop Test Skill\n\nProduce a structured output.\n\n## People\nList people.\n\n## Citations\nCite the source.\n', }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { const result = await runOnce(fixture, { optimizerMode: 'one-shot-rewrite' }); expect(result.outcome).toBe('accepted'); expect(result.receipt.optimizer_mode).toBe('one-shot-rewrite'); // Exactly ONE optimizer rewrite call — no epoch loop. expect(stats.oneShotCalls).toBe(1); expect(result.receipt.total_steps).toBe(1); const skill = fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8'); expect(skill).toContain('## Citations'); // Frontmatter preserved (D5). expect(skill).toContain('name: e2e-loop-skill'); }); } finally { uninstallStub(); } } finally { fixture.cleanup(); } }); test('reflectMode failure-only SKIPS the success reflect call; default fires it', async () => { // Mixed benchmark → baseline has both successes (People tasks) and failures // (Citations tasks), so default mode WOULD fire the success reflect. const failOnly = { successReflectCalls: 0, oneShotCalls: 0 }; const fixtureA = setupFixture(SKILL_PEOPLE_ONLY, SAMPLE_BENCHMARK); try { installStub({ stats: failOnly, failureEdit: { op: 'add', anchor: 'People', content: '## Citations\nCite.', reason: 'add' }, successEdit: { op: 'add', anchor: 'People', content: '', reason: 'note' }, }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixtureA.skillsDir }, async () => { await runOnce(fixtureA, { reflectMode: 'failure-only' }); expect(failOnly.successReflectCalls).toBe(0); }); } finally { uninstallStub(); } } finally { fixtureA.cleanup(); } const both = { successReflectCalls: 0, oneShotCalls: 0 }; const fixtureB = setupFixture(SKILL_PEOPLE_ONLY, SAMPLE_BENCHMARK); try { installStub({ stats: both, failureEdit: { op: 'add', anchor: 'People', content: '## Citations\nCite.', reason: 'add' }, successEdit: { op: 'add', anchor: 'People', content: '', reason: 'note' }, }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixtureB.skillsDir }, async () => { await runOnce(fixtureB); expect(both.successReflectCalls).toBeGreaterThan(0); }); } finally { uninstallStub(); } } finally { fixtureB.cleanup(); } }); test('disableValidationGate greedy-accepts a no-improvement edit the gate would reject', async () => { // BOTH_SECTIONS already scores 1.0; a benign success edit yields delta 0, // which the D12 gate rejects — unless disableValidationGate greedy-accepts. const benignEdit = { op: 'add' as const, anchor: 'Citations', content: 'Extra note.', reason: 'benign' }; const fixtureGated = setupFixture(SKILL_BOTH_SECTIONS, SAMPLE_BENCHMARK); try { installStub({ successEdit: benignEdit, failureEdit: null }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixtureGated.skillsDir }, async () => { const result = await runOnce(fixtureGated); expect(result.outcome).toBe('no_improvement'); // gate rejected }); } finally { uninstallStub(); } } finally { fixtureGated.cleanup(); } const fixtureGreedy = setupFixture(SKILL_BOTH_SECTIONS, SAMPLE_BENCHMARK); try { installStub({ successEdit: benignEdit, failureEdit: null }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixtureGreedy.skillsDir }, async () => { const result = await runOnce(fixtureGreedy, { disableValidationGate: true }); expect(result.outcome).toBe('accepted'); // greedy expect(result.receipt.validation_gate_disabled).toBe(true); }); } finally { uninstallStub(); } } finally { fixtureGreedy.cleanup(); } }); test('D2 no-DB-pollution: subagent_messages count unchanged across a full run', async () => { const fixture = setupFixture(SKILL_PEOPLE_ONLY, CITATIONS_BENCHMARK); const countMessages = async (): Promise => { const r = await engine.executeRaw('SELECT COUNT(*) AS c FROM subagent_messages', []); const rows = Array.isArray(r) ? r : ((r as { rows?: unknown[] }).rows ?? []); return Number((rows[0] as { c?: number | string })?.c ?? 0); }; try { const before = await countMessages(); installStub({ failureEdit: { op: 'add', anchor: 'People', content: '## Citations\nCite the source.', reason: 'add' }, successEdit: null, }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { await runOnce(fixture); }); } finally { uninstallStub(); } const after = await countMessages(); // Rollouts use gateway.toolLoop with no-op persistence (D2) → zero rows written. expect(after).toBe(before); } finally { fixture.cleanup(); } }); }); // ─── T3 (review follow-ups): maxRuntimeMin abort + receipt honesty ─────────── describe('skillopt T3 — runtime deadline + receipt score honesty', () => { test('maxRuntimeMin deadline aborts cleanly with no commit', async () => { const fixture = setupFixture(SKILL_PEOPLE_ONLY, CITATIONS_BENCHMARK); try { installStub({ failureEdit: { op: 'add', anchor: 'People', content: '## Citations\nCite the source.', reason: 'add' }, successEdit: null, }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { // maxRuntimeMin:0 → deadline == run start; the first step's deadline // check fires after the baseline eval has already elapsed → abort. const result = await runOnce(fixture, { maxRuntimeMin: 0 }); expect(result.outcome).toBe('aborted'); // No commit: SKILL.md unchanged, no committed history row. const skill = fs.readFileSync(skillPath(fixture.skillsDir, SKILL), 'utf8'); expect(skill).not.toContain('## Citations'); expect(loadHistory(fixture.skillsDir, SKILL).filter((r) => r.status === 'committed')).toHaveLength(0); }); } finally { uninstallStub(); } } finally { fixture.cleanup(); } }); test('receipt records the REAL baseline_sel_score + final-test scores (regression: was hardcoded 0)', async () => { // BOTH_SECTIONS already scores ~1.0 on the alternating benchmark, so a real // baseline read is ~1.0 — a hardcoded-0 receipt would fail this immediately. const fixture = setupFixture(SKILL_BOTH_SECTIONS, SAMPLE_BENCHMARK); try { installStub({ successEdit: null, failureEdit: null }); // baseline already perfect; no edits try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { const r = (await runOnce(fixture)).receipt; // Real baseline, not the old hardcoded 0. expect(r.baseline_sel_score).toBeGreaterThan(0.9); // Final-test eval populated both test scores (D_test non-empty under 4:1:5). expect(typeof r.test_score).toBe('number'); expect(typeof r.baseline_test_score).toBe('number'); expect(r.baseline_test_score!).toBeGreaterThan(0.9); }); } finally { uninstallStub(); } } finally { fixture.cleanup(); } }); }); describe('skillopt T3 — held-out independence guard', () => { test('held-out sharing task_ids with the benchmark is rejected (gaming defense)', async () => { const fixture = setupFixture(SKILL_PEOPLE_ONLY, CITATIONS_BENCHMARK); // Held-out file reuses benchmark task_ids (cit-001..006) → must be rejected // before any optimization, since an overlapping held-out can't catch overfit. const heldOutPath = writeHeldOut(fixture, CITATIONS_BENCHMARK.slice(0, 6)); try { installStub({ failureEdit: { op: 'add', anchor: 'People', content: '## Citations\nCite.', reason: 'add' }, successEdit: null, }); try { await withEnv({ GBRAIN_AUDIT_DIR: fixture.skillsDir }, async () => { await expect(runOnce(fixture, { heldOutPath })).rejects.toThrow(/independent|shares .* task_id/i); }); } finally { uninstallStub(); } } finally { fixture.cleanup(); } }); });