/** * §15 research orchestration tests — in-memory fake KV */ import { strict as assert } from 'node:assert'; import { test, describe } from 'node:test'; import { FROZEN_SRP_GUARD, isMaterialForJames, highestValueUnblockedJob, canTransition, pickIndependentReviewer, parseStructuredHandoff, assertSrpUntouched, createResearchJob, transitionResearchJob, applyHandoff, seedSrpResearchQueue, handleResearchRoutes } from './orchestration.js'; class FakeKV { constructor() { this.store = new Map(); } async get(key) { return this.store.has(key) ? this.store.get(key) : null; } async put(key, val) { this.store.set(key, String(val)); } async list({ prefix } = {}) { const keys = []; for (const name of this.store.keys()) { if (!prefix || name.startsWith(prefix)) keys.push({ name }); } return { keys }; } async delete(key) { this.store.delete(key); } } function makeDeps(kv) { let n = 0; return { requireAuth: () => null, json: (obj, status) => new Response(JSON.stringify(obj), { status: status || 200, headers: { 'Content-Type': 'application/json' } }), readJson: async (request) => { try { return { body: await request.json() }; } catch (_) { return { error: 'bad_json' }; } }, nowIso: () => new Date(Date.UTC(2026, 8, 9, 16, 0, n++)).toISOString(), newId: () => `id-${++n}-${Math.random().toString(16).slice(2, 8)}`, corsHeaders: () => ({}), putInbox: async (env, agent, message) => { const key = `inbox:${agent}`; const raw = await env.AI_HUB.get(key); let arr = []; try { arr = raw ? JSON.parse(raw) : []; } catch (_) { arr = []; } arr.push(message); await env.AI_HUB.put(key, JSON.stringify(arr)); } }; } function envWith(kv) { return { AI_HUB: kv, HUB_KEY: 'test-key' }; } describe('§15 research orchestration', () => { test('1. evidence_ready auto creates reviewer assignment + REVIEW state', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const job = await createResearchJob(env, deps, { title: 'Evidence test', state: 'RESEARCH', assigned_agent: 'grok', proposer: 'grok', expected_value: 80, priority: 80 }); const result = await applyHandoff(env, deps, job.id, { agent: 'grok', body: 'STATUS: EVIDENCE_READY\nEVIDENCE_IDS: e1,e2\n\nFound supporting data.' }); assert.equal(result.error, undefined); assert.equal(result.job.state, 'REVIEW'); assert.ok(result.job.reviewer); assert.notEqual(result.job.reviewer, 'grok'); assert.equal(result.job.reviewer, 'claude'); // prefer claude when proposer grok assert.ok(result.job.pending_actions.some((a) => a.type === 'review_task')); const inbox = JSON.parse(await kv.get('inbox:claude')); assert.ok(inbox.length >= 1); }); test('2. proposer cannot self-final-review', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const job = await createResearchJob(env, deps, { title: 'Self review', state: 'REVIEW', assigned_agent: 'grok', proposer: 'grok', reviewer: 'claude', expected_value: 70 }); const result = await applyHandoff(env, deps, job.id, { agent: 'grok', body: 'STATUS: DECISION\nDECISION: ACCEPT\nEVIDENCE_IDS: e1\n\nI accept my own work.' }); assert.equal(result.error, 'proposer_cannot_self_final_review'); assert.equal(result.status, 403); }); test('3. revision routes back to researcher', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const job = await createResearchJob(env, deps, { title: 'Needs revision', state: 'REVIEW', assigned_agent: 'grok', proposer: 'grok', reviewer: 'claude', expected_value: 70 }); const result = await applyHandoff(env, deps, job.id, { agent: 'claude', body: 'STATUS: REVISION\nDECISION: CHANGES_REQUESTED\n\nPlease add controls.' }); assert.equal(result.error, undefined); assert.equal(result.job.state, 'RESEARCH'); assert.equal(result.job.assigned_agent, 'grok'); }); test('4. accepted review can generate next job', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const job = await createResearchJob(env, deps, { title: 'Parent accept', state: 'REVIEW', assigned_agent: 'grok', proposer: 'grok', reviewer: 'claude', expected_value: 70 }); const result = await applyHandoff(env, deps, job.id, { agent: 'claude', body: 'STATUS: DECISION\nDECISION: ACCEPT\nEVIDENCE_IDS: e9\nNEXT_ACTION: Build sensor connector\n\nLooks good.', next_jobs: [ { title: 'Build sensor connector', assigned_agent: 'grok', state: 'QUEUED', expected_value: 65, priority: 60 } ] }); assert.equal(result.error, undefined); assert.ok(result.decision_record); assert.equal(result.decision_record.decision, 'ACCEPT'); assert.ok(result.spawned_jobs.length >= 1); assert.equal(result.spawned_jobs[0].title, 'Build sensor connector'); }); test('5. blocked job does not stall unrelated (highest skips blocked)', async () => { const jobs = [ { id: 'a', state: 'RESEARCH', blockers: ['waiting on data'], expected_value: 99, priority: 99, created: '2026-01-01', dependencies: [] }, { id: 'b', state: 'QUEUED', blockers: [], expected_value: 50, priority: 50, created: '2026-01-02', dependencies: [] } ]; const h = highestValueUnblockedJob(jobs); assert.equal(h.id, 'b'); }); test('6. parallel jobs independent', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const j1 = await createResearchJob(env, deps, { title: 'Branch A', state: 'RESEARCH', assigned_agent: 'grok', expected_value: 80, research_branch: 'A' }); const j2 = await createResearchJob(env, deps, { title: 'Branch B', state: 'QUEUED', assigned_agent: 'grok', expected_value: 70, research_branch: 'B' }); await applyHandoff(env, deps, j1.id, { agent: 'grok', body: 'STATUS: EVIDENCE_READY\nEVIDENCE_IDS: x\n\nA done.' }); const still = JSON.parse(await kv.get(`research:job:${j2.id}`)); assert.equal(still.state, 'QUEUED'); assert.equal(still.title, 'Branch B'); }); test('7. duplicate handoffs idempotent', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const job = await createResearchJob(env, deps, { title: 'Idem', state: 'RESEARCH', assigned_agent: 'grok', proposer: 'grok', expected_value: 60 }); const body = 'STATUS: EVIDENCE_READY\nEVIDENCE_IDS: a,b\nDECISION: \nNEXT_ACTION: review\n\nData.'; const r1 = await applyHandoff(env, deps, job.id, { agent: 'grok', body }); const r2 = await applyHandoff(env, deps, job.id, { agent: 'grok', body }); assert.equal(r1.idempotent, false); assert.equal(r2.idempotent, true); assert.equal(r2.job.state, r1.job.state); }); test('8. acknowledgement-only isMaterialForJames false', () => { assert.equal(isMaterialForJames({ STATUS: 'ACK' }, 'ACK'), false); assert.equal(isMaterialForJames({}, 'acknowledged'), false); assert.equal(isMaterialForJames({}, 'heartbeat'), false); assert.equal( isMaterialForJames({ STATUS: 'EVIDENCE_READY', EVIDENCE_IDS: 'e1' }, 'new evidence found'), true ); assert.equal(isMaterialForJames({ DECISION: 'ACCEPT' }, 'accepted with rationale'), true); }); test('9. potentially_novel triggers replication job', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const job = await createResearchJob(env, deps, { title: 'Novel finding', state: 'RESEARCH', assigned_agent: 'grok', proposer: 'grok', expected_value: 90 }); const result = await applyHandoff(env, deps, job.id, { agent: 'grok', body: 'STATUS: EVIDENCE_READY\nSIGNIFICANCE: POTENTIALLY_NOVEL\nREQUIRES_REPLICATION: YES\nEVIDENCE_IDS: n1\n\nUnexpected relationship.' }); assert.equal(result.error, undefined); assert.ok(result.replication_job); assert.equal(result.replication_job.significance, 'REPLICATION_REQUIRED'); assert.notEqual(result.replication_job.assigned_agent, 'grok'); }); test('10. transitions append-only (length increases, old preserved)', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const job = await createResearchJob(env, deps, { title: 'Append', state: 'QUEUED', assigned_agent: 'grok' }); const t0 = JSON.parse(await kv.get(`research:transitions:${job.id}`)); assert.ok(t0.length >= 1); const firstId = t0[0].id; await transitionResearchJob(env, deps, job.id, { to: 'RESEARCH', by: 'grok', reason: 'start' }); const t1 = JSON.parse(await kv.get(`research:transitions:${job.id}`)); assert.ok(t1.length > t0.length); assert.equal(t1[0].id, firstId); }); test('11. superseded decisions retrievable', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const job = await createResearchJob(env, deps, { title: 'Decisions', state: 'REVIEW', assigned_agent: 'grok', proposer: 'grok', reviewer: 'claude' }); const r1 = await applyHandoff(env, deps, job.id, { agent: 'claude', body: 'DECISION: CONDITIONAL_ACCEPT\nEVIDENCE_IDS: d1\n\nFirst decision.', meta: { STATUS: 'DECISION', DECISION: 'CONDITIONAL_ACCEPT', EVIDENCE_IDS: 'd1' } }); assert.ok(r1.decision_record); // Manually append a superseding decision record in KV (append-only history) const d1 = r1.decision_record; const d2 = { ...d1, id: deps.newId(), decision: 'ACCEPT', parent_decision_id: d1.id, supersedes: d1.id, rationale: 'Superseding after more evidence', at: deps.nowIso() }; await kv.put(`research:decisions:${d2.id}`, JSON.stringify(d2)); const idx = JSON.parse(await kv.get('research:decisions:idx')); idx.unshift(d2.id); await kv.put('research:decisions:idx', JSON.stringify(idx)); // Mark old as superseded_by (append field — old record still retrievable) d1.superseded_by = d2.id; await kv.put(`research:decisions:${d1.id}`, JSON.stringify(d1)); const old = JSON.parse(await kv.get(`research:decisions:${d1.id}`)); const neu = JSON.parse(await kv.get(`research:decisions:${d2.id}`)); assert.equal(old.decision, 'CONDITIONAL_ACCEPT'); assert.equal(old.superseded_by, d2.id); assert.equal(neu.supersedes, d1.id); }); test('12. orchestration does not mutate a frozen srpState object', async () => { const srpState = { constructs: { L1: { id: 'L1-US-v0.1', score_authorized: true }, M1: { score_authorized: false, status: 'UNKNOWN' } }, phase: 'UNKNOWN', alert: 'UNKNOWN' }; Object.freeze(srpState.constructs.L1); Object.freeze(srpState.constructs.M1); Object.freeze(srpState.constructs); Object.freeze(srpState); const before = JSON.stringify(srpState); assertSrpUntouched(srpState); assert.equal(FROZEN_SRP_GUARD.L1.score_authorized, true); assert.equal(FROZEN_SRP_GUARD.M1.score_authorized, false); // Run orchestration side effects — must not touch srpState const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); await seedSrpResearchQueue(env, deps); await createResearchJob(env, deps, { title: 'x', state: 'QUEUED' }); assert.equal(JSON.stringify(srpState), before); assertSrpUntouched(srpState); }); test('13. invalid transition fails safely without inventing state', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const job = await createResearchJob(env, deps, { title: 'Invalid', state: 'QUEUED', assigned_agent: 'grok' }); const result = await transitionResearchJob(env, deps, job.id, { to: 'VALIDATE', by: 'grok', reason: 'nope' }); assert.equal(result.error, 'invalid_transition'); const fresh = JSON.parse(await kv.get(`research:job:${job.id}`)); assert.equal(fresh.state, 'QUEUED'); assert.equal(canTransition('QUEUED', 'VALIDATE'), false); assert.equal(canTransition('QUEUED', 'RESEARCH'), true); }); test('helpers: parseStructuredHandoff + pickIndependentReviewer', () => { const { meta, body } = parseStructuredHandoff( 'STATUS: REVIEW\nDECISION: ACCEPT\nEVIDENCE_IDS: a,b\n\nHuman prose here.' ); assert.equal(meta.STATUS, 'REVIEW'); assert.equal(meta.DECISION, 'ACCEPT'); assert.match(body, /Human prose/); assert.equal(pickIndependentReviewer('grok'), 'claude'); assert.equal(pickIndependentReviewer('claude'), 'astra'); }); test('QA: nonexistent dependency rejected on create', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const job = await createResearchJob(env, deps, { title: 'qa-missing-dep', state: 'QUEUED', dependencies: ['does-not-exist-dep'], assigned_agent: 'grok', reviewer: 'claude', proposer: 'grok' }); assert.equal(job.error, 'missing_dependencies'); assert.ok(job.missing.includes('does-not-exist-dep')); }); test('QA: QUEUED→RESEARCH blocked when dependency missing', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const job = await createResearchJob(env, deps, { title: 'qa-transition-missing-dep', state: 'QUEUED', assigned_agent: 'grok', reviewer: 'claude', proposer: 'chatgpt' }); assert.ok(job.id); job.dependencies = ['no-such-dep-id']; await env.AI_HUB.put(`research:job:${job.id}`, JSON.stringify(job)); const result = await transitionResearchJob(env, deps, job.id, { to: 'RESEARCH', by: 'chatgpt', reason: 'qa' }); assert.equal(result.error, 'dependencies_unsatisfied'); assert.equal(result.status, 400); }); test('QA: EVIDENCE_READY with empty EVIDENCE_IDS rejected', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const job = await createResearchJob(env, deps, { title: 'qa-empty-evidence', state: 'RESEARCH', assigned_agent: 'grok', reviewer: 'claude', proposer: 'grok' }); const result = await applyHandoff(env, deps, job.id, { agent: 'grok', body: 'STATUS: EVIDENCE_READY\nEVIDENCE_IDS:\n\nNo ids.' }); assert.equal(result.error, 'evidence_ids_required'); const fresh = JSON.parse(await env.AI_HUB.get(`research:job:${job.id}`)); assert.equal(fresh.state, 'RESEARCH'); }); test('QA: self-reviewer on create auto-corrected', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); const job = await createResearchJob(env, deps, { title: 'qa-self-review', state: 'QUEUED', assigned_agent: 'grok', reviewer: 'grok', proposer: 'grok' }); assert.ok(job.id); assert.notEqual(job.reviewer, 'grok'); }); test('HTTP handleResearchRoutes queue + schema', async () => { const kv = new FakeKV(); const env = envWith(kv); const deps = makeDeps(kv); await seedSrpResearchQueue(env, deps); const req = new Request('https://example.com/research/queue'); const res = await handleResearchRoutes(req, env, deps); assert.ok(res); const data = await res.json(); assert.ok(data.count >= 6); assert.ok(data.highest_value_unblocked_job); assert.equal(data.highest_value_unblocked_job.research_branch, 'M1-LONG'); const schemaRes = await handleResearchRoutes( new Request('https://example.com/research/schema'), env, deps ); const schema = await schemaRes.json(); assert.ok(schema.states.includes('REVIEW')); assert.ok(schema.frozen_srp_guard); const miss = await handleResearchRoutes( new Request('https://example.com/jobs'), env, deps ); assert.equal(miss, null); }); });