// --------------------------------------------------------------------------- // caduceus — lens calibration acceptance test (v0.6.3 T15) // // Per `openspec/changes/archive/2026-08-18T16-30-00-000Z-caduceus-v0.6.3-lens-calibration/design.md` // §5.3 and CON-013: this test asserts the post-calibration false- // positive ratio is below the agreed thresholds. // // In v0.6.3 T1 the test is added in RED state (the pre-calibration // numbers exceed the thresholds). It transitions to GREEN as each // per-lens calibration lands (T4, T6, T8, T10, T12, T14). // // The test imports lib/lens/index.ts directly rather than shelling // out to scripts/lens-calibration.mjs (REQ-014); they share the // underlying registry and produce identical numbers on the same // change directories. // --------------------------------------------------------------------------- import test from "node:test"; import assert from "node:assert/strict"; import { defaultLensRegistry } from "../../lib/lens/index.ts"; const ARCHIVES = Object.freeze([ "openspec/changes/archive/2026-08-14T08-03-37-121Z-caduceus-v0.5.0-lifecycle-foundation", "openspec/changes/archive/2026-08-17T03-26-34-146Z-caduceus-v0.6.0-lens-collection", ]); type Counts = { p0: number; p1: number; p2: number; p3: number; selfFire: number }; const EMPTY_COUNTS: Counts = { p0: 0, p1: 0, p2: 0, p3: 0, selfFire: 0 }; async function runAll(changeDir: string): Promise { const reg = defaultLensRegistry(); const counts: Counts = { ...EMPTY_COUNTS }; for (const lens of reg.list()) { if (!lens.run) continue; const result = await lens.run(changeDir); for (const f of result.findings) { if (f.severity === "P0") counts.p0++; if (f.severity === "P1") counts.p1++; if (f.severity === "P2") counts.p2++; if (f.severity === "P3") counts.p3++; if (f.location.includes("lib/lens/")) counts.selfFire++; } } return counts; } test("CON-013: lens calibration floor met on both archives", async () => { let p0 = 0; let p1 = 0; let selfFire = 0; for (const a of ARCHIVES) { const c = await runAll(a); p0 += c.p0; p1 += c.p1; selfFire += c.selfFire; } assert.equal(p0, 0, `P0 total over both archives must be 0, got ${p0}`); assert.ok( p1 <= 8, `P1 total over both archives must be <= 8 (CON-013: <= 4 per archive), got ${p1}`, ); assert.equal(selfFire, 0, `no finding may point inside lib/lens/; got ${selfFire}`); }); test("CON-013a: P1 on v0.6.0 archive is <= 4 post-calibration", async () => { const c = await runAll(ARCHIVES[1]); assert.ok( c.p1 <= 4, `P1 on v0.6.0 archive must be <= 4 (CON-013), got ${c.p1}`, ); }); test("CON-013b: P1 on v0.5.0 archive is <= 4 post-calibration", async () => { const c = await runAll(ARCHIVES[0]); assert.ok( c.p1 <= 4, `P1 on v0.5.0 archive must be <= 4 (CON-013), got ${c.p1}`, ); }); test("CON-013c: P0 across both archives is 0", async () => { let p0 = 0; for (const a of ARCHIVES) { const c = await runAll(a); p0 += c.p0; } assert.equal(p0, 0, `P0 total must be 0, got ${p0}`); }); test("CON-013d: no self-firing findings across both archives", async () => { let selfFire = 0; for (const a of ARCHIVES) { const c = await runAll(a); selfFire += c.selfFire; } assert.equal(selfFire, 0, `no finding may point inside lib/lens/; got ${selfFire}`); });