diff --git a/src/__tests__/reasoning/detectors.test.ts b/src/__tests__/reasoning/detectors.test.ts index 3a84f3f..599a008 100644 --- a/src/__tests__/reasoning/detectors.test.ts +++ b/src/__tests__/reasoning/detectors.test.ts @@ -58,7 +58,7 @@ function withFiller(claims: FixtureClaim[], edges: FixtureEdge[], padTo: number // ── Load-bearing vibes ────────────────────────────────────────────────────── describe('detectLoadBearingVibes', () => { - it('fires when vibes claim has ≥3 downstream', () => { + it('fires when vibes claim has ≥2 downstream (threshold tuned in #129)', () => { const g = withFiller([ { id: 'v1', basis: 'vibes', text: 'we need auth' }, { id: 'd1', basis: 'deduction' }, @@ -80,10 +80,8 @@ describe('detectLoadBearingVibes', () => { const g = withFiller([ { id: 'v1', basis: 'vibes' }, { id: 'd1', basis: 'deduction' }, - { id: 'd2', basis: 'deduction' }, ], [ { from: 'd1', to: 'v1', type: 'depends_on' }, - { from: 'd2', to: 'v1', type: 'supports' }, ]); expect(detectLoadBearingVibes(g)).toHaveLength(0); }); @@ -152,7 +150,10 @@ describe('detectUnchallengedChain', () => { // chainHasChallenge inspects edges where both endpoints are in the chain, // so a challenge from outside doesn't count. To properly challenge, the // question edge needs both endpoints in the chain. - // Reconfigure: have c2 questions c1 within the chain. + // Note the detector walks a chain from EVERY node, so a sub-chain that + // starts below the challenged pair (here c2→c3→c4) is itself a candidate + // once it meets the length minimum. Challenge each link so no qualifying + // sub-chain is left unchallenged. const g2 = withFiller([ { id: 'c1', basis: 'assumption' }, { id: 'c2', basis: 'deduction' }, @@ -163,6 +164,7 @@ describe('detectUnchallengedChain', () => { { from: 'c2', to: 'c3', type: 'depends_on' }, { from: 'c3', to: 'c4', type: 'depends_on' }, { from: 'c2', to: 'c1', type: 'questions' }, + { from: 'c3', to: 'c2', type: 'questions' }, ]); expect(detectUnchallengedChain(g2)).toHaveLength(0); }); @@ -171,10 +173,8 @@ describe('detectUnchallengedChain', () => { const g = withFiller([ { id: 'c1', basis: 'assumption' }, { id: 'c2', basis: 'deduction' }, - { id: 'c3', basis: 'deduction' }, ], [ { from: 'c1', to: 'c2', type: 'depends_on' }, - { from: 'c2', to: 'c3', type: 'depends_on' }, ]); expect(detectUnchallengedChain(g)).toHaveLength(0); }); @@ -238,7 +238,7 @@ describe('detectEchoChamber', () => { // ── Bright: well-sourced load-bearer ──────────────────────────────────────── describe('detectWellSourcedLoadBearer', () => { - it('fires on research/empirical/deduction basis with ≥3 downstream', () => { + it('fires on research/empirical/deduction basis with ≥2 downstream (threshold tuned in #129)', () => { const g = withFiller([ { id: 'r1', basis: 'research', text: 'OWASP ranks XSS #3' }, { id: 'd1', basis: 'deduction' }, { id: 'd2', basis: 'deduction' }, { id: 'd3', basis: 'deduction' }, diff --git a/src/__tests__/reasoning/pipeline-per-detector.test.ts b/src/__tests__/reasoning/pipeline-per-detector.test.ts index 4bcb0f9..d1d346f 100644 --- a/src/__tests__/reasoning/pipeline-per-detector.test.ts +++ b/src/__tests__/reasoning/pipeline-per-detector.test.ts @@ -81,7 +81,13 @@ describe('pipeline integration — all 6 detectors end-to-end', () => { expect(t).toBe('unchallenged_chain'); }); - it('fires echo_chamber', () => { + it('echo_chamber graphs currently surface as load_bearing_vibes', () => { + // Since the #129 threshold tuning, any graph that qualifies for + // echo_chamber (user vibes claim, ≥2 assistant supports) also qualifies + // for load_bearing_vibes (≥2 downstream, same edges), and selection has + // no per-type priority — so load_bearing_vibes wins every time. This + // test pins the current behavior; whether echo_chamber should be able + // to surface at all again is an open product question. const t = seedAndRun({ claims: [ { text: 'im sure', basis: 'vibes', speaker: 'user', confidence: 'medium', external_id: 'u' }, @@ -96,7 +102,7 @@ describe('pipeline integration — all 6 detectors end-to-end', () => { { from: 'a2', to: 'u', type: 'supports' }, ], }); - expect(t).toBe('echo_chamber'); + expect(t).toBe('load_bearing_vibes'); }); it('fires well_sourced_load_bearer', () => { @@ -139,6 +145,9 @@ describe('pipeline integration — all 6 detectors end-to-end', () => { }); it('fires grounded_premise_adopted', () => { + // Exactly one assistant support: enough for grounded_premise_adopted + // (min 1 since #129) while staying below well_sourced_load_bearer's + // downstream minimum, so the grounded finding is the one that surfaces. const t = seedAndRun({ claims: [ { text: 'OWASP XSS #3', basis: 'research', speaker: 'user', confidence: 'high', external_id: 'u' }, @@ -150,7 +159,6 @@ describe('pipeline integration — all 6 detectors end-to-end', () => { ], edges: [ { from: 'a', to: 'u', type: 'supports' }, - { from: 'b', to: 'u', type: 'depends_on' }, ], }); expect(t).toBe('grounded_premise_adopted');