{"version":3,"file":"regression-corpus.test.d.ts","sourceRoot":"","sources":["../../../src/core/web-research/regression-corpus.test.ts"],"names":[],"mappings":"","sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { DeepResearchEngine } from \"./research.js\";\nimport { WebSearchProviderRegistry } from \"./search.js\";\nimport type {\n\tWebEvidenceRecord,\n\tWebFetchRequest,\n\tWebFetchResponse,\n\tWebSearchProvider,\n\tWebSearchResult,\n} from \"./types.js\";\n\n/**\n * Deterministic regression corpus modeled on the DarkOrbit-style failure classes\n * using fictional equipment sources. No live sites, no copyrighted text.\n *\n * Fixture sources:\n * A: official historical value (2017)\n * B: official rebalance value (2019)\n * C: maintained current value (2026)\n * D: community theory about \"best\"\n * E: search snippet only\n */\n\nconst RESULTS: WebSearchResult[] = [\n\t{\n\t\ttitle: \"Official 2017 weapon data\",\n\t\turl: \"https://official.example/2017-weapon\",\n\t\tsnippet: \"Official weapon damage 212 base.\",\n\t\tprovider: \"searxng\",\n\t\tengine: \"bing\",\n\t\trank: 1,\n\t},\n\t{\n\t\ttitle: \"Official 2019 rebalance notes\",\n\t\turl: \"https://official.example/2019-rebalance\",\n\t\tsnippet: \"Weapon damage changed from 212 to 225.\",\n\t\tprovider: \"searxng\",\n\t\tengine: \"brave\",\n\t\trank: 2,\n\t},\n\t{\n\t\ttitle: \"Maintained equipment database\",\n\t\turl: \"https://maintained.example/game/db\",\n\t\tsnippet: \"Current weapon damage 225, upgrades to level 10.\",\n\t\tprovider: \"searxng\",\n\t\tengine: \"bing\",\n\t\trank: 3,\n\t},\n\t{\n\t\ttitle: \"Community theory\",\n\t\turl: \"https://wiki.example/best-build\",\n\t\tsnippet: \"This is the best weapon, highest DPS in the game.\",\n\t\tprovider: \"searxng\",\n\t\tengine: \"brave\",\n\t\trank: 4,\n\t},\n];\n\nfunction searchProvider(): WebSearchProvider {\n\treturn {\n\t\tid: \"searxng\",\n\t\tcapabilities: { freshness: true, language: true, region: false, categories: true, safeSearch: true },\n\t\tsearch: async (request) => ({\n\t\t\tprovider: \"searxng\",\n\t\t\tquery: request.query,\n\t\t\tresults: RESULTS,\n\t\t\trawResultCount: RESULTS.length,\n\t\t\tdeduplicatedCount: 0,\n\t\t\tdurationMs: 1,\n\t\t}),\n\t\thealthCheck: async () => ({ provider: \"searxng\", status: \"healthy\" }),\n\t};\n}\n\nfunction evidence(url: string): WebEvidenceRecord {\n\tconst publishedAt = url.includes(\"2017\")\n\t\t? \"2017-06-01T00:00:00.000Z\"\n\t\t: url.includes(\"2019\")\n\t\t\t? \"2019-03-15T00:00:00.000Z\"\n\t\t\t: \"2026-05-01T00:00:00.000Z\";\n\tconst id = url.includes(\"2017\") ? \"web-a\" : url.includes(\"2019\") ? \"web-b\" : url.includes(\"db\") ? \"web-c\" : \"web-d\";\n\tconst content =\n\t\tid === \"web-a\"\n\t\t\t? \"Official 2017: weapon base damage is 212.\"\n\t\t\t: id === \"web-b\"\n\t\t\t\t? \"Official 2019 rebalance: weapon damage changed from 212 to 225.\"\n\t\t\t\t: id === \"web-c\"\n\t\t\t\t\t? \"Maintained 2026: current weapon base damage is 225; maximum upgrade level is 10.\"\n\t\t\t\t\t: \"Community theory: this weapon is the best and has the highest DPS in the game.\";\n\treturn {\n\t\tevidenceId: id,\n\t\tsourceType: \"web\",\n\t\trequestedUrl: url,\n\t\tfinalUrl: url,\n\t\tcanonicalUrl: url,\n\t\ttitle: id,\n\t\tretrievedAt: \"2026-08-02T00:00:00.000Z\",\n\t\tpublishedAt,\n\t\tcontentType: \"text/html\",\n\t\textractor: \"readability\",\n\t\tcontentSha256: id.padEnd(64, \"0\"),\n\t\tcompleteContentLocation: `session:tool-result:${id}`,\n\t\tcompleteContent: content,\n\t\trelevantPassages: [{ id: `passage-${id}`, text: content, startLine: 1, endLine: 1 }],\n\t\toutboundLinks: [],\n\t\tbytesDownloaded: 100,\n\t\tbytesExtracted: content.length,\n\t\ttruncated: false,\n\t\tuntrusted: true,\n\t};\n}\n\nfunction fetcherFor(_urls: string[]): { fetch(request: WebFetchRequest): Promise<WebFetchResponse> } {\n\treturn {\n\t\tfetch: async (request) => {\n\t\t\tconst record = evidence(request.url);\n\t\t\treturn { evidence: record, content: record.completeContent, rendered: false, durationMs: 1 };\n\t\t},\n\t};\n}\n\nfunction engine(): DeepResearchEngine {\n\tconst fallback = { ...searchProvider(), id: \"duckduckgo-lite\" as const };\n\treturn new DeepResearchEngine(\n\t\tnew WebSearchProviderRegistry([searchProvider(), fallback]),\n\t\t\"searxng\",\n\t\tfetcherFor(RESULTS.map((result) => result.url)),\n\t\t{\n\t\t\tmaxQueries: 4,\n\t\t\tmaxSources: 4,\n\t\t\tmaxBytes: 10_000,\n\t\t\tmaxBrowserRenders: 0,\n\t\t\tmaxElapsedMs: 5000,\n\t\t\tmaxParallelFetches: 2,\n\t\t},\n\t);\n}\n\ndescribe(\"canonical regression corpus (equipment comparison)\", () => {\n\tit(\"resolves historical and current values, validates arithmetic, and produces addressable citations\", async () => {\n\t\tconst result = await engine().run({\n\t\t\tobjective: \"Compare weapon damage across official rebalances and current maintained data\",\n\t\t\tmaxQueries: 2,\n\t\t\tmaxSources: 3,\n\t\t\tfacts: {\n\t\t\t\ttemporal: [\n\t\t\t\t\t{\n\t\t\t\t\t\tevidenceId: \"web-a\",\n\t\t\t\t\t\tsourceUrl: \"https://official.example/2017-weapon\",\n\t\t\t\t\t\tvalue: 212,\n\t\t\t\t\t\teffectiveAt: \"2017-06-01\",\n\t\t\t\t\t\tpublishedAt: \"2017-06-01\",\n\t\t\t\t\t\tauthority: 2,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tevidenceId: \"web-b\",\n\t\t\t\t\t\tsourceUrl: \"https://official.example/2019-rebalance\",\n\t\t\t\t\t\tvalue: 225,\n\t\t\t\t\t\teffectiveAt: \"2019-03-15\",\n\t\t\t\t\t\tpublishedAt: \"2019-03-15\",\n\t\t\t\t\t\tauthority: 2,\n\t\t\t\t\t\tchangeDeclaration: \"changed from 212 to 225\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tevidenceId: \"web-c\",\n\t\t\t\t\t\tsourceUrl: \"https://maintained.example/game/db\",\n\t\t\t\t\t\tvalue: 225,\n\t\t\t\t\t\tisMaintained: true,\n\t\t\t\t\t\tauthority: 2,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tnumericExpressions: [\n\t\t\t\t\t{\n\t\t\t\t\t\tfacts: [\n\t\t\t\t\t\t\t{ value: 225, unit: \"damage_per_shot\", evidenceId: \"web-c\" },\n\t\t\t\t\t\t\t{ value: 15, unit: \"flat_damage_bonus\", evidenceId: \"web-c\" },\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tcurrentValues: [\"225\"],\n\t\t\t},\n\t\t});\n\n\t\t// Temporal resolution: 212 historical, 225 current, not a contradiction.\n\t\texpect(result.bundle.temporal).toBeDefined();\n\t\tconst temporal = result.bundle.temporal!;\n\t\texpect(temporal.unresolved).toBe(false);\n\t\texpect(temporal.currentValue).toBe(225);\n\t\tconst classes = Object.fromEntries(temporal.resolutions.map((r) => [r.evidenceId, r.class]));\n\t\texpect(classes[\"web-a\"]).toBe(\"superseded\");\n\t\texpect(classes[\"web-c\"]).toBe(\"current\");\n\n\t\t// Numeric: flat +15 applied additively => 240, not 225 × 1.15.\n\t\texpect(result.bundle.numericVerifications).toHaveLength(1);\n\t\texpect(result.bundle.numericVerifications![0].outcome).toBe(\"verified\");\n\t\texpect(result.bundle.numericVerifications![0].computed).toBe(240);\n\n\t\t// Addressable citations on every claim.\n\t\texpect(result.synthesis).toContain(\"Temporal resolution:\");\n\t\texpect(result.synthesis).toContain(\"Numeric verification:\");\n\t\texpect(result.bundle.claims.every((claim) => claim.supports[0]?.evidenceId)).toBe(true);\n\t\texpect(result.bundle.claims.every((claim) => claim.supports[0]?.contentSha256)).toBe(true);\n\t\texpect(result.bundle.sourceConfidence).toBeDefined();\n\t});\n\n\tit(\"never emits internal tool bookkeeping in the synthesis\", async () => {\n\t\tconst result = await engine().run({\n\t\t\tobjective: \"Compare weapon damage\",\n\t\t\tmaxQueries: 2,\n\t\t\tmaxSources: 3,\n\t\t});\n\t\tconst leakedTokens = [\n\t\t\t\"todo_update\",\n\t\t\t\"TODO_READ_REQUIRED\",\n\t\t\t\"expectedRevision\",\n\t\t\t\"Current todo list\",\n\t\t\t\"todo_write\",\n\t\t\t\"todo_read\",\n\t\t];\n\t\tfor (const token of leakedTokens) {\n\t\t\texpect(result.synthesis.toLowerCase()).not.toContain(token.toLowerCase());\n\t\t}\n\t\t// Synthesis is a self-contained terminal report.\n\t\texpect(result.synthesis).toContain(\"Research objective:\");\n\t\texpect(result.synthesis).toContain(\"untrusted evidence\");\n\t});\n});\n\ndescribe(\"snippet-only and exact-claim policy in the canonical corpus\", () => {\n\tit(\"keeps the community theory as low confidence / snippet-only and does not promote it to fact\", async () => {\n\t\tconst result = await engine().run({\n\t\t\tobjective: \"Determine whether a community build is best\",\n\t\t\tmaxQueries: 2,\n\t\t\tmaxSources: 4,\n\t\t\tfacts: {\n\t\t\t\tcurrentValues: [\"225\"],\n\t\t\t\ttemporal: [\n\t\t\t\t\t{\n\t\t\t\t\t\tevidenceId: \"web-d\",\n\t\t\t\t\t\tsourceUrl: \"https://wiki.example/best-build\",\n\t\t\t\t\t\tvalue: \"best\",\n\t\t\t\t\t\tauthority: 0,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t});\n\t\t// Community theory must not become a high-confidence claim, and every\n\t\t// claim still carries addressable support.\n\t\texpect(result.bundle.sourceConfidence).toBeDefined();\n\t\texpect(result.bundle.sourceConfidence![\"web-d\"]).not.toBe(\"high\");\n\t\texpect(result.bundle.claims.every((claim) => claim.supports[0]?.evidenceId)).toBe(true);\n\t\texpect(result.bundle.claims.every((claim) => claim.supports[0]?.contentSha256)).toBe(true);\n\t});\n});\n"]}