@remnic/core 9.3.726 → 9.3.728
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-boundary.d.ts +5 -5
- package/dist/access-boundary.js +22 -22
- package/dist/access-cli.js +45 -45
- package/dist/access-http.d.ts +20 -5
- package/dist/access-http.js +27 -27
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +26 -26
- package/dist/access-operations-batch.js +23 -23
- package/dist/access-operations.d.ts +6 -6
- package/dist/access-operations.js +25 -25
- package/dist/access-schema.d.ts +4 -4
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-B2PlPtxh.d.ts → access-service-Bgpjxo4f.d.ts} +36 -4
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +21 -21
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +3 -3
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +7 -7
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +44 -2
- package/dist/capabilities.js +5 -3
- package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/{catalog-CzUb-GyD.d.ts → catalog-bMczlO2G.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +8 -8
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-BIA7ELIB.js → chunk-36SEDFAU.js} +3 -3
- package/dist/{chunk-UQSVUEN2.js → chunk-37436GML.js} +2 -2
- package/dist/{chunk-DVRHLN2O.js → chunk-3RCBIOOJ.js} +18 -2
- package/dist/{chunk-DVRHLN2O.js.map → chunk-3RCBIOOJ.js.map} +1 -1
- package/dist/{chunk-RRHHQYJS.js → chunk-3TSTGI6F.js} +2 -2
- package/dist/{chunk-5OOA7SGX.js → chunk-4ESAR77N.js} +2 -2
- package/dist/{chunk-FO3HO52S.js → chunk-4H4F3BFL.js} +2 -2
- package/dist/{chunk-BEUCACTV.js → chunk-4NOI577P.js} +2 -2
- package/dist/{chunk-BBIE7W52.js → chunk-4NR2PDZ5.js} +2 -2
- package/dist/{chunk-WGTWGDR5.js → chunk-76NHR6ZL.js} +5 -5
- package/dist/{chunk-PKMQK2WY.js → chunk-773DPI6T.js} +4 -4
- package/dist/{chunk-MQVKHFIU.js → chunk-7RLCCNHU.js} +8 -8
- package/dist/{chunk-XGTBNVIU.js → chunk-AAD2ZS6P.js} +11 -1
- package/dist/chunk-AAD2ZS6P.js.map +1 -0
- package/dist/{chunk-EZNDNMRH.js → chunk-AQLOP5F4.js} +2 -2
- package/dist/{chunk-UNNDFYUX.js → chunk-ARV3AUOM.js} +5 -5
- package/dist/{chunk-NPL7VDM6.js → chunk-ASKXIAHD.js} +2 -2
- package/dist/{chunk-TQQH2FTX.js → chunk-BQCHEV7O.js} +106 -15
- package/dist/{chunk-TQQH2FTX.js.map → chunk-BQCHEV7O.js.map} +1 -1
- package/dist/{chunk-ZA2S2VLL.js → chunk-C2PG7IEH.js} +5 -2
- package/dist/chunk-C2PG7IEH.js.map +1 -0
- package/dist/{chunk-K4ZNARDO.js → chunk-CBWI22JN.js} +2 -2
- package/dist/{chunk-ZOHMPFOB.js → chunk-CKFLLAIS.js} +3 -3
- package/dist/{chunk-JPUT5NGH.js → chunk-DMQWV6SQ.js} +2 -2
- package/dist/{chunk-F3XZN24Z.js → chunk-DRUDC2UX.js} +2 -2
- package/dist/{chunk-KJR66XOM.js → chunk-EY6MDMHK.js} +2 -2
- package/dist/{chunk-3TUT4PWB.js → chunk-FVZ2ISIX.js} +2 -2
- package/dist/{chunk-C4X4QGIO.js → chunk-G6NKALRN.js} +5 -2
- package/dist/chunk-G6NKALRN.js.map +1 -0
- package/dist/{chunk-6SQQOJ4U.js → chunk-GKRLPHPU.js} +2 -2
- package/dist/{chunk-PLG6JHQS.js → chunk-H5BWGCRZ.js} +43 -42
- package/dist/chunk-H5BWGCRZ.js.map +1 -0
- package/dist/{chunk-2NBP25BG.js → chunk-HGNOCPZX.js} +171 -79
- package/dist/chunk-HGNOCPZX.js.map +1 -0
- package/dist/{chunk-5NWYQUQC.js → chunk-HOHVIVDR.js} +2 -2
- package/dist/{chunk-DCKNMLEQ.js → chunk-I5REUWLH.js} +11 -5
- package/dist/chunk-I5REUWLH.js.map +1 -0
- package/dist/{chunk-6WAJQKBS.js → chunk-JYLFZWUX.js} +2 -2
- package/dist/{chunk-5W7CTZ2E.js → chunk-KQAFEZQX.js} +4 -4
- package/dist/{chunk-UKTDXTWX.js → chunk-M4AYVEWS.js} +7 -7
- package/dist/{chunk-YTZ3VGC7.js → chunk-M4V2JVSZ.js} +2 -2
- package/dist/{chunk-TA3Y6GEX.js → chunk-MLKWJOAJ.js} +2 -2
- package/dist/{chunk-DQXPJ6HD.js → chunk-O6YH5F47.js} +4 -4
- package/dist/{chunk-7AG54AF3.js → chunk-PUR2CKX2.js} +1 -1
- package/dist/chunk-PUR2CKX2.js.map +1 -0
- package/dist/{chunk-OAHOL3ZC.js → chunk-QNEUZIAH.js} +152 -62
- package/dist/chunk-QNEUZIAH.js.map +1 -0
- package/dist/{chunk-Q6D75LVH.js → chunk-UM5AWJNQ.js} +79 -17
- package/dist/chunk-UM5AWJNQ.js.map +1 -0
- package/dist/{chunk-53EEQYE5.js → chunk-UTHQPSRC.js} +8 -8
- package/dist/{chunk-BB57OZWC.js → chunk-UVPL565C.js} +2 -2
- package/dist/{chunk-6AFVIOVZ.js → chunk-V4GNSDFH.js} +2 -2
- package/dist/{chunk-EHISUJFN.js → chunk-WAS3NV46.js} +6 -3
- package/dist/chunk-WAS3NV46.js.map +1 -0
- package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-V7CMKMHM.js → chunk-Y4YD5TYW.js} +3 -3
- package/dist/{chunk-7NS7AWMZ.js → chunk-YGIQORJ3.js} +3 -3
- package/dist/{chunk-MDBZTH7N.js → chunk-YKBHOBJK.js} +44 -10
- package/dist/chunk-YKBHOBJK.js.map +1 -0
- package/dist/{chunk-3MS3QVX6.js → chunk-YOLZS7NY.js} +2 -2
- package/dist/{chunk-6OIDDKAA.js → chunk-YTU7WKYN.js} +35 -9
- package/dist/chunk-YTU7WKYN.js.map +1 -0
- package/dist/{cli-IOlDHSBT.d.ts → cli-BLME420h.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +49 -49
- package/dist/compat/codex-cli-fallback.d.ts +1 -0
- package/dist/compat/codex-cli-fallback.js +22 -0
- package/dist/compat/codex-cli-fallback.js.map +1 -0
- package/dist/compat/codex-thread-key.d.ts +1 -0
- package/dist/compat/codex-thread-key.js +18 -0
- package/dist/compat/codex-thread-key.js.map +1 -0
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +7 -7
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +3 -3
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +7 -7
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +7 -7
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/embedding-fallback.js +2 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +7 -7
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-faithfulness.js +2 -2
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +3 -2
- package/dist/faithfulness-config.d.ts +1 -0
- package/dist/faithfulness-config.js +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-PG5HBC3K.js → first-start-migration-22LPFHD5.js} +6 -2
- package/dist/first-start-migration-22LPFHD5.js.map +1 -0
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +64 -64
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +7 -7
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +9 -4
- package/dist/local-model-endpoint.js +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +7 -7
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
- package/dist/maintenance/rebuild-memory-projection.js +8 -8
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +14 -13
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +6 -5
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +7 -7
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +20 -20
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +9 -9
- package/dist/{orchestrator-C_OMSIMZ.d.ts → orchestrator-DqPAz8ot.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +33 -33
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/provenance.js +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +4 -4
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/embed-helper.js +2 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +5 -4
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +9 -8
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-B8LiYtvx.d.ts → semantic-consolidation-Cl15ksZy.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +8 -8
- package/dist/semantic-rule-promotion.js +7 -7
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +7 -7
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +6 -6
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-DmTaqob2.d.ts → types-ChQb9D6s.d.ts} +8 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +7 -7
- package/package.json +26 -2
- package/src/access-http.ts +68 -1
- package/src/access-service.ts +2 -1
- package/src/admin/admin-surfaces.test.ts +289 -0
- package/src/admin/admin-surfaces.ts +138 -7
- package/src/bitemporal-dedup-backfill.test.ts +294 -0
- package/src/capabilities.test.ts +155 -0
- package/src/capabilities.ts +91 -0
- package/src/chat/chat-engine.test.ts +104 -1
- package/src/chat/chat-http.test.ts +119 -1
- package/src/chat/chat-http.ts +252 -97
- package/src/chat/chat-session.ts +166 -7
- package/src/cli.ts +3 -2
- package/src/compat/codex-cli-fallback.ts +23 -0
- package/src/compat/codex-subpath-aliases.test.ts +93 -0
- package/src/compat/codex-thread-key.ts +23 -0
- package/src/embedding-fallback.ts +2 -1
- package/src/extraction-faithfulness.test.ts +240 -0
- package/src/extraction-faithfulness.ts +58 -6
- package/src/extraction.ts +7 -3
- package/src/faithfulness-config.ts +16 -1
- package/src/local-model-endpoint.ts +24 -3
- package/src/maintenance/first-start-migration.ts +3 -1
- package/src/orchestrator.ts +238 -34
- package/src/provenance-extraction.test.ts +266 -0
- package/src/provenance.ts +66 -16
- package/src/search/embed-helper.ts +3 -2
- package/src/types.ts +8 -0
- package/dist/chunk-2NBP25BG.js.map +0 -1
- package/dist/chunk-6OIDDKAA.js.map +0 -1
- package/dist/chunk-7AG54AF3.js.map +0 -1
- package/dist/chunk-C4X4QGIO.js.map +0 -1
- package/dist/chunk-DCKNMLEQ.js.map +0 -1
- package/dist/chunk-EHISUJFN.js.map +0 -1
- package/dist/chunk-MDBZTH7N.js.map +0 -1
- package/dist/chunk-OAHOL3ZC.js.map +0 -1
- package/dist/chunk-PLG6JHQS.js.map +0 -1
- package/dist/chunk-Q6D75LVH.js.map +0 -1
- package/dist/chunk-XGTBNVIU.js.map +0 -1
- package/dist/chunk-ZA2S2VLL.js.map +0 -1
- package/dist/first-start-migration-PG5HBC3K.js.map +0 -1
- /package/dist/{capsule-crypto-GWVG7LGC.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
- /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-BIA7ELIB.js.map → chunk-36SEDFAU.js.map} +0 -0
- /package/dist/{chunk-UQSVUEN2.js.map → chunk-37436GML.js.map} +0 -0
- /package/dist/{chunk-RRHHQYJS.js.map → chunk-3TSTGI6F.js.map} +0 -0
- /package/dist/{chunk-5OOA7SGX.js.map → chunk-4ESAR77N.js.map} +0 -0
- /package/dist/{chunk-FO3HO52S.js.map → chunk-4H4F3BFL.js.map} +0 -0
- /package/dist/{chunk-BEUCACTV.js.map → chunk-4NOI577P.js.map} +0 -0
- /package/dist/{chunk-BBIE7W52.js.map → chunk-4NR2PDZ5.js.map} +0 -0
- /package/dist/{chunk-WGTWGDR5.js.map → chunk-76NHR6ZL.js.map} +0 -0
- /package/dist/{chunk-PKMQK2WY.js.map → chunk-773DPI6T.js.map} +0 -0
- /package/dist/{chunk-MQVKHFIU.js.map → chunk-7RLCCNHU.js.map} +0 -0
- /package/dist/{chunk-EZNDNMRH.js.map → chunk-AQLOP5F4.js.map} +0 -0
- /package/dist/{chunk-UNNDFYUX.js.map → chunk-ARV3AUOM.js.map} +0 -0
- /package/dist/{chunk-NPL7VDM6.js.map → chunk-ASKXIAHD.js.map} +0 -0
- /package/dist/{chunk-K4ZNARDO.js.map → chunk-CBWI22JN.js.map} +0 -0
- /package/dist/{chunk-ZOHMPFOB.js.map → chunk-CKFLLAIS.js.map} +0 -0
- /package/dist/{chunk-JPUT5NGH.js.map → chunk-DMQWV6SQ.js.map} +0 -0
- /package/dist/{chunk-F3XZN24Z.js.map → chunk-DRUDC2UX.js.map} +0 -0
- /package/dist/{chunk-KJR66XOM.js.map → chunk-EY6MDMHK.js.map} +0 -0
- /package/dist/{chunk-3TUT4PWB.js.map → chunk-FVZ2ISIX.js.map} +0 -0
- /package/dist/{chunk-6SQQOJ4U.js.map → chunk-GKRLPHPU.js.map} +0 -0
- /package/dist/{chunk-5NWYQUQC.js.map → chunk-HOHVIVDR.js.map} +0 -0
- /package/dist/{chunk-6WAJQKBS.js.map → chunk-JYLFZWUX.js.map} +0 -0
- /package/dist/{chunk-5W7CTZ2E.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-UKTDXTWX.js.map → chunk-M4AYVEWS.js.map} +0 -0
- /package/dist/{chunk-YTZ3VGC7.js.map → chunk-M4V2JVSZ.js.map} +0 -0
- /package/dist/{chunk-TA3Y6GEX.js.map → chunk-MLKWJOAJ.js.map} +0 -0
- /package/dist/{chunk-DQXPJ6HD.js.map → chunk-O6YH5F47.js.map} +0 -0
- /package/dist/{chunk-53EEQYE5.js.map → chunk-UTHQPSRC.js.map} +0 -0
- /package/dist/{chunk-BB57OZWC.js.map → chunk-UVPL565C.js.map} +0 -0
- /package/dist/{chunk-6AFVIOVZ.js.map → chunk-V4GNSDFH.js.map} +0 -0
- /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-V7CMKMHM.js.map → chunk-Y4YD5TYW.js.map} +0 -0
- /package/dist/{chunk-7NS7AWMZ.js.map → chunk-YGIQORJ3.js.map} +0 -0
- /package/dist/{chunk-3MS3QVX6.js.map → chunk-YOLZS7NY.js.map} +0 -0
|
@@ -167,3 +167,297 @@ test("#1671: re-extraction with resolved invalidAt backfills an old promoted cop
|
|
|
167
167
|
await cleanup();
|
|
168
168
|
}
|
|
169
169
|
});
|
|
170
|
+
|
|
171
|
+
// ─── #1707 widenings (promoted-cascade, valid_at, non-fact guard) ────────
|
|
172
|
+
|
|
173
|
+
// Thread 2 — valid_at propagation. A re-extracted duplicate whose event time
|
|
174
|
+
// yields only a start bound ("since 2024", "yesterday") must get the corrected
|
|
175
|
+
// per-fact validFrom onto the existing copy so as-of recall uses the corrected
|
|
176
|
+
// start. The orchestrator helper's rule: patch valid_at when the incoming
|
|
177
|
+
// validFrom is EXTRACTED and the copy's start bound is batch-anchored
|
|
178
|
+
// (assumed/legacy); never clobber a copy that already carries an extracted
|
|
179
|
+
// per-fact anchor. This test pins that rule against the storage patch the
|
|
180
|
+
// helper issues.
|
|
181
|
+
test("#1707 thread 2: corrected extracted validFrom overwrites a batch-anchored valid_at", async () => {
|
|
182
|
+
const { storage, cleanup } = await makeStorage("bitemporal-1707-validFrom-");
|
|
183
|
+
try {
|
|
184
|
+
// Old promoted copy: batch-anchored valid_at, no per-fact event-time.
|
|
185
|
+
const content = "We have used Stripe for payments since 2024.";
|
|
186
|
+
const batchAnchor = "2026-06-01T00:00:00.000Z";
|
|
187
|
+
const id = await storage.writeMemory("fact", content, {
|
|
188
|
+
confidence: 0.9,
|
|
189
|
+
validAt: batchAnchor,
|
|
190
|
+
// No eventTimeSource → batch-anchored (legacy pre-#1670 copy).
|
|
191
|
+
});
|
|
192
|
+
const before = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
193
|
+
assert.ok(before);
|
|
194
|
+
assert.equal(before!.frontmatter.valid_at, batchAnchor);
|
|
195
|
+
assert.equal(before!.frontmatter.eventTimeSource, undefined);
|
|
196
|
+
|
|
197
|
+
// Simulate the helper's rule: incoming validFrom is extracted → patch
|
|
198
|
+
// because the copy is NOT extracted-anchored (fm.eventTimeSource !== "extracted").
|
|
199
|
+
const correctedValidFrom = "2024-01-01T00:00:00.000Z";
|
|
200
|
+
const incomingExtracted = true;
|
|
201
|
+
const copyExtracted = before!.frontmatter.eventTimeSource === "extracted";
|
|
202
|
+
const shouldPatchValidAt =
|
|
203
|
+
incomingExtracted && (copyExtracted === false || !before!.frontmatter.valid_at);
|
|
204
|
+
assert.equal(shouldPatchValidAt, true, "must correct the batch-anchored valid_at");
|
|
205
|
+
|
|
206
|
+
const ok = await storage.writeMemoryFrontmatter(before!, {
|
|
207
|
+
valid_at: correctedValidFrom,
|
|
208
|
+
eventTimeSource: "extracted",
|
|
209
|
+
observedAt: "2026-06-20T00:00:00.000Z",
|
|
210
|
+
});
|
|
211
|
+
assert.equal(ok, true);
|
|
212
|
+
|
|
213
|
+
const after = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
214
|
+
assert.ok(after);
|
|
215
|
+
assert.equal(after!.frontmatter.valid_at, correctedValidFrom);
|
|
216
|
+
assert.equal(after!.frontmatter.eventTimeSource, "extracted");
|
|
217
|
+
} finally {
|
|
218
|
+
await cleanup();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("#1707 thread 2: identical extracted validFrom is a no-op (equality short-circuit)", async () => {
|
|
223
|
+
const { storage, cleanup } = await makeStorage("bitemporal-1707-validFrom-equal-");
|
|
224
|
+
try {
|
|
225
|
+
// Copy already carries an extracted per-fact anchor.
|
|
226
|
+
const content = "The migration to MySQL completed in March 2025.";
|
|
227
|
+
const existingValidAt: string = "2025-03-01T00:00:00.000Z";
|
|
228
|
+
const id = await storage.writeMemory("fact", content, {
|
|
229
|
+
confidence: 0.9,
|
|
230
|
+
validAt: existingValidAt,
|
|
231
|
+
observedAt: "2025-06-01T00:00:00.000Z",
|
|
232
|
+
eventTimeSource: "extracted",
|
|
233
|
+
});
|
|
234
|
+
const before = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
235
|
+
assert.ok(before);
|
|
236
|
+
|
|
237
|
+
// Re-extraction resolves the SAME start bound (deterministic per #1670).
|
|
238
|
+
// The helper's equality short-circuit (fm.valid_at !== bounds.validFrom)
|
|
239
|
+
// skips the redundant write — the only no-clobber that holds without a
|
|
240
|
+
// fragile provenance heuristic (review codex PRRT_Ov7LKC).
|
|
241
|
+
const helperWouldPatch = existingValidAt !== existingValidAt; // equal → false
|
|
242
|
+
assert.equal(helperWouldPatch, false, "identical validFrom must not rewrite");
|
|
243
|
+
|
|
244
|
+
// No patch issued → valid_at unchanged.
|
|
245
|
+
const after = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
246
|
+
assert.ok(after);
|
|
247
|
+
assert.equal(after!.frontmatter.valid_at, existingValidAt);
|
|
248
|
+
} finally {
|
|
249
|
+
await cleanup();
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("#1707 thread 2: differing extracted validFrom overwrites (authoritative re-evaluation)", async () => {
|
|
254
|
+
const { storage, cleanup } = await makeStorage("bitemporal-1707-validFrom-differ-");
|
|
255
|
+
try {
|
|
256
|
+
// Copy carries an older (batch-anchored) valid_at.
|
|
257
|
+
const content = "We have used Stripe for payments since 2024.";
|
|
258
|
+
const staleValidAt: string = "2026-06-01T00:00:00.000Z"; // batch anchor
|
|
259
|
+
const id = await storage.writeMemory("fact", content, {
|
|
260
|
+
confidence: 0.9,
|
|
261
|
+
validAt: staleValidAt,
|
|
262
|
+
});
|
|
263
|
+
const before = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
264
|
+
assert.ok(before);
|
|
265
|
+
|
|
266
|
+
// Re-extraction resolves a DIFFERING extracted start bound. Exact-content
|
|
267
|
+
// dedup + #1670 determinism means stable content re-resolves to the same
|
|
268
|
+
// value (equality no-op above); a divergence means the copy's start is
|
|
269
|
+
// stale (batch/assumed) and the extracted validFrom is authoritative.
|
|
270
|
+
const correctedValidFrom: string = "2024-01-01T00:00:00.000Z";
|
|
271
|
+
const helperWouldPatch = staleValidAt !== correctedValidFrom; // differ → true
|
|
272
|
+
assert.equal(helperWouldPatch, true, "differing extracted validFrom must overwrite");
|
|
273
|
+
|
|
274
|
+
const ok = await storage.writeMemoryFrontmatter(before!, {
|
|
275
|
+
valid_at: correctedValidFrom,
|
|
276
|
+
eventTimeSource: "extracted",
|
|
277
|
+
});
|
|
278
|
+
assert.equal(ok, true);
|
|
279
|
+
|
|
280
|
+
const after = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
281
|
+
assert.ok(after);
|
|
282
|
+
assert.equal(after!.frontmatter.valid_at, correctedValidFrom);
|
|
283
|
+
assert.equal(after!.frontmatter.eventTimeSource, "extracted");
|
|
284
|
+
} finally {
|
|
285
|
+
await cleanup();
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Review (codex PRRT_Ov68oA + PRRT_Ov7LKC): an END-only extraction ("through
|
|
290
|
+
// 2026") sets eventTimeSource = "extracted" (end resolved) but leaves valid_at
|
|
291
|
+
// batch/assumed-anchored. A later START-bound extraction must still correct
|
|
292
|
+
// valid_at. The helper now uses an equality short-circuit (no fragile
|
|
293
|
+
// provenance heuristic): the differing extracted validFrom overwrites the
|
|
294
|
+
// stale start regardless of the copy's eventTimeSource.
|
|
295
|
+
test("#1707 thread 2: end-only extracted provenance does NOT block a later start-bound correction", async () => {
|
|
296
|
+
const { storage, cleanup } = await makeStorage("bitemporal-1707-endonly-");
|
|
297
|
+
try {
|
|
298
|
+
// Copy state after an end-only backfill: eventTimeSource = "extracted"
|
|
299
|
+
// (the end "through 2026" resolved), but valid_at is still the ingestion
|
|
300
|
+
// anchor (assumed start).
|
|
301
|
+
const observedAt = "2026-06-01T00:00:00.000Z";
|
|
302
|
+
const content = "The service was maintained through 2026.";
|
|
303
|
+
const id = await storage.writeMemory("fact", content, {
|
|
304
|
+
confidence: 0.9,
|
|
305
|
+
validAt: observedAt, // valid_at === observedAt (assumed/batch start)
|
|
306
|
+
observedAt,
|
|
307
|
+
eventTimeSource: "extracted", // set by the end-only extraction
|
|
308
|
+
invalidAt: "2026-12-31T00:00:00.000Z", // the extracted end bound
|
|
309
|
+
});
|
|
310
|
+
const before = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
311
|
+
assert.ok(before);
|
|
312
|
+
assert.equal(before!.frontmatter.eventTimeSource, "extracted");
|
|
313
|
+
assert.equal(before!.frontmatter.valid_at, before!.frontmatter.observedAt);
|
|
314
|
+
|
|
315
|
+
// The helper's valid_at gate (mirrors orchestrator.ts):
|
|
316
|
+
// bounds.validFrom && eventTimeSource === "extracted" && valid_at !== validFrom
|
|
317
|
+
// The stale valid_at differs from the incoming real start → overwrite.
|
|
318
|
+
|
|
319
|
+
// A later re-extraction with a real extracted start must correct valid_at.
|
|
320
|
+
const realStart = "2024-03-01T00:00:00.000Z";
|
|
321
|
+
const ok = await storage.writeMemoryFrontmatter(before!, {
|
|
322
|
+
valid_at: realStart,
|
|
323
|
+
});
|
|
324
|
+
assert.equal(ok, true);
|
|
325
|
+
|
|
326
|
+
const after = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
327
|
+
assert.ok(after);
|
|
328
|
+
assert.equal(after!.frontmatter.valid_at, realStart);
|
|
329
|
+
} finally {
|
|
330
|
+
await cleanup();
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Gate guard (review cursor PRRT_OvHk / codex PRRT_OvHxVH): resolveFactEventTime
|
|
335
|
+
// sets a validFrom for EVERY fact (assumed = ingestion anchor). The helper's
|
|
336
|
+
// I/O gate must NOT scan on an assumed validFrom — only an EXTRACTED start bound
|
|
337
|
+
// changes recall. This pins the gate decision so ordinary duplicate facts
|
|
338
|
+
// without an extracted event time do not pay a readAllMemories scan on every
|
|
339
|
+
// dedup hit.
|
|
340
|
+
test("#1707 thread 2: assumed (batch-anchored) validFrom does NOT trigger a backfill scan", () => {
|
|
341
|
+
// Mirror the helper's I/O gate (orchestrator.ts backfillTemporalBoundsOnDedupHit):
|
|
342
|
+
// const hasExtractedStart = bounds.validFrom !== undefined && bounds.eventTimeSource === "extracted";
|
|
343
|
+
// if (!bounds.invalidAt && !hasExtractedStart) return;
|
|
344
|
+
const gateWouldScan = (bounds: {
|
|
345
|
+
invalidAt?: string;
|
|
346
|
+
validFrom?: string;
|
|
347
|
+
eventTimeSource?: "extracted" | "assumed";
|
|
348
|
+
}) =>
|
|
349
|
+
Boolean(bounds.invalidAt) ||
|
|
350
|
+
(bounds.validFrom !== undefined && bounds.eventTimeSource === "extracted");
|
|
351
|
+
|
|
352
|
+
// Assumed validFrom alone (the common no-event-time case) → no scan.
|
|
353
|
+
assert.equal(
|
|
354
|
+
gateWouldScan({ validFrom: "2026-06-01T00:00:00.000Z", eventTimeSource: "assumed" }),
|
|
355
|
+
false,
|
|
356
|
+
"assumed validFrom must not trigger a scan",
|
|
357
|
+
);
|
|
358
|
+
// Extracted validFrom → scan (corrected start bound is recall-relevant).
|
|
359
|
+
assert.equal(
|
|
360
|
+
gateWouldScan({ validFrom: "2024-01-01T00:00:00.000Z", eventTimeSource: "extracted" }),
|
|
361
|
+
true,
|
|
362
|
+
);
|
|
363
|
+
// End bound alone → scan (expires the fact).
|
|
364
|
+
assert.equal(gateWouldScan({ invalidAt: "2025-06-01T00:00:00.000Z" }), true);
|
|
365
|
+
// No bounds at all → no scan.
|
|
366
|
+
assert.equal(gateWouldScan({}), false);
|
|
367
|
+
});
|
|
368
|
+
// Thread 1 — promoted-cascade backfill. When the source-namespace dedup
|
|
369
|
+
// short-circuit fires, the helper must also patch promotion-target copies
|
|
370
|
+
// (profile + shared namespaces) so cross-namespace recall does not surface an
|
|
371
|
+
// expired fact. This proves the mechanism the orchestrator's promotion-cascade
|
|
372
|
+
// closure relies on: backfilling two separate storages patches each
|
|
373
|
+
// independently.
|
|
374
|
+
test("#1707 thread 1: backfill patches promoted copies across multiple storages (cascade)", async () => {
|
|
375
|
+
const { storage: sourceStorage, cleanup: cleanupSource } = await makeStorage(
|
|
376
|
+
"bitemporal-1707-cascade-source-",
|
|
377
|
+
);
|
|
378
|
+
const { storage: promotedStorage, cleanup: cleanupPromoted } = await makeStorage(
|
|
379
|
+
"bitemporal-1707-cascade-promoted-",
|
|
380
|
+
);
|
|
381
|
+
try {
|
|
382
|
+
const content = "The API rate limit is 100 req/min until June 2025.";
|
|
383
|
+
// Both namespaces hold the same fact, written WITHOUT bounds (stale copies).
|
|
384
|
+
const sourceId = await sourceStorage.writeMemory("fact", content, { confidence: 0.9 });
|
|
385
|
+
const promotedId = await promotedStorage.writeMemory("fact", content, { confidence: 0.9 });
|
|
386
|
+
|
|
387
|
+
// Simulate re-extraction that now carries a resolved invalidAt. The
|
|
388
|
+
// promotion-cascade closure calls the same backfill against each target.
|
|
389
|
+
const bounds = {
|
|
390
|
+
invalid_at: "2025-06-01T00:00:00.000Z",
|
|
391
|
+
observedAt: "2025-06-20T00:00:00.000Z",
|
|
392
|
+
eventTimeSource: "extracted" as const,
|
|
393
|
+
};
|
|
394
|
+
for (const storage of [sourceStorage, promotedStorage]) {
|
|
395
|
+
const all = await storage.readAllMemories();
|
|
396
|
+
const existing = all.find(
|
|
397
|
+
(m) =>
|
|
398
|
+
m.frontmatter.category === "fact" &&
|
|
399
|
+
(m.frontmatter.status ?? "active") === "active" &&
|
|
400
|
+
!m.frontmatter.invalid_at &&
|
|
401
|
+
ContentHashIndex.normalizeContent(m.content ?? "") ===
|
|
402
|
+
ContentHashIndex.normalizeContent(content),
|
|
403
|
+
);
|
|
404
|
+
assert.ok(existing, "must find the existing copy in each storage");
|
|
405
|
+
const ok = await storage.writeMemoryFrontmatter(existing!, bounds);
|
|
406
|
+
assert.equal(ok, true);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Both copies now carry the resolved end bound — cross-namespace recall
|
|
410
|
+
// honours the expiry instead of surfacing the stale promoted copy.
|
|
411
|
+
const sourceAfter = (await sourceStorage.readAllMemories()).find(
|
|
412
|
+
(m) => m.frontmatter.id === sourceId,
|
|
413
|
+
);
|
|
414
|
+
const promotedAfter = (await promotedStorage.readAllMemories()).find(
|
|
415
|
+
(m) => m.frontmatter.id === promotedId,
|
|
416
|
+
);
|
|
417
|
+
assert.ok(sourceAfter && promotedAfter);
|
|
418
|
+
assert.equal(sourceAfter!.frontmatter.invalid_at, "2025-06-01T00:00:00.000Z");
|
|
419
|
+
assert.equal(promotedAfter!.frontmatter.invalid_at, "2025-06-01T00:00:00.000Z");
|
|
420
|
+
} finally {
|
|
421
|
+
await cleanupSource();
|
|
422
|
+
await cleanupPromoted();
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Thread 3 — non-fact guard. The source dedup backfill call site is gated on
|
|
427
|
+
// writeCategory === "fact", so a non-fact duplicate (preference/decision/
|
|
428
|
+
// procedure) never reaches the fact-only backfill scan. This proves the
|
|
429
|
+
// helper's category guard that makes that call-site gate correct: a non-fact
|
|
430
|
+
// copy is never matched/patched even if its content hash collides.
|
|
431
|
+
test("#1707 thread 3: backfill never patches a non-fact copy (category guard)", async () => {
|
|
432
|
+
const { storage, cleanup } = await makeStorage("bitemporal-1707-nonfact-");
|
|
433
|
+
try {
|
|
434
|
+
const content = "The user prefers dark mode.";
|
|
435
|
+
// A preference (non-fact) whose content would collide with an incoming
|
|
436
|
+
// duplicate candidate.
|
|
437
|
+
const id = await storage.writeMemory("preference", content, { confidence: 0.9 });
|
|
438
|
+
const before = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
439
|
+
assert.ok(before);
|
|
440
|
+
assert.equal(before!.frontmatter.category, "preference");
|
|
441
|
+
assert.equal(before!.frontmatter.invalid_at, undefined);
|
|
442
|
+
|
|
443
|
+
// The helper's lookup filters category === "fact" — a preference copy is
|
|
444
|
+
// never eligible, so no patch is issued even when bounds are present.
|
|
445
|
+
const all = await storage.readAllMemories();
|
|
446
|
+
const existing = all.find(
|
|
447
|
+
(m) =>
|
|
448
|
+
m.frontmatter.category === "fact" && // ← the guard under test
|
|
449
|
+
(m.frontmatter.status ?? "active") === "active" &&
|
|
450
|
+
ContentHashIndex.normalizeContent(m.content ?? "") ===
|
|
451
|
+
ContentHashIndex.normalizeContent(content),
|
|
452
|
+
);
|
|
453
|
+
assert.equal(existing, undefined, "non-fact copy must not be matched by the fact-only lookup");
|
|
454
|
+
|
|
455
|
+
// No patch → the preference copy is untouched.
|
|
456
|
+
const after = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
457
|
+
assert.ok(after);
|
|
458
|
+
assert.equal(after!.frontmatter.invalid_at, undefined);
|
|
459
|
+
assert.equal(after!.frontmatter.category, "preference");
|
|
460
|
+
} finally {
|
|
461
|
+
await cleanup();
|
|
462
|
+
}
|
|
463
|
+
});
|
package/src/capabilities.test.ts
CHANGED
|
@@ -153,6 +153,12 @@ import {
|
|
|
153
153
|
type GraphConstructionCapabilitySet,
|
|
154
154
|
} from "./capabilities.js";
|
|
155
155
|
|
|
156
|
+
import {
|
|
157
|
+
resolveMemoryLifecycleCapabilities,
|
|
158
|
+
type MemoryLifecycleCapabilitySet,
|
|
159
|
+
} from "./capabilities.js";
|
|
160
|
+
|
|
161
|
+
|
|
156
162
|
const GRAPH_FIELD_TO_FLAG: Record<keyof GraphConstructionCapabilitySet, string> = {
|
|
157
163
|
entityGraph: "entityGraphEnabled",
|
|
158
164
|
timeGraph: "timeGraphEnabled",
|
|
@@ -306,3 +312,152 @@ test("resolveGraphConstructionCapabilities matches pre-migration config reads (p
|
|
|
306
312
|
);
|
|
307
313
|
}
|
|
308
314
|
});
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// MemoryLifecycleCapabilitySet — gate-parity tests (issue #1523 batch 3).
|
|
319
|
+
//
|
|
320
|
+
// Same invariant as the sets above: every caps field must project from its
|
|
321
|
+
// `<field>Enabled` config flag, so a future edit that maps a field to the wrong
|
|
322
|
+
// flag fails loudly here. All ten flags are required booleans on PluginConfig
|
|
323
|
+
// (defaults resolved at the parse boundary), so the projection is a pure
|
|
324
|
+
// passthrough — no optional-default variants needed.
|
|
325
|
+
//
|
|
326
|
+
// Parity contract: a caps-resolved run and a config-derived run MUST produce
|
|
327
|
+
// identical boolean values for every gate — on AND off.
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
|
|
330
|
+
const LIFECYCLE_FIELD_TO_FLAG: Record<keyof MemoryLifecycleCapabilitySet, string> = {
|
|
331
|
+
temporalSupersession: "temporalSupersessionEnabled",
|
|
332
|
+
temporalMemoryTree: "temporalMemoryTreeEnabled",
|
|
333
|
+
lifecyclePolicy: "lifecyclePolicyEnabled",
|
|
334
|
+
lifecycleFilterStale: "lifecycleFilterStaleEnabled",
|
|
335
|
+
lifecycleMetrics: "lifecycleMetricsEnabled",
|
|
336
|
+
extractionScopeClassification: "extractionScopeClassificationEnabled",
|
|
337
|
+
extractionJudge: "extractionJudgeEnabled",
|
|
338
|
+
extractionDedupe: "extractionDedupeEnabled",
|
|
339
|
+
extractionTelemetryPrefilter: "extractionTelemetryPrefilterEnabled",
|
|
340
|
+
extractionJudgeTelemetry: "extractionJudgeTelemetryEnabled",
|
|
341
|
+
embeddingFallback: "embeddingFallbackEnabled",
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const LIFECYCLE_FIELDS = Object.keys(LIFECYCLE_FIELD_TO_FLAG) as Array<
|
|
345
|
+
keyof MemoryLifecycleCapabilitySet
|
|
346
|
+
>;
|
|
347
|
+
|
|
348
|
+
test("resolveMemoryLifecycleCapabilities projects every field from its flag (true variant)", () => {
|
|
349
|
+
const overrides: Record<string, boolean> = {};
|
|
350
|
+
for (const flag of Object.values(LIFECYCLE_FIELD_TO_FLAG)) overrides[flag] = true;
|
|
351
|
+
const config = parseConfig(overrides);
|
|
352
|
+
const caps = resolveMemoryLifecycleCapabilities(config);
|
|
353
|
+
|
|
354
|
+
for (const field of LIFECYCLE_FIELDS) {
|
|
355
|
+
const flag = LIFECYCLE_FIELD_TO_FLAG[field];
|
|
356
|
+
assert.equal(
|
|
357
|
+
caps[field],
|
|
358
|
+
(config as unknown as Record<string, boolean>)[flag],
|
|
359
|
+
`caps.${field} must equal config.${flag} (true variant)`,
|
|
360
|
+
);
|
|
361
|
+
assert.equal(caps[field], true, `caps.${field} should be true here`);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("resolveMemoryLifecycleCapabilities projects every field from its flag (false variant)", () => {
|
|
366
|
+
const overrides: Record<string, boolean> = {};
|
|
367
|
+
for (const flag of Object.values(LIFECYCLE_FIELD_TO_FLAG)) overrides[flag] = false;
|
|
368
|
+
const config = parseConfig(overrides);
|
|
369
|
+
const caps = resolveMemoryLifecycleCapabilities(config);
|
|
370
|
+
|
|
371
|
+
for (const field of LIFECYCLE_FIELDS) {
|
|
372
|
+
const flag = LIFECYCLE_FIELD_TO_FLAG[field];
|
|
373
|
+
assert.equal(
|
|
374
|
+
caps[field],
|
|
375
|
+
(config as unknown as Record<string, boolean>)[flag],
|
|
376
|
+
`caps.${field} must equal config.${flag} (false variant)`,
|
|
377
|
+
);
|
|
378
|
+
assert.equal(caps[field], false, `caps.${field} should be false here`);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("resolveMemoryLifecycleCapabilities returns a frozen object", () => {
|
|
383
|
+
const config = parseConfig({});
|
|
384
|
+
const caps = resolveMemoryLifecycleCapabilities(config);
|
|
385
|
+
assert.equal(Object.isFrozen(caps), true);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("resolveMemoryLifecycleCapabilities matches pre-migration config reads (parity contract)", () => {
|
|
389
|
+
// For representative on/off combinations, the caps-resolved values MUST be
|
|
390
|
+
// identical to what the old scattered `this.config.<flag>Enabled` reads
|
|
391
|
+
// produced. All ten flags used bare reads (no `!== false`/`=== true` at the
|
|
392
|
+
// call site that would change the value — the two sites that wrote
|
|
393
|
+
// `=== true` were redundant coercion on an already-boolean config field),
|
|
394
|
+
// so parity is a direct equality against the resolved config boolean.
|
|
395
|
+
const cases: Record<string, Record<string, boolean>> = {
|
|
396
|
+
"all-off": {
|
|
397
|
+
temporalSupersessionEnabled: false,
|
|
398
|
+
temporalMemoryTreeEnabled: false,
|
|
399
|
+
lifecyclePolicyEnabled: false,
|
|
400
|
+
lifecycleFilterStaleEnabled: false,
|
|
401
|
+
lifecycleMetricsEnabled: false,
|
|
402
|
+
extractionScopeClassificationEnabled: false,
|
|
403
|
+
extractionJudgeEnabled: false,
|
|
404
|
+
extractionDedupeEnabled: false,
|
|
405
|
+
extractionTelemetryPrefilterEnabled: false,
|
|
406
|
+
extractionJudgeTelemetryEnabled: false,
|
|
407
|
+
embeddingFallbackEnabled: false,
|
|
408
|
+
},
|
|
409
|
+
"all-on": {
|
|
410
|
+
temporalSupersessionEnabled: true,
|
|
411
|
+
temporalMemoryTreeEnabled: true,
|
|
412
|
+
lifecyclePolicyEnabled: true,
|
|
413
|
+
lifecycleFilterStaleEnabled: true,
|
|
414
|
+
lifecycleMetricsEnabled: true,
|
|
415
|
+
extractionScopeClassificationEnabled: true,
|
|
416
|
+
extractionJudgeEnabled: true,
|
|
417
|
+
extractionDedupeEnabled: true,
|
|
418
|
+
extractionTelemetryPrefilterEnabled: true,
|
|
419
|
+
extractionJudgeTelemetryEnabled: true,
|
|
420
|
+
embeddingFallbackEnabled: true,
|
|
421
|
+
},
|
|
422
|
+
"temporal-on-rest-off": {
|
|
423
|
+
temporalSupersessionEnabled: true,
|
|
424
|
+
temporalMemoryTreeEnabled: true,
|
|
425
|
+
lifecyclePolicyEnabled: false,
|
|
426
|
+
lifecycleFilterStaleEnabled: false,
|
|
427
|
+
lifecycleMetricsEnabled: false,
|
|
428
|
+
extractionScopeClassificationEnabled: false,
|
|
429
|
+
extractionJudgeEnabled: false,
|
|
430
|
+
extractionDedupeEnabled: false,
|
|
431
|
+
extractionTelemetryPrefilterEnabled: false,
|
|
432
|
+
extractionJudgeTelemetryEnabled: false,
|
|
433
|
+
embeddingFallbackEnabled: false,
|
|
434
|
+
},
|
|
435
|
+
"extraction-on-rest-off": {
|
|
436
|
+
temporalSupersessionEnabled: false,
|
|
437
|
+
temporalMemoryTreeEnabled: false,
|
|
438
|
+
lifecyclePolicyEnabled: false,
|
|
439
|
+
lifecycleFilterStaleEnabled: false,
|
|
440
|
+
lifecycleMetricsEnabled: false,
|
|
441
|
+
extractionScopeClassificationEnabled: true,
|
|
442
|
+
extractionJudgeEnabled: true,
|
|
443
|
+
extractionDedupeEnabled: true,
|
|
444
|
+
extractionTelemetryPrefilterEnabled: true,
|
|
445
|
+
extractionJudgeTelemetryEnabled: true,
|
|
446
|
+
embeddingFallbackEnabled: true,
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
for (const [label, overrides] of Object.entries(cases)) {
|
|
451
|
+
const config = parseConfig(overrides);
|
|
452
|
+
const caps = resolveMemoryLifecycleCapabilities(config);
|
|
453
|
+
|
|
454
|
+
for (const field of LIFECYCLE_FIELDS) {
|
|
455
|
+
const flag = LIFECYCLE_FIELD_TO_FLAG[field];
|
|
456
|
+
assert.equal(
|
|
457
|
+
caps[field],
|
|
458
|
+
(config as unknown as Record<string, boolean>)[flag],
|
|
459
|
+
`[${label}] caps.${field} must equal config.${flag}`,
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
});
|
package/src/capabilities.ts
CHANGED
|
@@ -203,3 +203,94 @@ export function resolveAccessSetupCapabilities(
|
|
|
203
203
|
recallAuditAnomalyDetection: config.recallAuditAnomalyDetectionEnabled === true,
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
|
+
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Memory-lifecycle capability set (issue #1523 batch 3).
|
|
209
|
+
//
|
|
210
|
+
// The flags below gate memory WRITE/EXTRACTION (extraction judge, scope
|
|
211
|
+
// classification, dedupe, telemetry prefilter), temporal supersession of
|
|
212
|
+
// stored facts, and the lifecycle policy pass (promotion / decay / stale
|
|
213
|
+
// filtering). They are read on the persistExtraction write path, the recall
|
|
214
|
+
// candidate-filter path, and the maintenance / lifecycle-pass path — so, like
|
|
215
|
+
// the graph-construction set above, they get their own projection resolved
|
|
216
|
+
// ONCE at each operation entry that reads them (never re-derived from raw
|
|
217
|
+
// config mid-operation — the gate-divergence defect class #1523 targets).
|
|
218
|
+
//
|
|
219
|
+
// Every field projects from an already-resolved PluginConfig boolean (defaults
|
|
220
|
+
// are applied at the config-parse boundary, rule 36), so the resolver is a
|
|
221
|
+
// pure projection — no `!== false` / `=== true` re-coercion here.
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Frozen projection of memory-lifecycle feature gates (issue #1523 batch 3).
|
|
226
|
+
*
|
|
227
|
+
* Resolved once per write / recall-filter / maintenance operation and threaded
|
|
228
|
+
* down. Composition lives ONLY in {@link resolveMemoryLifecycleCapabilities}.
|
|
229
|
+
*/
|
|
230
|
+
export interface MemoryLifecycleCapabilitySet {
|
|
231
|
+
/** `temporalSupersessionEnabled` — supersede stale structured facts on write, filter on recall. */
|
|
232
|
+
readonly temporalSupersession: boolean;
|
|
233
|
+
/** `temporalMemoryTreeEnabled` — temporal-memory-tree build + recall tier. */
|
|
234
|
+
readonly temporalMemoryTree: boolean;
|
|
235
|
+
/** `lifecyclePolicyEnabled` — lifecycle promotion / decay metadata pass. */
|
|
236
|
+
readonly lifecyclePolicy: boolean;
|
|
237
|
+
/** `lifecycleFilterStaleEnabled` — filter stale-lifecycle memories from recall. */
|
|
238
|
+
readonly lifecycleFilterStale: boolean;
|
|
239
|
+
/** `lifecycleMetricsEnabled` — emit lifecycle-pass metrics. */
|
|
240
|
+
readonly lifecycleMetrics: boolean;
|
|
241
|
+
/** `extractionScopeClassificationEnabled` — LLM classifies each fact's scope. */
|
|
242
|
+
readonly extractionScopeClassification: boolean;
|
|
243
|
+
/** `extractionJudgeEnabled` — LLM-as-judge fact-worthiness gate. */
|
|
244
|
+
readonly extractionJudge: boolean;
|
|
245
|
+
/** `extractionDedupeEnabled` — dedupe against recent extractions. */
|
|
246
|
+
readonly extractionDedupe: boolean;
|
|
247
|
+
/** `extractionTelemetryPrefilterEnabled` — skip mechanical-telemetry transcripts. */
|
|
248
|
+
readonly extractionTelemetryPrefilter: boolean;
|
|
249
|
+
/** `extractionJudgeTelemetryEnabled` — collect judge verdict telemetry. */
|
|
250
|
+
readonly extractionJudgeTelemetry: boolean;
|
|
251
|
+
/** `embeddingFallbackEnabled` — semantic-dedup / archive-search embedding fallback. */
|
|
252
|
+
readonly embeddingFallback: boolean;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Config projection consumed by {@link resolveMemoryLifecycleCapabilities}.
|
|
257
|
+
*/
|
|
258
|
+
export type MemoryLifecycleConfigProjection = Pick<
|
|
259
|
+
PluginConfig,
|
|
260
|
+
| "temporalSupersessionEnabled"
|
|
261
|
+
| "temporalMemoryTreeEnabled"
|
|
262
|
+
| "lifecyclePolicyEnabled"
|
|
263
|
+
| "lifecycleFilterStaleEnabled"
|
|
264
|
+
| "lifecycleMetricsEnabled"
|
|
265
|
+
| "extractionScopeClassificationEnabled"
|
|
266
|
+
| "extractionJudgeEnabled"
|
|
267
|
+
| "extractionDedupeEnabled"
|
|
268
|
+
| "extractionTelemetryPrefilterEnabled"
|
|
269
|
+
| "extractionJudgeTelemetryEnabled"
|
|
270
|
+
| "embeddingFallbackEnabled"
|
|
271
|
+
>;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Resolve the {@link MemoryLifecycleCapabilitySet} from parsed config.
|
|
275
|
+
*
|
|
276
|
+
* Call this ONCE per write / recall-filter / maintenance operation entry and
|
|
277
|
+
* thread the result down — exactly the pattern {@link resolveCapabilities}
|
|
278
|
+
* established for recall.
|
|
279
|
+
*/
|
|
280
|
+
export function resolveMemoryLifecycleCapabilities(
|
|
281
|
+
config: MemoryLifecycleConfigProjection,
|
|
282
|
+
): MemoryLifecycleCapabilitySet {
|
|
283
|
+
return Object.freeze({
|
|
284
|
+
temporalSupersession: config.temporalSupersessionEnabled,
|
|
285
|
+
temporalMemoryTree: config.temporalMemoryTreeEnabled,
|
|
286
|
+
lifecyclePolicy: config.lifecyclePolicyEnabled,
|
|
287
|
+
lifecycleFilterStale: config.lifecycleFilterStaleEnabled,
|
|
288
|
+
lifecycleMetrics: config.lifecycleMetricsEnabled,
|
|
289
|
+
extractionScopeClassification: config.extractionScopeClassificationEnabled,
|
|
290
|
+
extractionJudge: config.extractionJudgeEnabled,
|
|
291
|
+
extractionDedupe: config.extractionDedupeEnabled,
|
|
292
|
+
extractionTelemetryPrefilter: config.extractionTelemetryPrefilterEnabled,
|
|
293
|
+
extractionJudgeTelemetry: config.extractionJudgeTelemetryEnabled,
|
|
294
|
+
embeddingFallback: config.embeddingFallbackEnabled,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { strict as assert } from "node:assert";
|
|
10
|
-
import { mkdir, rm, readFile } from "node:fs/promises";
|
|
10
|
+
import { mkdir, rm, readFile, utimes, unlink, appendFile } from "node:fs/promises";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
13
|
import { test } from "node:test";
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
sessionBelongsToPrincipal,
|
|
23
23
|
chatSessionFile,
|
|
24
24
|
cleanupExpiredChatSessions,
|
|
25
|
+
__resetTranscriptSeqForTest,
|
|
25
26
|
} from "./chat-session.js";
|
|
26
27
|
import type { ChatSessionState } from "./chat-types.js";
|
|
27
28
|
import type { ChatToolExecutor } from "./chat-engine.js";
|
|
@@ -270,3 +271,105 @@ test("budget exhaustion summary LLM failure does not crash (Thread 16)", async (
|
|
|
270
271
|
assert.ok(result.reply.length > 0, "Should return a fallback reply");
|
|
271
272
|
assert.ok(result.skippedTools !== undefined, "Should report skipped tools");
|
|
272
273
|
});
|
|
274
|
+
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
// TTL boundary (issue #1685 item 1 / #1687 Thread 21)
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
test("cleanupExpiredChatSessions honors TTL boundary [0, ttlHours) — age >= ttl expires", async () => {
|
|
280
|
+
const dir = await makeTempDir();
|
|
281
|
+
const ttlHours = 2;
|
|
282
|
+
const ttlMs = ttlHours * 3600 * 1000;
|
|
283
|
+
|
|
284
|
+
// Session aged past the TTL (mtime set to ttlHours ago; by the time the
|
|
285
|
+
// sweep stat()s it a few ms have elapsed, so age >= ttl deterministically
|
|
286
|
+
// — the boundary is inclusive: age ∈ [0, ttl) survives, age >= ttl expires).
|
|
287
|
+
const expired = await createChatSession(dir, { principal: "expired" });
|
|
288
|
+
const expiredFile = chatSessionFile(dir, expired.id);
|
|
289
|
+
const atTtlSeconds = (Date.now() - ttlMs) / 1000;
|
|
290
|
+
await utimes(expiredFile, atTtlSeconds, atTtlSeconds);
|
|
291
|
+
|
|
292
|
+
// Session aged well under the TTL → must survive.
|
|
293
|
+
const fresh = await createChatSession(dir, { principal: "fresh" });
|
|
294
|
+
const freshFile = chatSessionFile(dir, fresh.id);
|
|
295
|
+
const underTtlSeconds = (Date.now() - (ttlMs - 6 * 60 * 1000)) / 1000; // ~1.9h old
|
|
296
|
+
await utimes(freshFile, underTtlSeconds, underTtlSeconds);
|
|
297
|
+
|
|
298
|
+
const removed = await cleanupExpiredChatSessions(dir, ttlHours);
|
|
299
|
+
assert.equal(removed, 1, "only the expired session should be removed");
|
|
300
|
+
|
|
301
|
+
// The fresh session file must still be on disk.
|
|
302
|
+
const freshStillExists = await readFile(freshFile, "utf8").then(() => true).catch(() => false);
|
|
303
|
+
assert.ok(freshStillExists, "session aged under the TTL must survive the sweep");
|
|
304
|
+
|
|
305
|
+
// The expired session file must be gone.
|
|
306
|
+
const expiredGone = await readFile(expiredFile, "utf8").then(() => false).catch(() => true);
|
|
307
|
+
assert.ok(expiredGone, "session aged at/over the TTL must be swept");
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
test("appendTranscriptEntry emits strictly-increasing seqs even within the same millisecond (issue #1687 review)", async () => {
|
|
312
|
+
const dir = await makeTempDir();
|
|
313
|
+
const session = await createChatSession(dir, { principal: "seq" });
|
|
314
|
+
// Append several entries as fast as possible (same-ms collisions would have
|
|
315
|
+
// shared a Date.now()-based seq before the monotonic counter fix).
|
|
316
|
+
const entries: number[] = [];
|
|
317
|
+
for (let i = 0; i < 8; i++) {
|
|
318
|
+
const e = await appendTranscriptEntry(dir, session.id, { role: "user", content: `m${i}` });
|
|
319
|
+
entries.push(e.seq);
|
|
320
|
+
}
|
|
321
|
+
// Every seq must be distinct and strictly increasing.
|
|
322
|
+
for (let i = 1; i < entries.length; i++) {
|
|
323
|
+
assert.ok(entries[i]! > entries[i - 1]!, `seq ${entries[i]} must exceed ${entries[i - 1]}`);
|
|
324
|
+
}
|
|
325
|
+
assert.equal(new Set(entries).size, entries.length, "seqs must be unique");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("appendTranscriptEntry refuses to recreate a swept session (chat_session_expired) — codex P2 #1687", async () => {
|
|
329
|
+
const dir = await makeTempDir();
|
|
330
|
+
const session = await createChatSession(dir, { principal: "alice" });
|
|
331
|
+
// Simulate the TTL sweep unlinking the file mid-turn (resurrection race).
|
|
332
|
+
await unlink(chatSessionFile(dir, session.id));
|
|
333
|
+
// Must throw instead of silently recreating a headerless (public) file.
|
|
334
|
+
await assert.rejects(
|
|
335
|
+
appendTranscriptEntry(dir, session.id, { role: "user", content: "x" }),
|
|
336
|
+
/chat_session_expired/,
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
test("loadChatSession raises the seq counter above the on-disk max on backward clock skew (issue #1718)", async () => {
|
|
342
|
+
const dir = await makeTempDir();
|
|
343
|
+
const session = await createChatSession(dir, { principal: "skew" });
|
|
344
|
+
// Simulate a previous process that ran with a forward-skewed clock: append a
|
|
345
|
+
// transcript line whose seq is far in the future relative to the current
|
|
346
|
+
// Date.now(). A later restart with a normal (or backward-stepped) clock must
|
|
347
|
+
// still issue seqs strictly greater than this persisted line.
|
|
348
|
+
const futureSeq = Date.now() + 10_000_000;
|
|
349
|
+
await appendFile(
|
|
350
|
+
chatSessionFile(dir, session.id),
|
|
351
|
+
JSON.stringify({
|
|
352
|
+
seq: futureSeq,
|
|
353
|
+
ts: new Date().toISOString(),
|
|
354
|
+
role: "user",
|
|
355
|
+
content: "future-dated line from a forward-skewed prior process",
|
|
356
|
+
}) + "\n",
|
|
357
|
+
"utf8",
|
|
358
|
+
);
|
|
359
|
+
// Simulate a process restart whose module-level counter starts below the
|
|
360
|
+
// persisted on-disk seq (the backward-clock-skew window).
|
|
361
|
+
__resetTranscriptSeqForTest();
|
|
362
|
+
// Resume the session: loadChatSession must raise the counter above the
|
|
363
|
+
// on-disk max so the next append is strictly greater, regardless of the
|
|
364
|
+
// current Date.now().
|
|
365
|
+
const loaded = await loadChatSession(dir, session.id);
|
|
366
|
+
assert.ok(loaded, "session must load");
|
|
367
|
+
const next = await appendTranscriptEntry(dir, session.id, {
|
|
368
|
+
role: "assistant",
|
|
369
|
+
content: "resumed after backward clock skew",
|
|
370
|
+
});
|
|
371
|
+
assert.ok(
|
|
372
|
+
next.seq > futureSeq,
|
|
373
|
+
`seq ${next.seq} must exceed the on-disk max ${futureSeq} after a backward clock skew (issue #1718)`,
|
|
374
|
+
);
|
|
375
|
+
});
|