@remnic/core 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-audit.d.ts +56 -0
- package/dist/access-audit.js +9 -0
- package/dist/access-cli.js +70 -53
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +16 -9
- package/dist/access-http.js +26 -18
- package/dist/access-mcp.d.ts +16 -9
- package/dist/access-mcp.js +30 -8
- package/dist/access-schema.d.ts +124 -33
- package/dist/access-schema.js +5 -1
- package/dist/{access-service-HmO1Trrx.d.ts → access-service-Br8ZydTK.d.ts} +158 -63
- package/dist/access-service.d.ts +13 -6
- package/dist/access-service.js +23 -14
- package/dist/bootstrap.d.ts +6 -3
- package/dist/briefing.d.ts +1 -0
- package/dist/briefing.js +8 -6
- package/dist/buffer-surprise-report.d.ts +70 -0
- package/dist/buffer-surprise-report.js +7 -0
- package/dist/buffer-surprise-report.js.map +1 -0
- package/dist/buffer-surprise.d.ts +98 -0
- package/dist/buffer-surprise.js +11 -0
- package/dist/buffer-surprise.js.map +1 -0
- package/dist/buffer.d.ts +100 -2
- package/dist/buffer.js +1 -1
- package/dist/calibration.js +6 -6
- package/dist/causal-behavior.js +4 -4
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +19 -18
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +4 -4
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-QNJMBKFK.js → chunk-2LGMW3DJ.js} +3 -2
- package/dist/chunk-2LGMW3DJ.js.map +1 -0
- package/dist/{chunk-QDYXG4CS.js → chunk-3FPTCC3Z.js} +4 -3
- package/dist/chunk-3FPTCC3Z.js.map +1 -0
- package/dist/chunk-3GPTTA4J.js +57 -0
- package/dist/chunk-3GPTTA4J.js.map +1 -0
- package/dist/{chunk-ITRLGI2T.js → chunk-3OGMS3PE.js} +2 -2
- package/dist/{chunk-DEPL3635.js → chunk-3YGHKTBF.js} +1446 -196
- package/dist/chunk-3YGHKTBF.js.map +1 -0
- package/dist/{chunk-BLKTA7MM.js → chunk-4HQS2HPX.js} +54 -21
- package/dist/chunk-4HQS2HPX.js.map +1 -0
- package/dist/chunk-54V4BZWP.js +139 -0
- package/dist/chunk-54V4BZWP.js.map +1 -0
- package/dist/chunk-5JRF2PZA.js +67 -0
- package/dist/chunk-5JRF2PZA.js.map +1 -0
- package/dist/chunk-64NJRYU2.js +332 -0
- package/dist/chunk-64NJRYU2.js.map +1 -0
- package/dist/{chunk-OIT5QGG4.js → chunk-6AUUAZEX.js} +72 -2
- package/dist/chunk-6AUUAZEX.js.map +1 -0
- package/dist/{chunk-3QHL5ABG.js → chunk-6YJHX2DL.js} +191 -10
- package/dist/chunk-6YJHX2DL.js.map +1 -0
- package/dist/chunk-AJU4PJGY.js +126 -0
- package/dist/chunk-AJU4PJGY.js.map +1 -0
- package/dist/chunk-ASAITVLA.js +64 -0
- package/dist/chunk-ASAITVLA.js.map +1 -0
- package/dist/{chunk-44ICJRF3.js → chunk-AYXIPSZO.js} +5 -5
- package/dist/{chunk-MBJHSA7F.js → chunk-BECYBZLX.js} +265 -20
- package/dist/chunk-BECYBZLX.js.map +1 -0
- package/dist/chunk-C4SQJZAF.js +486 -0
- package/dist/chunk-C4SQJZAF.js.map +1 -0
- package/dist/{chunk-6UJ47TVX.js → chunk-CUPFXL3J.js} +2 -2
- package/dist/chunk-DF3RVK3X.js +119 -0
- package/dist/chunk-DF3RVK3X.js.map +1 -0
- package/dist/{chunk-N42IWANG.js → chunk-DG6YMRDC.js} +3 -3
- package/dist/chunk-DGVM5SFL.js +69 -0
- package/dist/chunk-DGVM5SFL.js.map +1 -0
- package/dist/{chunk-3SV6CQHO.js → chunk-DIXB44VE.js} +102 -66
- package/dist/chunk-DIXB44VE.js.map +1 -0
- package/dist/chunk-EIR5VLIH.js +90 -0
- package/dist/chunk-EIR5VLIH.js.map +1 -0
- package/dist/{chunk-GV6NLQ4X.js → chunk-F5VP6YCB.js} +374 -16
- package/dist/chunk-F5VP6YCB.js.map +1 -0
- package/dist/{chunk-6ZH4TU6I.js → chunk-FAAFWE4G.js} +2 -1
- package/dist/chunk-FAAFWE4G.js.map +1 -0
- package/dist/{chunk-7WQ6SLIE.js → chunk-FVA6TGI3.js} +2 -2
- package/dist/{chunk-PAORGQRI.js → chunk-GA5P7RST.js} +37 -23
- package/dist/chunk-GA5P7RST.js.map +1 -0
- package/dist/chunk-GDFS42HT.js +206 -0
- package/dist/chunk-GDFS42HT.js.map +1 -0
- package/dist/chunk-IISBCCWR.js +52 -0
- package/dist/chunk-IISBCCWR.js.map +1 -0
- package/dist/chunk-JBMSGZEQ.js +441 -0
- package/dist/chunk-JBMSGZEQ.js.map +1 -0
- package/dist/{chunk-J4IYOZZ5.js → chunk-JXS5PDQ7.js} +3 -1
- package/dist/chunk-JXS5PDQ7.js.map +1 -0
- package/dist/chunk-KVBLZUKV.js +173 -0
- package/dist/chunk-KVBLZUKV.js.map +1 -0
- package/dist/{chunk-4LACOVZX.js → chunk-L7IXWRYE.js} +10 -5
- package/dist/chunk-L7IXWRYE.js.map +1 -0
- package/dist/chunk-LBLXEFWK.js +51 -0
- package/dist/chunk-LBLXEFWK.js.map +1 -0
- package/dist/{chunk-WBSAYXVI.js → chunk-LOIMBRDE.js} +201 -45
- package/dist/chunk-LOIMBRDE.js.map +1 -0
- package/dist/{chunk-3WHVNEN7.js → chunk-LTCGGW2D.js} +1 -1
- package/dist/chunk-LTCGGW2D.js.map +1 -0
- package/dist/{chunk-ZVBB3T7V.js → chunk-NBVAS5MT.js} +25 -23
- package/dist/chunk-NBVAS5MT.js.map +1 -0
- package/dist/{chunk-UEYA6UC7.js → chunk-NZLQTHS5.js} +25 -2
- package/dist/chunk-NZLQTHS5.js.map +1 -0
- package/dist/{chunk-NQEVYWX6.js → chunk-OC5OXUQ4.js} +211 -7
- package/dist/chunk-OC5OXUQ4.js.map +1 -0
- package/dist/{chunk-LK6SGL53.js → chunk-OR64ZGRZ.js} +3 -2
- package/dist/chunk-OR64ZGRZ.js.map +1 -0
- package/dist/{chunk-SYUK3VLY.js → chunk-PVICZTKG.js} +117 -5
- package/dist/chunk-PVICZTKG.js.map +1 -0
- package/dist/chunk-PVPWZSSI.js +37 -0
- package/dist/chunk-PVPWZSSI.js.map +1 -0
- package/dist/{chunk-JL2PU6AI.js → chunk-R2XRID2N.js} +2 -2
- package/dist/{chunk-4NRAJUDS.js → chunk-RBBWYEFJ.js} +1 -1
- package/dist/chunk-RFYAYKTD.js +146 -0
- package/dist/chunk-RFYAYKTD.js.map +1 -0
- package/dist/chunk-SOBJ6NEY.js +18 -0
- package/dist/chunk-SOBJ6NEY.js.map +1 -0
- package/dist/{chunk-JIU55F3X.js → chunk-SPI27QT6.js} +2 -2
- package/dist/{chunk-MVTHXUBX.js → chunk-STGWEHYR.js} +479 -20
- package/dist/chunk-STGWEHYR.js.map +1 -0
- package/dist/{chunk-6LX5ORAS.js → chunk-TMYO7B5P.js} +4 -4
- package/dist/chunk-TVVEYCNW.js +65 -0
- package/dist/chunk-TVVEYCNW.js.map +1 -0
- package/dist/chunk-ULYOGL6R.js +322 -0
- package/dist/chunk-ULYOGL6R.js.map +1 -0
- package/dist/{chunk-37UIFYWO.js → chunk-UWB5LMWY.js} +108 -9
- package/dist/chunk-UWB5LMWY.js.map +1 -0
- package/dist/{chunk-47UU5PU2.js → chunk-VBVG2M5G.js} +18 -3
- package/dist/chunk-VBVG2M5G.js.map +1 -0
- package/dist/{chunk-7ECD5ATE.js → chunk-VDX363PS.js} +2 -2
- package/dist/{chunk-O5ETUNBT.js → chunk-VTU2B4VF.js} +7 -3
- package/dist/chunk-VTU2B4VF.js.map +1 -0
- package/dist/{chunk-MTLYEMJB.js → chunk-WCLICCGB.js} +18 -3
- package/dist/chunk-WCLICCGB.js.map +1 -0
- package/dist/chunk-X6GF3FX2.js +26 -0
- package/dist/chunk-X6GF3FX2.js.map +1 -0
- package/dist/{chunk-3QFQGRHO.js → chunk-XMHBH5H6.js} +4 -4
- package/dist/{chunk-DHHP2Z4X.js → chunk-XXVWLXSG.js} +2 -2
- package/dist/{chunk-XZ2TIKGC.js → chunk-Y7R2XJ5Q.js} +25 -9
- package/dist/chunk-Y7R2XJ5Q.js.map +1 -0
- package/dist/{chunk-ALXMCZEU.js → chunk-Z2E7VW55.js} +6 -3
- package/dist/chunk-Z2E7VW55.js.map +1 -0
- package/dist/chunk-ZAIM4TUE.js +488 -0
- package/dist/chunk-ZAIM4TUE.js.map +1 -0
- package/dist/chunk-ZZTOURJI.js +91 -0
- package/dist/chunk-ZZTOURJI.js.map +1 -0
- package/dist/{cli-BneVIEvh.d.ts → cli-BkeRaYfk.d.ts} +2 -2
- package/dist/cli.d.ts +13 -6
- package/dist/cli.js +42 -31
- package/dist/config.js +2 -2
- package/dist/consolidation-operator.d.ts +41 -0
- package/dist/consolidation-operator.js +11 -0
- package/dist/consolidation-operator.js.map +1 -0
- package/dist/consolidation-provenance-check.d.ts +68 -0
- package/dist/consolidation-provenance-check.js +9 -0
- package/dist/consolidation-provenance-check.js.map +1 -0
- package/dist/consolidation-undo.d.ts +123 -0
- package/dist/consolidation-undo.js +426 -0
- package/dist/consolidation-undo.js.map +1 -0
- package/dist/{contradiction-scan-GR33PONM.js → contradiction-scan-E3GJTI4F.js} +43 -7
- package/dist/contradiction-scan-E3GJTI4F.js.map +1 -0
- package/dist/cross-namespace-budget.d.ts +133 -0
- package/dist/cross-namespace-budget.js +9 -0
- package/dist/cross-namespace-budget.js.map +1 -0
- package/dist/direct-answer-wiring.js +5 -70
- package/dist/direct-answer-wiring.js.map +1 -1
- package/dist/embedding-fallback.js +2 -1
- package/dist/{engine-5TIQBYZR.js → engine-72LSIWQP.js} +8 -7
- package/dist/engine-72LSIWQP.js.map +1 -0
- package/dist/entity-retrieval.d.ts +1 -0
- package/dist/entity-retrieval.js +7 -6
- package/dist/explicit-capture.d.ts +6 -3
- package/dist/explicit-capture.js +2 -2
- package/dist/extraction-judge-telemetry.d.ts +113 -0
- package/dist/extraction-judge-telemetry.js +14 -0
- package/dist/extraction-judge-telemetry.js.map +1 -0
- package/dist/extraction-judge-training.d.ts +85 -0
- package/dist/extraction-judge-training.js +16 -0
- package/dist/extraction-judge-training.js.map +1 -0
- package/dist/extraction-judge.d.ts +124 -2
- package/dist/extraction-judge.js +11 -1
- package/dist/extraction.js +10 -9
- package/dist/fallback-llm.js +3 -3
- package/dist/graph-recall.d.ts +100 -0
- package/dist/graph-recall.js +8 -0
- package/dist/graph-recall.js.map +1 -0
- package/dist/graph-retrieval.d.ts +271 -0
- package/dist/graph-retrieval.js +21 -0
- package/dist/graph-retrieval.js.map +1 -0
- package/dist/importance.js +1 -1
- package/dist/index.d.ts +585 -20
- package/dist/index.js +542 -344
- package/dist/index.js.map +1 -1
- package/dist/local-llm.js +2 -2
- package/dist/memory-worth-bench.d.ts +51 -0
- package/dist/memory-worth-bench.js +131 -0
- package/dist/memory-worth-bench.js.map +1 -0
- package/dist/memory-worth-filter.d.ts +128 -0
- package/dist/memory-worth-filter.js +10 -0
- package/dist/memory-worth-filter.js.map +1 -0
- package/dist/memory-worth-outcomes.d.ts +118 -0
- package/dist/memory-worth-outcomes.js +9 -0
- package/dist/memory-worth-outcomes.js.map +1 -0
- package/dist/memory-worth.d.ts +102 -0
- package/dist/memory-worth.js +7 -0
- package/dist/memory-worth.js.map +1 -0
- package/dist/operator-toolkit.d.ts +40 -1
- package/dist/operator-toolkit.js +25 -16
- package/dist/{orchestrator-DRYA6_lW.d.ts → orchestrator-CmJ-NTdJ.d.ts} +233 -8
- package/dist/orchestrator.d.ts +6 -3
- package/dist/orchestrator.js +54 -44
- package/dist/page-versioning.d.ts +12 -1
- package/dist/page-versioning.js +5 -3
- package/dist/{port-C1GZFv8h.d.ts → port-BADbLZU5.d.ts} +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +5 -3
- package/dist/qmd.js +3 -3
- package/dist/reasoning-trace-recall.d.ts +90 -0
- package/dist/reasoning-trace-recall.js +13 -0
- package/dist/reasoning-trace-recall.js.map +1 -0
- package/dist/reasoning-trace-types.d.ts +54 -0
- package/dist/reasoning-trace-types.js +17 -0
- package/dist/reasoning-trace-types.js.map +1 -0
- package/dist/recall-audit-anomaly.d.ts +112 -0
- package/dist/recall-audit-anomaly.js +11 -0
- package/dist/recall-audit-anomaly.js.map +1 -0
- package/dist/recall-audit.js +5 -44
- package/dist/recall-audit.js.map +1 -1
- package/dist/recall-explain-renderer.d.ts +49 -0
- package/dist/recall-explain-renderer.js +18 -0
- package/dist/recall-explain-renderer.js.map +1 -0
- package/dist/recall-state.d.ts +12 -1
- package/dist/recall-state.js +1 -1
- package/dist/recall-xray-cli.d.ts +40 -0
- package/dist/recall-xray-cli.js +11 -0
- package/dist/recall-xray-cli.js.map +1 -0
- package/dist/recall-xray-renderer.d.ts +44 -0
- package/dist/recall-xray-renderer.js +18 -0
- package/dist/recall-xray-renderer.js.map +1 -0
- package/dist/recall-xray.d.ts +179 -0
- package/dist/recall-xray.js +13 -0
- package/dist/recall-xray.js.map +1 -0
- package/dist/resolve-provider-secret.d.ts +5 -1
- package/dist/resolve-provider-secret.js +3 -1
- package/dist/resume-bundles.js +6 -6
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +17 -0
- package/dist/retrieval-tiers.js +9 -0
- package/dist/retrieval-tiers.js.map +1 -0
- package/dist/schemas.d.ts +309 -53
- package/dist/schemas.js +1 -1
- package/dist/{semantic-consolidation-DrvSYRdB.d.ts → semantic-consolidation-CxJU6MJk.d.ts} +62 -1
- package/dist/semantic-consolidation.d.ts +2 -1
- package/dist/semantic-consolidation.js +22 -7
- package/dist/semantic-rule-promotion.js +7 -6
- package/dist/semantic-rule-verifier.js +7 -6
- package/dist/storage.d.ts +82 -1
- package/dist/storage.js +6 -5
- package/dist/summarizer.js +6 -6
- package/dist/temporal-supersession.d.ts +1 -0
- package/dist/tier-migration.d.ts +2 -1
- package/dist/tokens.js +2 -1
- package/dist/types.d.ts +276 -2
- package/dist/types.js +1 -1
- package/dist/verified-recall.js +7 -6
- package/package.json +1 -1
- package/dist/chunk-37UIFYWO.js.map +0 -1
- package/dist/chunk-3QHL5ABG.js.map +0 -1
- package/dist/chunk-3SV6CQHO.js.map +0 -1
- package/dist/chunk-3WHVNEN7.js.map +0 -1
- package/dist/chunk-47UU5PU2.js.map +0 -1
- package/dist/chunk-4LACOVZX.js.map +0 -1
- package/dist/chunk-6ZH4TU6I.js.map +0 -1
- package/dist/chunk-ALXMCZEU.js.map +0 -1
- package/dist/chunk-BLKTA7MM.js.map +0 -1
- package/dist/chunk-DEPL3635.js.map +0 -1
- package/dist/chunk-GV6NLQ4X.js.map +0 -1
- package/dist/chunk-J4IYOZZ5.js.map +0 -1
- package/dist/chunk-LAYN4LDC.js +0 -267
- package/dist/chunk-LAYN4LDC.js.map +0 -1
- package/dist/chunk-LK6SGL53.js.map +0 -1
- package/dist/chunk-MBJHSA7F.js.map +0 -1
- package/dist/chunk-MTLYEMJB.js.map +0 -1
- package/dist/chunk-MVTHXUBX.js.map +0 -1
- package/dist/chunk-NQEVYWX6.js.map +0 -1
- package/dist/chunk-O5ETUNBT.js.map +0 -1
- package/dist/chunk-OIT5QGG4.js.map +0 -1
- package/dist/chunk-PAORGQRI.js.map +0 -1
- package/dist/chunk-QDYXG4CS.js.map +0 -1
- package/dist/chunk-QNJMBKFK.js.map +0 -1
- package/dist/chunk-SYUK3VLY.js.map +0 -1
- package/dist/chunk-UEYA6UC7.js.map +0 -1
- package/dist/chunk-UVJFDP7P.js +0 -202
- package/dist/chunk-UVJFDP7P.js.map +0 -1
- package/dist/chunk-WBSAYXVI.js.map +0 -1
- package/dist/chunk-XZ2TIKGC.js.map +0 -1
- package/dist/chunk-ZVBB3T7V.js.map +0 -1
- package/dist/contradiction-scan-GR33PONM.js.map +0 -1
- /package/dist/{engine-5TIQBYZR.js.map → access-audit.js.map} +0 -0
- /package/dist/{chunk-ITRLGI2T.js.map → chunk-3OGMS3PE.js.map} +0 -0
- /package/dist/{chunk-44ICJRF3.js.map → chunk-AYXIPSZO.js.map} +0 -0
- /package/dist/{chunk-6UJ47TVX.js.map → chunk-CUPFXL3J.js.map} +0 -0
- /package/dist/{chunk-N42IWANG.js.map → chunk-DG6YMRDC.js.map} +0 -0
- /package/dist/{chunk-7WQ6SLIE.js.map → chunk-FVA6TGI3.js.map} +0 -0
- /package/dist/{chunk-JL2PU6AI.js.map → chunk-R2XRID2N.js.map} +0 -0
- /package/dist/{chunk-4NRAJUDS.js.map → chunk-RBBWYEFJ.js.map} +0 -0
- /package/dist/{chunk-JIU55F3X.js.map → chunk-SPI27QT6.js.map} +0 -0
- /package/dist/{chunk-6LX5ORAS.js.map → chunk-TMYO7B5P.js.map} +0 -0
- /package/dist/{chunk-7ECD5ATE.js.map → chunk-VDX363PS.js.map} +0 -0
- /package/dist/{chunk-3QFQGRHO.js.map → chunk-XMHBH5H6.js.map} +0 -0
- /package/dist/{chunk-DHHP2Z4X.js.map → chunk-XXVWLXSG.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/contradiction/contradiction-judge.ts","../src/contradiction/contradiction-scan.ts"],"sourcesContent":["/**\n * Contradiction Judge — LLM-as-judge for semantic contradiction detection (issue #520).\n *\n * Pairs semantically-similar memories and classifies their relationship.\n * Reuses the extraction-judge adapter pattern but with a contradiction-specific\n * prompt and verdict taxonomy.\n *\n * Design constraints:\n * - Default verdict on any failure is \"needs-user\" (rule 48: least-privileged default).\n * - Never auto-resolve; all verdicts enter the review queue.\n * - Content-hash caching avoids redundant LLM calls across runs.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { log } from \"../logger.js\";\nimport type { PluginConfig } from \"../types.js\";\nimport type { LocalLlmClient } from \"../local-llm.js\";\nimport type { FallbackLlmClient } from \"../fallback-llm.js\";\nimport { extractJsonCandidates } from \"../json-extract.js\";\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type ContradictionVerdict =\n | \"contradicts\"\n | \"independent\"\n | \"duplicates\"\n | \"needs-user\";\n\nexport interface ContradictionJudgeInput {\n /** Memory ID of the first fact. */\n memoryIdA: string;\n /** Memory ID of the second fact. */\n memoryIdB: string;\n /** Content text of the first fact. */\n textA: string;\n /** Content text of the second fact. */\n textB: string;\n /** Category of the first fact (optional context). */\n categoryA?: string;\n /** Category of the second fact (optional context). */\n categoryB?: string;\n}\n\nexport interface ContradictionJudgeResult {\n /** Memory IDs of the pair. */\n memoryIdA: string;\n memoryIdB: string;\n /** Verdict from the judge. */\n verdict: ContradictionVerdict;\n /** Human-readable rationale. */\n rationale: string;\n /** Confidence in [0, 1]. */\n confidence: number;\n}\n\nexport interface ContradictionJudgeBatchResult {\n /** Results keyed by pair key (\"idA:idB\"). */\n results: Map<string, ContradictionJudgeResult>;\n /** Number served from cache. */\n cached: number;\n /** Number produced by LLM call. */\n judged: number;\n /** Total wall-clock time in ms. */\n elapsed: number;\n}\n\n// ── Prompt ─────────────────────────────────────────────────────────────────────\n\nconst CONTRADICTION_JUDGE_PROMPT = `You are a memory contradiction classifier. You will receive pairs of stored memories and must classify their semantic relationship.\n\nFor each pair, respond with a JSON array where each element has:\n- \"pairKey\": the pairKey provided in the input\n- \"verdict\": one of \"contradicts\", \"independent\", \"duplicates\", \"needs-user\"\n- \"rationale\": one sentence explaining why\n- \"confidence\": number between 0 and 1\n\nVERDICT DEFINITIONS:\n- \"contradicts\": The two memories make claims that cannot both be true. One must be wrong or outdated.\n- \"duplicates\": The two memories convey essentially the same information (near-paraphrase).\n- \"independent\": The memories are topically similar but do not conflict or duplicate.\n- \"needs-user\": Cannot determine with sufficient confidence; requires human review.\n\nIMPORTANT:\n- Be conservative. When in doubt, prefer \"needs-user\" over a wrong classification.\n- Two memories about the same entity/topic are NOT necessarily contradictory.\n- Temporal changes (\"Joshua uses pnpm\" vs \"Joshua switched to npm\") ARE contradictions.\n- Different aspects of the same entity (\"Joshua uses pnpm\" vs \"Joshua works on Remnic\") are \"independent\".`;\n\n// ── Cache ──────────────────────────────────────────────────────────────────────\n\n/** Module-level fallback cache — only used when caller does not supply one. */\nlet defaultVerdictCache: Map<string, ContradictionJudgeResult> = new Map();\nconst CACHE_MAX = 10_000;\n\nfunction pairKey(idA: string, idB: string): string {\n const sorted = [idA, idB].sort();\n return `${sorted[0]}:${sorted[1]}`;\n}\n\nfunction contentHash(a: ContradictionJudgeInput): string {\n // Sort each side pair to be order-independent (matching pairKey behavior)\n const sides = [\n [a.textA.trim(), (a.categoryA ?? \"\").trim()].join(\"|\"),\n [a.textB.trim(), (a.categoryB ?? \"\").trim()].join(\"|\"),\n ].sort();\n const normalized = sides.join(\"|||\");\n return createHash(\"sha256\").update(normalized).digest(\"hex\").slice(0, 16);\n}\n\nexport function createVerdictCache(): Map<string, ContradictionJudgeResult> {\n return new Map();\n}\n\nexport function clearVerdictCache(): void {\n defaultVerdictCache.clear();\n}\n\nexport function verdictCacheSize(): number {\n return defaultVerdictCache.size;\n}\n\n// ── Public API ──────────────────────────────────────────────────────────────────\n\n/**\n * Judge a batch of memory pairs for contradiction.\n *\n * Uses content-hash caching to skip pairs already judged in a prior run.\n * On any LLM failure, all unresolved pairs default to \"needs-user\".\n */\nexport async function judgeContradictionPairs(\n pairs: ContradictionJudgeInput[],\n config: PluginConfig,\n localLlm: LocalLlmClient | null,\n fallbackLlm: FallbackLlmClient | null,\n cache?: Map<string, ContradictionJudgeResult>,\n): Promise<ContradictionJudgeBatchResult> {\n const startTime = Date.now();\n const results = new Map<string, ContradictionJudgeResult>();\n const activeCache = cache ?? defaultVerdictCache;\n let cached = 0;\n let judged = 0;\n\n // Partition into cached vs needs-judging\n const toJudge: ContradictionJudgeInput[] = [];\n for (const pair of pairs) {\n const key = pairKey(pair.memoryIdA, pair.memoryIdB);\n const hash = contentHash(pair);\n const cachedResult = activeCache.get(hash);\n if (cachedResult) {\n results.set(key, { ...cachedResult, memoryIdA: pair.memoryIdA, memoryIdB: pair.memoryIdB });\n cached++;\n } else {\n toJudge.push(pair);\n }\n }\n\n if (toJudge.length === 0) {\n return { results, cached, judged, elapsed: Date.now() - startTime };\n }\n\n // Build the prompt with all pairs\n const pairDescriptions = toJudge.map((p, i) => {\n const pk = pairKey(p.memoryIdA, p.memoryIdB);\n const catA = p.categoryA ? ` [${p.categoryA}]` : \"\";\n const catB = p.categoryB ? ` [${p.categoryB}]` : \"\";\n return `Pair ${i + 1} (pairKey: \"${pk}\"):${catA} \"${p.textA}\"${catB} \"${p.textB}\"`;\n });\n\n const userMessage = `Classify these ${toJudge.length} memory pair(s):\\n\\n${pairDescriptions.join(\"\\n\\n\")}`;\n\n // Try LLM call\n let llmResponse: string | null = null;\n\n if (localLlm) {\n try {\n llmResponse = await callLlm(localLlm, config, userMessage);\n } catch (err) {\n log.warn(\"[contradiction-judge] local LLM call failed: %s\", err instanceof Error ? err.message : err);\n }\n }\n\n if (!llmResponse && fallbackLlm) {\n try {\n llmResponse = await callLlm(fallbackLlm, config, userMessage);\n } catch (err) {\n log.warn(\"[contradiction-judge] fallback LLM call failed: %s\", err instanceof Error ? err.message : err);\n }\n }\n\n // Parse response or default to needs-user\n if (llmResponse) {\n const candidates = extractJsonCandidates(llmResponse);\n const parsed = parseJudgeResponse(candidates, toJudge);\n\n for (const result of parsed) {\n const key = pairKey(result.memoryIdA, result.memoryIdB);\n results.set(key, result);\n\n // Update cache\n const input = toJudge.find(\n (p) => pairKey(p.memoryIdA, p.memoryIdB) === key,\n );\n if (input) {\n const hash = contentHash(input);\n if (activeCache.size >= CACHE_MAX) {\n const firstKey = activeCache.keys().next().value;\n if (firstKey !== undefined) activeCache.delete(firstKey);\n }\n activeCache.set(hash, result);\n }\n judged++;\n }\n } else {\n // All unresolved → needs-user (rule 48)\n for (const pair of toJudge) {\n const key = pairKey(pair.memoryIdA, pair.memoryIdB);\n const result: ContradictionJudgeResult = {\n memoryIdA: pair.memoryIdA,\n memoryIdB: pair.memoryIdB,\n verdict: \"needs-user\",\n rationale: \"LLM call failed; requires manual review\",\n confidence: 0,\n };\n results.set(key, result);\n judged++;\n }\n }\n\n return { results, cached, judged, elapsed: Date.now() - startTime };\n}\n\n// ── Internals ──────────────────────────────────────────────────────────────────\n\nasync function callLlm(\n client: LocalLlmClient | FallbackLlmClient,\n config: PluginConfig,\n userMessage: string,\n): Promise<string> {\n const messages: Array<{ role: \"user\" | \"assistant\" | \"system\"; content: string }> = [\n { role: \"system\", content: CONTRADICTION_JUDGE_PROMPT },\n { role: \"user\", content: userMessage },\n ];\n if (\"chatCompletion\" in client && typeof client.chatCompletion === \"function\") {\n const result = await client.chatCompletion(messages, {\n temperature: 0.1,\n maxTokens: 4096,\n });\n return result?.content ?? \"\";\n }\n // FallbackLlmClient — try OpenAI-compatible chat completions\n if (\"complete\" in client && typeof (client as Record<string, unknown>).complete === \"function\") {\n const result = await (client as { complete: (msg: Array<{ role: string; content: string }>) => Promise<{ content: string }> }).complete(messages);\n return result.content ?? \"\";\n }\n return \"\";\n}\n\nfunction parseJudgeResponse(\n candidates: string[],\n inputs: ContradictionJudgeInput[],\n): ContradictionJudgeResult[] {\n const VALID_VERDICTS: ContradictionVerdict[] = [\"contradicts\", \"independent\", \"duplicates\", \"needs-user\"];\n\n for (const candidate of candidates) {\n try {\n const parsed = JSON.parse(candidate);\n const items = Array.isArray(parsed) ? parsed : [parsed];\n const results: ContradictionJudgeResult[] = [];\n const matchedKeys = new Set<string>();\n\n for (const item of items) {\n if (!item || typeof item !== \"object\") continue;\n\n const verdict = typeof item.verdict === \"string\" && VALID_VERDICTS.includes(item.verdict as ContradictionVerdict)\n ? (item.verdict as ContradictionVerdict)\n : \"needs-user\";\n\n const pairKeyVal = typeof item.pairKey === \"string\" ? item.pairKey : null;\n const input = pairKeyVal\n ? inputs.find((p) => pairKey(p.memoryIdA, p.memoryIdB) === pairKeyVal)\n : null;\n\n // If we can't match the pairKey, fall back to index-based matching\n const fallbackInput = input ?? inputs[results.length] ?? inputs[0];\n if (!fallbackInput) continue;\n\n matchedKeys.add(pairKey(fallbackInput.memoryIdA, fallbackInput.memoryIdB));\n\n const confidence = typeof item.confidence === \"number\"\n ? Math.min(1, Math.max(0, item.confidence))\n : 0.5;\n\n results.push({\n memoryIdA: fallbackInput.memoryIdA,\n memoryIdB: fallbackInput.memoryIdB,\n verdict,\n rationale: typeof item.rationale === \"string\" ? item.rationale : \"No rationale provided\",\n confidence,\n });\n }\n\n // Backfill any inputs the LLM omitted with needs-user\n for (const inp of inputs) {\n const key = pairKey(inp.memoryIdA, inp.memoryIdB);\n if (!matchedKeys.has(key)) {\n results.push({\n memoryIdA: inp.memoryIdA,\n memoryIdB: inp.memoryIdB,\n verdict: \"needs-user\",\n rationale: \"LLM response omitted this pair\",\n confidence: 0,\n });\n }\n }\n\n if (results.length > 0) return results;\n } catch {\n continue;\n }\n }\n\n // All parse attempts failed → needs-user for every input\n return inputs.map((p) => ({\n memoryIdA: p.memoryIdA,\n memoryIdB: p.memoryIdB,\n verdict: \"needs-user\" as ContradictionVerdict,\n rationale: \"Failed to parse judge response\",\n confidence: 0,\n }));\n}\n\nexport { pairKey as _pairKey, contentHash as _contentHash };\n","/**\n * Contradiction Scan — pair generator + scan driver (issue #520).\n *\n * Nightly cron that pairs semantically-similar active memories within\n * the same namespace, sends candidate pairs to the LLM-as-judge\n * contradiction classifier, and drops contradicting pairs into a\n * review queue for user resolution.\n *\n * Pair generation (cheap pre-filter):\n * 1. Find candidate peers via embedding cosine >= similarityFloor.\n * 2. Restrict to pairs sharing at least one entityRef OR overlapping\n * topic tokens >= topicOverlapFloor.\n * 3. Skip pairs already judged independent/both-valid within cooldown.\n * 4. Cap per-run work at maxPairsPerRun.\n */\n\nimport type { StorageManager } from \"../storage.js\";\nimport type { PluginConfig, MemoryFile, MemoryStatus } from \"../types.js\";\nimport type { SemanticDedupLookup } from \"../dedup/semantic.js\";\nimport { log } from \"../logger.js\";\nimport { judgeContradictionPairs, type ContradictionJudgeInput } from \"./contradiction-judge.js\";\nimport {\n writePairs,\n listPairs,\n isCoolingDown,\n computePairId,\n type ContradictionPair,\n} from \"./contradiction-review.js\";\nimport type { LocalLlmClient } from \"../local-llm.js\";\nimport type { FallbackLlmClient } from \"../fallback-llm.js\";\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\n/** The set of statuses that represent \"live\" memories eligible for scanning. */\nexport const ACTIVE_STATUSES: Set<MemoryStatus> = new Set([\"active\"]);\n\n/** High-value taxonomy buckets to scan. */\nconst SCAN_CATEGORIES = new Set([\n \"decision\",\n \"principle\",\n \"rule\",\n \"entity\",\n \"fact\",\n \"preference\",\n]);\n\nexport interface ScanResult {\n /** Total active memories scanned. */\n scanned: number;\n /** Candidate pairs generated by the pre-filter. */\n candidates: number;\n /** Pairs sent to judge. */\n judged: number;\n /** Pairs written to review queue. */\n queued: number;\n /** Pairs skipped due to cooldown. */\n cooledDown: number;\n /** Total wall-clock time in ms. */\n elapsedMs: number;\n}\n\nexport interface ScanDependencies {\n storage: StorageManager;\n config: PluginConfig;\n memoryDir: string;\n /** Pre-built embedding lookup. When provided, used as-is for Strategy 3. */\n embeddingLookup?: SemanticDedupLookup;\n /**\n * Factory to build a namespace-scoped embedding lookup.\n * When provided, takes precedence over `embeddingLookup`.\n * The scan driver passes its own `storage` so the lookup queries the correct index.\n */\n embeddingLookupFactory?: (storage: StorageManager) => SemanticDedupLookup | undefined;\n localLlm: LocalLlmClient | null;\n fallbackLlm: FallbackLlmClient | null;\n namespace?: string;\n}\n\n// ── Main scan driver ───────────────────────────────────────────────────────────\n\n/**\n * Run a contradiction scan over the memory corpus.\n *\n * This is the entry point called by the nightly cron and by the CLI.\n */\nexport async function runContradictionScan(deps: ScanDependencies): Promise<ScanResult> {\n const startTime = Date.now();\n const { storage, config, memoryDir, embeddingLookup, embeddingLookupFactory, localLlm, fallbackLlm, namespace } = deps;\n const scanConfig = config.contradictionScan;\n\n // Prefer the factory (which uses the scan's own storage for correct namespace scoping)\n // over a pre-built lookup (which may use default-namespace storage).\n const scopedEmbeddingLookup = embeddingLookupFactory\n ? embeddingLookupFactory(storage)\n : embeddingLookup;\n\n if (!scanConfig.enabled) {\n log.info(\"[contradiction-scan] disabled by config\");\n return { scanned: 0, candidates: 0, judged: 0, queued: 0, cooledDown: 0, elapsedMs: 0 };\n }\n\n // 1. Load active memories in scan categories\n const memories = await loadEligibleMemories(storage, namespace);\n log.info(\"[contradiction-scan] loaded %d eligible memories\", memories.length);\n\n if (memories.length < 2) {\n return { scanned: memories.length, candidates: 0, judged: 0, queued: 0, cooledDown: 0, elapsedMs: Date.now() - startTime };\n }\n\n // 2. Load existing review pairs for cooldown checking\n const existingPairs = listPairs(memoryDir, { filter: \"all\", namespace, limit: 10000 }).pairs;\n const existingMap = new Map<string, ContradictionPair>();\n for (const p of existingPairs) {\n existingMap.set(p.pairId, p);\n }\n\n // 3. Generate candidate pairs\n const candidates = await generatePairs(memories, existingMap, scanConfig, scopedEmbeddingLookup);\n const cooledDown = candidates.skipped;\n log.info(\"[contradiction-scan] generated %d candidates (%d cooled down)\", candidates.pairs.length, cooledDown);\n\n if (candidates.pairs.length === 0) {\n return {\n scanned: memories.length,\n candidates: 0,\n judged: 0,\n queued: 0,\n cooledDown,\n elapsedMs: Date.now() - startTime,\n };\n }\n\n // 4. Cap at maxPairsPerRun (deterministic selection by pairId)\n const capped = candidates.pairs\n .sort((a, b) => computePairId(a.idA, a.idB).localeCompare(computePairId(b.idA, b.idB)))\n .slice(0, scanConfig.maxPairsPerRun);\n\n // 5. Build judge inputs\n const judgeInputs: ContradictionJudgeInput[] = capped.map((pair) => ({\n memoryIdA: pair.idA,\n memoryIdB: pair.idB,\n textA: pair.textA,\n textB: pair.textB,\n categoryA: pair.categoryA,\n categoryB: pair.categoryB,\n }));\n\n // 6. Send to judge (per-scan cache avoids module-level singleton leak)\n const scanCache = new Map<string, import(\"./contradiction-judge.js\").ContradictionJudgeResult>();\n const judgeResult = await judgeContradictionPairs(judgeInputs, config, localLlm, fallbackLlm, scanCache);\n log.info(\"[contradiction-scan] judge completed: %d judged, %d cached in %dms\", judgeResult.judged, judgeResult.cached, judgeResult.elapsed);\n\n // 7. Write to review queue\n const queueEntries: Array<Omit<ContradictionPair, \"pairId\"> & { memoryIds: [string, string] }> = [];\n for (const [key, result] of judgeResult.results) {\n queueEntries.push({\n memoryIds: [result.memoryIdA, result.memoryIdB],\n verdict: result.verdict,\n rationale: result.rationale,\n confidence: result.confidence,\n detectedAt: new Date().toISOString(),\n // Set lastReviewedAt for non-actionable verdicts so cooldown prevents re-judging\n lastReviewedAt: result.verdict === \"independent\" ? new Date().toISOString() : undefined,\n namespace,\n });\n }\n\n const written = writePairs(memoryDir, queueEntries);\n const elapsed = Date.now() - startTime;\n log.info(\"[contradiction-scan] complete: %d queued in %dms\", written.length, elapsed);\n\n return {\n scanned: memories.length,\n candidates: candidates.pairs.length,\n judged: judgeResult.judged,\n queued: written.length,\n cooledDown,\n elapsedMs: elapsed,\n };\n}\n\n// ── Pair generation ────────────────────────────────────────────────────────────\n\ninterface CandidatePair {\n idA: string;\n idB: string;\n textA: string;\n textB: string;\n categoryA?: string;\n categoryB?: string;\n}\n\ninterface PairGenResult {\n pairs: CandidatePair[];\n skipped: number;\n}\n\nasync function generatePairs(\n memories: MemoryFile[],\n existingPairs: Map<string, ContradictionPair>,\n scanConfig: PluginConfig[\"contradictionScan\"],\n embeddingLookup?: SemanticDedupLookup,\n): Promise<PairGenResult> {\n const pairs: CandidatePair[] = [];\n const embeddingPairs: CandidatePair[] = [];\n let skipped = 0;\n const seen = new Set<string>();\n\n // Build index by entityRef for fast lookup\n const byEntity = new Map<string, MemoryFile[]>();\n for (const mem of memories) {\n const entity = mem.frontmatter.entityRef;\n if (entity) {\n const existing = byEntity.get(entity) ?? [];\n existing.push(mem);\n byEntity.set(entity, existing);\n }\n }\n\n // Strategy 1: Entity-ref based pairing (high precision)\n for (const [, group] of byEntity) {\n if (group.length < 2) continue;\n for (let i = 0; i < group.length; i++) {\n for (let j = i + 1; j < group.length; j++) {\n const a = group[i];\n const b = group[j];\n const pairId = computePairId(a.frontmatter.id!, b.frontmatter.id!);\n\n if (seen.has(pairId)) continue;\n seen.add(pairId);\n\n // Check cooldown\n const existing = existingPairs.get(pairId);\n if (existing && isCoolingDown(existing, scanConfig.cooldownDays)) {\n skipped++;\n continue;\n }\n\n pairs.push({\n idA: a.frontmatter.id!,\n idB: b.frontmatter.id!,\n textA: a.content,\n textB: b.content,\n categoryA: a.frontmatter.category as string | undefined,\n categoryB: b.frontmatter.category as string | undefined,\n });\n }\n }\n }\n\n // Strategy 2: Tag/topic overlap for memories without shared entityRef\n const noEntity = memories.filter((m) => !m.frontmatter.entityRef);\n for (let i = 0; i < noEntity.length; i++) {\n for (let j = i + 1; j < noEntity.length; j++) {\n const a = noEntity[i];\n const b = noEntity[j];\n const overlap = jaccardOverlap(\n (a.frontmatter.tags as string[]) ?? [],\n (b.frontmatter.tags as string[]) ?? [],\n );\n\n if (overlap < scanConfig.topicOverlapFloor) continue;\n\n const pairId = computePairId(a.frontmatter.id!, b.frontmatter.id!);\n if (seen.has(pairId)) continue;\n seen.add(pairId);\n\n const existing = existingPairs.get(pairId);\n if (existing && isCoolingDown(existing, scanConfig.cooldownDays)) {\n skipped++;\n continue;\n }\n\n pairs.push({\n idA: a.frontmatter.id!,\n idB: b.frontmatter.id!,\n textA: a.content,\n textB: b.content,\n categoryA: a.frontmatter.category as string | undefined,\n categoryB: b.frontmatter.category as string | undefined,\n });\n }\n }\n\n // Strategy 3: Embedding cosine similarity (enforces similarityFloor config)\n if (embeddingLookup) {\n const memoryById = new Map(memories.map((m) => [m.frontmatter.id!, m]));\n for (const mem of memories) {\n const id = mem.frontmatter.id!;\n try {\n const hits = await embeddingLookup(mem.content, 20);\n for (const hit of hits) {\n if (hit.score < scanConfig.similarityFloor) continue;\n if (hit.id === id) continue;\n const peer = memoryById.get(hit.id);\n if (!peer) continue;\n\n const pairId = computePairId(id, hit.id);\n if (seen.has(pairId)) continue;\n seen.add(pairId);\n\n const existing = existingPairs.get(pairId);\n if (existing && isCoolingDown(existing, scanConfig.cooldownDays)) {\n skipped++;\n continue;\n }\n\n embeddingPairs.push({\n idA: id,\n idB: hit.id,\n textA: mem.content,\n textB: peer.content,\n categoryA: mem.frontmatter.category as string | undefined,\n categoryB: peer.frontmatter.category as string | undefined,\n });\n }\n } catch {\n // Embedding backend unavailable — skip, entity-ref/tag strategies already covered\n }\n }\n }\n\n // Append embedding pairs after high-precision entity/topic pairs so the\n // downstream sort+slice(maxPairsPerRun) keeps precision-first ordering.\n pairs.push(...embeddingPairs);\n\n return { pairs, skipped };\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────────────\n\nasync function loadEligibleMemories(storage: StorageManager, namespace?: string): Promise<MemoryFile[]> {\n let all: MemoryFile[];\n try {\n all = await storage.readAllMemories();\n } catch {\n return [];\n }\n\n return all.filter((mem) => {\n const fm = mem.frontmatter;\n // Only active memories (rule 53: explicit ACTIVE_STATUSES set)\n const status = (fm.status as MemoryStatus) ?? \"active\";\n if (!ACTIVE_STATUSES.has(status)) return false;\n\n // Only scan high-value categories\n const category = fm.category as string | undefined;\n if (category && !SCAN_CATEGORIES.has(category)) return false;\n\n // Must have content\n if (!mem.content || mem.content.trim().length === 0) return false;\n\n // Must have an ID\n if (!fm.id) return false;\n\n // Namespace scoping is handled at the storage layer — memoryDir is\n // already namespace-scoped, so readAllMemories() returns only memories\n // within the requested namespace.\n\n return true;\n });\n}\n\nfunction jaccardOverlap(a: string[], b: string[]): number {\n if (a.length === 0 && b.length === 0) return 0;\n const setA = new Set(a.map((t) => t.toLowerCase()));\n const setB = new Set(b.map((t) => t.toLowerCase()));\n let intersection = 0;\n for (const item of setA) {\n if (setB.has(item)) intersection++;\n }\n const union = setA.size + setB.size - intersection;\n return union === 0 ? 0 : intersection / union;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-principal cross-namespace query-budget limiter (issue #565 PR 4/5).
|
|
3
|
+
*
|
|
4
|
+
* Detects and throttles bursts of recall-type operations that a principal
|
|
5
|
+
* issues against namespaces *other than their own*. Thresholds come from the
|
|
6
|
+
* memory-extraction threat model (`docs/security/memory-extraction-threat-model.md`
|
|
7
|
+
* §6.2) and the ADAM baseline report (`docs/security/adam-baseline-2026-04.md`):
|
|
8
|
+
* a T2-class same-namespace attacker plateaus at 61 queries in the published
|
|
9
|
+
* baseline, so the default window is set well below that to force any
|
|
10
|
+
* adaptive loop to noticeably slow down.
|
|
11
|
+
*
|
|
12
|
+
* Shape:
|
|
13
|
+
* - Pure, in-process, per-principal sliding window. No persistence.
|
|
14
|
+
* - Only cross-namespace reads count: a principal hitting only their own
|
|
15
|
+
* namespace is never throttled.
|
|
16
|
+
* - The limiter is behind the `recallCrossNamespaceBudgetEnabled` feature
|
|
17
|
+
* flag (defaults to `false`) and is a no-op when disabled. This mirrors
|
|
18
|
+
* the canonical "new filter/transform needs an enabled check" pattern
|
|
19
|
+
* (see CLAUDE.md gotcha #30).
|
|
20
|
+
*
|
|
21
|
+
* The module has no side effects beyond incrementing its own counters, and
|
|
22
|
+
* it does NOT take a clock dependency — callers pass the current epoch ms
|
|
23
|
+
* (or let the default `Date.now()` do it) so tests can step time
|
|
24
|
+
* deterministically.
|
|
25
|
+
*/
|
|
26
|
+
interface CrossNamespaceBudgetConfig {
|
|
27
|
+
/** Feature flag. Defaults to false — a disabled limiter is always allow. */
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Rolling window size in milliseconds. Counts decay out of the window
|
|
31
|
+
* as the clock advances. Default: 60_000 (1 minute).
|
|
32
|
+
*/
|
|
33
|
+
windowMs?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Soft cap. Once a principal has `softLimit` cross-namespace reads in the
|
|
36
|
+
* window, the limiter *records* a warning on the decision but still
|
|
37
|
+
* allows the call. Used by PR 5's anomaly detector to surface flags
|
|
38
|
+
* without blocking. Default: 10.
|
|
39
|
+
*/
|
|
40
|
+
softLimit?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Hard cap. Once `hardLimit` is reached, the limiter denies the call.
|
|
43
|
+
* Default: 30 — picked to be well below the T2 baseline of ~60 queries
|
|
44
|
+
* at half-plateau, so an ADAM-style adaptive loop is throttled before it
|
|
45
|
+
* meaningfully leaks.
|
|
46
|
+
*/
|
|
47
|
+
hardLimit?: number;
|
|
48
|
+
}
|
|
49
|
+
declare const DEFAULT_CROSS_NAMESPACE_BUDGET: Required<CrossNamespaceBudgetConfig>;
|
|
50
|
+
/**
|
|
51
|
+
* Why a call was denied / warned. Stable strings so callers can key log
|
|
52
|
+
* lines and metrics on them.
|
|
53
|
+
*/
|
|
54
|
+
type BudgetDecisionReason = "allowed-same-namespace" | "allowed-no-limit" | "allowed-under-soft" | "warn-over-soft" | "deny-over-hard";
|
|
55
|
+
interface BudgetDecision {
|
|
56
|
+
allowed: boolean;
|
|
57
|
+
reason: BudgetDecisionReason;
|
|
58
|
+
/** Cross-namespace reads by this principal currently in the window. */
|
|
59
|
+
count: number;
|
|
60
|
+
/** Active config snapshot at decision time. */
|
|
61
|
+
limit: {
|
|
62
|
+
softLimit: number;
|
|
63
|
+
hardLimit: number;
|
|
64
|
+
windowMs: number;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* In-process cross-namespace budget limiter. Instantiate once per
|
|
69
|
+
* orchestrator / access-service.
|
|
70
|
+
*
|
|
71
|
+
* Threadsafe-by-construction: Node.js is single-threaded per process for
|
|
72
|
+
* application code, and the limiter never awaits between read-modify-write
|
|
73
|
+
* operations on its internal state.
|
|
74
|
+
*/
|
|
75
|
+
declare class CrossNamespaceBudget {
|
|
76
|
+
private readonly config;
|
|
77
|
+
private readonly buckets;
|
|
78
|
+
constructor(config?: CrossNamespaceBudgetConfig);
|
|
79
|
+
/** Exposed for tests / audit surfaces. Never mutate the returned value. */
|
|
80
|
+
getConfig(): Required<CrossNamespaceBudgetConfig>;
|
|
81
|
+
/**
|
|
82
|
+
* Check whether `principal` is allowed to issue another cross-namespace
|
|
83
|
+
* read. Call site is expected to compare `principalNamespace` against
|
|
84
|
+
* `queryNamespace` and only pass through reads where they differ — the
|
|
85
|
+
* limiter treats every call as a cross-namespace event.
|
|
86
|
+
*
|
|
87
|
+
* @param principal Stable identifier for the calling principal (token
|
|
88
|
+
* subject, session principal, etc.). Must be non-empty.
|
|
89
|
+
* @param now Epoch-ms clock read. Defaults to `Date.now()`; tests pass a
|
|
90
|
+
* fixed value to step time deterministically.
|
|
91
|
+
*/
|
|
92
|
+
record(principal: string, now?: number): BudgetDecision;
|
|
93
|
+
/**
|
|
94
|
+
* Read-only peek at whether a call would be allowed, WITHOUT recording a
|
|
95
|
+
* timestamp. Useful when the caller must inspect multiple namespaces before
|
|
96
|
+
* deciding to record a single event. The returned `count` reflects the
|
|
97
|
+
* current window state at call time.
|
|
98
|
+
*/
|
|
99
|
+
peek(args: {
|
|
100
|
+
principal: string;
|
|
101
|
+
principalNamespace: string;
|
|
102
|
+
queryNamespace: string;
|
|
103
|
+
now?: number;
|
|
104
|
+
}): BudgetDecision;
|
|
105
|
+
/**
|
|
106
|
+
* Convenience guard that also skips the limiter when `principalNamespace`
|
|
107
|
+
* equals `queryNamespace` (same-namespace is never cross-namespace).
|
|
108
|
+
* Returns an `allowed-same-namespace` decision in that case.
|
|
109
|
+
*/
|
|
110
|
+
check(args: {
|
|
111
|
+
principal: string;
|
|
112
|
+
principalNamespace: string;
|
|
113
|
+
queryNamespace: string;
|
|
114
|
+
now?: number;
|
|
115
|
+
}): BudgetDecision;
|
|
116
|
+
/**
|
|
117
|
+
* Clear all state. Intended for tests and for the orchestrator's
|
|
118
|
+
* lifecycle `before_reset` hook.
|
|
119
|
+
*/
|
|
120
|
+
reset(): void;
|
|
121
|
+
/**
|
|
122
|
+
* Evict buckets whose entire timestamp list has slid out of the
|
|
123
|
+
* active window by `now`. Intended to be called periodically by a
|
|
124
|
+
* long-lived host process (e.g. from a maintenance cron) that sees
|
|
125
|
+
* many transient principals. Safe to call at any time; returns the
|
|
126
|
+
* number of buckets evicted.
|
|
127
|
+
*/
|
|
128
|
+
gc(now?: number): number;
|
|
129
|
+
/** For tests: current number of live buckets. */
|
|
130
|
+
bucketCount(): number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { type BudgetDecision, type BudgetDecisionReason, CrossNamespaceBudget, type CrossNamespaceBudgetConfig, DEFAULT_CROSS_NAMESPACE_BUDGET };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,74 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
throwIfAborted
|
|
9
|
-
} from "./chunk-PVGDJXVK.js";
|
|
10
|
-
import {
|
|
11
|
-
normalizeRecallTokens
|
|
12
|
-
} from "./chunk-DT5TVLJE.js";
|
|
13
|
-
|
|
14
|
-
// src/direct-answer-wiring.ts
|
|
15
|
-
async function tryDirectAnswer(input) {
|
|
16
|
-
const { query, namespace, config, sources, queryEntityRefs, abortSignal } = input;
|
|
17
|
-
const eligibilityConfig = {
|
|
18
|
-
enabled: config.recallDirectAnswerEnabled,
|
|
19
|
-
tokenOverlapFloor: config.recallDirectAnswerTokenOverlapFloor,
|
|
20
|
-
importanceFloor: config.recallDirectAnswerImportanceFloor,
|
|
21
|
-
ambiguityMargin: config.recallDirectAnswerAmbiguityMargin,
|
|
22
|
-
eligibleTaxonomyBuckets: config.recallDirectAnswerEligibleTaxonomyBuckets
|
|
23
|
-
};
|
|
24
|
-
if (!eligibilityConfig.enabled) {
|
|
25
|
-
return isDirectAnswerEligible({
|
|
26
|
-
query,
|
|
27
|
-
candidates: [],
|
|
28
|
-
config: eligibilityConfig,
|
|
29
|
-
queryEntityRefs
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
if (normalizeRecallTokens(query).length === 0) {
|
|
33
|
-
return isDirectAnswerEligible({
|
|
34
|
-
query,
|
|
35
|
-
candidates: [],
|
|
36
|
-
config: eligibilityConfig,
|
|
37
|
-
queryEntityRefs
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
throwIfAborted(abortSignal, "direct-answer wiring aborted");
|
|
41
|
-
const memories = await sources.listCandidateMemories({ namespace, abortSignal });
|
|
42
|
-
throwIfAborted(abortSignal, "direct-answer wiring aborted");
|
|
43
|
-
const candidates = [];
|
|
44
|
-
for (const memory of memories) {
|
|
45
|
-
throwIfAborted(abortSignal, "direct-answer wiring aborted");
|
|
46
|
-
const trustZone = await sources.trustZoneFor(memory.frontmatter.id);
|
|
47
|
-
throwIfAborted(abortSignal, "direct-answer wiring aborted");
|
|
48
|
-
if (trustZone !== "trusted") continue;
|
|
49
|
-
const decision = resolveCategory(
|
|
50
|
-
memory.content,
|
|
51
|
-
memory.frontmatter.category,
|
|
52
|
-
sources.taxonomy
|
|
53
|
-
);
|
|
54
|
-
const taxonomyBucket = decision.categoryId;
|
|
55
|
-
if (!eligibilityConfig.eligibleTaxonomyBuckets.includes(taxonomyBucket)) continue;
|
|
56
|
-
const importanceScore = sources.importanceFor(memory);
|
|
57
|
-
candidates.push({
|
|
58
|
-
memory,
|
|
59
|
-
trustZone,
|
|
60
|
-
taxonomyBucket,
|
|
61
|
-
importanceScore
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
throwIfAborted(abortSignal, "direct-answer wiring aborted");
|
|
65
|
-
return isDirectAnswerEligible({
|
|
66
|
-
query,
|
|
67
|
-
candidates,
|
|
68
|
-
config: eligibilityConfig,
|
|
69
|
-
queryEntityRefs
|
|
70
|
-
});
|
|
71
|
-
}
|
|
2
|
+
tryDirectAnswer
|
|
3
|
+
} from "./chunk-6AUUAZEX.js";
|
|
4
|
+
import "./chunk-Y4FHOFJ2.js";
|
|
5
|
+
import "./chunk-PVGDJXVK.js";
|
|
6
|
+
import "./chunk-DT5TVLJE.js";
|
|
72
7
|
export {
|
|
73
8
|
tryDirectAnswer
|
|
74
9
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CompoundingEngine,
|
|
3
3
|
defaultTierMigrationCycleBudget
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-XXVWLXSG.js";
|
|
5
|
+
import "./chunk-F5VP6YCB.js";
|
|
6
|
+
import "./chunk-LTCGGW2D.js";
|
|
7
7
|
import "./chunk-4KAN3GZ3.js";
|
|
8
|
-
import "./chunk-6ZH4TU6I.js";
|
|
9
|
-
import "./chunk-SCU65EZI.js";
|
|
10
|
-
import "./chunk-BOUYNNYD.js";
|
|
11
8
|
import "./chunk-6PFRXT4K.js";
|
|
12
9
|
import "./chunk-TP4FZJIZ.js";
|
|
10
|
+
import "./chunk-SCU65EZI.js";
|
|
11
|
+
import "./chunk-BOUYNNYD.js";
|
|
13
12
|
import "./chunk-DM2T26WE.js";
|
|
14
13
|
import "./chunk-QSVPYQPG.js";
|
|
15
14
|
import "./chunk-M62O4P4T.js";
|
|
16
15
|
import "./chunk-4DJQYKMN.js";
|
|
16
|
+
import "./chunk-X6GF3FX2.js";
|
|
17
|
+
import "./chunk-FAAFWE4G.js";
|
|
17
18
|
import "./chunk-2ODBA7MQ.js";
|
|
18
19
|
export {
|
|
19
20
|
CompoundingEngine,
|
|
20
21
|
defaultTierMigrationCycleBudget
|
|
21
22
|
};
|
|
22
|
-
//# sourceMappingURL=engine-
|
|
23
|
+
//# sourceMappingURL=engine-72LSIWQP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/entity-retrieval.js
CHANGED
|
@@ -3,20 +3,21 @@ import {
|
|
|
3
3
|
entityIndexVersion,
|
|
4
4
|
entityRecentTranscriptLookbackHours,
|
|
5
5
|
readRecentEntityTranscriptEntries
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-FVA6TGI3.js";
|
|
7
7
|
import "./chunk-7SEAZFFB.js";
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-F5VP6YCB.js";
|
|
9
|
+
import "./chunk-LTCGGW2D.js";
|
|
10
10
|
import "./chunk-4KAN3GZ3.js";
|
|
11
|
-
import "./chunk-6ZH4TU6I.js";
|
|
12
|
-
import "./chunk-SCU65EZI.js";
|
|
13
|
-
import "./chunk-BOUYNNYD.js";
|
|
14
11
|
import "./chunk-6PFRXT4K.js";
|
|
15
12
|
import "./chunk-TP4FZJIZ.js";
|
|
13
|
+
import "./chunk-SCU65EZI.js";
|
|
14
|
+
import "./chunk-BOUYNNYD.js";
|
|
16
15
|
import "./chunk-DM2T26WE.js";
|
|
17
16
|
import "./chunk-QSVPYQPG.js";
|
|
18
17
|
import "./chunk-M62O4P4T.js";
|
|
19
18
|
import "./chunk-4DJQYKMN.js";
|
|
19
|
+
import "./chunk-X6GF3FX2.js";
|
|
20
|
+
import "./chunk-FAAFWE4G.js";
|
|
20
21
|
import "./chunk-2ODBA7MQ.js";
|
|
21
22
|
export {
|
|
22
23
|
buildEntityRecallSection,
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import { O as Orchestrator } from './orchestrator-
|
|
1
|
+
import { O as Orchestrator } from './orchestrator-CmJ-NTdJ.js';
|
|
2
2
|
import { MemoryCategory, PluginConfig } from './types.js';
|
|
3
3
|
import './buffer.js';
|
|
4
4
|
import './storage.js';
|
|
5
5
|
import './page-versioning.js';
|
|
6
|
+
import './consolidation-operator.js';
|
|
6
7
|
import './memory-projection-store-DeSXPh1j.js';
|
|
7
8
|
import 'better-sqlite3';
|
|
8
|
-
import './port-
|
|
9
|
+
import './port-BADbLZU5.js';
|
|
9
10
|
import './transcript.js';
|
|
10
11
|
import './session-integrity.js';
|
|
11
12
|
import './summarizer.js';
|
|
12
13
|
import './model-registry.js';
|
|
13
14
|
import './local-llm.js';
|
|
15
|
+
import './fallback-llm.js';
|
|
14
16
|
import './relevance.js';
|
|
15
17
|
import './negative.js';
|
|
16
18
|
import './recall-state.js';
|
|
19
|
+
import './recall-xray.js';
|
|
17
20
|
import './session-observer-state.js';
|
|
18
21
|
import './embedding-fallback.js';
|
|
19
|
-
import './semantic-consolidation-
|
|
22
|
+
import './semantic-consolidation-CxJU6MJk.js';
|
|
20
23
|
import './codex-materialize-CQlLTzke.js';
|
|
21
24
|
import './logger.js';
|
|
22
25
|
import 'zod';
|
package/dist/explicit-capture.js
CHANGED
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
shouldSkipImplicitExtraction,
|
|
8
8
|
stripInlineExplicitCaptureNotes,
|
|
9
9
|
validateExplicitCaptureInput
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-3FPTCC3Z.js";
|
|
11
|
+
import "./chunk-2LGMW3DJ.js";
|
|
12
12
|
import "./chunk-M62O4P4T.js";
|
|
13
13
|
export {
|
|
14
14
|
hasInlineExplicitCaptureMarkup,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { JudgeVerdictKind } from './extraction-judge.js';
|
|
2
|
+
import './types.js';
|
|
3
|
+
import './local-llm.js';
|
|
4
|
+
import './model-registry.js';
|
|
5
|
+
import './fallback-llm.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extraction Judge Telemetry (issue #562, PR 3).
|
|
9
|
+
*
|
|
10
|
+
* Appends one structured event per judge verdict to the observation ledger
|
|
11
|
+
* under a dedicated JSONL file. The ledger is the same directory used by
|
|
12
|
+
* the turn-count observation writer
|
|
13
|
+
* (`state/observation-ledger/rebuilt-observations.jsonl`) but judge events
|
|
14
|
+
* live in their own file so aggregation stays cheap and schemas do not
|
|
15
|
+
* collide.
|
|
16
|
+
*
|
|
17
|
+
* Emit path is best-effort and fail-open: a telemetry write must never
|
|
18
|
+
* block an extraction run.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Observation-ledger category for judge verdict events. Callers that
|
|
23
|
+
* aggregate across event kinds can filter on this constant.
|
|
24
|
+
*/
|
|
25
|
+
declare const EXTRACTION_JUDGE_VERDICT_CATEGORY = "EXTRACTION_JUDGE_VERDICT";
|
|
26
|
+
/**
|
|
27
|
+
* Structured event written for every judge verdict (including verdicts
|
|
28
|
+
* served from cache). The `version` field lets future readers upgrade the
|
|
29
|
+
* schema without breaking older rows.
|
|
30
|
+
*/
|
|
31
|
+
interface JudgeVerdictEvent {
|
|
32
|
+
version: 1;
|
|
33
|
+
category: typeof EXTRACTION_JUDGE_VERDICT_CATEGORY;
|
|
34
|
+
ts: string;
|
|
35
|
+
verdictKind: JudgeVerdictKind;
|
|
36
|
+
/** Short free-text reason from the judge / deterministic fallback. */
|
|
37
|
+
reason: string;
|
|
38
|
+
/**
|
|
39
|
+
* How many times this candidate's content hash had already been deferred
|
|
40
|
+
* before this verdict. 0 for the first defer, 1 for the second, etc.
|
|
41
|
+
* Not applicable for non-defer kinds — emitted as 0.
|
|
42
|
+
*/
|
|
43
|
+
deferrals: number;
|
|
44
|
+
/**
|
|
45
|
+
* Milliseconds between batch start and batch return (the whole
|
|
46
|
+
* `judgeFactDurability` call, shared across all verdicts in a single
|
|
47
|
+
* batch).
|
|
48
|
+
*/
|
|
49
|
+
elapsedMs: number;
|
|
50
|
+
/** Candidate metadata for coarse filtering. */
|
|
51
|
+
candidateCategory: string;
|
|
52
|
+
confidence?: number;
|
|
53
|
+
/** SHA-256 of the (text\0category) pair, same as the verdict-cache key. */
|
|
54
|
+
contentHash: string;
|
|
55
|
+
/** Whether the verdict came from the in-memory verdict cache. */
|
|
56
|
+
fromCache: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* True when the judge forcibly converted a defer to reject because the
|
|
59
|
+
* defer cap was reached. Only set on cap-triggered rejects.
|
|
60
|
+
*/
|
|
61
|
+
deferCapTriggered?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Options to control emit behavior. `enabled` gates all writes; when
|
|
65
|
+
* false, `recordJudgeVerdict` is a no-op so callers can unconditionally
|
|
66
|
+
* invoke it.
|
|
67
|
+
*/
|
|
68
|
+
interface JudgeTelemetryOptions {
|
|
69
|
+
enabled: boolean;
|
|
70
|
+
memoryDir: string;
|
|
71
|
+
}
|
|
72
|
+
declare function judgeTelemetryPath(memoryDir: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Append a single verdict event as a JSONL row. Fails open — if the write
|
|
75
|
+
* cannot be completed (directory missing, permissions, disk full), the
|
|
76
|
+
* error is logged at debug level and swallowed so extraction never fails
|
|
77
|
+
* because of telemetry.
|
|
78
|
+
*/
|
|
79
|
+
declare function recordJudgeVerdict(event: JudgeVerdictEvent, options: JudgeTelemetryOptions): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Aggregate statistics over the verdict ledger, optionally restricted to a
|
|
82
|
+
* time window. Returns zero-count stats when the ledger is missing or
|
|
83
|
+
* empty — callers do not need to special-case a cold install.
|
|
84
|
+
*/
|
|
85
|
+
interface JudgeVerdictStats {
|
|
86
|
+
total: number;
|
|
87
|
+
accept: number;
|
|
88
|
+
reject: number;
|
|
89
|
+
defer: number;
|
|
90
|
+
deferCapTriggered: number;
|
|
91
|
+
/** Mean elapsed milliseconds across all events in the window. */
|
|
92
|
+
meanElapsedMs: number;
|
|
93
|
+
/** Defer rate as `defer / total`, in `[0, 1]`. `0` when total is 0. */
|
|
94
|
+
deferRate: number;
|
|
95
|
+
/** First and last event timestamps observed in the window. */
|
|
96
|
+
firstTs?: string;
|
|
97
|
+
lastTs?: string;
|
|
98
|
+
/** Number of rows skipped because they were malformed or wrong shape. */
|
|
99
|
+
malformed: number;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Read and aggregate events from the verdict ledger.
|
|
103
|
+
*
|
|
104
|
+
* `sinceMs` / `untilMs` bound by `ts` parse — events with unparseable
|
|
105
|
+
* timestamps are counted toward `malformed`. The upper bound is exclusive
|
|
106
|
+
* (`ts < untilMs`), per CLAUDE.md gotcha 35.
|
|
107
|
+
*/
|
|
108
|
+
declare function readJudgeVerdictStats(memoryDir: string, opts?: {
|
|
109
|
+
sinceMs?: number;
|
|
110
|
+
untilMs?: number;
|
|
111
|
+
}): Promise<JudgeVerdictStats>;
|
|
112
|
+
|
|
113
|
+
export { EXTRACTION_JUDGE_VERDICT_CATEGORY, type JudgeTelemetryOptions, type JudgeVerdictEvent, type JudgeVerdictStats, judgeTelemetryPath, readJudgeVerdictStats, recordJudgeVerdict };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EXTRACTION_JUDGE_VERDICT_CATEGORY,
|
|
3
|
+
judgeTelemetryPath,
|
|
4
|
+
readJudgeVerdictStats,
|
|
5
|
+
recordJudgeVerdict
|
|
6
|
+
} from "./chunk-AJU4PJGY.js";
|
|
7
|
+
import "./chunk-2ODBA7MQ.js";
|
|
8
|
+
export {
|
|
9
|
+
EXTRACTION_JUDGE_VERDICT_CATEGORY,
|
|
10
|
+
judgeTelemetryPath,
|
|
11
|
+
readJudgeVerdictStats,
|
|
12
|
+
recordJudgeVerdict
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=extraction-judge-telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|