// tests/scripts/trekreview-armb-workflow.test.mjs // The opt-in Workflow substrate for /trekreview Phase 5–6, run for real with // stubbed Workflow globals (agent/parallel/phase/log). Ordre 5: a reviewer or // engine that did not report forbids ALLOW, and the verdict's floor is // recomputed from the findings the coordinator returns. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { readFileSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { illegalBriefLowering } from '../../lib/review/coordinator-contract.mjs'; import { RULE_CATALOGUE } from '../../lib/review/rule-catalogue.mjs'; const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..'); const SRC = readFileSync(join(ROOT, 'scripts', 'trekreview-armB.workflow.mjs'), 'utf-8') .replace('export const meta', 'const meta'); const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor; const run = new AsyncFunction('args', 'agent', 'parallel', 'phase', 'log', SRC); const BASE = { briefPath: 'b.md', diffPath: 'd.diff', triage: '' }; const engine = (reviewer, findings = []) => ({ reviewer, findings }); function stubs({ reviewers = [], coordinator = null } = {}) { let i = 0; const calls = { coordinatorPrompt: null, coordinatorOpts: null }; return { calls, agent: async (prompt, opts) => { if (opts.label === 'coordinator') { calls.coordinatorPrompt = prompt; calls.coordinatorOpts = opts; return coordinator; } return reviewers[i++] ?? null; }, parallel: async (thunks) => Promise.all(thunks.map((t) => t())), phase: () => {}, log: () => {}, }; } async function exec(args, s) { return run(args, s.agent, s.parallel, s.phase, s.log); } test('armB — both reviewers and both engines clean → ALLOW (known-positive control)', async () => { const s = stubs({ reviewers: [{ reviewer: 'c', findings: [] }, { reviewer: 'k', findings: [] }] }); const r = await exec({ ...BASE, enginePayloads: [engine('claude-code-review'), engine('claude-security-review')] }, s); assert.equal(r.verdict, 'ALLOW'); assert.deepEqual(r.missing_engines, []); }); test('armB — engines the caller never mentioned are missing → not ALLOW', async () => { const s = stubs({ reviewers: [{ reviewer: 'c', findings: [] }, { reviewer: 'k', findings: [] }] }); const r = await exec({ ...BASE }, s); assert.equal(r.verdict, 'WARN'); assert.deepEqual(r.missing_engines, ['claude-code-review', 'claude-security-review']); }); test('armB — a reviewer that returned nothing → not ALLOW', async () => { const s = stubs({ reviewers: [null, { reviewer: 'k', findings: [] }] }); const r = await exec({ ...BASE, enginePayloads: [engine('claude-code-review'), engine('claude-security-review')] }, s); assert.equal(r.verdict, 'WARN'); }); test('armB — a coordinator ALLOW over a BLOCKER finding is recomputed to BLOCK', async () => { const f = { severity: 'BLOCKER', rule_key: 'UNIMPLEMENTED_CRITERION', file: 'a.mjs', line: 1, title: 't', detail: 'd' }; const s = stubs({ reviewers: [{ reviewer: 'c', findings: [f] }, { reviewer: 'k', findings: [] }], coordinator: { verdict: 'ALLOW', findings: [f] }, }); const r = await exec({ ...BASE, enginePayloads: [engine('claude-code-review'), engine('claude-security-review')] }, s); assert.equal(r.verdict, 'BLOCK'); }); test('armB — quick mode expects only the /code-review engine', async () => { const s = stubs({ reviewers: [{ reviewer: 'k', findings: [] }] }); const r = await exec({ ...BASE, quick: true, enginePayloads: [engine('claude-code-review')] }, s); assert.equal(r.verdict, 'ALLOW'); assert.deepEqual(r.missing_engines, []); }); // ---- Step 9: the brief-conflict rule on the Workflow path --------------------------- // // Each case stubs the reviewers' and engines' payloads (so `merged` holds the // input), the coordinator's returned findings and its verdict, then asserts the // workflow's verdict. `merged` is read from the prompt the coordinator stub // receives. For every case the test states the value of `illegalBriefLowering` // on the subject finding as returned, with `engine` set from its counterpart's // source (removed when the counterpart is a reviewer finding or absent), and // checks the floor raised a finding exactly where that value is true — plus // case 7, where a false `engine` label alone raises. const RANK = ['BLOCKER', 'MAJOR', 'MINOR', 'SUGGESTION']; const rank = (s) => (RANK.indexOf(s) === -1 ? RANK.length : RANK.indexOf(s)); const MARK = 'Non-Goal: no new runtime dependency'; const FILE = 'src/a.mjs'; const LINE = 10; const F = (rule_key, severity, extra = {}) => ({ severity, rule_key, file: FILE, line: LINE, title: 't', detail: 'd', ...extra }); const PROMPT_JSON_MARK = '=== PRE-DEDUPED FINDINGS (JSON) ==='; const triplet = (f) => `${f.file}\u0000${f.line}\u0000${f.rule_key}`; async function floorCase({ reviewerFindings = [], codeFindings = [], securityFindings = [], quick = false, returned, verdict }) { const before = structuredClone(returned); const coordinator = { verdict, findings: structuredClone(returned) }; const reviewers = quick ? [{ reviewer: 'code-correctness', findings: structuredClone(reviewerFindings) }] : [{ reviewer: 'brief-conformance', findings: structuredClone(reviewerFindings) }, { reviewer: 'code-correctness', findings: [] }]; const enginePayloads = quick ? [engine('claude-code-review', structuredClone(codeFindings))] : [engine('claude-code-review', structuredClone(codeFindings)), engine('claude-security-review', structuredClone(securityFindings))]; const s = stubs({ reviewers, coordinator }); const r = await exec({ ...BASE, quick, enginePayloads }, s); const prompt = s.calls.coordinatorPrompt; assert.ok(typeof prompt === 'string', 'the coordinator ran'); const merged = JSON.parse(prompt.slice(prompt.indexOf(PROMPT_JSON_MARK) + PROMPT_JSON_MARK.length)); const raised = r.findings.some((f, i) => rank(f.severity) < rank(before[i].severity)); return { r, merged, before, raised, s }; } // The subject (index 0 of `returned`) with `engine` from its counterpart's source. function subjectIllegal(before, merged, idx = 0) { const subject = { ...before[idx] }; const counterpart = merged.find((m) => triplet(m) === triplet(subject)); if (counterpart && counterpart.engine) subject.engine = counterpart.engine; else delete subject.engine; return illegalBriefLowering(subject); } function checkRaise({ before, merged, raised }, statedIllegal, extraRaise = false) { assert.equal(subjectIllegal(before, merged), statedIllegal, 'illegalBriefLowering on the subject'); assert.equal(raised, statedIllegal || extraRaise, 'the floor raised a finding exactly where the shape is illegal'); } test('armB brief rule (1) — an unlabelled SECURITY_REVIEW_VULNERABILITY returned at MINOR over a /security-review BLOCKER → BLOCK', async () => { const c = await floorCase({ securityFindings: [F('SECURITY_REVIEW_VULNERABILITY', 'BLOCKER')], returned: [F('SECURITY_REVIEW_VULNERABILITY', 'MINOR')], verdict: 'ALLOW', }); assert.equal(c.r.verdict, 'BLOCK'); assert.equal(c.r.findings[0].engine, 'security-review'); checkRaise(c, true); }); test('armB brief rule (2) — an unlabelled CODE_REVIEW_DEFECT returned at MINOR over a /code-review MAJOR with ALLOW → WARN', async () => { const c = await floorCase({ codeFindings: [F('CODE_REVIEW_DEFECT', 'MAJOR')], returned: [F('CODE_REVIEW_DEFECT', 'MINOR')], verdict: 'ALLOW', }); assert.equal(c.r.verdict, 'WARN'); checkRaise(c, true); }); for (const withEngine of [true, false]) { test(`armB brief rule (3) — the legal lowering keeps ALLOW (returned ${withEngine ? 'with' : 'without'} engine) and carries engine code-review`, async () => { const lowered = F('CODE_REVIEW_DEFECT', 'MINOR', { contradicts_brief: MARK, brief_lowered_from: 'MAJOR' }); if (withEngine) lowered.engine = 'code-review'; const c = await floorCase({ codeFindings: [F('CODE_REVIEW_DEFECT', 'MAJOR')], returned: [lowered], verdict: 'ALLOW', }); assert.equal(c.r.verdict, 'ALLOW'); assert.equal(c.r.findings[0].engine, 'code-review'); assert.equal(c.r.findings[0].severity, 'MINOR'); checkRaise(c, false); }); } test('armB brief rule (4) — the legal shape over a reviewer CODE_REVIEW_DEFECT asserted BLOCKER (relabelled engine) → BLOCK, no engine', async () => { const c = await floorCase({ reviewerFindings: [F('CODE_REVIEW_DEFECT', 'BLOCKER')], returned: [F('CODE_REVIEW_DEFECT', 'MINOR', { engine: 'code-review', contradicts_brief: MARK, brief_lowered_from: 'MAJOR' })], verdict: 'ALLOW', }); assert.equal(c.r.verdict, 'BLOCK'); assert.equal(c.r.findings[0].engine, undefined); checkRaise(c, true); }); test('armB brief rule (5) — a reviewer finding that carries engine code-review is not an engine finding: the source decides → WARN', async () => { const c = await floorCase({ reviewerFindings: [F('CODE_REVIEW_DEFECT', 'MAJOR', { engine: 'code-review' })], returned: [F('CODE_REVIEW_DEFECT', 'MINOR', { engine: 'code-review', contradicts_brief: MARK, brief_lowered_from: 'MAJOR' })], verdict: 'ALLOW', }); assert.equal(c.merged[0].engine, undefined, 'a reviewer engine field never reaches merged'); assert.equal(c.r.verdict, 'WARN'); checkRaise(c, true); }); test('armB brief rule (6) — a marked reviewer MISSING_TEST asserted BLOCKER, returned at MAJOR with WARN → BLOCK', async () => { const c = await floorCase({ reviewerFindings: [F('MISSING_TEST', 'BLOCKER')], returned: [F('MISSING_TEST', 'MAJOR', { contradicts_brief: MARK })], verdict: 'WARN', }); assert.equal(c.r.verdict, 'BLOCK'); checkRaise(c, true); }); test('armB brief rule (7) — a reviewer MISSING_TEST asserted BLOCKER, returned at MAJOR with only engine security-review → BLOCK, no engine', async () => { const c = await floorCase({ reviewerFindings: [F('MISSING_TEST', 'BLOCKER')], returned: [F('MISSING_TEST', 'MAJOR', { engine: 'security-review' })], verdict: 'WARN', }); assert.equal(c.r.verdict, 'BLOCK'); assert.equal(c.r.findings[0].engine, undefined); checkRaise(c, false, true); }); test('armB brief rule (8) — a stray brief_lowered_from on a reviewer finding asserted BLOCKER, returned at MAJOR with WARN → BLOCK', async () => { const c = await floorCase({ reviewerFindings: [F('MISSING_TEST', 'BLOCKER')], returned: [F('MISSING_TEST', 'MAJOR', { brief_lowered_from: 'MAJOR' })], verdict: 'WARN', }); assert.equal(c.r.verdict, 'BLOCK'); checkRaise(c, true); }); test('armB brief rule (9) — a marked finding with no counterpart and an unknown rule_key, returned at MINOR with ALLOW → BLOCK', async () => { const other = { severity: 'MINOR', rule_key: 'MISSING_ERROR_HANDLING', file: 'src/b.mjs', line: 3, title: 't', detail: 'd' }; const c = await floorCase({ reviewerFindings: [other], returned: [{ severity: 'MINOR', rule_key: 'NOT_IN_THE_CATALOGUE', file: 'src/ghost.mjs', line: 7, title: 't', detail: 'd', contradicts_brief: MARK }, other], verdict: 'ALLOW', }); assert.equal(c.r.verdict, 'BLOCK'); checkRaise(c, true); }); test('armB brief rule (10) — a marked BLOCKER-tier /code-review finding returned at MINOR → BLOCK', async () => { const c = await floorCase({ codeFindings: [F('SECURITY_INJECTION', 'BLOCKER')], returned: [F('SECURITY_INJECTION', 'MINOR', { engine: 'code-review', contradicts_brief: MARK })], verdict: 'ALLOW', }); assert.equal(c.r.verdict, 'BLOCK'); checkRaise(c, true); }); test('armB brief rule (11) — dedup keeps the /code-review MAJOR over a first-seen reviewer MINOR on the same triplet → WARN', async () => { const c = await floorCase({ reviewerFindings: [F('CODE_REVIEW_DEFECT', 'MINOR')], codeFindings: [F('CODE_REVIEW_DEFECT', 'MAJOR')], returned: [F('CODE_REVIEW_DEFECT', 'MINOR')], verdict: 'ALLOW', }); assert.equal(c.merged.length, 1); assert.equal(c.merged[0].severity, 'MAJOR'); assert.equal(c.merged[0].engine, 'code-review'); assert.equal(c.r.deduped_count, 1); assert.equal(c.r.raw_finding_count, 2); assert.equal(c.r.verdict, 'WARN'); checkRaise(c, true); }); test('armB brief rule (12) — an unlabelled reviewer MISSING_TEST asserted BLOCKER, returned at MAJOR (catalogue reset) with WARN stays WARN', async () => { const c = await floorCase({ reviewerFindings: [F('MISSING_TEST', 'BLOCKER')], returned: [F('MISSING_TEST', 'MAJOR')], verdict: 'WARN', }); assert.equal(c.r.verdict, 'WARN'); checkRaise(c, false); }); test('armB brief rule (13) — a marked MINOR-tier engine finding at its tier (CODE_REVIEW_IMPROVEMENT) keeps ALLOW', async () => { const c = await floorCase({ codeFindings: [F('CODE_REVIEW_IMPROVEMENT', 'MINOR')], returned: [F('CODE_REVIEW_IMPROVEMENT', 'MINOR', { engine: 'code-review', contradicts_brief: MARK })], verdict: 'ALLOW', }); assert.equal(c.r.verdict, 'ALLOW'); assert.equal(c.r.findings[0].engine, 'code-review'); checkRaise(c, false); }); test('armB brief rule (14) — an unlabelled reviewer MISSING_TEST at MINOR (quick mode) keeps the coordinator verdict', async () => { const c = await floorCase({ quick: true, reviewerFindings: [F('MISSING_TEST', 'MINOR')], returned: [F('MISSING_TEST', 'MINOR')], verdict: 'ALLOW', }); assert.equal(c.r.verdict, 'ALLOW'); checkRaise(c, false); }); test('armB brief rule — RULE_TIERS in the script equals the catalogue (same keys, same tiers)', () => { const start = SRC.indexOf('const RULE_TIERS = {'); assert.ok(start !== -1, 'the script carries a RULE_TIERS literal block'); const block = SRC.slice(start, SRC.indexOf('};', start)); const tiers = Object.fromEntries([...block.matchAll(/([A-Z_]+):\s*'([A-Z]+)'/g)].map((m) => [m[1], m[2]])); const catalogue = Object.fromEntries(RULE_CATALOGUE.map((r) => [r.rule_key, r.severity])); assert.deepEqual(tiers, catalogue); }); test('armB brief rule — VERDICT_SCHEMA declares engine, contradicts_brief and brief_lowered_from; the prompt states the rule', async () => { const c = await floorCase({ codeFindings: [F('CODE_REVIEW_DEFECT', 'MAJOR')], returned: [F('CODE_REVIEW_DEFECT', 'MAJOR')], verdict: 'WARN', }); const props = c.s.calls.coordinatorOpts.schema.properties.findings.items.properties; assert.deepEqual(props.engine, { type: 'string', enum: ['code-review', 'security-review'] }); assert.deepEqual(props.contradicts_brief, { type: 'string' }); assert.deepEqual(props.brief_lowered_from, { type: 'string', enum: ['MAJOR'] }); const prompt = c.s.calls.coordinatorPrompt; assert.ok(prompt.includes('contradicts_brief')); assert.ok(prompt.includes('brief_lowered_from')); assert.ok(prompt.includes('## Non-Goals')); assert.ok(prompt.includes('## Constraints')); assert.ok(prompt.includes('## Preferences')); assert.ok(prompt.includes('## Open Questions / Assumptions')); assert.ok(/only an engine finding/i.test(prompt), 'engine findings only'); assert.ok(/kept and marked, never removed/i.test(prompt)); }); // Decision 13 (operator 12.10): on a severity tie a reviewer-agent finding // survives dedup over an engine finding, so the engine's mark can never lower // what a reviewer agent asserted (the same rule as coordinator-contract Pass 1). test('armB brief rule (15) — a tie between a reviewer MAJOR and a /code-review MAJOR keeps the reviewer: the legal shape returned with ALLOW → WARN, no engine', async () => { const c = await floorCase({ reviewerFindings: [F('CODE_REVIEW_DEFECT', 'MAJOR')], codeFindings: [F('CODE_REVIEW_DEFECT', 'MAJOR')], returned: [F('CODE_REVIEW_DEFECT', 'MINOR', { contradicts_brief: MARK, brief_lowered_from: 'MAJOR', engine: 'code-review' })], verdict: 'ALLOW', }); assert.equal(c.merged.length, 1); assert.equal(c.merged[0].severity, 'MAJOR'); assert.equal('engine' in c.merged[0], false, 'the tie keeps the reviewer finding'); assert.equal(c.r.verdict, 'WARN'); assert.equal(c.r.findings[0].severity, 'MAJOR'); assert.equal('engine' in c.r.findings[0], false); checkRaise(c, true); });