@remnic/core 10.0.83 → 10.0.85
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-admin-ops-surface.js +67 -66
- package/dist/access-authorization-probe.js +68 -67
- package/dist/access-boundary.js +67 -66
- package/dist/access-cli.js +148 -146
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +67 -66
- package/dist/access-http-briefing.js +68 -67
- package/dist/access-http-query.js +69 -68
- package/dist/access-http.js +91 -89
- package/dist/access-identity-continuity-surface.js +67 -66
- package/dist/access-lcm-surface.js +67 -66
- package/dist/access-mcp-frames.d.ts +29 -0
- package/dist/access-mcp-frames.d.ts.map +1 -0
- package/dist/access-mcp-frames.js +10 -0
- package/dist/access-mcp.d.ts +1 -1
- package/dist/access-mcp.d.ts.map +1 -1
- package/dist/access-mcp.js +82 -80
- package/dist/access-namespace-preflight.js +67 -66
- package/dist/access-observe-write-category.js +9 -9
- package/dist/access-observe-write-surface.d.ts.map +1 -1
- package/dist/access-observe-write-surface.js +67 -66
- package/dist/access-offline-manifest.js +3 -3
- package/dist/access-operations-batch.js +71 -70
- package/dist/access-operations.d.ts +1 -1
- package/dist/access-operations.js +78 -77
- package/dist/access-recall-concurrency.js +67 -66
- package/dist/access-recall-response.js +67 -66
- package/dist/access-recall-surface.js +67 -66
- package/dist/access-schema.js +4 -4
- package/dist/access-service.js +67 -66
- package/dist/access-surface-catalog.js +69 -68
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +31 -31
- package/dist/{auto-sync-C7E6G7Y2.js → auto-sync-3AZW7MXI.js} +6 -6
- package/dist/briefing.js +25 -25
- package/dist/buffer-persistence.d.ts +8 -4
- package/dist/buffer-persistence.d.ts.map +1 -1
- package/dist/buffer-persistence.js +1 -1
- package/dist/buffer-turn-helpers.d.ts +5 -0
- package/dist/buffer-turn-helpers.d.ts.map +1 -1
- package/dist/buffer-turn-helpers.js +1 -1
- package/dist/buffer.d.ts +4 -1
- package/dist/buffer.d.ts.map +1 -1
- package/dist/buffer.js +9 -9
- package/dist/calibration.js +2 -2
- package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-GWVG7LGC.js} +2 -2
- package/dist/capsule-gunzip-KGBDGF7P.js +11 -0
- package/dist/{capsule-merge-XVTQJJ6R.js → capsule-merge-2IIXFXN5.js} +5 -11
- package/dist/capsule-merge-2IIXFXN5.js.map +1 -0
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +32 -32
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-T6T2RMWW.js → chunk-23B4HLWG.js} +5 -5
- package/dist/{chunk-LECSS7EE.js → chunk-2ASEDFOA.js} +2 -2
- package/dist/{chunk-TPX7XXY4.js → chunk-2JAT4S7N.js} +2 -2
- package/dist/{chunk-XRBHIR7Q.js → chunk-2URKYLMK.js} +2 -2
- package/dist/{chunk-IN4OFAHX.js → chunk-2XYDL2FG.js} +2 -2
- package/dist/{chunk-HQGATKG2.js → chunk-3KGCHTTK.js} +2 -2
- package/dist/{chunk-YAUHSNPB.js → chunk-3LHPTBWG.js} +2 -2
- package/dist/{chunk-7PMOCQOL.js → chunk-3ODZLCHC.js} +192 -63
- package/dist/chunk-3ODZLCHC.js.map +1 -0
- package/dist/{chunk-XT45WCKX.js → chunk-3VU2FDTU.js} +8 -8
- package/dist/{chunk-CIIHIAJ2.js → chunk-433EXTC4.js} +3 -3
- package/dist/{chunk-QZSIDSB2.js → chunk-44FQCC5D.js} +2 -2
- package/dist/{chunk-YUTT4ZTI.js → chunk-45VUHDV2.js} +2 -2
- package/dist/{chunk-54FDG7K7.js → chunk-4DPLCYEK.js} +2 -2
- package/dist/{chunk-KO3YBHOV.js → chunk-4IWEPQEV.js} +2 -2
- package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
- package/dist/{chunk-K6RTQZA4.js → chunk-6DANGCMO.js} +2 -2
- package/dist/{chunk-2IW5PT7U.js → chunk-6KFFRMPQ.js} +2 -2
- package/dist/{chunk-MSIRVXQ3.js → chunk-73YVMZ7D.js} +4 -4
- package/dist/{chunk-A7BM2JTH.js → chunk-AJHNXIQU.js} +2 -2
- package/dist/{chunk-2D7BTSDW.js → chunk-ANZZLRUX.js} +2 -2
- package/dist/{chunk-YOEYL6CH.js → chunk-AP3TSV6S.js} +2 -2
- package/dist/{chunk-GQZSGXR6.js → chunk-BATPQNRG.js} +2 -2
- package/dist/{chunk-D5SSBCYY.js → chunk-BLGQUZFB.js} +2 -2
- package/dist/{chunk-BDN3TDGV.js → chunk-BLMTOIBK.js} +2 -2
- package/dist/{chunk-B74NPUHT.js → chunk-BMIM6ESC.js} +43 -43
- package/dist/chunk-BMIM6ESC.js.map +1 -0
- package/dist/{chunk-R45L4TV6.js → chunk-BUKK5SWA.js} +6 -6
- package/dist/{chunk-TFLPDNQV.js → chunk-CCU7URRP.js} +2 -2
- package/dist/{chunk-2XJZLSXE.js → chunk-CMRHXPLQ.js} +20 -20
- package/dist/{chunk-BK4FBHZ3.js → chunk-D6THW7PA.js} +3 -3
- package/dist/{chunk-BFBS7OFQ.js → chunk-DC55RIEZ.js} +4 -4
- package/dist/{chunk-VKDAJJQP.js → chunk-DI5ET2IE.js} +5 -5
- package/dist/{chunk-QZBTIYKD.js → chunk-DM4RRTN7.js} +3 -3
- package/dist/{chunk-S57QBPTA.js → chunk-DPPLNZ3A.js} +4 -6
- package/dist/chunk-DPPLNZ3A.js.map +1 -0
- package/dist/{chunk-EBOHFZI4.js → chunk-E5QEF3JO.js} +2 -2
- package/dist/{chunk-DD6OIFDG.js → chunk-E7V5AK7I.js} +3 -3
- package/dist/{chunk-WAD32CZG.js → chunk-ELWI2R6H.js} +26 -13
- package/dist/chunk-ELWI2R6H.js.map +1 -0
- package/dist/{chunk-BV2BRHD4.js → chunk-FHGWKODN.js} +2 -2
- package/dist/{chunk-HPFVZPRH.js → chunk-FK4WC6VK.js} +3 -3
- package/dist/{chunk-VLMBSGQF.js → chunk-FRM4UTDR.js} +6 -6
- package/dist/{chunk-LTDLJCMQ.js → chunk-FZPDRCOB.js} +2 -2
- package/dist/{chunk-ZJJFUEPO.js → chunk-GBOYOL5L.js} +2 -2
- package/dist/{chunk-YUTG35M4.js → chunk-GJVGFKOU.js} +2 -2
- package/dist/{chunk-77R7BQZ2.js → chunk-GKYIRTNX.js} +33 -11
- package/dist/{chunk-77R7BQZ2.js.map → chunk-GKYIRTNX.js.map} +1 -1
- package/dist/{chunk-K36RMINQ.js → chunk-GOZXQNIM.js} +3 -3
- package/dist/{chunk-HWHNQVRV.js → chunk-GYNRNKMW.js} +2 -2
- package/dist/{chunk-LU66I56Q.js → chunk-H34INI3P.js} +29 -3
- package/dist/chunk-H34INI3P.js.map +1 -0
- package/dist/{chunk-AYTDCMEL.js → chunk-H4D5YURJ.js} +80 -94
- package/dist/chunk-H4D5YURJ.js.map +1 -0
- package/dist/{chunk-6LCWKS62.js → chunk-HVFX2N2G.js} +60 -59
- package/dist/chunk-HVFX2N2G.js.map +1 -0
- package/dist/chunk-IGLJNHOX.js +62 -0
- package/dist/chunk-IGLJNHOX.js.map +1 -0
- package/dist/{chunk-KFLD7ZZG.js → chunk-IUKYFTGW.js} +3 -3
- package/dist/{chunk-YK7GOPIE.js → chunk-JFH6JAGY.js} +6 -4
- package/dist/chunk-JFH6JAGY.js.map +1 -0
- package/dist/{chunk-BNQJKWWZ.js → chunk-K3FH3LAQ.js} +4 -4
- package/dist/{chunk-FJ2VJTGA.js → chunk-KHZVAISH.js} +2 -2
- package/dist/{chunk-WHQXWFH7.js → chunk-LT3CMO65.js} +2 -2
- package/dist/{chunk-NUSAAN75.js → chunk-MHSBOEH7.js} +2 -2
- package/dist/{chunk-KJCL2FZO.js → chunk-MNYQVYJ4.js} +2 -2
- package/dist/{chunk-RZH5RVJC.js → chunk-MTTYIXSU.js} +2 -2
- package/dist/{chunk-FVRTCYZ3.js → chunk-MVOJDLXS.js} +2 -2
- package/dist/{chunk-KESJ5XX3.js → chunk-MYCCO6AA.js} +2 -2
- package/dist/{chunk-66JOLMWS.js → chunk-MZCGHPIJ.js} +2 -2
- package/dist/{chunk-B4ZU34JQ.js → chunk-N6II66E5.js} +3 -3
- package/dist/{chunk-BK7AJUWB.js → chunk-NIV7RPWQ.js} +6 -6
- package/dist/{chunk-TLHF75OR.js → chunk-O55AGDTI.js} +10 -10
- package/dist/{chunk-GGJFRVRD.js → chunk-OMYRPMQC.js} +2 -2
- package/dist/{chunk-SVCHO337.js → chunk-ONN2E5QD.js} +32 -23
- package/dist/chunk-ONN2E5QD.js.map +1 -0
- package/dist/{chunk-XHGYZRVW.js → chunk-OSTFDWZJ.js} +4 -4
- package/dist/{chunk-BXLOS5AJ.js → chunk-OWHERGF2.js} +2 -2
- package/dist/{chunk-WC475JOG.js → chunk-P225W4RQ.js} +6 -6
- package/dist/{chunk-TFA6HPOA.js → chunk-QCP75JGO.js} +5 -5
- package/dist/{chunk-JRVMCZE3.js → chunk-QEYTRFR3.js} +2 -2
- package/dist/chunk-QV2VZKD5.js +31 -0
- package/dist/chunk-QV2VZKD5.js.map +1 -0
- package/dist/{chunk-IZJQNDGI.js → chunk-R6HMAIKM.js} +2 -2
- package/dist/{chunk-VUROWXFL.js → chunk-RNGDKFY2.js} +2 -2
- package/dist/{chunk-FF6N7BXC.js → chunk-S45INSV3.js} +4 -4
- package/dist/{chunk-2RUTN7PR.js → chunk-S7GJNA3K.js} +2 -2
- package/dist/{chunk-XYP5WBDO.js → chunk-SGHV6LZ7.js} +13 -13
- package/dist/chunk-SGHV6LZ7.js.map +1 -0
- package/dist/{chunk-RJWL3RHB.js → chunk-TFEHSPGQ.js} +2 -2
- package/dist/{chunk-T77AX6DG.js → chunk-TQVECGUQ.js} +4 -4
- package/dist/{chunk-OQSSQOKQ.js → chunk-TV65NR7L.js} +2 -2
- package/dist/{chunk-KQUSP6LB.js → chunk-UDKWFDV2.js} +1 -1
- package/dist/chunk-UDKWFDV2.js.map +1 -0
- package/dist/{chunk-HQPFACXO.js → chunk-UH3FPX3L.js} +2 -2
- package/dist/{chunk-4EACCIPO.js → chunk-UHPHH74F.js} +2 -2
- package/dist/{chunk-DQEMWVMT.js → chunk-UVYI6VIX.js} +1 -1
- package/dist/{chunk-6QYPL6NI.js → chunk-V45TEVDA.js} +3 -3
- package/dist/{chunk-GCAQ3VUJ.js → chunk-VCVEMQJK.js} +2 -2
- package/dist/{chunk-6L63IN3M.js → chunk-VEFGHRKV.js} +1 -1
- package/dist/{chunk-E33UJHY3.js → chunk-WFDWLWDK.js} +2 -2
- package/dist/{chunk-4KLQI3L2.js → chunk-WLNWFHX3.js} +1 -1
- package/dist/chunk-WLNWFHX3.js.map +1 -0
- package/dist/{chunk-KQLBXUUQ.js → chunk-WYG5UNDY.js} +2 -2
- package/dist/{chunk-MPGLM2VU.js → chunk-X7REO4CH.js} +2 -2
- package/dist/{chunk-MPDLLLOG.js → chunk-XO23OKMK.js} +9 -5
- package/dist/chunk-XO23OKMK.js.map +1 -0
- package/dist/{chunk-Z3DDQXM4.js → chunk-XSFQN7TP.js} +35 -36
- package/dist/chunk-XSFQN7TP.js.map +1 -0
- package/dist/{chunk-FZVROZKC.js → chunk-XVZA6VXC.js} +4 -4
- package/dist/{chunk-WV254W4V.js → chunk-Y3IYKNBY.js} +2 -2
- package/dist/{chunk-FUOAIJKQ.js → chunk-Y43X3E24.js} +2 -2
- package/dist/{chunk-3T3VHXUH.js → chunk-YAAKVTZA.js} +2 -2
- package/dist/{chunk-VPEIWMUT.js → chunk-YLTU2DC4.js} +2 -2
- package/dist/{chunk-65SKM4BF.js → chunk-YNBMBL4C.js} +2 -2
- package/dist/{chunk-PJYK7HH2.js → chunk-ZWTHVUT5.js} +2 -2
- package/dist/cli.js +110 -108
- package/dist/coding/export-okf-codegraph.js +25 -25
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +27 -27
- package/dist/config.js +28 -28
- package/dist/connectors/codex-materialize-runner.js +25 -25
- package/dist/connectors/index.js +25 -25
- package/dist/contradiction/index.js +4 -4
- package/dist/convergence-refresh.js +27 -27
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +27 -27
- package/dist/deep-recall-http-glue.js +5 -5
- package/dist/entity-mention-index-cache.d.ts.map +1 -1
- package/dist/entity-mention-index-cache.js +2 -1
- package/dist/entity-retrieval.js +27 -26
- package/dist/event-order-recall.js +2 -2
- package/dist/explicit-capture.js +5 -5
- package/dist/external-wiki-access.js +68 -67
- package/dist/extraction-evidence-class.js +2 -2
- package/dist/extraction-quote-grounding.d.ts.map +1 -1
- package/dist/extraction-quote-grounding.js +1 -1
- package/dist/extraction-source-grounding-rules.js +2 -2
- package/dist/extraction-source-grounding.js +5 -5
- package/dist/extraction-span-materialize.js +2 -2
- package/dist/extraction.js +40 -40
- package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
- package/dist/{graph-edge-decay-CXTNJ7IQ.js → graph-edge-decay-EKWXRBWF.js} +5 -5
- package/dist/graph-edge-decay-EKWXRBWF.js.map +1 -0
- package/dist/graph-jsonl.js +3 -3
- package/dist/graph-snapshot.js +3 -3
- package/dist/graph.js +2 -2
- package/dist/harmonic-retrieval.js +25 -25
- package/dist/index.js +205 -203
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +2 -2
- package/dist/local-llm.js +2 -2
- package/dist/location/index.js +28 -28
- package/dist/maintenance/memory-governance.js +25 -25
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +25 -25
- package/dist/maintenance/rebuild-memory-projection-runner.js +27 -27
- package/dist/maintenance/rebuild-memory-projection.js +26 -26
- package/dist/memory-browse-operations.js +68 -67
- package/dist/memory-subject.js +2 -2
- package/dist/namespaces/migrate.js +26 -26
- package/dist/namespaces/search.d.ts +8 -0
- package/dist/namespaces/search.d.ts.map +1 -1
- package/dist/namespaces/search.js +10 -6
- package/dist/namespaces/storage.js +25 -25
- package/dist/operator-doctor-corpus.js +28 -28
- package/dist/operator-toolkit.js +39 -39
- package/dist/orchestration/backlog-scheduler.d.ts +3 -2
- package/dist/orchestration/backlog-scheduler.d.ts.map +1 -1
- package/dist/orchestration/backlog-scheduler.js +159 -157
- package/dist/orchestration/backlog-scheduler.js.map +1 -1
- package/dist/orchestration/buffer-drain-runner.d.ts +6 -1
- package/dist/orchestration/buffer-drain-runner.d.ts.map +1 -1
- package/dist/orchestration/buffer-drain-runner.js +149 -147
- package/dist/orchestration/extraction-persist.d.ts.map +1 -1
- package/dist/orchestration/fact-dedup-window.d.ts +1 -4
- package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
- package/dist/orchestration/maintenance.js +39 -39
- package/dist/orchestrator.js +148 -146
- package/dist/provenance.js +2 -2
- package/dist/recall-navigation-operations.js +68 -67
- package/dist/recall-navigation.js +67 -66
- package/dist/reconcile/manifest.js +2 -2
- package/dist/{resolution-BNWE5Y25.js → resolution-23ZBDM6A.js} +5 -5
- package/dist/resolution-23ZBDM6A.js.map +1 -0
- package/dist/resume-bundles.js +29 -29
- package/dist/salvage-envelope.js +4 -4
- package/dist/search/factory.js +5 -5
- package/dist/search/index.js +9 -9
- package/dist/semantic-consolidation.js +26 -26
- package/dist/semantic-rule-promotion.js +25 -25
- package/dist/semantic-rule-verifier.js +25 -25
- package/dist/source-agent-qualifier.js +31 -31
- package/dist/standup/build.js +25 -25
- package/dist/storage/buffer-drain-claims.d.ts +18 -6
- package/dist/storage/buffer-drain-claims.d.ts.map +1 -1
- package/dist/storage/buffer-drain-claims.js +2 -2
- package/dist/storage/buffer-session-files.js +2 -2
- package/dist/storage/content-hash-index.d.ts +8 -0
- package/dist/storage/content-hash-index.d.ts.map +1 -1
- package/dist/storage.d.ts +10 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +24 -24
- package/dist/subject-config.js +3 -3
- package/dist/summarizer.js +3 -3
- package/dist/support-passport/index.js +70 -69
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +3 -3
- package/dist/transfer/capsule-gunzip.d.ts +14 -0
- package/dist/transfer/capsule-gunzip.d.ts.map +1 -0
- package/dist/transfer/capsule-import.d.ts.map +1 -1
- package/dist/transfer/capsule-import.js +3 -2
- package/dist/transfer/capsule-merge.d.ts.map +1 -1
- package/dist/transfer/export-okf.js +25 -25
- package/dist/verified-recall.js +25 -25
- package/dist/who-knows-cli.js +26 -26
- package/dist/who-knows.js +25 -25
- package/dist/write-envelope.js +3 -3
- package/package.json +2 -2
- package/src/access-mcp-correction-tools.test.ts +45 -0
- package/src/access-mcp-frames.test.ts +47 -0
- package/src/access-mcp-frames.ts +74 -0
- package/src/access-mcp.test.ts +146 -0
- package/src/access-mcp.ts +30 -30
- package/src/access-observe-write-surface.ts +4 -4
- package/src/backlog-scheduler.test.ts +443 -3
- package/src/buffer-persistence.ts +15 -6
- package/src/buffer-turn-helpers.ts +6 -0
- package/src/buffer.ts +6 -6
- package/src/cli.ts +4 -4
- package/src/compounding/weekly-evidence.test.ts +237 -0
- package/src/entity-mention-index-cache.ts +1 -6
- package/src/extraction-quote-grounding.test.ts +49 -0
- package/src/extraction-quote-grounding.ts +50 -12
- package/src/fact-dedup-window.test.ts +103 -20
- package/src/maintenance/rebuild-memory-projection-runner.test.ts +90 -0
- package/src/namespaces/search.test.ts +339 -0
- package/src/namespaces/search.ts +54 -1
- package/src/orchestration/backlog-scheduler.ts +13 -12
- package/src/orchestration/buffer-drain-runner.ts +40 -16
- package/src/orchestration/extraction-persist.ts +2 -10
- package/src/orchestration/fact-dedup-window.ts +5 -12
- package/src/orchestration/full-corpus-heap-guard.test.ts +52 -0
- package/src/projection-rebuild-config.test.ts +79 -0
- package/src/storage/buffer-drain-claims.test.ts +330 -6
- package/src/storage/buffer-drain-claims.ts +226 -80
- package/src/storage/buffer-session-files.ts +2 -2
- package/src/storage/content-hash-index.ts +9 -0
- package/src/storage/entity-store-symlink.test.ts +44 -0
- package/src/storage.ts +15 -0
- package/src/transfer/capsule-gunzip.ts +39 -0
- package/src/transfer/capsule-import.test.ts +73 -0
- package/src/transfer/capsule-import.ts +2 -2
- package/src/transfer/capsule-merge.test.ts +34 -0
- package/src/transfer/capsule-merge.ts +2 -10
- package/src/utils/malformed-entity-record.test.ts +69 -0
- package/dist/capsule-merge-XVTQJJ6R.js.map +0 -1
- package/dist/chunk-4KLQI3L2.js.map +0 -1
- package/dist/chunk-6LCWKS62.js.map +0 -1
- package/dist/chunk-7PMOCQOL.js.map +0 -1
- package/dist/chunk-AYTDCMEL.js.map +0 -1
- package/dist/chunk-B74NPUHT.js.map +0 -1
- package/dist/chunk-KQUSP6LB.js.map +0 -1
- package/dist/chunk-LU66I56Q.js.map +0 -1
- package/dist/chunk-MPDLLLOG.js.map +0 -1
- package/dist/chunk-S57QBPTA.js.map +0 -1
- package/dist/chunk-SVCHO337.js.map +0 -1
- package/dist/chunk-WAD32CZG.js.map +0 -1
- package/dist/chunk-XYP5WBDO.js.map +0 -1
- package/dist/chunk-YK7GOPIE.js.map +0 -1
- package/dist/chunk-Z3DDQXM4.js.map +0 -1
- /package/dist/{capsule-crypto-YO5QJ6L3.js.map → access-mcp-frames.js.map} +0 -0
- /package/dist/{auto-sync-C7E6G7Y2.js.map → auto-sync-3AZW7MXI.js.map} +0 -0
- /package/dist/{graph-edge-decay-CXTNJ7IQ.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
- /package/dist/{resolution-BNWE5Y25.js.map → capsule-gunzip-KGBDGF7P.js.map} +0 -0
- /package/dist/{chunk-T6T2RMWW.js.map → chunk-23B4HLWG.js.map} +0 -0
- /package/dist/{chunk-LECSS7EE.js.map → chunk-2ASEDFOA.js.map} +0 -0
- /package/dist/{chunk-TPX7XXY4.js.map → chunk-2JAT4S7N.js.map} +0 -0
- /package/dist/{chunk-XRBHIR7Q.js.map → chunk-2URKYLMK.js.map} +0 -0
- /package/dist/{chunk-IN4OFAHX.js.map → chunk-2XYDL2FG.js.map} +0 -0
- /package/dist/{chunk-HQGATKG2.js.map → chunk-3KGCHTTK.js.map} +0 -0
- /package/dist/{chunk-YAUHSNPB.js.map → chunk-3LHPTBWG.js.map} +0 -0
- /package/dist/{chunk-XT45WCKX.js.map → chunk-3VU2FDTU.js.map} +0 -0
- /package/dist/{chunk-CIIHIAJ2.js.map → chunk-433EXTC4.js.map} +0 -0
- /package/dist/{chunk-QZSIDSB2.js.map → chunk-44FQCC5D.js.map} +0 -0
- /package/dist/{chunk-YUTT4ZTI.js.map → chunk-45VUHDV2.js.map} +0 -0
- /package/dist/{chunk-54FDG7K7.js.map → chunk-4DPLCYEK.js.map} +0 -0
- /package/dist/{chunk-KO3YBHOV.js.map → chunk-4IWEPQEV.js.map} +0 -0
- /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
- /package/dist/{chunk-K6RTQZA4.js.map → chunk-6DANGCMO.js.map} +0 -0
- /package/dist/{chunk-2IW5PT7U.js.map → chunk-6KFFRMPQ.js.map} +0 -0
- /package/dist/{chunk-MSIRVXQ3.js.map → chunk-73YVMZ7D.js.map} +0 -0
- /package/dist/{chunk-A7BM2JTH.js.map → chunk-AJHNXIQU.js.map} +0 -0
- /package/dist/{chunk-2D7BTSDW.js.map → chunk-ANZZLRUX.js.map} +0 -0
- /package/dist/{chunk-YOEYL6CH.js.map → chunk-AP3TSV6S.js.map} +0 -0
- /package/dist/{chunk-GQZSGXR6.js.map → chunk-BATPQNRG.js.map} +0 -0
- /package/dist/{chunk-D5SSBCYY.js.map → chunk-BLGQUZFB.js.map} +0 -0
- /package/dist/{chunk-BDN3TDGV.js.map → chunk-BLMTOIBK.js.map} +0 -0
- /package/dist/{chunk-R45L4TV6.js.map → chunk-BUKK5SWA.js.map} +0 -0
- /package/dist/{chunk-TFLPDNQV.js.map → chunk-CCU7URRP.js.map} +0 -0
- /package/dist/{chunk-2XJZLSXE.js.map → chunk-CMRHXPLQ.js.map} +0 -0
- /package/dist/{chunk-BK4FBHZ3.js.map → chunk-D6THW7PA.js.map} +0 -0
- /package/dist/{chunk-BFBS7OFQ.js.map → chunk-DC55RIEZ.js.map} +0 -0
- /package/dist/{chunk-VKDAJJQP.js.map → chunk-DI5ET2IE.js.map} +0 -0
- /package/dist/{chunk-QZBTIYKD.js.map → chunk-DM4RRTN7.js.map} +0 -0
- /package/dist/{chunk-EBOHFZI4.js.map → chunk-E5QEF3JO.js.map} +0 -0
- /package/dist/{chunk-DD6OIFDG.js.map → chunk-E7V5AK7I.js.map} +0 -0
- /package/dist/{chunk-BV2BRHD4.js.map → chunk-FHGWKODN.js.map} +0 -0
- /package/dist/{chunk-HPFVZPRH.js.map → chunk-FK4WC6VK.js.map} +0 -0
- /package/dist/{chunk-VLMBSGQF.js.map → chunk-FRM4UTDR.js.map} +0 -0
- /package/dist/{chunk-LTDLJCMQ.js.map → chunk-FZPDRCOB.js.map} +0 -0
- /package/dist/{chunk-ZJJFUEPO.js.map → chunk-GBOYOL5L.js.map} +0 -0
- /package/dist/{chunk-YUTG35M4.js.map → chunk-GJVGFKOU.js.map} +0 -0
- /package/dist/{chunk-K36RMINQ.js.map → chunk-GOZXQNIM.js.map} +0 -0
- /package/dist/{chunk-HWHNQVRV.js.map → chunk-GYNRNKMW.js.map} +0 -0
- /package/dist/{chunk-KFLD7ZZG.js.map → chunk-IUKYFTGW.js.map} +0 -0
- /package/dist/{chunk-BNQJKWWZ.js.map → chunk-K3FH3LAQ.js.map} +0 -0
- /package/dist/{chunk-FJ2VJTGA.js.map → chunk-KHZVAISH.js.map} +0 -0
- /package/dist/{chunk-WHQXWFH7.js.map → chunk-LT3CMO65.js.map} +0 -0
- /package/dist/{chunk-NUSAAN75.js.map → chunk-MHSBOEH7.js.map} +0 -0
- /package/dist/{chunk-KJCL2FZO.js.map → chunk-MNYQVYJ4.js.map} +0 -0
- /package/dist/{chunk-RZH5RVJC.js.map → chunk-MTTYIXSU.js.map} +0 -0
- /package/dist/{chunk-FVRTCYZ3.js.map → chunk-MVOJDLXS.js.map} +0 -0
- /package/dist/{chunk-KESJ5XX3.js.map → chunk-MYCCO6AA.js.map} +0 -0
- /package/dist/{chunk-66JOLMWS.js.map → chunk-MZCGHPIJ.js.map} +0 -0
- /package/dist/{chunk-B4ZU34JQ.js.map → chunk-N6II66E5.js.map} +0 -0
- /package/dist/{chunk-BK7AJUWB.js.map → chunk-NIV7RPWQ.js.map} +0 -0
- /package/dist/{chunk-TLHF75OR.js.map → chunk-O55AGDTI.js.map} +0 -0
- /package/dist/{chunk-GGJFRVRD.js.map → chunk-OMYRPMQC.js.map} +0 -0
- /package/dist/{chunk-XHGYZRVW.js.map → chunk-OSTFDWZJ.js.map} +0 -0
- /package/dist/{chunk-BXLOS5AJ.js.map → chunk-OWHERGF2.js.map} +0 -0
- /package/dist/{chunk-WC475JOG.js.map → chunk-P225W4RQ.js.map} +0 -0
- /package/dist/{chunk-TFA6HPOA.js.map → chunk-QCP75JGO.js.map} +0 -0
- /package/dist/{chunk-JRVMCZE3.js.map → chunk-QEYTRFR3.js.map} +0 -0
- /package/dist/{chunk-IZJQNDGI.js.map → chunk-R6HMAIKM.js.map} +0 -0
- /package/dist/{chunk-VUROWXFL.js.map → chunk-RNGDKFY2.js.map} +0 -0
- /package/dist/{chunk-FF6N7BXC.js.map → chunk-S45INSV3.js.map} +0 -0
- /package/dist/{chunk-2RUTN7PR.js.map → chunk-S7GJNA3K.js.map} +0 -0
- /package/dist/{chunk-RJWL3RHB.js.map → chunk-TFEHSPGQ.js.map} +0 -0
- /package/dist/{chunk-T77AX6DG.js.map → chunk-TQVECGUQ.js.map} +0 -0
- /package/dist/{chunk-OQSSQOKQ.js.map → chunk-TV65NR7L.js.map} +0 -0
- /package/dist/{chunk-HQPFACXO.js.map → chunk-UH3FPX3L.js.map} +0 -0
- /package/dist/{chunk-4EACCIPO.js.map → chunk-UHPHH74F.js.map} +0 -0
- /package/dist/{chunk-DQEMWVMT.js.map → chunk-UVYI6VIX.js.map} +0 -0
- /package/dist/{chunk-6QYPL6NI.js.map → chunk-V45TEVDA.js.map} +0 -0
- /package/dist/{chunk-GCAQ3VUJ.js.map → chunk-VCVEMQJK.js.map} +0 -0
- /package/dist/{chunk-6L63IN3M.js.map → chunk-VEFGHRKV.js.map} +0 -0
- /package/dist/{chunk-E33UJHY3.js.map → chunk-WFDWLWDK.js.map} +0 -0
- /package/dist/{chunk-KQLBXUUQ.js.map → chunk-WYG5UNDY.js.map} +0 -0
- /package/dist/{chunk-MPGLM2VU.js.map → chunk-X7REO4CH.js.map} +0 -0
- /package/dist/{chunk-FZVROZKC.js.map → chunk-XVZA6VXC.js.map} +0 -0
- /package/dist/{chunk-WV254W4V.js.map → chunk-Y3IYKNBY.js.map} +0 -0
- /package/dist/{chunk-FUOAIJKQ.js.map → chunk-Y43X3E24.js.map} +0 -0
- /package/dist/{chunk-3T3VHXUH.js.map → chunk-YAAKVTZA.js.map} +0 -0
- /package/dist/{chunk-VPEIWMUT.js.map → chunk-YLTU2DC4.js.map} +0 -0
- /package/dist/{chunk-65SKM4BF.js.map → chunk-YNBMBL4C.js.map} +0 -0
- /package/dist/{chunk-PJYK7HH2.js.map → chunk-ZWTHVUT5.js.map} +0 -0
- /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import type { MemoryFile } from "../types.js";
|
|
8
|
+
import { rememberRawFrontmatter } from "../storage/memory-frontmatter-metadata.js";
|
|
9
|
+
import {
|
|
10
|
+
CORRECTION_FILE_BYTE_LIMIT,
|
|
11
|
+
WEEKLY_CORRECTION_READ_LIMIT,
|
|
12
|
+
WEEKLY_CORRECTION_SCAN_LIMIT,
|
|
13
|
+
buildActionFailurePatterns,
|
|
14
|
+
capWeeklyPatternDetails,
|
|
15
|
+
isoWeekStartMs,
|
|
16
|
+
provenanceVisibleInStoreRecall,
|
|
17
|
+
readFeedbackEntriesForWeekFile,
|
|
18
|
+
readWeeklyCorrections,
|
|
19
|
+
type WeeklyActionEvent,
|
|
20
|
+
type WeeklyEvidenceStorage,
|
|
21
|
+
} from "./weekly-evidence.js";
|
|
22
|
+
import { isoWeekId } from "./iso-week.js";
|
|
23
|
+
|
|
24
|
+
test("isoWeekStartMs returns Monday UTC for valid ids and null otherwise", () => {
|
|
25
|
+
const start = isoWeekStartMs("2026-W01");
|
|
26
|
+
assert.ok(start !== null);
|
|
27
|
+
assert.equal(new Date(start).toISOString(), "2025-12-29T00:00:00.000Z");
|
|
28
|
+
assert.equal(isoWeekId(new Date(start)), "2026-W01");
|
|
29
|
+
|
|
30
|
+
const w02 = isoWeekStartMs("2026-W02");
|
|
31
|
+
assert.ok(w02 !== null);
|
|
32
|
+
assert.equal(w02 - start!, 7 * 86_400_000);
|
|
33
|
+
|
|
34
|
+
assert.equal(isoWeekStartMs("2026-W1"), null);
|
|
35
|
+
assert.equal(isoWeekStartMs("2026-W00"), null);
|
|
36
|
+
assert.equal(isoWeekStartMs("not-a-week"), null);
|
|
37
|
+
assert.equal(isoWeekStartMs("2026-W54"), null);
|
|
38
|
+
// 2026 has 53 weeks only if the calendar emits it; W53 that does not land is null.
|
|
39
|
+
const w53 = isoWeekStartMs("2015-W53");
|
|
40
|
+
assert.ok(w53 !== null);
|
|
41
|
+
assert.equal(isoWeekId(new Date(w53)), "2015-W53");
|
|
42
|
+
assert.equal(isoWeekStartMs("2016-W53"), null);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("readFeedbackEntriesForWeekFile filters by ISO week and skips bad lines", async () => {
|
|
46
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-weekly-feedback-"));
|
|
47
|
+
try {
|
|
48
|
+
const inbox = path.join(root, "feedback.jsonl");
|
|
49
|
+
const inWeek = "2026-01-02T12:00:00.000Z"; // 2026-W01
|
|
50
|
+
const otherWeek = "2026-01-08T12:00:00.000Z"; // 2026-W02
|
|
51
|
+
await writeFile(
|
|
52
|
+
inbox,
|
|
53
|
+
[
|
|
54
|
+
JSON.stringify({
|
|
55
|
+
agent: "alpha",
|
|
56
|
+
decision: "approved",
|
|
57
|
+
reason: "looks good",
|
|
58
|
+
date: inWeek,
|
|
59
|
+
}),
|
|
60
|
+
"not-json",
|
|
61
|
+
JSON.stringify({ agent: "", decision: "approved", reason: "x", date: inWeek }),
|
|
62
|
+
JSON.stringify({
|
|
63
|
+
agent: "beta",
|
|
64
|
+
decision: "rejected",
|
|
65
|
+
reason: "no",
|
|
66
|
+
date: otherWeek,
|
|
67
|
+
}),
|
|
68
|
+
JSON.stringify({
|
|
69
|
+
agent: "gamma",
|
|
70
|
+
decision: "approved_with_feedback",
|
|
71
|
+
reason: "tweak",
|
|
72
|
+
date: "2025-12-30T08:00:00.000Z",
|
|
73
|
+
}),
|
|
74
|
+
"",
|
|
75
|
+
].join("\n"),
|
|
76
|
+
"utf8",
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const entries = await readFeedbackEntriesForWeekFile(inbox, "2026-W01");
|
|
80
|
+
assert.equal(entries.length, 2);
|
|
81
|
+
assert.deepEqual(
|
|
82
|
+
entries.map((row) => row.entry.agent).sort(),
|
|
83
|
+
["alpha", "gamma"],
|
|
84
|
+
);
|
|
85
|
+
assert.equal(entries.find((row) => row.entry.agent === "alpha")?.sourceLine, 1);
|
|
86
|
+
assert.equal(entries.find((row) => row.entry.agent === "gamma")?.sourceLine, 5);
|
|
87
|
+
|
|
88
|
+
assert.deepEqual(await readFeedbackEntriesForWeekFile(path.join(root, "missing.jsonl"), "2026-W01"), []);
|
|
89
|
+
} finally {
|
|
90
|
+
await rm(root, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("buildActionFailurePatterns keeps failures, skips, and policy decisions", () => {
|
|
95
|
+
const events: WeeklyActionEvent[] = [
|
|
96
|
+
{
|
|
97
|
+
line: 1,
|
|
98
|
+
action: "forget",
|
|
99
|
+
outcome: "failed",
|
|
100
|
+
policyDecision: null,
|
|
101
|
+
namespace: "default",
|
|
102
|
+
reason: "locked",
|
|
103
|
+
ledgerLabel: "memory-actions.jsonl",
|
|
104
|
+
actionId: "a1",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
line: 2,
|
|
108
|
+
action: "promote",
|
|
109
|
+
outcome: "applied",
|
|
110
|
+
policyDecision: null,
|
|
111
|
+
namespace: "default",
|
|
112
|
+
reason: null,
|
|
113
|
+
ledgerLabel: "memory-actions.jsonl",
|
|
114
|
+
actionId: "a2",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
line: 3,
|
|
118
|
+
action: "edit",
|
|
119
|
+
outcome: "applied",
|
|
120
|
+
policyDecision: "deny",
|
|
121
|
+
namespace: "ns1",
|
|
122
|
+
reason: " blocked by policy ",
|
|
123
|
+
ledgerLabel: "namespaces/ns1/state/memory-actions.jsonl",
|
|
124
|
+
actionId: "a3",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
line: 4,
|
|
128
|
+
action: "merge",
|
|
129
|
+
outcome: "skipped",
|
|
130
|
+
policyDecision: null,
|
|
131
|
+
namespace: "default",
|
|
132
|
+
reason: null,
|
|
133
|
+
ledgerLabel: "memory-actions.jsonl",
|
|
134
|
+
actionId: "a4",
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const patterns = buildActionFailurePatterns(events);
|
|
139
|
+
assert.equal(patterns.length, 3);
|
|
140
|
+
assert.match(patterns[0]!.pattern, /memory-action\/default: forget failed - locked/);
|
|
141
|
+
assert.equal(patterns[0]!.provenance, "memory-actions.jsonl:*");
|
|
142
|
+
assert.match(patterns[1]!.pattern, /memory-action\/ns1: edit applied\/deny - blocked by policy/);
|
|
143
|
+
assert.match(patterns[2]!.pattern, /memory-action\/default: merge skipped$/);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("readWeeklyCorrections skips files over the byte cap and keeps in-week active ones", async () => {
|
|
147
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-weekly-corr-"));
|
|
148
|
+
try {
|
|
149
|
+
const correctionsDir = path.join(root, "corrections");
|
|
150
|
+
await mkdir(correctionsDir, { recursive: true });
|
|
151
|
+
|
|
152
|
+
const inWeekMs = Date.parse("2026-01-02T15:00:00.000Z");
|
|
153
|
+
const smallName = `correction-${inWeekMs}.md`;
|
|
154
|
+
const largeName = `correction-${inWeekMs + 1000}.md`;
|
|
155
|
+
const smallBody = `---
|
|
156
|
+
id: corr-small
|
|
157
|
+
category: correction
|
|
158
|
+
status: active
|
|
159
|
+
created: 2026-01-02T15:00:00.000Z
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
Fix the pricing table.
|
|
163
|
+
`;
|
|
164
|
+
await writeFile(path.join(correctionsDir, smallName), smallBody, "utf8");
|
|
165
|
+
await writeFile(
|
|
166
|
+
path.join(correctionsDir, largeName),
|
|
167
|
+
`${"x".repeat(CORRECTION_FILE_BYTE_LIMIT + 1)}`,
|
|
168
|
+
"utf8",
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// SAFETY: rememberRawFrontmatter reads only path, content and frontmatter; this literal sets all three.
|
|
172
|
+
const smallMemory = rememberRawFrontmatter(
|
|
173
|
+
{
|
|
174
|
+
path: path.join(correctionsDir, smallName),
|
|
175
|
+
content: "Fix the pricing table.",
|
|
176
|
+
frontmatter: {
|
|
177
|
+
id: "corr-small",
|
|
178
|
+
category: "correction",
|
|
179
|
+
status: "active",
|
|
180
|
+
created: "2026-01-02T15:00:00.000Z",
|
|
181
|
+
},
|
|
182
|
+
} as MemoryFile,
|
|
183
|
+
smallBody,
|
|
184
|
+
);
|
|
185
|
+
const byPath = new Map<string, MemoryFile>([[path.join(correctionsDir, smallName), smallMemory]]);
|
|
186
|
+
|
|
187
|
+
const storage: WeeklyEvidenceStorage = {
|
|
188
|
+
async readMemoryByPath(filePath) {
|
|
189
|
+
return byPath.get(filePath) ?? null;
|
|
190
|
+
},
|
|
191
|
+
async readMemoryActionEventRows() {
|
|
192
|
+
return [];
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const evidence = await readWeeklyCorrections({
|
|
197
|
+
storage,
|
|
198
|
+
memoryDir: root,
|
|
199
|
+
weekId: "2026-W01",
|
|
200
|
+
});
|
|
201
|
+
assert.equal(evidence.length, 1);
|
|
202
|
+
assert.equal(evidence[0]!.id, "corr-small");
|
|
203
|
+
assert.equal(evidence[0]!.note, "Fix the pricing table.");
|
|
204
|
+
assert.equal(evidence[0]!.provenance, `corrections/${smallName}`);
|
|
205
|
+
|
|
206
|
+
assert.ok(CORRECTION_FILE_BYTE_LIMIT === 256 * 1024);
|
|
207
|
+
assert.ok(WEEKLY_CORRECTION_READ_LIMIT === 500);
|
|
208
|
+
assert.ok(WEEKLY_CORRECTION_SCAN_LIMIT === 2000);
|
|
209
|
+
} finally {
|
|
210
|
+
await rm(root, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("provenanceVisibleInStoreRecall and capWeeklyPatternDetails prefer visible actions", () => {
|
|
215
|
+
assert.equal(provenanceVisibleInStoreRecall(["facts/a.md"]), true);
|
|
216
|
+
assert.equal(provenanceVisibleInStoreRecall(["namespaces/other/x.md"]), false);
|
|
217
|
+
assert.equal(provenanceVisibleInStoreRecall(["namespaces/home/x.md"], "namespaces/home/"), true);
|
|
218
|
+
assert.equal(provenanceVisibleInStoreRecall([]), true);
|
|
219
|
+
|
|
220
|
+
const details = [
|
|
221
|
+
{ pattern: "foreign", provenance: ["namespaces/other/a.md"] as const },
|
|
222
|
+
{ pattern: "action-a", provenance: ["memory-actions.jsonl:*"] as const },
|
|
223
|
+
{ pattern: "corr", provenance: ["corrections/c.md"] as const },
|
|
224
|
+
{ pattern: "action-b", provenance: ["memory-actions.jsonl:*"] as const },
|
|
225
|
+
{ pattern: "overflow", provenance: ["facts/x.md"] as const },
|
|
226
|
+
];
|
|
227
|
+
const capped = capWeeklyPatternDetails(
|
|
228
|
+
details,
|
|
229
|
+
(pattern) => (pattern.startsWith("action") ? "action" : "correction"),
|
|
230
|
+
null,
|
|
231
|
+
3,
|
|
232
|
+
);
|
|
233
|
+
assert.deepEqual(
|
|
234
|
+
capped.map((row) => row.pattern),
|
|
235
|
+
["action-a", "action-b", "corr"],
|
|
236
|
+
);
|
|
237
|
+
});
|
|
@@ -3,6 +3,7 @@ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { raceAbort } from "./abort-error.js";
|
|
4
4
|
import { entityMentionEpoch } from "./entity-mention-epoch.js";
|
|
5
5
|
import { log } from "./logger.js";
|
|
6
|
+
import { compareStrings } from "./eval-trajectory-contracts.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* In-memory entity mention index.
|
|
@@ -158,12 +159,6 @@ function throwIfMentionAborted(signal?: AbortSignal): void {
|
|
|
158
159
|
throw reason instanceof Error ? reason : new Error("entity mention rebuild aborted");
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
function compareStrings(left: string, right: string): number {
|
|
162
|
-
if (left < right) return -1;
|
|
163
|
-
if (left > right) return 1;
|
|
164
|
-
return 0;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
162
|
function deduped(values: readonly string[]): string[] {
|
|
168
163
|
const seen = new Set<string>();
|
|
169
164
|
const out: string[] = [];
|
|
@@ -410,6 +410,55 @@ test("a claim that inverts a negated quote is rejected, a restated exclusion is
|
|
|
410
410
|
assert.deepEqual(keptContents(grounded), ["Alice would avoid approving the budget."]);
|
|
411
411
|
});
|
|
412
412
|
|
|
413
|
+
test("a claim that keeps one quote negation and flips another is rejected", () => {
|
|
414
|
+
const source = "Notes: Alice did not approve the budget. Bob did not review the ledger.";
|
|
415
|
+
const quote = "Alice did not approve the budget. Bob did not review the ledger.";
|
|
416
|
+
const grounded = filterExtractionResultBySource(
|
|
417
|
+
resultOf([
|
|
418
|
+
{ content: "Alice did not approve the budget, and Bob reviewed the ledger.", quote },
|
|
419
|
+
{
|
|
420
|
+
content: "Alice did not approve the budget, and Bob did not review the ledger.",
|
|
421
|
+
quote,
|
|
422
|
+
},
|
|
423
|
+
]),
|
|
424
|
+
source,
|
|
425
|
+
source,
|
|
426
|
+
);
|
|
427
|
+
assert.deepEqual(keptContents(grounded), [
|
|
428
|
+
"Alice did not approve the budget, and Bob did not review the ledger.",
|
|
429
|
+
]);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test("a claim that keeps the denial in a pronoun clause is kept", () => {
|
|
433
|
+
const source = "Notes: Alice did not approve the budget.";
|
|
434
|
+
const quote = "Alice did not approve the budget.";
|
|
435
|
+
const claims = [
|
|
436
|
+
"Alice discussed the budget; she did not approve it.",
|
|
437
|
+
"Alice reviewed the budget, but she did not approve it.",
|
|
438
|
+
];
|
|
439
|
+
const grounded = filterExtractionResultBySource(
|
|
440
|
+
resultOf(claims.map((content) => ({ content, quote }))),
|
|
441
|
+
source,
|
|
442
|
+
source,
|
|
443
|
+
);
|
|
444
|
+
assert.deepEqual(keptContents(grounded), claims);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("a negator in a claim clause that shares only the subject does not keep the denial", () => {
|
|
448
|
+
const source = "Notes: Alice did not approve the budget. Alice did not review the contract.";
|
|
449
|
+
const grounded = filterExtractionResultBySource(
|
|
450
|
+
resultOf([
|
|
451
|
+
{
|
|
452
|
+
content: "Alice did not review the contract. She approved the budget.",
|
|
453
|
+
quote: "Alice did not approve the budget.",
|
|
454
|
+
},
|
|
455
|
+
]),
|
|
456
|
+
source,
|
|
457
|
+
source,
|
|
458
|
+
);
|
|
459
|
+
assert.equal(grounded.facts.length, 0);
|
|
460
|
+
});
|
|
461
|
+
|
|
413
462
|
test("a claim that inverts a denial verb in the quote is rejected", () => {
|
|
414
463
|
const source = "Notes: Alice denied approving the budget. Bob reviewed it.";
|
|
415
464
|
const grounded = filterExtractionResultBySource(
|
|
@@ -130,24 +130,62 @@ function hasNegation(text: string): boolean {
|
|
|
130
130
|
return NEGATOR_PATTERN.test(text.replace(/[\u2018\u2019]/gu, "'")) || hasDenialReporting(text);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
const CLAUSE_PATTERN = /[^;.!?\n]+[;.!?]*/gu;
|
|
134
|
+
/** Split "X, and Y" claim coordination without touching list commas. */
|
|
135
|
+
const CLAIM_COORD_SPLIT = /,\s{1,8}(?:and|but|or|nor)\s{1,8}/iu;
|
|
136
|
+
|
|
137
|
+
function splitClaimClauses(content: string): string[] {
|
|
138
|
+
return (content.match(CLAUSE_PATTERN) ?? [content]).flatMap((clause) => {
|
|
139
|
+
const parts = clause
|
|
140
|
+
.split(CLAIM_COORD_SPLIT)
|
|
141
|
+
.map((part) => part.trim())
|
|
142
|
+
.filter((part) => part.length > 0);
|
|
143
|
+
return parts.length > 0 ? parts : [clause];
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function clauseOverlap(clauseTokens: readonly string[], target: string) {
|
|
148
|
+
const has = tokenPool(tokenize(target));
|
|
149
|
+
const shared = clauseTokens.filter(has).length;
|
|
150
|
+
return { shared, ratio: clauseTokens.length === 0 ? 0 : shared / clauseTokens.length };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** What a quote clause's first negator governs: the words after it. */
|
|
154
|
+
function negatedScope(clause: string): string {
|
|
155
|
+
const match = NEGATOR_PATTERN.exec(clause.replace(/[\u2018\u2019]/gu, "'"));
|
|
156
|
+
return match ? clause.slice(match.index + match[0].length) : clause;
|
|
157
|
+
}
|
|
158
|
+
|
|
133
159
|
/**
|
|
134
160
|
* True when the claim asserts what a quote clause denies or does not assert:
|
|
135
161
|
* "did not approve the budget" or "denied approving it" restated as "approved
|
|
136
162
|
* the budget", "suspects Bob stole funds" or "Is Alice the CEO?" restated as a
|
|
137
|
-
* plain statement.
|
|
138
|
-
*
|
|
163
|
+
* plain statement. The claim must restate the quote clause, and none of its
|
|
164
|
+
* clauses that share a word with what the negator governs may keep the
|
|
165
|
+
* negator, exclusion word, or reporting marker ("do not X" reworded as "X is
|
|
166
|
+
* excluded", or "Alice reviewed it, but she did not approve it"). A negator in
|
|
167
|
+
* a clause about something else does not count, so a claim that keeps one
|
|
168
|
+
* quote negation can still misstate another quote clause, and a claim clause
|
|
169
|
+
* that shares only the subject does not satisfy the check.
|
|
139
170
|
*/
|
|
140
171
|
function misstatesQuote(content: string, quote: string): boolean {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
172
|
+
const claimClauses = splitClaimClauses(content);
|
|
173
|
+
return (quote.match(CLAUSE_PATTERN) ?? []).some((quoteClause) => {
|
|
174
|
+
const needsNegation = hasNegation(quoteClause);
|
|
175
|
+
const needsReporting = quoteClause.trim().endsWith("?") || hasNonAssertiveReporting(quoteClause);
|
|
176
|
+
if (!needsNegation && !needsReporting) return false;
|
|
177
|
+
const clauseTokens = [...tokenize(quoteClause)];
|
|
178
|
+
const whole = clauseOverlap(clauseTokens, content);
|
|
179
|
+
if (whole.shared < GROUNDING_MIN_SHARED_TOKENS || whole.ratio < 0.5) return false;
|
|
180
|
+
const clausesSharing = (tokens: readonly string[]) =>
|
|
181
|
+
claimClauses.filter((claimClause) => clauseOverlap(tokens, claimClause).shared > 0);
|
|
182
|
+
if (needsNegation) {
|
|
183
|
+
const scopeTokens = [...tokenize(negatedScope(quoteClause))];
|
|
184
|
+
const related = clausesSharing(scopeTokens.length > 0 ? scopeTokens : clauseTokens);
|
|
185
|
+
if (!related.some((text) => hasNegation(text) || EXCLUSION_PATTERN.test(text))) return true;
|
|
186
|
+
}
|
|
187
|
+
return needsReporting
|
|
188
|
+
&& !clausesSharing(clauseTokens).some((text) => hasNonAssertiveReporting(text) || text.trim().endsWith("?"));
|
|
151
189
|
});
|
|
152
190
|
}
|
|
153
191
|
|
|
@@ -1,14 +1,27 @@
|
|
|
1
|
-
import test from "node:test";
|
|
1
|
+
import test, { mock } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { StorageManager } from "./storage.js";
|
|
7
7
|
import { withHeldFileLock } from "./utils/serialize-mutations.js";
|
|
8
8
|
import { confirmActiveFactDuplicate, persistUnderFactDedupWindow } from "./orchestration/fact-dedup-window.js";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const SKY_OPTS = {
|
|
11
|
+
category: "fact",
|
|
12
|
+
contentHashDedupKey: "the sky is blue",
|
|
13
|
+
connector: undefined,
|
|
14
|
+
citationEnabled: false,
|
|
15
|
+
citationTemplate: "",
|
|
16
|
+
} as const;
|
|
17
|
+
const CONTENDED_LOCK_OPTIONS = { maxWaitMs: 40, pollMs: 10, retryMaxAttempts: 2, retryBaseMs: 10 };
|
|
18
|
+
|
|
19
|
+
/** Hold a fresh fact-hash index lock the way a peer mid-publish does; returns the release. */
|
|
20
|
+
async function holdIndexLock(root: string): Promise<() => Promise<void>> {
|
|
21
|
+
const lockPath = path.join(root, "state", "fact-hashes.txt.lock");
|
|
22
|
+
await mkdir(path.dirname(lockPath), { recursive: true });
|
|
23
|
+
await writeFile(lockPath, `99999 peer-owner ${new Date().toISOString()}\n`);
|
|
24
|
+
return () => rm(lockPath, { force: true });
|
|
12
25
|
}
|
|
13
26
|
|
|
14
27
|
async function makeStorage(): Promise<{ storage: StorageManager; cleanup: () => Promise<void> }> {
|
|
@@ -19,14 +32,7 @@ async function makeStorage(): Promise<{ storage: StorageManager; cleanup: () =>
|
|
|
19
32
|
test("confirmActiveFactDuplicate sees a fact another session committed into the same namespace", async () => {
|
|
20
33
|
const { storage, cleanup } = await makeStorage();
|
|
21
34
|
try {
|
|
22
|
-
const recheck = () =>
|
|
23
|
-
confirmActiveFactDuplicate(probe(storage), storage, {
|
|
24
|
-
category: "fact",
|
|
25
|
-
contentHashDedupKey: "the sky is blue",
|
|
26
|
-
connector: undefined,
|
|
27
|
-
citationEnabled: false,
|
|
28
|
-
citationTemplate: "",
|
|
29
|
-
});
|
|
35
|
+
const recheck = () => confirmActiveFactDuplicate(storage, SKY_OPTS);
|
|
30
36
|
assert.equal(await recheck(), false, "clean namespace must not report a duplicate");
|
|
31
37
|
|
|
32
38
|
// A concurrent session (separate manager, same dir) commits the fact.
|
|
@@ -48,14 +54,7 @@ test("persistUnderFactDedupWindow suppresses on recheck hit and writes on a miss
|
|
|
48
54
|
await storage.writeMemory("fact", "the sky is blue");
|
|
49
55
|
return "written";
|
|
50
56
|
};
|
|
51
|
-
const recheck = () =>
|
|
52
|
-
confirmActiveFactDuplicate(probe(storage), storage, {
|
|
53
|
-
category: "fact",
|
|
54
|
-
contentHashDedupKey: "the sky is blue",
|
|
55
|
-
connector: undefined,
|
|
56
|
-
citationEnabled: false,
|
|
57
|
-
citationTemplate: "",
|
|
58
|
-
});
|
|
57
|
+
const recheck = () => confirmActiveFactDuplicate(storage, SKY_OPTS);
|
|
59
58
|
|
|
60
59
|
// First session: recheck misses, write proceeds.
|
|
61
60
|
const first = await persistUnderFactDedupWindow(storage["baseDir"], recheck, write);
|
|
@@ -114,3 +113,87 @@ test("persistUnderFactDedupWindow fails closed when a peer holds fact-dedup.lock
|
|
|
114
113
|
await cleanup();
|
|
115
114
|
}
|
|
116
115
|
});
|
|
116
|
+
|
|
117
|
+
test("lookupContentHash never pairs a stale MISS with authority while a peer holds the index lock", async () => {
|
|
118
|
+
const { storage, cleanup } = await makeStorage();
|
|
119
|
+
try {
|
|
120
|
+
storage.factHashIndexLockOptions = CONTENDED_LOCK_OPTIONS;
|
|
121
|
+
assert.equal(await storage.isFactContentHashAuthoritative(), true, "starts authoritative");
|
|
122
|
+
const peer = new StorageManager(storage["baseDir"]);
|
|
123
|
+
await peer.writeMemory("fact", "The sky is BLUE!");
|
|
124
|
+
const release = await holdIndexLock(storage["baseDir"]);
|
|
125
|
+
try {
|
|
126
|
+
assert.deepEqual(
|
|
127
|
+
await storage.lookupContentHash(SKY_OPTS.contentHashDedupKey),
|
|
128
|
+
{ present: false, authoritative: false },
|
|
129
|
+
"the rebuild cannot run, so the stale MISS must not claim authority",
|
|
130
|
+
);
|
|
131
|
+
assert.equal(await confirmActiveFactDuplicate(storage, SKY_OPTS), true, "the corpus confirms the peer's fact");
|
|
132
|
+
} finally {
|
|
133
|
+
await release();
|
|
134
|
+
}
|
|
135
|
+
assert.deepEqual(
|
|
136
|
+
await storage.lookupContentHash(SKY_OPTS.contentHashDedupKey),
|
|
137
|
+
{ present: true, authoritative: true },
|
|
138
|
+
"once the lock is free the rebuild publishes and the hit is authoritative",
|
|
139
|
+
);
|
|
140
|
+
} finally {
|
|
141
|
+
await cleanup();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("confirmActiveFactDuplicate stays a duplicate when the peer releases the index lock right after the lookup", async () => {
|
|
146
|
+
const { storage, cleanup } = await makeStorage();
|
|
147
|
+
try {
|
|
148
|
+
storage.factHashIndexLockOptions = CONTENDED_LOCK_OPTIONS;
|
|
149
|
+
assert.equal(await storage.isFactContentHashAuthoritative(), true, "starts authoritative");
|
|
150
|
+
const peer = new StorageManager(storage["baseDir"]);
|
|
151
|
+
await peer.writeMemory("fact", "The sky is BLUE!");
|
|
152
|
+
const release = await holdIndexLock(storage["baseDir"]);
|
|
153
|
+
const lookup = storage.lookupContentHash.bind(storage);
|
|
154
|
+
mock.method(storage, "lookupContentHash", async (content: string) => {
|
|
155
|
+
const answer = await lookup(content);
|
|
156
|
+
await release();
|
|
157
|
+
return answer;
|
|
158
|
+
});
|
|
159
|
+
assert.equal(
|
|
160
|
+
await confirmActiveFactDuplicate(storage, SKY_OPTS),
|
|
161
|
+
true,
|
|
162
|
+
"a lock released after the lookup must not turn the stale MISS into a clean not-duplicate",
|
|
163
|
+
);
|
|
164
|
+
} finally {
|
|
165
|
+
await cleanup();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("two managers on one directory commit the same fact through the window exactly once", async () => {
|
|
170
|
+
const { storage, cleanup } = await makeStorage();
|
|
171
|
+
try {
|
|
172
|
+
const peer = new StorageManager(storage["baseDir"]);
|
|
173
|
+
const commit = (manager: StorageManager) =>
|
|
174
|
+
persistUnderFactDedupWindow(
|
|
175
|
+
manager.dir,
|
|
176
|
+
() => confirmActiveFactDuplicate(manager, SKY_OPTS),
|
|
177
|
+
() => manager.writeMemory("fact", "The sky is blue"),
|
|
178
|
+
);
|
|
179
|
+
const outcomes = await Promise.all([commit(storage), commit(peer)]);
|
|
180
|
+
assert.equal(outcomes.filter((outcome) => !outcome.suppressed).length, 1, "one writer wins, the other is suppressed");
|
|
181
|
+
const stored = await new StorageManager(storage["baseDir"]).readAllMemories();
|
|
182
|
+
assert.equal(stored.filter((memory) => memory.content.includes("sky is blue")).length, 1);
|
|
183
|
+
} finally {
|
|
184
|
+
await cleanup();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("confirmActiveFactDuplicate keeps the authoritative MISS fast path", async () => {
|
|
189
|
+
const { storage, cleanup } = await makeStorage();
|
|
190
|
+
try {
|
|
191
|
+
assert.equal(await storage.isFactContentHashAuthoritative(), true, "warm the index so no rebuild reads the corpus");
|
|
192
|
+
const corpusReads = mock.method(storage, "readAllMemories");
|
|
193
|
+
assert.equal(await confirmActiveFactDuplicate(storage, SKY_OPTS), false);
|
|
194
|
+
assert.equal(corpusReads.mock.callCount(), 0, "authoritative MISS must not scan the corpus");
|
|
195
|
+
} finally {
|
|
196
|
+
await cleanup();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import test from "node:test";
|
|
8
|
+
|
|
9
|
+
import { PROJECTION_REBUILD_RUNNER_FLAG } from "./rebuild-memory-projection-runner.js";
|
|
10
|
+
|
|
11
|
+
const RUNNER = fileURLToPath(new URL("./rebuild-memory-projection-runner.ts", import.meta.url));
|
|
12
|
+
|
|
13
|
+
type RunnerMessage =
|
|
14
|
+
| { ok: true; currentRows: number; timelineRows: number; entityMentionRows: number }
|
|
15
|
+
| { ok: false; error: string };
|
|
16
|
+
|
|
17
|
+
async function runRunner(args: string[]): Promise<{
|
|
18
|
+
code: number | null;
|
|
19
|
+
messages: RunnerMessage[];
|
|
20
|
+
stderr: string;
|
|
21
|
+
}> {
|
|
22
|
+
const child = spawn(
|
|
23
|
+
process.execPath,
|
|
24
|
+
["--import", "tsx", RUNNER, PROJECTION_REBUILD_RUNNER_FLAG, ...args],
|
|
25
|
+
{
|
|
26
|
+
stdio: ["ignore", "ignore", "pipe", "ipc"],
|
|
27
|
+
env: { ...process.env },
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
const messages: RunnerMessage[] = [];
|
|
31
|
+
let stderr = "";
|
|
32
|
+
child.stderr?.setEncoding("utf8");
|
|
33
|
+
child.stderr?.on("data", (chunk: string) => {
|
|
34
|
+
stderr += chunk;
|
|
35
|
+
});
|
|
36
|
+
child.on("message", (message) => {
|
|
37
|
+
// SAFETY: the runner posts only RunnerMessage values over IPC; the assertions below check each field.
|
|
38
|
+
messages.push(message as RunnerMessage);
|
|
39
|
+
});
|
|
40
|
+
const { promise: exitPromise, resolve, reject } = Promise.withResolvers<number | null>();
|
|
41
|
+
child.once("error", reject);
|
|
42
|
+
child.once("exit", (exitCode) => resolve(exitCode));
|
|
43
|
+
const code = await exitPromise;
|
|
44
|
+
return { code, messages, stderr };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test("PROJECTION_REBUILD_RUNNER_FLAG is the documented argv marker", () => {
|
|
48
|
+
assert.equal(PROJECTION_REBUILD_RUNNER_FLAG, "--remnic-projection-rebuild-runner");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("runner rejects missing argv with an IPC failure message", async () => {
|
|
52
|
+
const result = await runRunner([]);
|
|
53
|
+
assert.equal(result.code, 1);
|
|
54
|
+
assert.equal(result.messages.length, 1);
|
|
55
|
+
assert.equal(result.messages[0]!.ok, false);
|
|
56
|
+
if (result.messages[0]!.ok === false) {
|
|
57
|
+
assert.match(result.messages[0]!.error, /requires memoryDir, defaultNamespace, and now/);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("runner rejects a non-timestamp now value", async () => {
|
|
62
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-proj-runner-bad-now-"));
|
|
63
|
+
try {
|
|
64
|
+
const result = await runRunner([root, "default", "not-a-date"]);
|
|
65
|
+
assert.equal(result.code, 1);
|
|
66
|
+
assert.equal(result.messages[0]!.ok, false);
|
|
67
|
+
if (result.messages[0]!.ok === false) {
|
|
68
|
+
assert.match(result.messages[0]!.error, /not a timestamp/);
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
await rm(root, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("runner rebuilds an empty memory dir and reports zero rows", async () => {
|
|
76
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-proj-runner-ok-"));
|
|
77
|
+
try {
|
|
78
|
+
const result = await runRunner([root, "default", "2026-06-01T00:00:00.000Z"]);
|
|
79
|
+
assert.equal(result.code, 0, result.stderr || JSON.stringify(result.messages));
|
|
80
|
+
assert.equal(result.messages.length, 1);
|
|
81
|
+
assert.deepEqual(result.messages[0], {
|
|
82
|
+
ok: true,
|
|
83
|
+
currentRows: 0,
|
|
84
|
+
timelineRows: 0,
|
|
85
|
+
entityMentionRows: 0,
|
|
86
|
+
});
|
|
87
|
+
} finally {
|
|
88
|
+
await rm(root, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
});
|