/** * Tests for `orchestrate.ts` (cache-ordered candidate pool). * * The orchestrator composes the stable-prefix lanes (curated core + frecency * hot, both computed at lane init and passed in) with three deterministic * finder lanes — the section-grain BM25 needle, the dense lane, and link-graph * edge expansion — into ONE cache-ordered pool, then runs a SINGLE forced-tool * select over it. The result is this turn's selections only; cross-turn * persistence is the injector's job, not the orchestrator's. * * The select provider is stubbed (no network); a single stub answers the one * `select_pages` call per turn by reading the numbered `` block. * The dense lane is stubbed at the module boundary. The needle and edge graph * are real, built from tiny inline fixtures so the pool assembly is exercised * end-to-end. */ import { afterAll, beforeEach, describe, expect, mock, spyOn, test, } from "bun:test"; import type { Message, Provider, ProviderResponse } from "@vellumai/plugin-api"; import { runWithLatencySubSpans } from "../../../../../daemon/turn-latency-sub-spans.js"; import { MEMORY_CONTEXT_PHASE_KEY, TurnLatencyTracker, } from "../../../../../daemon/turn-latency-tracker.js"; import type { PageIndexEntry } from "../../substrate/page-index.js"; import { renderCard } from "../card.js"; import type { EdgeGraph } from "../edge.js"; import { buildEdgeGraph } from "../edge.js"; import type { V3GateConfig } from "../gate.js"; import type { OrchestrateDeps } from "../orchestrate.js"; import { buildSectionNeedle } from "../section-needle.js"; import { buildSectionIndex } from "../sections.js"; import type { MemoryRoutingTurn, SectionIndex, Slug } from "../types.js"; // --------------------------------------------------------------------------- // Module stubs installed BEFORE the orchestrator import so pool-select and the // dense lane observe them at load time. // --------------------------------------------------------------------------- let providerStub: Provider | null = null; const realPluginApi = await import("@vellumai/plugin-api"); mock.module("@vellumai/plugin-api", () => ({ ...realPluginApi, getConfiguredProvider: async () => providerStub, })); mock.module("../../../../../util/logger.js", () => ({ getLogger: () => new Proxy({} as Record, { get: (_t, prop) => (prop === "child" ? () => ({}) : () => {}), }), })); // The dense lane is stubbed: each test sets `denseHits` to control which // articles (+ matched ordinals) the dense lane returns. The stub DELEGATES to // the real `denseLane` unless this file's tests are running (`denseMockActive`), // so the process-global `mock.module` cannot leak fake behavior into // dense.test.ts (which exercises the real lane). Spread the real module so // every other export (`OVERSAMPLE`) stays present. const realDense = { ...(await import("../dense.js")) }; let denseMockActive = false; let denseHits: Array<{ article: Slug; section: number; score?: number }> = []; // Per-query hit override for tests that need the full-message and span-chunk // dense calls to return DIFFERENT results; falls back to `denseHits`. let denseHitsByQuery = new Map< string, Array<{ article: Slug; section: number; score?: number }> >(); let denseCalls: Array<{ query: string; k: number }> = []; mock.module("../dense.js", () => ({ ...realDense, denseLane: async (...args: Parameters) => { if (!denseMockActive) { return realDense.denseLane(...args); } denseCalls.push({ query: args[1], k: args[2] }); return args[2] <= 0 ? [] : (denseHitsByQuery.get(args[1]) ?? denseHits); }, // Orchestrate now calls the SCORED variant; the mock must intercept it too // (else the `...realDense` spread resolves the real lane and hits Qdrant). // Same active-flag guard and `denseHits` fixture, defaulting an unset score // to 1 so existing `denseK: 100` tests keep working without a score field. denseLaneScored: async ( ...args: Parameters ) => { if (!denseMockActive) { return realDense.denseLaneScored(...args); } denseCalls.push({ query: args[1], k: args[2] }); return args[2] <= 0 ? [] : (denseHitsByQuery.get(args[1]) ?? denseHits).map((h) => ({ ...h, score: h.score ?? 1, })); }, })); // The watchdog telemetry store is stubbed with the same active-flag delegation // as the dense lane: gate tests capture the per-run gate telemetry; any other // test file sharing the process falls through to the real store. Spread the // real module so the query/type exports stay present. const realWatchdogStore = { ...(await import("../../../../../telemetry/watchdog-events-store.js")), }; let watchdogMockActive = false; type RecordedEvent = { checkName: string; value?: number | null; detail?: Record | null; }; let recordedGateEvents: RecordedEvent[] = []; // Split by check_name rather than into one bucket: orchestrate emits a gate // event AND a selection event per turn, and the gate assertions below count // their events exactly. let recordedSelectionEvents: RecordedEvent[] = []; mock.module("../../../../../telemetry/watchdog-events-store.js", () => ({ ...realWatchdogStore, recordWatchdogEvent: ( record: Parameters[0], ) => { if (!watchdogMockActive) { return realWatchdogStore.recordWatchdogEvent(record); } if (record.checkName === MEMORY_V3_SELECTION_CHECK_NAME) { recordedSelectionEvents.push(record); return; } recordedGateEvents.push(record); }, })); const { orchestrate, DEFAULT_NEEDLE_K, DEFAULT_DENSE_K, MEMORY_V3_INJECTION_GATE_CHECK_NAME, MEMORY_V3_SELECTION_CHECK_NAME, } = await import("../orchestrate.js"); // --------------------------------------------------------------------------- // Fixtures: a tiny corpus of pages with bodies + `links:` frontmatter. // --------------------------------------------------------------------------- const PAGES: Record = { "topic-a": "lead for topic a\n## Details\napple banana about topic a", "topic-b": "lead for topic b\n## More\ncherry date about topic b", "topic-c": "lead for topic c\n## Notes\nelderberry fig about topic c", "topic-d": "lead for topic d\n## Notes\ngrape about topic d", }; /** Raw page = frontmatter (with `links:`) + body. */ const RAW: Record = { "topic-a": `---\nlinks:\n - "topic-d — the curated edge from a to d"\n---\n${PAGES["topic-a"]}`, "topic-b": `---\nedges: []\n---\n${PAGES["topic-b"]}`, "topic-c": `---\nedges: []\n---\n${PAGES["topic-c"]}`, "topic-d": `---\nedges: []\n---\n${PAGES["topic-d"]}`, }; const SLUGS = Object.keys(PAGES); function makeEntries(): PageIndexEntry[] { return SLUGS.map((slug, i) => ({ id: i + 1, slug, summary: `summary of ${slug}`, edges: [], leaves: [], modifiedAt: 0, freshAt: null, })); } interface Lanes { sectionIndex: SectionIndex; needle: ReturnType; edgeGraph: EdgeGraph; } async function buildLanes(): Promise { const sectionIndex = await buildSectionIndex(SLUGS, async (s) => PAGES[s]!); const needle = buildSectionNeedle(sectionIndex); const edgeGraph = await buildEdgeGraph(makeEntries(), async (s) => RAW[s]!); return { sectionIndex, needle, edgeGraph }; } const config = {} as never; /** * Orchestrate deps with empty stable-prefix lanes unless overridden. Mirrors * lane init: every core/hot slug gets a pre-rendered card (from the RAW * fixture when one exists) unless the test overrides `prefixCards` itself. */ function depsOf( lanes: Lanes, overrides: Partial = {}, ): OrchestrateDeps { const coreSlugs = overrides.coreSlugs ?? []; const hotSlugs = overrides.hotSlugs ?? []; const freshSlugs = overrides.freshSlugs ?? []; const alwaysCandidateSlugs = overrides.alwaysCandidateSlugs ?? []; const prefixCards = new Map( [...coreSlugs, ...hotSlugs, ...freshSlugs, ...alwaysCandidateSlugs].map( (slug) => [slug, renderCard(slug, RAW[slug] ?? "")], ), ); return { sectionIndex: lanes.sectionIndex, needle: lanes.needle, denseConfig: config, edgeGraph: lanes.edgeGraph, coreSlugs, hotSlugs, freshSlugs, prefixCards, ...overrides, }; } function makeTurn( turnNumber: number, currentMessage: string, previousAssistantMessage?: string, ): MemoryRoutingTurn { return { conversationId: "conv-xyz", turnNumber, currentMessage, recentContext: "prior context", previousAssistantMessage, }; } function toolUseResponse(input: Record): ProviderResponse { return { model: "stub-model", stopReason: "tool_use", usage: { inputTokens: 0, outputTokens: 0 }, content: [{ type: "tool_use", id: "tu-1", name: "select_pages", input }], }; } /** * Parse the two-segment selector input back into the globally-numbered pool: * stable-prefix cards (``, identified by their * `[i] # memory/concepts/.md` header line) and finder lines * (``, `[i] slug — descriptor`). Also captures the raw stable * prefix block for byte-identity assertions. */ function parsePool(messages: Message[]): { slugs: Slug[]; lines: string[]; prefixBlock: string | null; } { const entries: Array<{ id: number; slug: string; line: string }> = []; let prefixBlock: string | null = null; for (const msg of messages) { for (const block of msg.content) { if (block.type !== "text") { continue; } const cards = /\n([\s\S]*?)\n<\/candidate_cards>/.exec( block.text, ); if (cards) { prefixBlock = cards[0]; for (const m of cards[1].matchAll( /^\[(\d+)\] # memory\/concepts\/(.+)\.md$/gm, )) { entries.push({ id: Number(m[1]), slug: m[2]!, line: m[0] }); } } const finder = /\n([\s\S]*?)\n<\/candidates>/.exec( block.text, ); if (finder) { for (const line of finder[1].split("\n")) { const m = /^\[(\d+)\] (?:\([^)]*\) )?(\S+)(?: — |$)/.exec(line); if (m) { entries.push({ id: Number(m[1]), slug: m[2]!, line }); } } } } } entries.sort((a, b) => a.id - b.id); return { slugs: entries.map((e) => e.slug), lines: entries.map((e) => e.line), prefixBlock, }; } /** * Provider that selects the pool candidates whose slug is in `keep` (mapping * each back to its 1-based id), pinning those in `pin`. Captures the rendered * candidate list (slugs and raw lines) for pool assertions. */ let lastPool: Slug[] = []; let lastPoolLines: string[] = []; let lastPrefixBlock: string | null = null; let selectCalls = 0; function selectProvider(keep: Slug[], pin: Slug[] = []): Provider { return { name: "stub", sendMessage: async (messages) => { selectCalls++; const parsed = parsePool(messages); lastPool = parsed.slugs; lastPoolLines = parsed.lines; lastPrefixBlock = parsed.prefixBlock; const ids: number[] = []; const pinned_ids: number[] = []; parsed.slugs.forEach((slug, i) => { if (keep.includes(slug)) { ids.push(i + 1); } if (pin.includes(slug)) { pinned_ids.push(i + 1); } }); return toolUseResponse({ ids, pinned_ids }); }, }; } beforeEach(() => { denseMockActive = true; watchdogMockActive = true; providerStub = null; denseHits = []; denseHitsByQuery = new Map(); denseCalls = []; recordedGateEvents = []; recordedSelectionEvents = []; lastPool = []; lastPoolLines = []; lastPrefixBlock = null; selectCalls = 0; }); afterAll(() => { denseMockActive = false; watchdogMockActive = false; }); // --------------------------------------------------------------------------- // Pool composition: the candidate pool is the cache-ordered union of the // lanes. Synthetic capability pages are not always-added — they enter through // a lane (see the dedicated test below). // --------------------------------------------------------------------------- describe("orchestrate — candidate pool composition", () => { test("pool unions needle ∪ dense ∪ edge; one select runs", async () => { const lanes = await buildLanes(); // "apple" hits topic-a (needle). Dense returns topic-b. topic-a links to // topic-d (edge). denseHits = [{ article: "topic-b", section: 0 }]; providerStub = selectProvider([]); // selection is irrelevant to pool union await orchestrate(makeTurn(1, "apple"), depsOf(lanes, { denseK: 100 })); expect(selectCalls).toBe(1); expect(new Set(lastPool)).toEqual( new Set(["topic-a", "topic-b", "topic-d"]), ); }); test("a synthetic capability page enters the pool via the needle lane", async () => { // A capability slug indexed with body content (the section index treats it // like any other page) is ranked by the real needle when the query matches // its text — this is how synthetic pages reach the pool now that they are no // longer always-added. const CAP: Slug = "skills/example"; const sectionIndex = await buildSectionIndex([...SLUGS, CAP], async (s) => s === CAP ? "# Skill: example\nuse the kumquat skill to do the thing" : PAGES[s]!, ); const needle = buildSectionNeedle(sectionIndex); const edgeGraph = await buildEdgeGraph(makeEntries(), async (s) => RAW[s]!); providerStub = selectProvider([]); // selection irrelevant to pool union await orchestrate( makeTurn(1, "kumquat"), depsOf({ sectionIndex, needle, edgeGraph }), ); // The needle ranked the capability page on the "kumquat" term, so it is in // the candidate pool. expect(lastPool).toContain(CAP); }); test("always-candidate slugs are pinned into the stable prefix and are selectable", async () => { const lanes = await buildLanes(); const WF: Slug = "skills/workflows"; // No retrieval lane surfaces WF for "apple" (hits topic-a/b/d) — it reaches // the selector ONLY because it is an always-candidate. providerStub = selectProvider([WF]); const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { alwaysCandidateSlugs: [WF] }), ); expect(lastPool).toContain(WF); expect(result.selections.map((s) => s.slug)).toContain(WF); }); test("an always-candidate slug already in core is not double-listed", async () => { const lanes = await buildLanes(); const WF: Slug = "skills/workflows"; providerStub = selectProvider([]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { coreSlugs: [WF], alwaysCandidateSlugs: [WF] }), ); expect(lastPool.filter((s) => s === WF)).toHaveLength(1); }); test("edge curated link description becomes the edge candidate's descriptor", async () => { const lanes = await buildLanes(); providerStub = selectProvider([]); await orchestrate(makeTurn(1, "apple"), depsOf(lanes)); const line = lastPoolLines.find((l) => / topic-d — /.test(l)); expect(line).toContain("the curated edge from a to d"); }); test("needleK and denseK default to their constants", async () => { const lanes = await buildLanes(); let needleK = -1; // Orchestrate drives the finder via the SCORED query, so capture the budget // there (the unscored `query` is no longer the orchestrate entry point). const needle = { query: () => [], queryScored: (_t: string, k: number) => { needleK = k; return []; }, bestSection: () => -1, idf: () => 0, }; providerStub = selectProvider([]); await orchestrate(makeTurn(1, "x"), depsOf(lanes, { needle })); expect(needleK).toBe(DEFAULT_NEEDLE_K); expect(DEFAULT_NEEDLE_K).toBe(12); expect(denseCalls).toEqual([]); expect(DEFAULT_DENSE_K).toBe(0); }); test("denseK override controls the embedding-backed candidate budget", async () => { const lanes = await buildLanes(); providerStub = selectProvider([]); await orchestrate(makeTurn(1, "apple"), depsOf(lanes, { denseK: 7 })); expect(denseCalls.map((call) => call.k)).toEqual([7]); }); test("denseK = 0 disables dense retrieval for both current and reply queries", async () => { const lanes = await buildLanes(); providerStub = selectProvider([]); await orchestrate( makeTurn(1, "apple", "previous reply mentioned cherry"), depsOf(lanes, { denseK: 0, replyQueryK: 12 }), ); expect(denseCalls).toEqual([]); }); test("selectorEnabled=false keeps all pooled candidates without calling the selector provider", async () => { const lanes = await buildLanes(); // "apple" hits topic-a (needle), dense returns topic-b, and topic-a links // to topic-d. With the selector disabled, every pooled candidate is passed // through as a selection without requiring the L2 callsite. denseHits = [{ article: "topic-b", section: 0 }]; const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100, selectorEnabled: false }), ); expect(selectCalls).toBe(0); expect(new Set(result.selections.map((s) => s.slug))).toEqual( new Set(["topic-a", "topic-b", "topic-d"]), ); }); test("selectorEnabled=false with zero candidate lanes returns no selections", async () => { const lanes = await buildLanes(); const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { needleK: 0, denseK: 0, replyQueryK: 0, selectorEnabled: false, }), ); expect(selectCalls).toBe(0); expect(result.selections).toEqual([]); expect(result.lanes).toEqual({ core: [], hot: [], fresh: [], finder: [] }); }); test("matchedSections is populated from matched lane sections", async () => { const lanes = await buildLanes(); denseHits = []; providerStub = selectProvider(["topic-a"]); const result = await orchestrate(makeTurn(1, "apple"), depsOf(lanes)); // topic-a matched "apple" in its `## Details` section. expect(result.matchedSections.get("topic-a")?.article).toBe("topic-a"); expect(result.matchedSections.get("topic-a")?.text).toContain("apple"); }); }); // --------------------------------------------------------------------------- // Cache order: the pool's stable prefix is core (file order) then hot (score // order); finder candidates follow, deduped against the prefix. The prefix is // byte-identical across turns while the lanes are unchanged — that is the // whole point of the ordering (selector-input KV cache). // --------------------------------------------------------------------------- describe("orchestrate — cache-ordered pool (core + hot + finders)", () => { test("pool order is core, then hot, then finder candidates", async () => { const lanes = await buildLanes(); // Core and hot pages do not match "apple"; the needle surfaces topic-a. // topic-a links to topic-d, but topic-d is HOT (stable prefix), so the // edge lane does not re-surface it. denseHits = [{ article: "topic-b", section: 0 }]; providerStub = selectProvider([]); const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { coreSlugs: ["topic-c"], hotSlugs: ["topic-d"], denseK: 100, }), ); expect(lastPool).toEqual(["topic-c", "topic-d", "topic-a", "topic-b"]); expect(result.lanes.core).toEqual(["topic-c"]); expect(result.lanes.hot).toEqual(["topic-d"]); // The edge lane skipped topic-d (already in the stable prefix). expect(result.lanes.finder.map((c) => c.slug)).toEqual([ "topic-a", "topic-b", ]); }); test("fresh follows hot in the pool and dedups against core/hot", async () => { const lanes = await buildLanes(); denseHits = []; providerStub = selectProvider([]); const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { coreSlugs: ["topic-c"], hotSlugs: ["topic-d"], // topic-c (core) and topic-d (hot) are defensively dropped; only // topic-b earns a fresh slot. freshSlugs: ["topic-c", "topic-d", "topic-b"], }), ); expect(lastPool).toEqual(["topic-c", "topic-d", "topic-b", "topic-a"]); expect(result.lanes.fresh).toEqual(["topic-b"]); }); test('reply-query hits join the finder tail tagged "reply", after primary lanes and before edge', async () => { const lanes = await buildLanes(); denseHits = []; providerStub = selectProvider([]); // Primary query matches topic-a; the previous reply matches topic-b. The // edge lane expands topic-a's curated link to topic-d. const result = await orchestrate( makeTurn(1, "apple", "cherry date"), depsOf(lanes, { replyQueryK: 5 }), ); expect(result.lanes.finder.map((c) => [c.slug, c.lane])).toEqual([ ["topic-a", "needle"], ["topic-b", "reply"], ["topic-d", "edge"], ]); // The reply-matched section is recorded for injection/spotlight. expect(result.matchedSections.has("topic-b")).toBe(true); }); test("a slug both queries surface keeps its primary-lane attribution", async () => { const lanes = await buildLanes(); denseHits = []; providerStub = selectProvider([]); const result = await orchestrate( makeTurn(1, "apple", "apple banana"), depsOf(lanes, { replyQueryK: 5 }), ); const topicA = result.lanes.finder.filter((c) => c.slug === "topic-a"); expect(topicA).toHaveLength(1); expect(topicA[0]!.lane).toBe("needle"); }); test("no previous assistant message → no reply-lane candidates", async () => { const lanes = await buildLanes(); denseHits = []; providerStub = selectProvider([]); const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { replyQueryK: 5 }), ); expect(result.lanes.finder.some((c) => c.lane === "reply")).toBe(false); }); test("replyQueryK = 0 disables the pass even with a previous reply", async () => { const lanes = await buildLanes(); denseHits = []; providerStub = selectProvider([]); const result = await orchestrate( makeTurn(1, "apple", "cherry date"), depsOf(lanes, { replyQueryK: 0 }), ); expect(result.lanes.finder.some((c) => c.lane === "reply")).toBe(false); }); test('learned-edge expansion surfaces association neighbours tagged "learned", after the static edge lane', async () => { const lanes = await buildLanes(); denseHits = []; providerStub = selectProvider([]); // Needle("apple") surfaces topic-a; static links expand a → d; the // learned graph associates a → c (no authored link exists). const learnedGraph = { adjacency: new Map([["topic-a", new Map([["topic-c", undefined]])]]), hubs: new Set(), slugs: new Set(SLUGS), }; const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { learnedGraph, learnedPerSeed: 3, learnedCap: 20 }), ); expect(result.lanes.finder.map((c) => [c.slug, c.lane])).toEqual([ ["topic-a", "needle"], ["topic-d", "edge"], ["topic-c", "learned"], ]); // Association, not lexical relevance, surfaced topic-c — no matched // section is recorded (injection falls back to the full page). expect(result.matchedSections.has("topic-c")).toBe(false); }); test("learnedCap = 0 disables the learned pass", async () => { const lanes = await buildLanes(); denseHits = []; providerStub = selectProvider([]); const learnedGraph = { adjacency: new Map([["topic-a", new Map([["topic-c", undefined]])]]), hubs: new Set(), slugs: new Set(SLUGS), }; const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { learnedGraph, learnedCap: 0 }), ); expect(result.lanes.finder.some((c) => c.lane === "learned")).toBe(false); }); test("the rendered stable prefix is byte-identical across turns with different queries", async () => { const lanes = await buildLanes(); const deps = depsOf(lanes, { coreSlugs: ["topic-c"], hotSlugs: ["topic-b"], }); providerStub = selectProvider([]); await orchestrate(makeTurn(1, "apple"), deps); const prefix1 = lastPrefixBlock; await orchestrate(makeTurn(2, "grape"), deps); const prefix2 = lastPrefixBlock; // Stable-prefix cards are pre-rendered and query-independent, so the // whole rendered cards block matches byte-for-byte across turns. expect(prefix1).not.toBeNull(); expect(prefix2).toBe(prefix1!); expect(prefix1).toContain("topic-c"); expect(prefix1).toContain("topic-b"); }); test("stable-prefix candidates render their pre-rendered cards verbatim", async () => { const lanes = await buildLanes(); providerStub = selectProvider([]); await orchestrate( makeTurn(1, "zzzz"), depsOf(lanes, { coreSlugs: ["topic-c"], hotSlugs: ["topic-d"], prefixCards: new Map([ [ "topic-c", "# memory/concepts/topic-c.md\nlead for topic c\n\n[sections: §Notes]", ], ["topic-d", "# memory/concepts/topic-d.md\nlead for topic d"], ]), }), ); expect(lastPrefixBlock).toContain( "[1] # memory/concepts/topic-c.md\nlead for topic c\n\n[sections: §Notes]", ); expect(lastPrefixBlock).toContain( "[2] # memory/concepts/topic-d.md\nlead for topic d", ); }); test("a stable-prefix slug with no pre-rendered card throws (never silently degrades)", async () => { const lanes = await buildLanes(); providerStub = selectProvider([]); await expect( orchestrate( makeTurn(1, "zzzz"), depsOf(lanes, { coreSlugs: ["topic-c"], hotSlugs: ["topic-d"], // topic-d is missing — a lane-init bug; a degraded card would break // the byte-stable-prefix contract, so orchestrate must throw. prefixCards: new Map([["topic-c", renderCard("topic-c", "")]]), }), ), ).rejects.toThrow('no pre-rendered card for stable-prefix slug "topic-d"'); }); test("a finder hit on a core page repeats as a finder line and dedupes on selection", async () => { const lanes = await buildLanes(); // topic-a is CORE and the needle also hits it on "apple". The stub keeps // BOTH occurrences (card id + finder-line id). providerStub = selectProvider(["topic-a"]); const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { coreSlugs: ["topic-a"] }), ); // The pool lists topic-a twice — once as the stable-prefix card, once as // a finder line carrying its CURRENT matched section (the tail is not // deduped against the prefix, by design: filtering would not change the // prefix here, but the snippet line is the page's current relevance). expect(lastPool.filter((s) => s === "topic-a")).toHaveLength(2); expect(lastPool[0]).toBe("topic-a"); // The finder line shows the matched-section snippet. expect( lastPoolLines.find((l) => /^\[2\] (?:\([^)]*\) )?topic-a — /.test(l)), ).toContain("apple"); // Selecting both ids still yields ONE selection (slug dedup), the finder // lane records the hit, and the matched section survives downstream. expect(result.selections).toEqual([{ slug: "topic-a", pinned: false }]); expect(result.lanes.finder.map((c) => c.slug)).toContain("topic-a"); expect(result.matchedSections.get("topic-a")?.text).toContain("apple"); }); test("a hot slug duplicated into core is defensively dropped from hot", async () => { const lanes = await buildLanes(); providerStub = selectProvider([]); const result = await orchestrate( makeTurn(1, "zzzz"), depsOf(lanes, { coreSlugs: ["topic-c"], hotSlugs: ["topic-c", "topic-d"], }), ); expect(result.lanes.core).toEqual(["topic-c"]); expect(result.lanes.hot).toEqual(["topic-d"]); expect(lastPool).toEqual(["topic-c", "topic-d"]); }); test("selections are current-turn only — no carried set is unioned in", async () => { const lanes = await buildLanes(); const deps = depsOf(lanes, { coreSlugs: ["topic-c"] }); // Turn 1 selects topic-a. providerStub = selectProvider(["topic-a"]); const t1 = await orchestrate(makeTurn(1, "apple"), deps); expect(t1.selections.map((s) => s.slug)).toEqual(["topic-a"]); // Turn 2 selects only topic-b; topic-a does NOT reappear (cross-turn // persistence is the injector's job now, not orchestration's). denseHits = [{ article: "topic-b", section: 0 }]; providerStub = selectProvider(["topic-b"]); const t2 = await orchestrate(makeTurn(2, "cherry"), deps); expect(t2.selections.map((s) => s.slug)).toEqual(["topic-b"]); }); test("pinned flags survive selection dedup", async () => { const lanes = await buildLanes(); providerStub = selectProvider(["topic-a"], ["topic-a"]); const result = await orchestrate(makeTurn(1, "apple"), depsOf(lanes)); expect(result.selections).toEqual([{ slug: "topic-a", pinned: true }]); }); }); // --------------------------------------------------------------------------- // Edge-only injection: a page surfaced ONLY by the edge lane (the query did not // lexically hit it) records NO matched section, so injection falls back to the // FULL page (the curated `links` description — not the often-empty lead the // query never hit — is what made the candidate relevant). The best-section text // is kept only as the select-pool descriptor fallback. // --------------------------------------------------------------------------- describe("orchestrate — edge-only injection", () => { test("an edge-only page records NO matchedSections entry (→ full-page inject)", async () => { const lanes = await buildLanes(); // "apple" hits topic-a (needle); topic-a links to topic-d (edge-only — the // query never hits topic-d). Select topic-d so it is in the result. denseHits = []; providerStub = selectProvider(["topic-d"]); const result = await orchestrate(makeTurn(1, "apple"), depsOf(lanes)); // topic-d was selected, but with NO matched section — so // `renderV3SectionContent(slug, undefined)` falls back to the full page. expect(result.selections.map((s) => s.slug)).toContain("topic-d"); expect(result.matchedSections.has("topic-d")).toBe(false); }); test("an edge-only page with no curated description falls back to bestSection text as the descriptor", async () => { // A bare `links:` entry (no ` — `) carries NO description, so the edge // candidate's descriptor falls back to the page's best section against the // query. The query never hits dst-page, so bestSection returns its lead; // that lead text becomes the descriptor (and the page is still injected in // full, with no matchedSections entry). const pages: Record = { "src-page": "lead for src\n## Body\nalpha bravo about src", "dst-page": "lead content for dst page\n## Extra\nnothing relevant here", }; const raw: Record = { // bare slug — no ` — ` separator → undefined curated description. "src-page": `---\nlinks:\n - "dst-page"\n---\n${pages["src-page"]}`, "dst-page": `---\nedges: []\n---\n${pages["dst-page"]}`, }; const slugs = Object.keys(pages); const entries: PageIndexEntry[] = slugs.map((slug, i) => ({ id: i + 1, slug, summary: `summary of ${slug}`, edges: [], leaves: [], modifiedAt: 0, freshAt: null, })); const sectionIndex = await buildSectionIndex(slugs, async (s) => pages[s]!); const needle = buildSectionNeedle(sectionIndex); const edgeGraph = await buildEdgeGraph(entries, async (s) => raw[s]!); providerStub = selectProvider([]); // "alpha" hits src-page (needle); src-page links to dst-page (edge-only, no // curated description). const result = await orchestrate( makeTurn(1, "alpha"), depsOf({ sectionIndex, needle, edgeGraph }), ); // Descriptor fell back to dst-page's lead text; still no matched section. const line = lastPoolLines.find((l) => / dst-page — /.test(l)); expect(line).toContain("lead content for dst page"); expect(result.matchedSections.has("dst-page")).toBe(false); }); }); // --------------------------------------------------------------------------- // Dense liveness: a dense hit whose article is no longer in the live section // index (its page was deleted; its Qdrant points linger) is dropped from the // pool, so a deleted page can never be surfaced via the dense lane. // --------------------------------------------------------------------------- describe("orchestrate — dense liveness filter", () => { test("a dense hit absent from sectionIndex.byArticle is dropped from the pool", async () => { const lanes = await buildLanes(); // Dense returns a live page (topic-b) AND a deleted page (gone-page) whose // points still linger in Qdrant but which is absent from the section index. denseHits = [ { article: "topic-b", section: 0 }, { article: "gone-page", section: 0 }, ]; providerStub = selectProvider([]); // selection irrelevant to pool membership const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100 }), ); // The live dense hit is pooled; the deleted page is dropped entirely. expect(lastPool).toContain("topic-b"); expect(lastPool).not.toContain("gone-page"); expect(result.lanes.finder.map((c) => c.slug)).not.toContain("gone-page"); }); test("a dense hit with an unresolvable ordinal falls back to the lead-section snippet", async () => { const lanes = await buildLanes(); // Ordinal 99 resolves to no section, so the candidate carries no match // text — its finder line falls back to the page's lead-section text. denseHits = [{ article: "topic-b", section: 99 }]; providerStub = selectProvider([]); await orchestrate(makeTurn(1, "zzzz"), depsOf(lanes, { denseK: 100 })); const line = lastPoolLines.find((l) => / topic-b — /.test(l)); expect(line).toContain("lead for topic b"); }); }); // --------------------------------------------------------------------------- // Lane provenance: each finder candidate records the lane that FIRST surfaced // it (needle → dense → edge precedence), exposed via `result.lanes.finder` so // the selection telemetry can attribute true sources. // --------------------------------------------------------------------------- describe("orchestrate — finder lane provenance", () => { test("each finder candidate is tagged with its surfacing lane", async () => { const lanes = await buildLanes(); // "apple" hits topic-a (needle). Dense returns topic-b. topic-a links to // topic-d (edge). So each lane contributes exactly one distinct slug. denseHits = [{ article: "topic-b", section: 0 }]; providerStub = selectProvider([]); // selection irrelevant to pool provenance const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100 }), ); const laneOf = new Map(result.lanes.finder.map((c) => [c.slug, c.lane])); expect(laneOf.get("topic-a")).toBe("needle"); expect(laneOf.get("topic-b")).toBe("dense"); expect(laneOf.get("topic-d")).toBe("edge"); }); test("a slug surfaced by needle AND dense keeps the needle lane (first wins)", async () => { const lanes = await buildLanes(); // topic-a is surfaced by the needle on "apple"; dense ALSO returns topic-a. // Needle runs first, so the recorded lane stays needle. denseHits = [{ article: "topic-a", section: 0 }]; providerStub = selectProvider([]); const result = await orchestrate(makeTurn(1, "apple"), depsOf(lanes)); const entries = result.lanes.finder.filter((c) => c.slug === "topic-a"); expect(entries).toHaveLength(1); expect(entries[0]!.lane).toBe("needle"); }); }); // --------------------------------------------------------------------------- // Degradation: an empty pool and the recall-safe omitted-ids path. // --------------------------------------------------------------------------- describe("orchestrate — degradation", () => { test("an empty pool yields no selections", async () => { const lanes = await buildLanes(); providerStub = selectProvider([]); const result = await orchestrate( makeTurn(1, "zzzzz no-match"), depsOf(lanes, { needle: { query: () => [], queryScored: () => [], bestSection: () => -1, idf: () => 0, }, }), ); expect(result.selections).toEqual([]); expect(result.lanes.finder).toEqual([]); }); test("omitted ids keeps ALL pooled candidates (recall-safe)", async () => { const lanes = await buildLanes(); denseHits = [{ article: "topic-b", section: 0 }]; providerStub = { name: "stub", sendMessage: async () => toolUseResponse({}), // omitted ids → keep all }; const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { coreSlugs: ["topic-c"], denseK: 100 }), ); expect(new Set(result.selections.map((s) => s.slug))).toEqual( new Set(["topic-c", "topic-a", "topic-b", "topic-d"]), ); }); }); // --------------------------------------------------------------------------- // Entity lane: the heading section is the identity the lane exists to surface, // so it overrides a bulk-theme section a prior lane already recorded for the // same page, and surfaces a heading-named page no other lane found. // --------------------------------------------------------------------------- describe("orchestrate — entity lane", () => { test("overrides the matched section + descriptor to the heading when another lane already surfaced the page", async () => { const lanes = await buildLanes(); const { sectionIndex } = lanes; // topic-a sections: [leadDoc] = lead (bulk), [headingDoc] = "## Details". const [leadDoc, headingDoc] = sectionIndex.byArticle.get("topic-a")!; const lead = sectionIndex.sections[leadDoc!]!; const heading = sectionIndex.sections[headingDoc!]!; // needle surfaces topic-a for its BULK lead section; the entity catalog // maps a message token to topic-a's HEADING section. const needle = { query: () => [{ article: "topic-a", section: leadDoc! }], queryScored: () => [{ article: "topic-a", section: leadDoc!, score: 1 }], bestSection: () => leadDoc!, idf: () => 0, }; const entityIndex = new Map([["widget", [headingDoc!]]]); const result = await orchestrate( makeTurn(1, "tell me about the widget"), depsOf(lanes, { needle, entityIndex, selectorEnabled: false }), ); // The page is surfaced once and keeps the needle's first-lane attribution… const hits = result.lanes.finder.filter((c) => c.slug === "topic-a"); expect(hits).toHaveLength(1); expect(hits[0]!.lane).toBe("needle"); // …but its matched section and pool descriptor are the HEADING, not the // bulk lead the needle recorded. expect(result.matchedSections.get("topic-a")).toBe(heading); expect(result.matchedSections.get("topic-a")).not.toBe(lead); expect(hits[0]!.descriptor).toBe(heading.text); }); test("surfaces a heading-named page no other lane found, tagged `entity`", async () => { const lanes = await buildLanes(); const { sectionIndex } = lanes; const [, headingDoc] = sectionIndex.byArticle.get("topic-c")!; const heading = sectionIndex.sections[headingDoc!]!; const needle = { query: () => [] as { article: Slug; section: number }[], queryScored: () => [], bestSection: () => -1, idf: () => 0, }; const entityIndex = new Map([["gadget", [headingDoc!]]]); const result = await orchestrate( makeTurn(1, "what about the gadget"), depsOf(lanes, { needle, entityIndex, selectorEnabled: false }), ); const hits = result.lanes.finder.filter((c) => c.slug === "topic-c"); expect(hits).toHaveLength(1); expect(hits[0]!.lane).toBe("entity"); expect(result.matchedSections.get("topic-c")).toBe(heading); expect(result.selections.map((s) => s.slug)).toContain("topic-c"); }); }); // --------------------------------------------------------------------------- // Injection gate: an OPT-IN, pass-open score check between the finder lanes and // edge expansion. A passing gate proceeds to selectPool as before; a closing // gate skips the selector entirely (empty selections) — or, with // `bypassForCore`, selects over the stable prefix only. Disabled/omitted is a // no-op. `selectCalls` (incremented by the provider stub) detects whether the // selector ran. // --------------------------------------------------------------------------- /** A `V3GateConfig` literal from the schema (`memory.v3.gate`) defaults plus the * effective `enabled` flag, overridable per test. */ function gateConfigOf(overrides: Partial = {}): V3GateConfig { return { enabled: true, denseThreshold: 0.52, sparseThreshold: 0.35, sparseOnlyThreshold: 0.45, denseClusterThreshold: 0.47, denseClusterMaxDelta: 0.04, topK: 5, bm25NormK: null, bypassForCore: false, ...overrides, }; } describe("orchestrate — injection gate", () => { test("gate pass → selects normally (provider runs once, selections produced)", async () => { const lanes = await buildLanes(); // Dense top-1 (0.9) clears the default denseThreshold (0.52) → dense_pass. denseHits = [{ article: "topic-b", section: 0, score: 0.9 }]; providerStub = selectProvider(["topic-a"]); // "apple" needles topic-a const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf() }), ); expect(selectCalls).toBe(1); expect(result.selections.map((s) => s.slug)).toContain("topic-a"); }); test("gate fail → empty selections, selector never called", async () => { const lanes = await buildLanes(); // No needle-term overlap (zero sparse signal) and a dense top-1 (0.1) well // below every dense threshold → the gate closes. denseHits = [{ article: "topic-b", section: 0, score: 0.1 }]; providerStub = selectProvider([]); // would increment selectCalls if reached const result = await orchestrate( makeTurn(1, "zzzz nomatch"), depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf() }), ); expect(selectCalls).toBe(0); expect(result.selections).toEqual([]); expect(result.lanes.finder).toEqual([]); }); test("gate stays inert when the dense lane is off (denseK = 0 new-user profile)", async () => { const lanes = await buildLanes(); // denseK: 0 leaves `densed` empty — the lean new-user profile. A low-score // needle hit (norm ≈ 0.011) would CLOSE the gate if it ran on sparse signal // alone, but zero dense hits means dense is unavailable, not low-relevance: // the gate is dense-gated and never runs, so selection proceeds and memory // is not suppressed. const needle = { query: () => [], queryScored: () => [{ article: "topic-a", section: 0, score: 0.1 }], bestSection: () => -1, idf: () => 0, }; providerStub = selectProvider(["topic-a"]); const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { needle, denseK: 0, gateConfig: gateConfigOf() }), ); expect(selectCalls).toBe(1); expect(result.selections.map((s) => s.slug)).toContain("topic-a"); }); test("stale dense hits for deleted pages don't fake availability or close the gate", async () => { const lanes = await buildLanes(); // Dense returns ONLY a deleted page (gone-page) whose points linger in // Qdrant but which is absent from the live section index, at a LOW score // that would close the gate if scored. The live needle lane has a low-score // hit on a real page. With the raw `densed` the gate would see a hit and // close on the low scores; with `liveDensed` (empty) it takes the // dense-unavailable pass-open branch, so selection still runs. denseHits = [{ article: "gone-page", section: 0, score: 0.1 }]; const needle = { query: () => [], queryScored: () => [{ article: "topic-a", section: 0, score: 0.1 }], bestSection: () => -1, idf: () => 0, }; providerStub = selectProvider(["topic-a"]); const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { needle, denseK: 100, gateConfig: gateConfigOf() }), ); expect(selectCalls).toBe(1); expect(result.selections.map((s) => s.slug)).toContain("topic-a"); // The stale deleted page never reaches the pool either. expect(lastPool).not.toContain("gone-page"); // The lane WAS enabled, so this reports as an outage, not as disabled. expect(recordedGateEvents[0]!.detail).toMatchObject({ pass: true, reason: "dense_unavailable", }); }); test("bypassForCore: true on a closed gate selects the stable prefix only (no finder lines)", async () => { const lanes = await buildLanes(); // Non-empty low-score dense (0.1, below every dense threshold) makes the // dense-gated gate run and close for the low-signal query; bypassForCore // then runs selectPool over the stable prefix (core+hot) with an empty // finder tail. denseHits = [{ article: "topic-b", section: 0, score: 0.1 }]; providerStub = selectProvider([]); const result = await orchestrate( makeTurn(1, "zzzz nomatch"), depsOf(lanes, { coreSlugs: ["topic-c"], hotSlugs: ["topic-d"], denseK: 100, gateConfig: gateConfigOf({ bypassForCore: true }), }), ); expect(selectCalls).toBe(1); // The selector input is exactly the stable prefix — two card lines, no // finder candidate lines. expect(lastPool).toEqual(["topic-c", "topic-d"]); expect(lastPoolLines).toHaveLength(2); expect(lastPoolLines.every((l) => l.includes("# memory/concepts/"))).toBe( true, ); expect(result.lanes.finder).toEqual([]); expect(result.matchedSections.size).toBe(0); }); test("bypassForCore honors selectorEnabled: false — stable prefix passes through without the selector", async () => { const lanes = await buildLanes(); // Non-empty low-score dense closes the gate; with the selector off (the // new-user profile), the bypass mirrors the normal path and passes the // stable prefix straight through via selectAllPoolCandidates rather than // forcing the selectPool LLM call. denseHits = [{ article: "topic-b", section: 0, score: 0.1 }]; providerStub = selectProvider([]); // must NOT be called const result = await orchestrate( makeTurn(1, "zzzz nomatch"), depsOf(lanes, { coreSlugs: ["topic-c"], hotSlugs: ["topic-d"], denseK: 100, selectorEnabled: false, gateConfig: gateConfigOf({ bypassForCore: true }), }), ); // The selector never ran; selections are exactly the stable-prefix slugs in // cache order (selectAllPoolCandidates over the stable-only pool). expect(selectCalls).toBe(0); expect(result.selections.map((s) => s.slug)).toEqual([ "topic-c", "topic-d", ]); expect(result.lanes.finder).toEqual([]); expect(result.matchedSections.size).toBe(0); }); test("gate disabled (omitted) → unchanged behavior (selector runs, selections produced)", async () => { const lanes = await buildLanes(); providerStub = selectProvider(["topic-a"]); const result = await orchestrate(makeTurn(1, "apple"), depsOf(lanes)); expect(selectCalls).toBe(1); expect(result.selections.map((s) => s.slug)).toContain("topic-a"); }); test("gate enabled:false is a no-op even with gate-closing fixtures", async () => { const lanes = await buildLanes(); // These fixtures would CLOSE the gate if it ran (weak needle, sub-threshold // dense). With enabled:false the gate never runs, so selection proceeds. denseHits = [{ article: "topic-b", section: 0, score: 0.1 }]; providerStub = selectProvider(["topic-a"]); const result = await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf({ enabled: false }), }), ); expect(selectCalls).toBe(1); expect(result.selections.map((s) => s.slug)).toContain("topic-a"); }); test("a scored gate run records ONE telemetry event carrying the decision", async () => { const lanes = await buildLanes(); // Dense top-1 (0.9) clears denseThreshold (0.52) → dense_pass. denseHits = [{ article: "topic-b", section: 0, score: 0.9 }]; providerStub = selectProvider(["topic-a"]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf() }), ); expect(recordedGateEvents).toHaveLength(1); const event = recordedGateEvents[0]!; expect(event.checkName).toBe(MEMORY_V3_INJECTION_GATE_CHECK_NAME); expect(event.value).toBe(1); expect(event.detail).toMatchObject({ pass: true, reason: "dense_pass", scored: true, top_dense_score: 0.9, }); }); test("a closed gate records pass:false with the failure reason", async () => { const lanes = await buildLanes(); // No needle-term overlap and sub-threshold dense → fail_no_signal. denseHits = [{ article: "topic-b", section: 0, score: 0.1 }]; providerStub = selectProvider([]); await orchestrate( makeTurn(1, "zzzz nomatch"), depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf() }), ); expect(recordedGateEvents).toHaveLength(1); expect(recordedGateEvents[0]!.detail).toMatchObject({ pass: false, reason: "fail_no_signal", }); }); test("denseK: 0 records reason dense_disabled, not dense_unavailable", async () => { const lanes = await buildLanes(); // denseK: 0 → the lane never ran → the gate passes open without scoring. // Deliberate configuration (the lean new-user profile), so it must NOT be // reported as an outage. const needle = { query: () => [], queryScored: () => [{ article: "topic-a", section: 0, score: 0.1 }], bestSection: () => -1, idf: () => 0, }; providerStub = selectProvider(["topic-a"]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { needle, denseK: 0, gateConfig: gateConfigOf() }), ); expect(recordedGateEvents).toHaveLength(1); expect(recordedGateEvents[0]!.detail).toMatchObject({ pass: true, reason: "dense_disabled", scored: false, }); }); test("an enabled dense lane yielding no live hits records reason dense_unavailable", async () => { const lanes = await buildLanes(); // denseK > 0 but the lane came back empty — a degraded embedding backend or // a Qdrant error swallowed to [] by denseLaneScored. Dense SHOULD have // scored this turn and didn't, so this is the alertable reason. denseHits = []; const needle = { query: () => [], queryScored: () => [{ article: "topic-a", section: 0, score: 0.1 }], bestSection: () => -1, idf: () => 0, }; providerStub = selectProvider(["topic-a"]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { needle, denseK: 100, gateConfig: gateConfigOf() }), ); expect(recordedGateEvents).toHaveLength(1); expect(recordedGateEvents[0]!.detail).toMatchObject({ pass: true, reason: "dense_unavailable", scored: false, }); }); test("records the corpus size on a scored run", async () => { const lanes = await buildLanes(); denseHits = [{ article: "topic-b", section: 0, score: 0.9 }]; providerStub = selectProvider(["topic-a"]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100, realConceptPageCount: 42, gateConfig: gateConfigOf(), }), ); expect(recordedGateEvents[0]!.detail).toMatchObject({ reason: "dense_pass", real_concept_page_count: 42, }); }); test("records the corpus size on a dense_disabled run", async () => { const lanes = await buildLanes(); // The sub-threshold cohort is the whole reason the field exists: without it // `dense_disabled` says only "below the threshold", not how far below. const needle = { query: () => [], queryScored: () => [{ article: "topic-a", section: 0, score: 0.1 }], bestSection: () => -1, idf: () => 0, }; providerStub = selectProvider(["topic-a"]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { needle, denseK: 0, realConceptPageCount: 3, gateConfig: gateConfigOf(), }), ); expect(recordedGateEvents[0]!.detail).toMatchObject({ reason: "dense_disabled", real_concept_page_count: 3, }); }); test("omits the corpus size when the dep is not threaded", async () => { const lanes = await buildLanes(); denseHits = [{ article: "topic-b", section: 0, score: 0.9 }]; providerStub = selectProvider(["topic-a"]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf() }), ); expect(recordedGateEvents).toHaveLength(1); expect(recordedGateEvents[0]!.detail).not.toHaveProperty( "real_concept_page_count", ); }); test("a passed gate that selects NOTHING is recorded as a zero selection", async () => { const lanes = await buildLanes(); // The case the gate's own pass rate cannot see: retrieval was confident // enough to spend the selector call, and the selector judged that no // candidate was relevant. Pass rate says 100%; injection rate says 0%. denseHits = [{ article: "topic-b", section: 0, score: 0.9 }]; providerStub = selectProvider([]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf() }), ); expect(recordedGateEvents[0]!.detail).toMatchObject({ pass: true, reason: "dense_pass", }); expect(recordedSelectionEvents).toHaveLength(1); const event = recordedSelectionEvents[0]!; expect(event.checkName).toBe(MEMORY_V3_SELECTION_CHECK_NAME); expect(event.value).toBe(0); expect(event.detail).toMatchObject({ gate_reason: "dense_pass", gate_pass: true, selector_ran: true, selected_count: 0, }); }); test("the selection event carries the gate reason and a non-zero count", async () => { const lanes = await buildLanes(); denseHits = [{ article: "topic-b", section: 0, score: 0.9 }]; providerStub = selectProvider(["topic-a"]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100, realConceptPageCount: 42, gateConfig: gateConfigOf(), }), ); expect(recordedSelectionEvents).toHaveLength(1); expect(recordedSelectionEvents[0]!.value).toBe(1); expect(recordedSelectionEvents[0]!.detail).toMatchObject({ gate_reason: "dense_pass", selector_ran: true, selected_count: 1, real_concept_page_count: 42, }); expect( Number(recordedSelectionEvents[0]!.detail!.pool_size), ).toBeGreaterThan(0); }); test("selectorEnabled: false marks the passthrough as selector_ran: false", async () => { const lanes = await buildLanes(); // The lean profile passes the whole pool through without consulting the // selector, so `selected_count` there is pool size, not a relevance // judgment. Without this flag those turns would read as a 100% hit rate. denseHits = [{ article: "topic-b", section: 0, score: 0.9 }]; await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100, coreSlugs: ["topic-a"], selectorEnabled: false, gateConfig: gateConfigOf(), }), ); expect(recordedSelectionEvents).toHaveLength(1); expect(recordedSelectionEvents[0]!.detail).toMatchObject({ selector_ran: false, }); expect( Number(recordedSelectionEvents[0]!.detail!.selected_count), ).toBeGreaterThan(0); }); test("the recall-safe fallback (omitted ids) records selector_kept_all: true", async () => { const lanes = await buildLanes(); // The model omitting `ids` keeps the whole pool — indistinguishable from an // explicit large selection by `selected_count` alone. This is the flag that // separates "gave up judging" from "judged everything relevant". denseHits = [{ article: "topic-b", section: 0, score: 0.9 }]; providerStub = { name: "stub", sendMessage: async () => toolUseResponse({}), // omitted ids → keep all }; await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { coreSlugs: ["topic-c"], denseK: 100, gateConfig: gateConfigOf(), }), ); expect(recordedSelectionEvents).toHaveLength(1); expect(recordedSelectionEvents[0]!.detail).toMatchObject({ selector_ran: true, selector_kept_all: true, }); }); test("an explicit selection records selector_kept_all: false", async () => { const lanes = await buildLanes(); denseHits = [{ article: "topic-b", section: 0, score: 0.9 }]; providerStub = selectProvider(["topic-a"]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf() }), ); expect(recordedSelectionEvents[0]!.detail).toMatchObject({ selector_kept_all: false, }); }); test("net_new_count counts only selections not already live in the conversation", async () => { const lanes = await buildLanes(); denseHits = [{ article: "topic-b", section: 0, score: 0.9 }]; // Selector picks topic-a and topic-b; topic-a is already resident, so only // topic-b is a net-new injection this turn. providerStub = selectProvider(["topic-a", "topic-b"]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100, activeSlugs: new Set(["topic-a"]), gateConfig: gateConfigOf(), }), ); expect(recordedSelectionEvents[0]!.detail).toMatchObject({ selected_count: 2, net_new_count: 1, }); }); test("net_new_count is omitted when activeSlugs is not threaded", async () => { const lanes = await buildLanes(); denseHits = [{ article: "topic-b", section: 0, score: 0.9 }]; providerStub = selectProvider(["topic-a"]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf() }), ); expect(recordedSelectionEvents[0]!.detail).not.toHaveProperty( "net_new_count", ); }); test("an empty pool is not a selector judgment", async () => { const lanes = await buildLanes(); // `selectPool` returns [] before it ever reaches the provider when the pool // is empty, so a candidate-less turn must not count as "the selector found // nothing relevant" — that would drag the relevance rate down with turns the // selector never saw. const needle = { query: () => [], queryScored: () => [], bestSection: () => -1, idf: () => 0, }; denseHits = []; providerStub = selectProvider([]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { needle, denseK: 0, coreSlugs: [], hotSlugs: [], freshSlugs: [], selectorEnabled: true, gateConfig: gateConfigOf(), }), ); expect(recordedSelectionEvents).toHaveLength(1); expect(recordedSelectionEvents[0]!.detail).toMatchObject({ selector_ran: false, selected_count: 0, pool_size: 0, }); }); test("a hard-closed gate records a zero selection with selector_ran: false", async () => { const lanes = await buildLanes(); // Zero BY CONSTRUCTION — the selector was never asked. Must not be counted // as "the selector found nothing relevant". denseHits = [{ article: "topic-b", section: 0, score: 0.1 }]; providerStub = selectProvider([]); await orchestrate( makeTurn(1, "zzzz nomatch"), depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf() }), ); expect(recordedSelectionEvents).toHaveLength(1); expect(recordedSelectionEvents[0]!.detail).toMatchObject({ gate_reason: "fail_no_signal", gate_pass: false, selector_ran: false, selected_count: 0, pool_size: 0, }); }); test("gate_reason is null on a selection when the gate never ran", async () => { const lanes = await buildLanes(); denseHits = [{ article: "topic-b", section: 0, score: 0.9 }]; providerStub = selectProvider(["topic-a"]); await orchestrate(makeTurn(1, "apple"), depsOf(lanes, { denseK: 100 })); expect(recordedGateEvents).toEqual([]); expect(recordedSelectionEvents).toHaveLength(1); expect(recordedSelectionEvents[0]!.detail).toMatchObject({ gate_reason: null, gate_pass: null, selected_count: 1, }); }); test("no gate telemetry when the gate is disabled or omitted", async () => { const lanes = await buildLanes(); denseHits = [{ article: "topic-b", section: 0, score: 0.1 }]; providerStub = selectProvider(["topic-a"]); await orchestrate( makeTurn(1, "apple"), depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf({ enabled: false }), }), ); await orchestrate(makeTurn(2, "apple"), depsOf(lanes)); expect(recordedGateEvents).toEqual([]); }); }); describe("latency sub-spans", () => { test("orchestration inside a sub-span scope records v3_lanes / v3_expand / v3_selection", async () => { // Each `Date.now()` call advances the clock 20ms so every measured span // clears the recorder's floor without real waiting. Scoped to this test. let clock = 0; const nowSpy = spyOn(Date, "now").mockImplementation(() => (clock += 20)); try { const lanes = await buildLanes(); denseHits = [{ article: "topic-b", section: 0 }]; providerStub = selectProvider(["topic-a"]); const tracker = new TurnLatencyTracker(); await runWithLatencySubSpans(tracker, MEMORY_CONTEXT_PHASE_KEY, () => orchestrate( makeTurn(1, "apple banana"), depsOf(lanes, { denseK: 100 }), ), ); tracker.mark("turn_start"); tracker.mark("prompt_hook_start"); tracker.mark("prompt_hook_end"); const memory = tracker .serializeSince(0) .breakdown?.phases.find((p) => p.key === MEMORY_CONTEXT_PHASE_KEY); expect(memory?.subPhases?.map((s) => s.key)).toEqual([ "v3_lanes", "v3_expand", "v3_selection", ]); } finally { nowSpy.mockRestore(); } }); test("a gate hard-skip records no v3_expand and no v3_selection", async () => { let clock = 0; const nowSpy = spyOn(Date, "now").mockImplementation(() => (clock += 20)); try { const lanes = await buildLanes(); // Low dense score against a high threshold closes the gate; no bypass. denseHits = [{ article: "topic-b", section: 0, score: 0.01 }]; providerStub = selectProvider(["topic-a"]); const tracker = new TurnLatencyTracker(); await runWithLatencySubSpans(tracker, MEMORY_CONTEXT_PHASE_KEY, () => orchestrate( makeTurn(1, "apple banana"), depsOf(lanes, { denseK: 100, gateConfig: gateConfigOf({ enabled: true, bypassForCore: false }), }), ), ); tracker.mark("turn_start"); tracker.mark("prompt_hook_start"); tracker.mark("prompt_hook_end"); const memory = tracker .serializeSince(0) .breakdown?.phases.find((p) => p.key === MEMORY_CONTEXT_PHASE_KEY); expect(memory?.subPhases?.map((s) => s.key)).toEqual(["v3_lanes"]); } finally { nowSpy.mockRestore(); } }); }); // --------------------------------------------------------------------------- // Span-query pass: the dense lane re-run over the current message's clause // chunks as separate queries at `spanQueryK`, union-additive into the pool. // --------------------------------------------------------------------------- describe("orchestrate — span-query pass", () => { const TWO_CHUNK_MESSAGE = "apple appears in the first sentence here. elderberry shows up in the second sentence."; const CHUNK_1 = "apple appears in the first sentence here."; const CHUNK_2 = "elderberry shows up in the second sentence."; test("spanQueryK omitted (default) runs no span dense calls", async () => { const lanes = await buildLanes(); await orchestrate( makeTurn(1, TWO_CHUNK_MESSAGE), depsOf(lanes, { denseK: 100, selectorEnabled: false }), ); expect(denseCalls).toEqual([{ query: TWO_CHUNK_MESSAGE, k: 100 }]); }); test("span pass is inert when denseK=0", async () => { const lanes = await buildLanes(); await orchestrate( makeTurn(1, TWO_CHUNK_MESSAGE), depsOf(lanes, { denseK: 0, spanQueryK: 7, selectorEnabled: false }), ); expect(denseCalls).toEqual([]); }); test("single-chunk message skips the span pass", async () => { const lanes = await buildLanes(); await orchestrate( makeTurn(1, "apple in one single sentence."), depsOf(lanes, { denseK: 100, spanQueryK: 7, selectorEnabled: false }), ); expect(denseCalls).toEqual([ { query: "apple in one single sentence.", k: 100 }, ]); }); test("multi-chunk message adds span-lane candidates additively", async () => { const lanes = await buildLanes(); // Full-message dense (and any chunk without an override) returns topic-b; // chunk 1 re-surfaces topic-a (already a needle hit — attribution must // stay "needle"); chunk 2 surfaces topic-d, which no other lane reaches // ("grape" is not in the message, and the topic-a → topic-d curated edge // must not re-surface an already-seen slug). denseHits = [{ article: "topic-b", section: 0 }]; denseHitsByQuery.set(CHUNK_1, [{ article: "topic-a", section: 1 }]); denseHitsByQuery.set(CHUNK_2, [{ article: "topic-d", section: 0 }]); const result = await orchestrate( makeTurn(1, TWO_CHUNK_MESSAGE), depsOf(lanes, { denseK: 100, spanQueryK: 7, selectorEnabled: false }), ); expect(denseCalls).toEqual([ { query: TWO_CHUNK_MESSAGE, k: 100 }, { query: CHUNK_1, k: 7 }, { query: CHUNK_2, k: 7 }, ]); const laneOf = new Map( result.lanes.finder.map((c) => [c.slug, c.lane] as const), ); // "apple"/"elderberry" needle hits keep their lanes; the full-message // dense hit keeps "dense"; only the genuinely span-surfaced page tags // "span". expect(laneOf.get("topic-a")).toBe("needle"); expect(laneOf.get("topic-c")).toBe("needle"); expect(laneOf.get("topic-b")).toBe("dense"); expect(laneOf.get("topic-d")).toBe("span"); expect(result.lanes.finder.filter((c) => c.slug === "topic-a").length).toBe( 1, ); // The span hit's matched section is recorded for injection/spotlight. expect(result.matchedSections.get("topic-d")?.article).toBe("topic-d"); expect(result.matchedSections.get("topic-d")?.text).toContain( "lead for topic d", ); // Additive: the span-only page joins the passthrough selections alongside // every other lane's candidates. expect(new Set(result.selections.map((s) => s.slug))).toEqual( new Set(["topic-a", "topic-b", "topic-c", "topic-d"]), ); }); test("an article hit by several chunks records its highest-scoring section", async () => { const lanes = await buildLanes(); // Both chunks surface topic-d; the EARLIER chunk's hit is the weaker one. // Chunk order must not decide the recorded section — the strong match // (ordinal 1, `## Notes`) wins over the lead (ordinal 0). denseHits = []; denseHitsByQuery.set(CHUNK_1, [ { article: "topic-d", section: 0, score: 0.2 }, ]); denseHitsByQuery.set(CHUNK_2, [ { article: "topic-d", section: 1, score: 0.9 }, ]); const result = await orchestrate( makeTurn(1, TWO_CHUNK_MESSAGE), depsOf(lanes, { denseK: 100, spanQueryK: 7, selectorEnabled: false }), ); expect(result.matchedSections.get("topic-d")?.ordinal).toBe(1); expect(result.matchedSections.get("topic-d")?.text).toContain("grape"); expect( result.lanes.finder.filter((c) => c.slug === "topic-d"), ).toHaveLength(1); }); test("a strictly stronger span cosine upgrades a dense-recorded section, keeping the lane", async () => { const lanes = await buildLanes(); // Full-message dense records topic-b's lead (ordinal 0) at cosine 0.4; // chunk 2 finds `## More` (ordinal 1) at 0.9 — same encoder and // collection, strictly stronger, so the recorded section and finder // descriptor upgrade while the lane attribution stays "dense". denseHits = [{ article: "topic-b", section: 0, score: 0.4 }]; denseHitsByQuery.set(CHUNK_2, [ { article: "topic-b", section: 1, score: 0.9 }, ]); const result = await orchestrate( makeTurn(1, TWO_CHUNK_MESSAGE), depsOf(lanes, { denseK: 100, spanQueryK: 7, selectorEnabled: false }), ); expect(result.matchedSections.get("topic-b")?.ordinal).toBe(1); expect(result.matchedSections.get("topic-b")?.text).toContain( "cherry date", ); const entry = result.lanes.finder.find((c) => c.slug === "topic-b"); expect(entry?.lane).toBe("dense"); expect(entry?.descriptor).toContain("cherry date"); }); test("weaker span hits and needle-recorded sections are not overridden", async () => { const lanes = await buildLanes(); // topic-b: span cosine 0.3 < full-message 0.4 — the lead stays recorded. // topic-a: needle recorded the `## Details` match; span scores are not // comparable to BM25, so the span hit must not touch it. denseHits = [{ article: "topic-b", section: 0, score: 0.4 }]; denseHitsByQuery.set(CHUNK_1, [ { article: "topic-a", section: 0, score: 0.99 }, ]); denseHitsByQuery.set(CHUNK_2, [ { article: "topic-b", section: 1, score: 0.3 }, ]); const result = await orchestrate( makeTurn(1, TWO_CHUNK_MESSAGE), depsOf(lanes, { denseK: 100, spanQueryK: 7, selectorEnabled: false }), ); expect(result.matchedSections.get("topic-b")?.ordinal).toBe(0); expect(result.matchedSections.get("topic-a")?.text).toContain("apple"); expect(result.lanes.finder.find((c) => c.slug === "topic-a")?.lane).toBe( "needle", ); }); });