@remnic/core 9.3.704 → 9.3.706
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 +6 -6
- package/dist/access-boundary.js +29 -29
- package/dist/access-cli.js +59 -57
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +34 -33
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +33 -32
- package/dist/access-operations-batch.d.ts +2 -0
- package/dist/access-operations-batch.js +120 -0
- package/dist/access-operations.d.ts +6 -5
- package/dist/access-operations.js +32 -31
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-C8hc8N2i.d.ts → access-service-j1c1gptF.d.ts} +3 -3
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +28 -28
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/access-surface-catalog.js +57 -54
- package/dist/access-surface-catalog.js.map +1 -1
- 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/{auto-sync-3AAP25FV.js → auto-sync-24NNS3GU.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 +1 -1
- package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/capsule-crypto-7FJQINUR.js.map +1 -0
- package/dist/{catalog-CAeU0e1C.d.ts → catalog-CKxilpzS.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-behavior.js +5 -5
- package/dist/causal-chain.js +3 -3
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +11 -11
- package/dist/causal-retrieval.js +3 -3
- package/dist/causal-trajectory-graph.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-CTY6QSLZ.js → chunk-24FGNOQS.js} +4 -4
- package/dist/{chunk-CTY6QSLZ.js.map → chunk-24FGNOQS.js.map} +1 -1
- package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-NMW3T6T6.js → chunk-2VQYHHWB.js} +17 -11
- package/dist/chunk-2VQYHHWB.js.map +1 -0
- package/dist/{chunk-7G4KPGR3.js → chunk-3PTOZJOI.js} +2 -2
- package/dist/{chunk-PJ5MDXOF.js → chunk-4DZATVK5.js} +10 -10
- package/dist/{chunk-UOIRJ45Z.js → chunk-4E2QCH46.js} +2 -2
- package/dist/{chunk-KKK7YTYN.js → chunk-5GSUBCZM.js} +6 -2
- package/dist/chunk-5GSUBCZM.js.map +1 -0
- package/dist/{chunk-RYDT7LAI.js → chunk-66TSLESZ.js} +2 -2
- package/dist/chunk-66TSLESZ.js.map +1 -0
- package/dist/{chunk-IREVVFTM.js → chunk-6CIAGZ6X.js} +9 -1
- package/dist/chunk-6CIAGZ6X.js.map +1 -0
- package/dist/{chunk-QHNJVWF6.js → chunk-6HPJMR5I.js} +6 -6
- package/dist/{chunk-4NFVPDIL.js → chunk-74MYJSKB.js} +5 -5
- package/dist/{chunk-PKMQK2WY.js → chunk-773DPI6T.js} +4 -4
- package/dist/{chunk-AGNBY3VG.js → chunk-APJQ6UEA.js} +4 -4
- package/dist/{chunk-UNNDFYUX.js → chunk-ARV3AUOM.js} +5 -5
- package/dist/{chunk-XXDOJ5NL.js → chunk-BGAHTI4C.js} +7 -7
- package/dist/{chunk-6GHWF4QH.js → chunk-C63MK3WL.js} +2 -2
- package/dist/{chunk-VKJHM6PL.js → chunk-DGTDRIUO.js} +5 -5
- package/dist/{chunk-25FHSOEB.js → chunk-EJDAXR7O.js} +60 -6
- package/dist/chunk-EJDAXR7O.js.map +1 -0
- package/dist/{chunk-K7QRDDSR.js → chunk-ESMY4RJ4.js} +5 -5
- package/dist/{chunk-YLB7NMLD.js → chunk-GKI6LX5L.js} +2 -2
- package/dist/{chunk-TUPNAHHQ.js → chunk-HIV5E57C.js} +2 -2
- package/dist/{chunk-YOI3ELXF.js → chunk-IF362TF6.js} +2 -2
- package/dist/{chunk-SFOAQQDJ.js → chunk-IONFO7UK.js} +3 -3
- package/dist/{chunk-AE3C4PY6.js → chunk-IVCQW4C4.js} +2 -2
- package/dist/{chunk-6AFVIOVZ.js → chunk-K22IJO4R.js} +2 -2
- package/dist/{chunk-5W7CTZ2E.js → chunk-KQAFEZQX.js} +4 -4
- package/dist/chunk-M7R5R6OJ.js +355 -0
- package/dist/chunk-M7R5R6OJ.js.map +1 -0
- package/dist/{chunk-IX3UQT4H.js → chunk-MLLTU5FX.js} +1 -1
- package/dist/chunk-MLLTU5FX.js.map +1 -0
- package/dist/{chunk-ORXVO6TH.js → chunk-MRX6ZXHZ.js} +6 -6
- package/dist/chunk-N75N5SNX.js +1303 -0
- package/dist/chunk-N75N5SNX.js.map +1 -0
- package/dist/{chunk-S24J5LLQ.js → chunk-NM6TSEGQ.js} +3 -3
- package/dist/{chunk-QDIVMS25.js → chunk-NUWZFSKC.js} +2 -2
- package/dist/{chunk-53EEQYE5.js → chunk-OMSTU233.js} +4 -4
- package/dist/{chunk-R64MNFPV.js → chunk-PDJQVAGM.js} +114 -77
- package/dist/chunk-PDJQVAGM.js.map +1 -0
- package/dist/{chunk-WAHI7SPK.js → chunk-RH2OSRQY.js} +2 -2
- package/dist/{chunk-YTMDF6S7.js → chunk-SPETAWFE.js} +2 -2
- package/dist/{chunk-ZXFNSKPY.js → chunk-STYMKPFT.js} +34 -34
- package/dist/{chunk-OMNTQPZO.js → chunk-TKZ3AL5V.js} +2 -2
- package/dist/{chunk-ABCGDW3A.js → chunk-VYZNWH4J.js} +40 -9
- package/dist/chunk-VYZNWH4J.js.map +1 -0
- package/dist/{chunk-QV5WFNZ2.js → chunk-W2S3Z5MT.js} +2 -2
- package/dist/{chunk-OCFNG4LB.js → chunk-W2WQ4LE7.js} +2 -2
- package/dist/{chunk-DE2OPIQD.js → chunk-WDH3KUZU.js} +6 -6
- package/dist/{chunk-3EHCV6YV.js → chunk-WQADZ3ZY.js} +67 -67
- package/dist/{chunk-OOEVMJK4.js → chunk-WSWYIKXX.js} +6 -6
- package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-WN4GHSDH.js → chunk-XKMDDM7P.js} +2 -2
- package/dist/{chunk-6Q7JFY4P.js → chunk-Y6YHAGQP.js} +2 -2
- package/dist/{chunk-KF74X62T.js → chunk-YGKUAX2B.js} +4 -4
- package/dist/{chunk-YN4ZT4CW.js → chunk-YI7RZO7Y.js} +1 -1
- package/dist/{cli-ChE0XlJY.d.ts → cli-CbT-pyM4.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +58 -57
- 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/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/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/event-time.d.ts +35 -1
- package/dist/event-time.js +6 -320
- package/dist/event-time.js.map +1 -1
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/extraction-faithfulness.d.ts +1 -1
- 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 +10 -1
- package/dist/extraction.js +3 -3
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{forget-6SOIPUMQ.js → forget-U66KPDPY.js} +3 -3
- package/dist/{graph-edge-decay-D7OESCBR.js → graph-edge-decay-JLOF3YW4.js} +3 -3
- package/dist/graph-snapshot.js +3 -3
- package/dist/graph.js +2 -2
- 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 +103 -101
- package/dist/index.js.map +1 -1
- 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 +4 -4
- 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/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +8 -8
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
- package/dist/maintenance/rebuild-memory-projection.js +9 -9
- 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 +11 -11
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +3 -3
- 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 +18 -18
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +9 -9
- package/dist/{orchestrator-m_PnhyHj.d.ts → orchestrator-D4ovYV3x.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +46 -45
- 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/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 +1 -1
- 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/schemas.d.ts +38 -0
- package/dist/schemas.js +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +2 -2
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +4 -4
- 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-DVBbq6Wr.d.ts → semantic-consolidation-DyMUCsfN.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +9 -9
- package/dist/semantic-rule-promotion.js +7 -7
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +8 -8
- 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 +5 -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-supersession.js +2 -2
- package/dist/temporal-validity.d.ts +2 -2
- package/dist/temporal-validity.js +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/transfer/import-sqlite.js +2 -2
- package/dist/{types-CJrvHJjx.d.ts → types-Couvz-L3.d.ts} +12 -2
- 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 +8 -8
- package/package.json +2 -2
- package/src/access-boundary.ts +102 -1
- package/src/access-http.ts +54 -0
- package/src/access-operations-batch.ts +1468 -0
- package/src/access-operations.ts +4 -0
- package/src/access-surface-catalog.test.ts +17 -1
- package/src/access-surface-catalog.ts +57 -54
- package/src/event-time.ts +73 -0
- package/src/extraction-eventtime-wiring.test.ts +269 -0
- package/src/extraction.ts +50 -3
- package/src/orchestrator.ts +71 -17
- package/src/schemas.ts +12 -0
- package/src/storage-contract/atomicity.test.ts +113 -0
- package/src/storage-contract/cache-coherence.test.ts +100 -0
- package/src/storage-contract/content-hash.test.ts +102 -0
- package/src/storage-contract/failure-semantics.test.ts +108 -0
- package/src/storage-contract/harness.ts +148 -0
- package/src/storage-contract/namespace-routing.test.ts +135 -0
- package/src/storage-contract/round-trip.test.ts +226 -0
- package/src/storage-contract/surface.test.ts +90 -0
- package/src/storage-contract/versioning.test.ts +139 -0
- package/src/storage.ts +13 -2
- package/src/temporal-validity.test.ts +33 -0
- package/src/temporal-validity.ts +24 -1
- package/src/types.ts +12 -2
- package/dist/chunk-25FHSOEB.js.map +0 -1
- package/dist/chunk-ABCGDW3A.js.map +0 -1
- package/dist/chunk-IREVVFTM.js.map +0 -1
- package/dist/chunk-IX3UQT4H.js.map +0 -1
- package/dist/chunk-KKK7YTYN.js.map +0 -1
- package/dist/chunk-NMW3T6T6.js.map +0 -1
- package/dist/chunk-R64MNFPV.js.map +0 -1
- package/dist/chunk-RYDT7LAI.js.map +0 -1
- /package/dist/{capsule-crypto-GWVG7LGC.js.map → access-operations-batch.js.map} +0 -0
- /package/dist/{auto-sync-3AAP25FV.js.map → auto-sync-24NNS3GU.js.map} +0 -0
- /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-7G4KPGR3.js.map → chunk-3PTOZJOI.js.map} +0 -0
- /package/dist/{chunk-PJ5MDXOF.js.map → chunk-4DZATVK5.js.map} +0 -0
- /package/dist/{chunk-UOIRJ45Z.js.map → chunk-4E2QCH46.js.map} +0 -0
- /package/dist/{chunk-QHNJVWF6.js.map → chunk-6HPJMR5I.js.map} +0 -0
- /package/dist/{chunk-4NFVPDIL.js.map → chunk-74MYJSKB.js.map} +0 -0
- /package/dist/{chunk-PKMQK2WY.js.map → chunk-773DPI6T.js.map} +0 -0
- /package/dist/{chunk-AGNBY3VG.js.map → chunk-APJQ6UEA.js.map} +0 -0
- /package/dist/{chunk-UNNDFYUX.js.map → chunk-ARV3AUOM.js.map} +0 -0
- /package/dist/{chunk-XXDOJ5NL.js.map → chunk-BGAHTI4C.js.map} +0 -0
- /package/dist/{chunk-6GHWF4QH.js.map → chunk-C63MK3WL.js.map} +0 -0
- /package/dist/{chunk-VKJHM6PL.js.map → chunk-DGTDRIUO.js.map} +0 -0
- /package/dist/{chunk-K7QRDDSR.js.map → chunk-ESMY4RJ4.js.map} +0 -0
- /package/dist/{chunk-YLB7NMLD.js.map → chunk-GKI6LX5L.js.map} +0 -0
- /package/dist/{chunk-TUPNAHHQ.js.map → chunk-HIV5E57C.js.map} +0 -0
- /package/dist/{chunk-YOI3ELXF.js.map → chunk-IF362TF6.js.map} +0 -0
- /package/dist/{chunk-SFOAQQDJ.js.map → chunk-IONFO7UK.js.map} +0 -0
- /package/dist/{chunk-AE3C4PY6.js.map → chunk-IVCQW4C4.js.map} +0 -0
- /package/dist/{chunk-6AFVIOVZ.js.map → chunk-K22IJO4R.js.map} +0 -0
- /package/dist/{chunk-5W7CTZ2E.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-ORXVO6TH.js.map → chunk-MRX6ZXHZ.js.map} +0 -0
- /package/dist/{chunk-S24J5LLQ.js.map → chunk-NM6TSEGQ.js.map} +0 -0
- /package/dist/{chunk-QDIVMS25.js.map → chunk-NUWZFSKC.js.map} +0 -0
- /package/dist/{chunk-53EEQYE5.js.map → chunk-OMSTU233.js.map} +0 -0
- /package/dist/{chunk-WAHI7SPK.js.map → chunk-RH2OSRQY.js.map} +0 -0
- /package/dist/{chunk-YTMDF6S7.js.map → chunk-SPETAWFE.js.map} +0 -0
- /package/dist/{chunk-ZXFNSKPY.js.map → chunk-STYMKPFT.js.map} +0 -0
- /package/dist/{chunk-OMNTQPZO.js.map → chunk-TKZ3AL5V.js.map} +0 -0
- /package/dist/{chunk-QV5WFNZ2.js.map → chunk-W2S3Z5MT.js.map} +0 -0
- /package/dist/{chunk-OCFNG4LB.js.map → chunk-W2WQ4LE7.js.map} +0 -0
- /package/dist/{chunk-DE2OPIQD.js.map → chunk-WDH3KUZU.js.map} +0 -0
- /package/dist/{chunk-3EHCV6YV.js.map → chunk-WQADZ3ZY.js.map} +0 -0
- /package/dist/{chunk-OOEVMJK4.js.map → chunk-WSWYIKXX.js.map} +0 -0
- /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-WN4GHSDH.js.map → chunk-XKMDDM7P.js.map} +0 -0
- /package/dist/{chunk-6Q7JFY4P.js.map → chunk-Y6YHAGQP.js.map} +0 -0
- /package/dist/{chunk-KF74X62T.js.map → chunk-YGKUAX2B.js.map} +0 -0
- /package/dist/{chunk-YN4ZT4CW.js.map → chunk-YI7RZO7Y.js.map} +0 -0
- /package/dist/{forget-6SOIPUMQ.js.map → forget-U66KPDPY.js.map} +0 -0
- /package/dist/{graph-edge-decay-D7OESCBR.js.map → graph-edge-decay-JLOF3YW4.js.map} +0 -0
package/src/orchestrator.ts
CHANGED
|
@@ -121,6 +121,7 @@ import {
|
|
|
121
121
|
shouldFilterSupersededFromRecall,
|
|
122
122
|
} from "./temporal-supersession.js";
|
|
123
123
|
import { isValidAsOf, isValidityExpiredNow } from "./temporal-validity.js";
|
|
124
|
+
import { resolveFactEventTime } from "./event-time.js";
|
|
124
125
|
import { RelevanceStore } from "./relevance.js";
|
|
125
126
|
import { NegativeExampleStore } from "./negative.js";
|
|
126
127
|
import {
|
|
@@ -12150,10 +12151,16 @@ export class Orchestrator {
|
|
|
12150
12151
|
: typeof sessionKey === "string" && sessionKey.length > 0
|
|
12151
12152
|
? sessionKey
|
|
12152
12153
|
: "default";
|
|
12154
|
+
const captureTimestamp = new Date().toISOString();
|
|
12153
12155
|
const turn: BufferTurn = {
|
|
12154
12156
|
role,
|
|
12155
12157
|
content,
|
|
12156
|
-
timestamp:
|
|
12158
|
+
timestamp: captureTimestamp,
|
|
12159
|
+
// #1578: anchor live-capture turns to wall-clock when bi-temporal is on;
|
|
12160
|
+
// replay/import turns carry sourceValidAt explicitly (codex P1).
|
|
12161
|
+
...(this.config.temporalBiTemporal
|
|
12162
|
+
? { sourceValidAt: captureTimestamp }
|
|
12163
|
+
: {}),
|
|
12157
12164
|
sessionKey,
|
|
12158
12165
|
logicalSessionKey: options.logicalSessionKey ?? bufferKey,
|
|
12159
12166
|
providerThreadId: options.providerThreadId ?? null,
|
|
@@ -13922,6 +13929,11 @@ export class Orchestrator {
|
|
|
13922
13929
|
intentEntityTypes?: string[];
|
|
13923
13930
|
memoryKind?: MemoryFrontmatter["memoryKind"];
|
|
13924
13931
|
validAt?: string;
|
|
13932
|
+
// #1578 — bi-temporal bounds + ingestion provenance forwarded to profile-
|
|
13933
|
+
// target copies (same defect class as shared promotion; cursor bugbot).
|
|
13934
|
+
invalidAt?: string;
|
|
13935
|
+
observedAt?: string;
|
|
13936
|
+
eventTimeSource?: "extracted" | "assumed";
|
|
13925
13937
|
source: string;
|
|
13926
13938
|
sources?: ProvenanceSource[];
|
|
13927
13939
|
provenance?: "verified" | "unverified" | "none";
|
|
@@ -13980,6 +13992,10 @@ export class Orchestrator {
|
|
|
13980
13992
|
intentEntityTypes: options.intentEntityTypes,
|
|
13981
13993
|
memoryKind: options.memoryKind,
|
|
13982
13994
|
validAt: options.validAt,
|
|
13995
|
+
// #1578 — forward bi-temporal bounds + ingestion provenance.
|
|
13996
|
+
...(options.invalidAt ? { invalidAt: options.invalidAt } : {}),
|
|
13997
|
+
...(options.observedAt ? { observedAt: options.observedAt } : {}),
|
|
13998
|
+
...(options.eventTimeSource ? { eventTimeSource: options.eventTimeSource } : {}),
|
|
13983
13999
|
contentHashSource: options.category === "fact" ? dedupContent : rawContent,
|
|
13984
14000
|
...(options.sources && options.sources.length > 0 ? { sources: options.sources } : {}),
|
|
13985
14001
|
...(options.provenance ? { provenance: options.provenance } : {}),
|
|
@@ -13999,7 +14015,7 @@ export class Orchestrator {
|
|
|
13999
14015
|
entityRef: options.entityRef,
|
|
14000
14016
|
structuredAttributes: options.structuredAttributes,
|
|
14001
14017
|
createdAt: supersessionOrderingAt(options.validAt),
|
|
14002
|
-
enabled:
|
|
14018
|
+
enabled: !(options.eventTimeSource === "extracted" && !options.validAt),
|
|
14003
14019
|
});
|
|
14004
14020
|
} catch (profileSupersessionErr) {
|
|
14005
14021
|
log.warn(
|
|
@@ -14042,6 +14058,12 @@ export class Orchestrator {
|
|
|
14042
14058
|
intentEntityTypes?: string[];
|
|
14043
14059
|
memoryKind?: MemoryFrontmatter["memoryKind"];
|
|
14044
14060
|
validAt?: string;
|
|
14061
|
+
// #1578 — bi-temporal bounds + ingestion provenance forwarded to the
|
|
14062
|
+
// shared-namespace copy so shared recall honours the same invalid_at
|
|
14063
|
+
// window as the source fact (cursor bugbot).
|
|
14064
|
+
invalidAt?: string;
|
|
14065
|
+
observedAt?: string;
|
|
14066
|
+
eventTimeSource?: "extracted" | "assumed";
|
|
14045
14067
|
source: string;
|
|
14046
14068
|
/** Claim-level provenance spans (issue #1575 PR 2). */
|
|
14047
14069
|
sources?: ProvenanceSource[];
|
|
@@ -14196,7 +14218,7 @@ export class Orchestrator {
|
|
|
14196
14218
|
entityRef: options.entityRef,
|
|
14197
14219
|
structuredAttributes: options.structuredAttributes,
|
|
14198
14220
|
createdAt: supersessionOrderingAt(options.validAt),
|
|
14199
|
-
enabled:
|
|
14221
|
+
enabled: !(options.eventTimeSource === "extracted" && !options.validAt),
|
|
14200
14222
|
useCallerTimestamp: true,
|
|
14201
14223
|
});
|
|
14202
14224
|
// Catalog touch (issue #1499 — codex P2 NElSf): this dedup branch
|
|
@@ -14271,6 +14293,10 @@ export class Orchestrator {
|
|
|
14271
14293
|
intentEntityTypes: options.intentEntityTypes,
|
|
14272
14294
|
memoryKind: options.memoryKind,
|
|
14273
14295
|
validAt: options.validAt,
|
|
14296
|
+
// #1578 — forward bi-temporal bounds + ingestion provenance.
|
|
14297
|
+
...(options.invalidAt ? { invalidAt: options.invalidAt } : {}),
|
|
14298
|
+
...(options.observedAt ? { observedAt: options.observedAt } : {}),
|
|
14299
|
+
...(options.eventTimeSource ? { eventTimeSource: options.eventTimeSource } : {}),
|
|
14274
14300
|
contentHashSource: options.category === "fact" ? dedupContent : rawContent,
|
|
14275
14301
|
// Claim-level provenance spans (issue #1575 PR 2).
|
|
14276
14302
|
...(options.sources && options.sources.length > 0 ? { sources: options.sources } : {}),
|
|
@@ -14296,7 +14322,7 @@ export class Orchestrator {
|
|
|
14296
14322
|
entityRef: options.entityRef,
|
|
14297
14323
|
structuredAttributes: options.structuredAttributes,
|
|
14298
14324
|
createdAt: supersessionOrderingAt(options.validAt),
|
|
14299
|
-
enabled:
|
|
14325
|
+
enabled: !(options.eventTimeSource === "extracted" && !options.validAt),
|
|
14300
14326
|
});
|
|
14301
14327
|
} catch (sharedSupersessionErr) {
|
|
14302
14328
|
log.warn(
|
|
@@ -14677,7 +14703,7 @@ export class Orchestrator {
|
|
|
14677
14703
|
typeof (fact as any).confidence === "number"
|
|
14678
14704
|
? (fact as any).confidence
|
|
14679
14705
|
: 0.7;
|
|
14680
|
-
|
|
14706
|
+
const biTemporal = this.config.temporalBiTemporal && sourceContext?.validAt ? resolveFactEventTime(fact.eventTime, sourceContext.validAt) : undefined;
|
|
14681
14707
|
// Content-hash dedup check (v6.0)
|
|
14682
14708
|
//
|
|
14683
14709
|
// Canonicalize pre-tagged facts before hashing (Codex P2 — issue #369).
|
|
@@ -15159,7 +15185,6 @@ export class Orchestrator {
|
|
|
15159
15185
|
// back, leaving a dangling deindex with no replacement reference.
|
|
15160
15186
|
// Child chunks intentionally do NOT carry supersedes; only the
|
|
15161
15187
|
// parent represents the logical memory unit.
|
|
15162
|
-
//
|
|
15163
15188
|
// Canonicalize contentHashSource before writing (Thread 3 — Codex P2,
|
|
15164
15189
|
// issue #369). If fact.content already carries an inline citation
|
|
15165
15190
|
// (e.g. re-processed or relayed fact), strip it so contentHashSource
|
|
@@ -15187,7 +15212,8 @@ export class Orchestrator {
|
|
|
15187
15212
|
intentEntityTypes: inferredIntent?.entityTypes,
|
|
15188
15213
|
memoryKind,
|
|
15189
15214
|
structuredAttributes: fact.structuredAttributes,
|
|
15190
|
-
validAt: sourceContext?.validAt,
|
|
15215
|
+
validAt: biTemporal ? biTemporal.validFrom : sourceContext?.validAt,
|
|
15216
|
+
...(biTemporal ? { observedAt: biTemporal.observedAt, eventTimeSource: biTemporal.eventTimeSource, ...(biTemporal.validUntil ? { invalidAt: biTemporal.validUntil } : {}) } : {}),
|
|
15191
15217
|
contentHashSource: rawChunkedContent,
|
|
15192
15218
|
// Faithfulness gate (issue #1576).
|
|
15193
15219
|
...(faithfulnessFm ? { faithfulness: faithfulnessFm } : {}),
|
|
@@ -15229,7 +15255,17 @@ export class Orchestrator {
|
|
|
15229
15255
|
intentActionType: inferredIntent?.actionType,
|
|
15230
15256
|
intentEntityTypes: inferredIntent?.entityTypes,
|
|
15231
15257
|
memoryKind,
|
|
15232
|
-
validAt: sourceContext?.validAt,
|
|
15258
|
+
validAt: biTemporal ? biTemporal.validFrom : sourceContext?.validAt,
|
|
15259
|
+
// #1578: propagate end bound + provenance to chunks (cursor bugbot).
|
|
15260
|
+
...(biTemporal
|
|
15261
|
+
? {
|
|
15262
|
+
observedAt: biTemporal.observedAt,
|
|
15263
|
+
eventTimeSource: biTemporal.eventTimeSource,
|
|
15264
|
+
...(biTemporal.validUntil
|
|
15265
|
+
? { invalidAt: biTemporal.validUntil }
|
|
15266
|
+
: {}),
|
|
15267
|
+
}
|
|
15268
|
+
: {}),
|
|
15233
15269
|
// Faithfulness gate (issue #1576): propagate the parent
|
|
15234
15270
|
// fact's verdict + enforce status so a pending_review fact
|
|
15235
15271
|
// is not indexed as active through its chunks (chatgpt P2).
|
|
@@ -15287,8 +15323,12 @@ export class Orchestrator {
|
|
|
15287
15323
|
newMemoryId: parentId,
|
|
15288
15324
|
entityRef: supersessionEntityRef,
|
|
15289
15325
|
structuredAttributes: fact.structuredAttributes,
|
|
15290
|
-
createdAt: supersessionOrderingAt(sourceContext?.validAt),
|
|
15291
|
-
|
|
15326
|
+
createdAt: supersessionOrderingAt(biTemporal?.validFrom ?? sourceContext?.validAt),
|
|
15327
|
+
// #1578 r3: an extracted end-only bound (validFrom absent) is
|
|
15328
|
+
// historical, not a new authoritative state — never let it
|
|
15329
|
+
// supersede a later active fact (codex P1 on :15534).
|
|
15330
|
+
enabled: this.config.temporalSupersessionEnabled &&
|
|
15331
|
+
!(biTemporal && !biTemporal.validFrom),
|
|
15292
15332
|
});
|
|
15293
15333
|
} catch (err) {
|
|
15294
15334
|
log.warn(`temporal-supersession (chunked): unexpected error: ${err}`);
|
|
@@ -15311,7 +15351,14 @@ export class Orchestrator {
|
|
|
15311
15351
|
intentActionType: inferredIntent?.actionType,
|
|
15312
15352
|
intentEntityTypes: inferredIntent?.entityTypes,
|
|
15313
15353
|
memoryKind,
|
|
15314
|
-
validAt: sourceContext?.validAt,
|
|
15354
|
+
validAt: biTemporal ? biTemporal.validFrom : sourceContext?.validAt,
|
|
15355
|
+
...(biTemporal
|
|
15356
|
+
? {
|
|
15357
|
+
observedAt: biTemporal.observedAt,
|
|
15358
|
+
eventTimeSource: biTemporal.eventTimeSource,
|
|
15359
|
+
...(biTemporal.validUntil ? { invalidAt: biTemporal.validUntil } : {}),
|
|
15360
|
+
}
|
|
15361
|
+
: {}),
|
|
15315
15362
|
source: extractionWriteSource,
|
|
15316
15363
|
...(fact.sources && fact.sources.length > 0 ? { sources: fact.sources } : {}),
|
|
15317
15364
|
...(fact.provenance ? { provenance: fact.provenance } : {}),
|
|
@@ -15444,13 +15491,11 @@ export class Orchestrator {
|
|
|
15444
15491
|
: undefined;
|
|
15445
15492
|
|
|
15446
15493
|
// Normal write (no chunking)
|
|
15447
|
-
//
|
|
15448
15494
|
// Compute the cited content once so that writeMemory and writeArtifact
|
|
15449
15495
|
// (when verbatim artifacts are enabled) share the same citation timestamp.
|
|
15450
15496
|
// Calling applyInlineCitation twice on the same raw content would produce
|
|
15451
15497
|
// two different timestamps, creating duplicate citations with divergent
|
|
15452
15498
|
// provenance metadata on the memory and artifact copies of the same fact.
|
|
15453
|
-
//
|
|
15454
15499
|
// Pass the RAW (pre-citation) fact as `contentHashSource` so the
|
|
15455
15500
|
// fact-content hash index records the hash of the canonical fact text
|
|
15456
15501
|
// rather than the citation-annotated variant. When inline attribution is
|
|
@@ -15483,7 +15528,8 @@ export class Orchestrator {
|
|
|
15483
15528
|
intentEntityTypes: inferredIntent?.entityTypes,
|
|
15484
15529
|
memoryKind,
|
|
15485
15530
|
structuredAttributes: fact.structuredAttributes,
|
|
15486
|
-
validAt: sourceContext?.validAt,
|
|
15531
|
+
validAt: biTemporal ? biTemporal.validFrom : sourceContext?.validAt,
|
|
15532
|
+
...(biTemporal ? { observedAt: biTemporal.observedAt, eventTimeSource: biTemporal.eventTimeSource, ...(biTemporal.validUntil ? { invalidAt: biTemporal.validUntil } : {}) } : {}),
|
|
15487
15533
|
contentHashSource: writeCategory === "fact" ? fact.content : undefined,
|
|
15488
15534
|
// Faithfulness gate (issue #1576).
|
|
15489
15535
|
...(faithfulnessFm ? { faithfulness: faithfulnessFm } : {}),
|
|
@@ -15516,8 +15562,9 @@ export class Orchestrator {
|
|
|
15516
15562
|
newMemoryId: memoryId,
|
|
15517
15563
|
entityRef: supersessionEntityRef,
|
|
15518
15564
|
structuredAttributes: fact.structuredAttributes,
|
|
15519
|
-
createdAt: supersessionOrderingAt(sourceContext?.validAt),
|
|
15520
|
-
enabled: this.config.temporalSupersessionEnabled
|
|
15565
|
+
createdAt: supersessionOrderingAt(biTemporal?.validFrom ?? sourceContext?.validAt),
|
|
15566
|
+
enabled: this.config.temporalSupersessionEnabled &&
|
|
15567
|
+
!(biTemporal && !biTemporal.validFrom),
|
|
15521
15568
|
});
|
|
15522
15569
|
} catch (err) {
|
|
15523
15570
|
log.warn(`temporal-supersession: unexpected error: ${err}`);
|
|
@@ -15564,7 +15611,14 @@ export class Orchestrator {
|
|
|
15564
15611
|
intentActionType: inferredIntent?.actionType,
|
|
15565
15612
|
intentEntityTypes: inferredIntent?.entityTypes,
|
|
15566
15613
|
memoryKind,
|
|
15567
|
-
validAt: sourceContext?.validAt,
|
|
15614
|
+
validAt: biTemporal ? biTemporal.validFrom : sourceContext?.validAt,
|
|
15615
|
+
...(biTemporal
|
|
15616
|
+
? {
|
|
15617
|
+
observedAt: biTemporal.observedAt,
|
|
15618
|
+
eventTimeSource: biTemporal.eventTimeSource,
|
|
15619
|
+
...(biTemporal.validUntil ? { invalidAt: biTemporal.validUntil } : {}),
|
|
15620
|
+
}
|
|
15621
|
+
: {}),
|
|
15568
15622
|
source: extractionWriteSource,
|
|
15569
15623
|
...(fact.sources && fact.sources.length > 0 ? { sources: fact.sources } : {}),
|
|
15570
15624
|
...(fact.provenance ? { provenance: fact.provenance } : {}),
|
package/src/schemas.ts
CHANGED
|
@@ -166,6 +166,18 @@ export const ExtractedFactSchema = z.object({
|
|
|
166
166
|
.describe(
|
|
167
167
|
'For category "reasoning_trace" only: a stored solution chain with ordered steps, a final answer, and an optional observed outcome. Require at least two steps.',
|
|
168
168
|
),
|
|
169
|
+
eventTime: z
|
|
170
|
+
.string()
|
|
171
|
+
.optional()
|
|
172
|
+
.nullable()
|
|
173
|
+
.describe(
|
|
174
|
+
'Optional event-time expression for bi-temporal validity (#1578). An ISO date ("2025-03-01") or a relative expression verbatim ("last March", "yesterday", "since 2024", "until 2025-06-01"). Resolved against the source turn timestamp at write time — NOT wall-clock. Omit when the fact has no explicit temporal anchor.',
|
|
175
|
+
),
|
|
176
|
+
event_time: z
|
|
177
|
+
.string()
|
|
178
|
+
.optional()
|
|
179
|
+
.nullable()
|
|
180
|
+
.describe("Alias for eventTime (snake_case). Gateway-tolerance shim (#1578)."),
|
|
169
181
|
}).superRefine((value, ctx) => {
|
|
170
182
|
if (value.category === "procedure" && (!Array.isArray(value.procedureSteps) || value.procedureSteps.length < 2)) {
|
|
171
183
|
ctx.addIssue({
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1533 — Phase A contract test: atomic write semantics.
|
|
3
|
+
*
|
|
4
|
+
* Rule 54 (CLAUDE.md): replace operations MUST write temp-then-rename, NEVER
|
|
5
|
+
* delete-before-write. This test pins that invariant by making rename fail
|
|
6
|
+
* (read-only target directory) and asserting the original file survives the
|
|
7
|
+
* failed overwrite.
|
|
8
|
+
*
|
|
9
|
+
* The atomic write primitive lives in secure-fs (`writeMaybeEncryptedFile`)
|
|
10
|
+
* and is consumed by StorageManager via `writeStorageSecureFile`. We test the
|
|
11
|
+
* primitive directly here because it is the single atomicity seam every write
|
|
12
|
+
* entry point depends on.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import assert from "node:assert/strict";
|
|
16
|
+
import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
17
|
+
import os from "node:os";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import test from "node:test";
|
|
20
|
+
|
|
21
|
+
import { writeMaybeEncryptedFile, readMaybeEncryptedFile } from "../secure-store/secure-fs.js";
|
|
22
|
+
|
|
23
|
+
test("atomicity: writeMaybeEncryptedFile writes temp-then-rename (no temp files left behind on success)", async () => {
|
|
24
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-atomic-"));
|
|
25
|
+
try {
|
|
26
|
+
const target = path.join(dir, "target.md");
|
|
27
|
+
await writeMaybeEncryptedFile(target, "first content", null, {}, dir);
|
|
28
|
+
|
|
29
|
+
// No temp files left behind
|
|
30
|
+
const { readdir } = await import("node:fs/promises");
|
|
31
|
+
const entries = await readdir(dir);
|
|
32
|
+
assert.deepEqual(entries.sort(), ["target.md"]);
|
|
33
|
+
|
|
34
|
+
// Overwrite — still only the target, no leftover temp
|
|
35
|
+
await writeMaybeEncryptedFile(target, "second content", null, {}, dir);
|
|
36
|
+
const entries2 = await readdir(dir);
|
|
37
|
+
assert.deepEqual(entries2.sort(), ["target.md"]);
|
|
38
|
+
|
|
39
|
+
const read = await readMaybeEncryptedFile(target, null, dir);
|
|
40
|
+
assert.equal(read, "second content");
|
|
41
|
+
} finally {
|
|
42
|
+
await rm(dir, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("atomicity: a failed rename leaves the original file intact (never delete-before-write)", async (t) => {
|
|
47
|
+
// Skip on root — chmod restrictions don't apply.
|
|
48
|
+
if (process.getuid && process.getuid() === 0) {
|
|
49
|
+
t.skip("chmod-based test is unreliable when running as root");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-atomic-fail-"));
|
|
54
|
+
try {
|
|
55
|
+
const target = path.join(dir, "survivor.md");
|
|
56
|
+
await writeMaybeEncryptedFile(target, "original", null, {}, dir);
|
|
57
|
+
|
|
58
|
+
// Make the directory read-only so the rename into place fails.
|
|
59
|
+
await chmod(dir, 0o500);
|
|
60
|
+
try {
|
|
61
|
+
await assert.rejects(
|
|
62
|
+
() => writeMaybeEncryptedFile(target, "would-be-replacement", null, {}, dir),
|
|
63
|
+
// rename (or temp write) fails — the exact error varies by platform
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// THE CONTRACT: the original file must still be readable with its
|
|
67
|
+
// original content. Delete-before-write would have destroyed it before
|
|
68
|
+
// the failed rename.
|
|
69
|
+
const survived = await readMaybeEncryptedFile(target, null, dir);
|
|
70
|
+
assert.equal(survived, "original", "original file must survive a failed atomic replace");
|
|
71
|
+
} finally {
|
|
72
|
+
// Restore perms so cleanup works.
|
|
73
|
+
await chmod(dir, 0o700);
|
|
74
|
+
}
|
|
75
|
+
} finally {
|
|
76
|
+
await rm(dir, { recursive: true, force: true });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("atomicity: StorageManager.updateMemory preserves the original on overwrite success", async (t) => {
|
|
81
|
+
const { StorageManager } = await import("../storage.js");
|
|
82
|
+
const { makeStorage } = await import("./harness.js");
|
|
83
|
+
const { storage, cleanup } = await makeStorage();
|
|
84
|
+
try {
|
|
85
|
+
const id = await storage.writeMemory("fact", "version one");
|
|
86
|
+
const before = await storage.getMemoryById(id);
|
|
87
|
+
assert.ok(before);
|
|
88
|
+
assert.equal(before!.content, "version one");
|
|
89
|
+
|
|
90
|
+
const updated = await storage.updateMemory(id, "version two");
|
|
91
|
+
assert.equal(updated, true);
|
|
92
|
+
|
|
93
|
+
const after = await storage.getMemoryById(id);
|
|
94
|
+
assert.ok(after);
|
|
95
|
+
assert.equal(after!.content, "version two");
|
|
96
|
+
// The id is stable across the update (same file path, not a new memory)
|
|
97
|
+
assert.equal(after!.frontmatter.id, id);
|
|
98
|
+
} finally {
|
|
99
|
+
await cleanup();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("atomicity: mkdir is implicit — writeMaybeEncryptedFile creates parent dirs", async () => {
|
|
104
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-atomic-mkdir-"));
|
|
105
|
+
try {
|
|
106
|
+
const target = path.join(dir, "nested", "deep", "file.md");
|
|
107
|
+
await writeMaybeEncryptedFile(target, "content", null, {}, dir);
|
|
108
|
+
const read = await readMaybeEncryptedFile(target, null, dir);
|
|
109
|
+
assert.equal(read, "content");
|
|
110
|
+
} finally {
|
|
111
|
+
await rm(dir, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1533 — Phase A contract test: cache coherence across instances.
|
|
3
|
+
*
|
|
4
|
+
* Rule 37 (memory-cache): write via instance A, read via a FRESH instance B
|
|
5
|
+
* over the same dir → B sees the write. The module-level caches
|
|
6
|
+
* (readAllMemories in-flight, version sentinels) are keyed by baseDir and
|
|
7
|
+
* shared/static — this test pins that a write invalidates correctly so a
|
|
8
|
+
* second instance never serves stale data.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import test from "node:test";
|
|
13
|
+
|
|
14
|
+
import { StorageManager } from "../storage.js";
|
|
15
|
+
import { resetStaticCaches } from "./harness.js";
|
|
16
|
+
|
|
17
|
+
test("cache-coherence: write via instance A, read via fresh instance B → B sees the write", async () => {
|
|
18
|
+
const { mkdtemp, rm } = await import("node:fs/promises");
|
|
19
|
+
const os = await import("node:os");
|
|
20
|
+
const path = await import("node:path");
|
|
21
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-cache-coherence-"));
|
|
22
|
+
try {
|
|
23
|
+
resetStaticCaches();
|
|
24
|
+
const smA = new StorageManager(dir);
|
|
25
|
+
await smA.ensureDirectories();
|
|
26
|
+
|
|
27
|
+
const id = await smA.writeMemory("fact", "written by A");
|
|
28
|
+
|
|
29
|
+
// Fresh instance over the same dir
|
|
30
|
+
const smB = new StorageManager(dir);
|
|
31
|
+
await smB.ensureDirectories();
|
|
32
|
+
const found = await smB.getMemoryById(id);
|
|
33
|
+
assert.ok(found, "instance B must see the memory written by instance A");
|
|
34
|
+
assert.equal(found!.content, "written by A");
|
|
35
|
+
} finally {
|
|
36
|
+
resetStaticCaches();
|
|
37
|
+
await rm(dir, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("cache-coherence: delete via instance A is reflected in fresh instance B", async () => {
|
|
42
|
+
const { mkdtemp, rm } = await import("node:fs/promises");
|
|
43
|
+
const os = await import("node:os");
|
|
44
|
+
const path = await import("node:path");
|
|
45
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-cache-del-"));
|
|
46
|
+
try {
|
|
47
|
+
resetStaticCaches();
|
|
48
|
+
const smA = new StorageManager(dir);
|
|
49
|
+
await smA.ensureDirectories();
|
|
50
|
+
const id = await smA.writeMemory("fact", "to be deleted");
|
|
51
|
+
|
|
52
|
+
// Confirm B sees it
|
|
53
|
+
const smB = new StorageManager(dir);
|
|
54
|
+
await smB.ensureDirectories();
|
|
55
|
+
assert.ok(await smB.getMemoryById(id));
|
|
56
|
+
|
|
57
|
+
// A deletes
|
|
58
|
+
await smA.invalidateMemory(id);
|
|
59
|
+
|
|
60
|
+
// Fresh instance C must NOT see it
|
|
61
|
+
const smC = new StorageManager(dir);
|
|
62
|
+
await smC.ensureDirectories();
|
|
63
|
+
const found = await smC.getMemoryById(id);
|
|
64
|
+
assert.equal(found, null, "fresh instance must not see a deleted memory");
|
|
65
|
+
} finally {
|
|
66
|
+
resetStaticCaches();
|
|
67
|
+
await rm(dir, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("cache-coherence: readAllMemories reflects writes across instances after invalidation", async () => {
|
|
72
|
+
const { mkdtemp, rm } = await import("node:fs/promises");
|
|
73
|
+
const os = await import("node:os");
|
|
74
|
+
const path = await import("node:path");
|
|
75
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-cache-list-"));
|
|
76
|
+
try {
|
|
77
|
+
resetStaticCaches();
|
|
78
|
+
const smA = new StorageManager(dir);
|
|
79
|
+
await smA.ensureDirectories();
|
|
80
|
+
await smA.writeMemory("fact", "first");
|
|
81
|
+
await smA.writeMemory("decision", "second");
|
|
82
|
+
|
|
83
|
+
const smB = new StorageManager(dir);
|
|
84
|
+
await smB.ensureDirectories();
|
|
85
|
+
const all = await smB.readAllMemories();
|
|
86
|
+
assert.ok(all.length >= 2, "fresh instance must list all written memories");
|
|
87
|
+
|
|
88
|
+
// Write more via A
|
|
89
|
+
await smA.writeMemory("principle", "third");
|
|
90
|
+
|
|
91
|
+
const smC = new StorageManager(dir);
|
|
92
|
+
await smC.ensureDirectories();
|
|
93
|
+
const allAfter = await smC.readAllMemories();
|
|
94
|
+
assert.ok(allAfter.length >= 3, "fresh instance must see the additional write");
|
|
95
|
+
assert.ok(allAfter.some((m) => m.content === "third"));
|
|
96
|
+
} finally {
|
|
97
|
+
resetStaticCaches();
|
|
98
|
+
await rm(dir, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1533 — Phase A contract test: content hash identity.
|
|
3
|
+
*
|
|
4
|
+
* Rule 23: the write-side and dedup-side MUST hash the same `rawContent`,
|
|
5
|
+
* never the timestamped/cited form. `writeMemory` persists a `contentHash` on
|
|
6
|
+
* the frontmatter for facts; `hasFactContentHash` queries the dedup index.
|
|
7
|
+
* This test pins that the two sides agree so subsequent extractions of the
|
|
8
|
+
* same logical fact are deduped correctly even when citation timestamps differ.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import test from "node:test";
|
|
13
|
+
|
|
14
|
+
import { ContentHashIndex } from "../storage.js";
|
|
15
|
+
import { makeStorage } from "./harness.js";
|
|
16
|
+
|
|
17
|
+
test("content-hash: ContentHashIndex.computeHash is deterministic for the same input", () => {
|
|
18
|
+
const a = ContentHashIndex.computeHash("the sky is blue");
|
|
19
|
+
const b = ContentHashIndex.computeHash("the sky is blue");
|
|
20
|
+
assert.equal(a, b);
|
|
21
|
+
assert.notEqual(
|
|
22
|
+
ContentHashIndex.computeHash("the sky is blue"),
|
|
23
|
+
ContentHashIndex.computeHash("the sky is green"),
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("content-hash: writeMemory for fact registers the hash so hasFactContentHash returns true", async () => {
|
|
28
|
+
const { storage, cleanup } = await makeStorage();
|
|
29
|
+
try {
|
|
30
|
+
const rawFact = "PostgreSQL is our primary database";
|
|
31
|
+
await storage.writeMemory("fact", rawFact);
|
|
32
|
+
|
|
33
|
+
const has = await storage.hasFactContentHash(rawFact);
|
|
34
|
+
assert.equal(has, true, "hasFactContentHash must return true for a just-written fact");
|
|
35
|
+
} finally {
|
|
36
|
+
await cleanup();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("content-hash: contentHashSource overrides the persisted body for dedup (rule 23)", async () => {
|
|
41
|
+
const { storage, cleanup } = await makeStorage();
|
|
42
|
+
try {
|
|
43
|
+
const rawFact = "We migrated to MySQL in March";
|
|
44
|
+
// Persist a citation-annotated body, but index the RAW fact text
|
|
45
|
+
await storage.writeMemory("fact", `${rawFact} [cited 2026-01-01]`, {
|
|
46
|
+
contentHashSource: rawFact,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Querying with the RAW fact (not the cited form) must hit
|
|
50
|
+
assert.equal(
|
|
51
|
+
await storage.hasFactContentHash(rawFact),
|
|
52
|
+
true,
|
|
53
|
+
"hasFactContentHash must match on the raw fact, not the cited body",
|
|
54
|
+
);
|
|
55
|
+
// The cited form should NOT be separately indexed (it has the citation suffix)
|
|
56
|
+
assert.equal(
|
|
57
|
+
await storage.hasFactContentHash(`${rawFact} [cited 2026-01-01]`),
|
|
58
|
+
false,
|
|
59
|
+
"the cited/timestamped form must not be the indexed hash",
|
|
60
|
+
);
|
|
61
|
+
} finally {
|
|
62
|
+
await cleanup();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("content-hash: non-fact categories do not register a content hash", async () => {
|
|
67
|
+
const { storage, cleanup } = await makeStorage();
|
|
68
|
+
try {
|
|
69
|
+
await storage.writeMemory("decision", "we chose option B");
|
|
70
|
+
assert.equal(
|
|
71
|
+
await storage.hasFactContentHash("we chose option B"),
|
|
72
|
+
false,
|
|
73
|
+
"non-fact categories must not enter the fact dedup index",
|
|
74
|
+
);
|
|
75
|
+
} finally {
|
|
76
|
+
await cleanup();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("content-hash: normalizeContent produces stable text for equivalent inputs", () => {
|
|
81
|
+
const a = ContentHashIndex.normalizeContent(" Hello World ");
|
|
82
|
+
const b = ContentHashIndex.normalizeContent("Hello World");
|
|
83
|
+
assert.equal(a, b, "normalizeContent must collapse whitespace consistently");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("content-hash: the frontmatter contentHash matches computeHash of the raw content", async () => {
|
|
87
|
+
const { storage, cleanup } = await makeStorage();
|
|
88
|
+
try {
|
|
89
|
+
const rawFact = "deterministic hash check";
|
|
90
|
+
const id = await storage.writeMemory("fact", rawFact);
|
|
91
|
+
const memory = await storage.getMemoryById(id);
|
|
92
|
+
assert.ok(memory);
|
|
93
|
+
assert.ok(memory!.frontmatter.contentHash, "frontmatter must carry a contentHash for facts");
|
|
94
|
+
assert.equal(
|
|
95
|
+
memory!.frontmatter.contentHash,
|
|
96
|
+
ContentHashIndex.computeHash(rawFact),
|
|
97
|
+
"frontmatter contentHash must equal computeHash of the raw content",
|
|
98
|
+
);
|
|
99
|
+
} finally {
|
|
100
|
+
await cleanup();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1533 — Phase A contract test: failure semantics.
|
|
3
|
+
*
|
|
4
|
+
* Rule 34: reads distinguish empty-dir from unreadable-dir — `{ok:false}` vs
|
|
5
|
+
* `[]`. Where the current surface already does this, we pin it; where it
|
|
6
|
+
* doesn't, we record the gap (for the silent-failures issue) rather than
|
|
7
|
+
* change behavior here.
|
|
8
|
+
*
|
|
9
|
+
* Also pins containment: a FILE where a directory is expected is skipped via
|
|
10
|
+
* lstat().isDirectory(), not existsSync() (rule 24).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
15
|
+
import os from "node:os";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import test from "node:test";
|
|
18
|
+
|
|
19
|
+
import { StorageManager } from "../storage.js";
|
|
20
|
+
import { makeStorage } from "./harness.js";
|
|
21
|
+
|
|
22
|
+
test("failure-semantics: readAllMemories on a fresh (empty) dir returns [] not null", async () => {
|
|
23
|
+
const { storage, cleanup } = await makeStorage();
|
|
24
|
+
try {
|
|
25
|
+
const result = await storage.readAllMemories();
|
|
26
|
+
assert.ok(Array.isArray(result), "readAllMemories must return an array");
|
|
27
|
+
assert.equal(result.length, 0, "empty store must return an empty array");
|
|
28
|
+
} finally {
|
|
29
|
+
await cleanup();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("failure-semantics: getMemoryById on empty store returns null (not throw)", async () => {
|
|
34
|
+
const { storage, cleanup } = await makeStorage();
|
|
35
|
+
try {
|
|
36
|
+
const result = await storage.getMemoryById("anything");
|
|
37
|
+
assert.equal(result, null);
|
|
38
|
+
} finally {
|
|
39
|
+
await cleanup();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("failure-semantics: readMemoryByPath on a non-existent file returns null (not throw)", async () => {
|
|
44
|
+
const { storage, baseDir, cleanup } = await makeStorage();
|
|
45
|
+
try {
|
|
46
|
+
const result = await storage.readMemoryByPath(path.join(baseDir, "facts", "2026-01-01", "nope.md"));
|
|
47
|
+
assert.equal(result, null);
|
|
48
|
+
} finally {
|
|
49
|
+
await cleanup();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("failure-semantics: readMemoryByPath on a malformed markdown file returns null", async () => {
|
|
54
|
+
const { storage, baseDir, cleanup } = await makeStorage();
|
|
55
|
+
try {
|
|
56
|
+
const dir = path.join(baseDir, "facts", "2026-01-01");
|
|
57
|
+
await mkdir(dir, { recursive: true });
|
|
58
|
+
const filePath = path.join(dir, "garbage.md");
|
|
59
|
+
await writeFile(filePath, "this has no frontmatter at all\njust random text", "utf-8");
|
|
60
|
+
|
|
61
|
+
const result = await storage.readMemoryByPath(filePath);
|
|
62
|
+
assert.equal(result, null, "malformed file (no frontmatter, not entity) must return null");
|
|
63
|
+
} finally {
|
|
64
|
+
await cleanup();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("failure-semantics: a FILE where a category directory is expected does not crash readAllMemories", async () => {
|
|
69
|
+
// Create a FILE named "facts" (not a directory) at the base dir, then scan.
|
|
70
|
+
// collectActiveMemoryPaths uses lstat().isDirectory() (not existsSync) so a
|
|
71
|
+
// file-as-directory entry is skipped without crashing (rule 24 containment).
|
|
72
|
+
// We do NOT call ensureDirectories — it would try to mkdir facts/<today> and
|
|
73
|
+
// fail on the file. The scan path must be robust independently of dir creation.
|
|
74
|
+
const baseDir = await mkdtemp(path.join(os.tmpdir(), "remnic-fail-file-as-dir-"));
|
|
75
|
+
try {
|
|
76
|
+
StorageManager.clearAllStaticCaches();
|
|
77
|
+
await writeFile(path.join(baseDir, "facts"), "I am a file not a directory", "utf-8");
|
|
78
|
+
const storage = new StorageManager(baseDir);
|
|
79
|
+
|
|
80
|
+
const result = await storage.readAllMemories();
|
|
81
|
+
assert.ok(Array.isArray(result));
|
|
82
|
+
assert.equal(result.length, 0, "a file-as-directory must not produce spurious memories");
|
|
83
|
+
} finally {
|
|
84
|
+
StorageManager.clearAllStaticCaches();
|
|
85
|
+
await rm(baseDir, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("failure-semantics: invalidateMemory on a missing memory returns false (not throw)", async () => {
|
|
90
|
+
const { storage, cleanup } = await makeStorage();
|
|
91
|
+
try {
|
|
92
|
+
const result = await storage.invalidateMemory("totally-missing-id-99999");
|
|
93
|
+
assert.equal(result, false);
|
|
94
|
+
} finally {
|
|
95
|
+
await cleanup();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("failure-semantics: ensureDirectories is idempotent (safe to call multiple times)", async () => {
|
|
100
|
+
const { storage, cleanup } = await makeStorage();
|
|
101
|
+
try {
|
|
102
|
+
await storage.ensureDirectories();
|
|
103
|
+
await storage.ensureDirectories();
|
|
104
|
+
await storage.ensureDirectories();
|
|
105
|
+
} finally {
|
|
106
|
+
await cleanup();
|
|
107
|
+
}
|
|
108
|
+
});
|