@remnic/core 9.7.7 → 9.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +6 -6
- package/dist/access-admin-ops-surface.js +31 -29
- package/dist/access-audit.js +2 -2
- package/dist/access-boundary.d.ts +6 -6
- package/dist/access-boundary.js +32 -30
- package/dist/access-cli.js +88 -86
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +6 -6
- package/dist/access-http.js +39 -37
- package/dist/access-identity-continuity-surface.d.ts +5 -5
- package/dist/access-identity-continuity-surface.js +31 -29
- package/dist/access-lcm-surface.d.ts +6 -6
- package/dist/access-lcm-surface.js +31 -29
- package/dist/access-mcp.d.ts +6 -6
- package/dist/access-mcp.js +36 -34
- package/dist/access-observe-write-surface.d.ts +6 -6
- package/dist/access-observe-write-surface.js +31 -29
- package/dist/access-operations-batch.js +34 -32
- package/dist/access-operations.d.ts +6 -6
- package/dist/access-operations.js +35 -33
- package/dist/access-recall-surface.d.ts +6 -6
- package/dist/access-recall-surface.js +31 -29
- package/dist/{access-service-CxDZXTKt.d.ts → access-service-DSuzikHQ.d.ts} +4 -4
- package/dist/access-service.d.ts +6 -6
- package/dist/access-service.js +31 -29
- package/dist/access-surface-catalog.d.ts +6 -6
- 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 +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/{auto-sync-676N6N6Y.js → auto-sync-AAMTVZMK.js} +5 -5
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +5 -5
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +14 -12
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/calibration.js +2 -2
- package/dist/capabilities.d.ts +1 -1
- package/dist/{capsule-merge-GK5E647P.js → capsule-merge-Z2HUO6IK.js} +10 -2
- package/dist/capsule-merge-Z2HUO6IK.js.map +1 -0
- package/dist/{catalog-DzU9HowE.d.ts → catalog-BNwjACa5.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 +15 -13
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-TZJVTH3F.js → chunk-27VEKQQA.js} +14 -14
- package/dist/{chunk-JO5AT6QT.js → chunk-3ZVFLMFQ.js} +1 -1
- package/dist/chunk-3ZVFLMFQ.js.map +1 -0
- package/dist/{chunk-AD2KK5MT.js → chunk-5CFHYQU3.js} +9 -3
- package/dist/chunk-5CFHYQU3.js.map +1 -0
- package/dist/{chunk-7XH7VJN4.js → chunk-5S7Z5SHA.js} +2 -2
- package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
- package/dist/{chunk-EJCVMPIM.js → chunk-7LMEY5KZ.js} +15 -1
- package/dist/chunk-7LMEY5KZ.js.map +1 -0
- package/dist/{chunk-3F4N4WDH.js → chunk-AHLPZ2SF.js} +2 -2
- package/dist/{chunk-AH3M6F4X.js → chunk-CDGGQGUU.js} +2 -2
- package/dist/{chunk-ZHKD4EZM.js → chunk-CK6RI52P.js} +3 -3
- package/dist/{chunk-6C35R7P4.js → chunk-DJCKXI4M.js} +2 -2
- package/dist/{chunk-GL3SMLMZ.js → chunk-DLPXS7BE.js} +109 -85
- package/dist/chunk-DLPXS7BE.js.map +1 -0
- package/dist/{chunk-NMMKRVUF.js → chunk-EIKBKQ6L.js} +6 -6
- package/dist/{chunk-O3BZ6PFY.js → chunk-F3FC7SMO.js} +2 -2
- package/dist/{chunk-ZW6OKHVX.js → chunk-FKCJH7KP.js} +2 -2
- package/dist/{chunk-DQPPRXFO.js → chunk-HQ7QZJM5.js} +2 -2
- package/dist/{chunk-UYZUJ4EB.js → chunk-I2THZUAF.js} +2 -2
- package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
- package/dist/{chunk-KIPLG7S7.js → chunk-JKLJ6A7W.js} +2 -2
- package/dist/{chunk-HXDKC3BI.js → chunk-JU3GHO6J.js} +2 -2
- package/dist/{chunk-74MJH7I3.js → chunk-KGNFFCZJ.js} +12 -3
- package/dist/chunk-KGNFFCZJ.js.map +1 -0
- package/dist/{chunk-Q6JPMCPO.js → chunk-KKASNHGF.js} +62 -57
- package/dist/chunk-KKASNHGF.js.map +1 -0
- package/dist/{chunk-R2SJDIPO.js → chunk-KPXAJ77O.js} +9 -7
- package/dist/chunk-KPXAJ77O.js.map +1 -0
- package/dist/{chunk-SYNYI4ZM.js → chunk-L5HJQC36.js} +2 -2
- package/dist/{chunk-VXMWDXIJ.js → chunk-LC7OODWZ.js} +2 -2
- package/dist/{chunk-CNWC274T.js → chunk-MLT5EG6K.js} +28 -28
- package/dist/{chunk-M5QKGHCR.js → chunk-OC35KQIN.js} +6 -2
- package/dist/chunk-OC35KQIN.js.map +1 -0
- package/dist/chunk-OKUEZOGW.js +27 -0
- package/dist/chunk-OKUEZOGW.js.map +1 -0
- package/dist/{chunk-PKGWJFNR.js → chunk-OVMS3DCB.js} +2 -2
- package/dist/chunk-PDJTHRAW.js +36 -0
- package/dist/chunk-PDJTHRAW.js.map +1 -0
- package/dist/{chunk-ZUFJ7ZTR.js → chunk-PHXCPVSX.js} +6 -2
- package/dist/chunk-PHXCPVSX.js.map +1 -0
- package/dist/{chunk-57INMZ6F.js → chunk-POUD5BLV.js} +2 -2
- package/dist/{chunk-KTOH7EJE.js → chunk-PV7E2FGY.js} +307 -43
- package/dist/chunk-PV7E2FGY.js.map +1 -0
- package/dist/{chunk-LRGSLV7M.js → chunk-PYHOAXTI.js} +4 -4
- package/dist/{chunk-WKE4VXSZ.js → chunk-QKVX4Y6D.js} +2 -2
- package/dist/{chunk-FXGC5UUH.js → chunk-RT7IP3D3.js} +2 -2
- package/dist/{chunk-CHVU4RE5.js → chunk-SKXBOZDF.js} +3 -3
- package/dist/{chunk-TVRPRFUB.js → chunk-TY43GBX2.js} +9 -7
- package/dist/chunk-TY43GBX2.js.map +1 -0
- package/dist/{chunk-UVIQHSS7.js → chunk-UP25ZZCN.js} +9 -3
- package/dist/chunk-UP25ZZCN.js.map +1 -0
- package/dist/{chunk-GKCCHHZK.js → chunk-UXKMMNJD.js} +4 -4
- package/dist/{chunk-XFTPUXCE.js → chunk-UXWGPODH.js} +4 -4
- package/dist/{chunk-2FSHROYK.js → chunk-V53GJUSL.js} +8 -8
- package/dist/{chunk-LKT755LP.js → chunk-VA4WSW54.js} +2 -2
- package/dist/{chunk-FYKIEOG6.js → chunk-VCFADTFS.js} +3 -3
- package/dist/{chunk-VF4XKTX3.js → chunk-VZ5OJE4X.js} +10 -2
- package/dist/chunk-VZ5OJE4X.js.map +1 -0
- package/dist/{chunk-AWJ2FHCF.js → chunk-X47LWMKA.js} +49 -21
- package/dist/chunk-X47LWMKA.js.map +1 -0
- package/dist/{chunk-VMGMY2AI.js → chunk-XG5CGR3K.js} +8 -8
- package/dist/{chunk-VMGMY2AI.js.map → chunk-XG5CGR3K.js.map} +1 -1
- package/dist/{chunk-T7VNN3ZM.js → chunk-XVLEGMJI.js} +2 -2
- package/dist/{chunk-CRFTLUWT.js → chunk-Y5U35ZVS.js} +2 -2
- package/dist/{chunk-UHGHTOR5.js → chunk-YGAOLHKN.js} +7 -7
- package/dist/{chunk-QDCJNRKK.js → chunk-YXOAQAH2.js} +3 -3
- package/dist/{cli-BqyGVyOe.d.ts → cli-DACyuK8-.d.ts} +5 -5
- package/dist/cli.d.ts +8 -8
- package/dist/cli.js +65 -63
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +14 -12
- 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 +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +14 -12
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +14 -12
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-provenance-check.js +6 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/consolidation-undo.js +8 -1
- package/dist/consolidation-undo.js.map +1 -1
- package/dist/contradiction/index.d.ts +2 -2
- 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 +2 -2
- package/dist/entity-retrieval.js +14 -12
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +5 -5
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-faithfulness.js +3 -3
- 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-judge.js +3 -3
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +3 -3
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/fallback-llm.js +2 -2
- package/dist/{forget-KHF7A44H.js → forget-CQCLB5LB.js} +2 -2
- 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/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +19 -0
- package/dist/in-flight-reads.js +16 -0
- package/dist/in-flight-reads.js.map +1 -0
- package/dist/index.d.ts +13 -13
- package/dist/index.js +105 -103
- 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 +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +14 -12
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +14 -12
- package/dist/maintenance/rebuild-memory-projection.js +15 -13
- package/dist/mcp-memory-inspector-app.d.ts +6 -6
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +24 -13
- package/dist/memory-cache.js +3 -1
- package/dist/memory-corpus-version.d.ts +19 -0
- package/dist/memory-corpus-version.js +13 -0
- package/dist/memory-corpus-version.js.map +1 -0
- 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 +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/migrate.js +21 -19
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +14 -12
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +2 -2
- package/dist/operator-toolkit.js +26 -24
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +16 -14
- package/dist/{orchestrator-37Cx7yyw.d.ts → orchestrator-D83AvrPf.d.ts} +4 -4
- package/dist/orchestrator.d.ts +5 -5
- package/dist/orchestrator.js +88 -86
- package/dist/page-versioning.js +5 -1
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-C6onljHc.d.ts → pipeline-BSqGpYl1.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{qmd-J08G7MEm.d.ts → qmd-CutnpRVe.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/qmd.js +2 -2
- 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-planner-llm.js +2 -2
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.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/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +2 -2
- 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/factory.d.ts +1 -1
- package/dist/search/factory.js +6 -6
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +12 -12
- 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-BFtoVosl.d.ts → semantic-consolidation-btIVRC4l.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +15 -13
- package/dist/semantic-rule-promotion.js +14 -12
- package/dist/semantic-rule-verifier.d.ts +5 -1
- package/dist/semantic-rule-verifier.js +14 -12
- 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-CXjwBr69.d.ts → storage-BVv_Mqa0.d.ts} +133 -3
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +13 -11
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +3 -3
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.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 +2 -2
- 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/capsule-import.js +6 -2
- package/dist/transfer/import-sqlite.js +2 -2
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-DpxqC8dI.d.ts → types-Cmkz7csa.d.ts} +27 -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.d.ts +4 -0
- package/dist/verified-recall.js +14 -12
- package/package.json +12 -2
- package/src/binary-lifecycle/pipeline.ts +22 -0
- package/src/cli.ts +8 -0
- package/src/compounding/engine.ts +9 -1
- package/src/config.ts +14 -0
- package/src/consolidation-undo.ts +8 -0
- package/src/curation/index.ts +9 -0
- package/src/in-flight-reads.ts +63 -0
- package/src/maintenance/memory-governance.ts +6 -0
- package/src/memory-cache-hot.test.ts +131 -0
- package/src/memory-cache.ts +146 -22
- package/src/memory-corpus-version.ts +45 -0
- package/src/namespaces/storage.ts +20 -1
- package/src/operator-toolkit.ts +7 -1
- package/src/orchestration/recall-internal.ts +6 -3
- package/src/orchestrator.ts +25 -1
- package/src/page-versioning.ts +14 -1
- package/src/provenance-frontmatter.test.ts +6 -0
- package/src/review/index.ts +16 -8
- package/src/semantic-rule-promotion.ts +9 -1
- package/src/semantic-rule-verifier.ts +19 -6
- package/src/spaces/index.ts +10 -0
- package/src/storage/memory-read-store.ts +9 -2
- package/src/storage-contract/cache-coherence.test.ts +242 -0
- package/src/storage-contract/namespace-routing.test.ts +26 -1
- package/src/storage.ts +513 -31
- package/src/transfer/capsule-cache-coherence.test.ts +107 -0
- package/src/transfer/capsule-import.ts +8 -0
- package/src/transfer/capsule-merge.ts +7 -0
- package/src/types.ts +27 -0
- package/src/verified-recall.ts +20 -6
- package/dist/capsule-merge-GK5E647P.js.map +0 -1
- package/dist/chunk-74MJH7I3.js.map +0 -1
- package/dist/chunk-AD2KK5MT.js.map +0 -1
- package/dist/chunk-AWJ2FHCF.js.map +0 -1
- package/dist/chunk-EJCVMPIM.js.map +0 -1
- package/dist/chunk-GL3SMLMZ.js.map +0 -1
- package/dist/chunk-JO5AT6QT.js.map +0 -1
- package/dist/chunk-KTOH7EJE.js.map +0 -1
- package/dist/chunk-M5QKGHCR.js.map +0 -1
- package/dist/chunk-Q6JPMCPO.js.map +0 -1
- package/dist/chunk-R2SJDIPO.js.map +0 -1
- package/dist/chunk-TVRPRFUB.js.map +0 -1
- package/dist/chunk-UVIQHSS7.js.map +0 -1
- package/dist/chunk-VF4XKTX3.js.map +0 -1
- package/dist/chunk-ZUFJ7ZTR.js.map +0 -1
- /package/dist/{auto-sync-676N6N6Y.js.map → auto-sync-AAMTVZMK.js.map} +0 -0
- /package/dist/{chunk-TZJVTH3F.js.map → chunk-27VEKQQA.js.map} +0 -0
- /package/dist/{chunk-7XH7VJN4.js.map → chunk-5S7Z5SHA.js.map} +0 -0
- /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
- /package/dist/{chunk-3F4N4WDH.js.map → chunk-AHLPZ2SF.js.map} +0 -0
- /package/dist/{chunk-AH3M6F4X.js.map → chunk-CDGGQGUU.js.map} +0 -0
- /package/dist/{chunk-ZHKD4EZM.js.map → chunk-CK6RI52P.js.map} +0 -0
- /package/dist/{chunk-6C35R7P4.js.map → chunk-DJCKXI4M.js.map} +0 -0
- /package/dist/{chunk-NMMKRVUF.js.map → chunk-EIKBKQ6L.js.map} +0 -0
- /package/dist/{chunk-O3BZ6PFY.js.map → chunk-F3FC7SMO.js.map} +0 -0
- /package/dist/{chunk-ZW6OKHVX.js.map → chunk-FKCJH7KP.js.map} +0 -0
- /package/dist/{chunk-DQPPRXFO.js.map → chunk-HQ7QZJM5.js.map} +0 -0
- /package/dist/{chunk-UYZUJ4EB.js.map → chunk-I2THZUAF.js.map} +0 -0
- /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
- /package/dist/{chunk-KIPLG7S7.js.map → chunk-JKLJ6A7W.js.map} +0 -0
- /package/dist/{chunk-HXDKC3BI.js.map → chunk-JU3GHO6J.js.map} +0 -0
- /package/dist/{chunk-SYNYI4ZM.js.map → chunk-L5HJQC36.js.map} +0 -0
- /package/dist/{chunk-VXMWDXIJ.js.map → chunk-LC7OODWZ.js.map} +0 -0
- /package/dist/{chunk-CNWC274T.js.map → chunk-MLT5EG6K.js.map} +0 -0
- /package/dist/{chunk-PKGWJFNR.js.map → chunk-OVMS3DCB.js.map} +0 -0
- /package/dist/{chunk-57INMZ6F.js.map → chunk-POUD5BLV.js.map} +0 -0
- /package/dist/{chunk-LRGSLV7M.js.map → chunk-PYHOAXTI.js.map} +0 -0
- /package/dist/{chunk-WKE4VXSZ.js.map → chunk-QKVX4Y6D.js.map} +0 -0
- /package/dist/{chunk-FXGC5UUH.js.map → chunk-RT7IP3D3.js.map} +0 -0
- /package/dist/{chunk-CHVU4RE5.js.map → chunk-SKXBOZDF.js.map} +0 -0
- /package/dist/{chunk-GKCCHHZK.js.map → chunk-UXKMMNJD.js.map} +0 -0
- /package/dist/{chunk-XFTPUXCE.js.map → chunk-UXWGPODH.js.map} +0 -0
- /package/dist/{chunk-2FSHROYK.js.map → chunk-V53GJUSL.js.map} +0 -0
- /package/dist/{chunk-LKT755LP.js.map → chunk-VA4WSW54.js.map} +0 -0
- /package/dist/{chunk-FYKIEOG6.js.map → chunk-VCFADTFS.js.map} +0 -0
- /package/dist/{chunk-T7VNN3ZM.js.map → chunk-XVLEGMJI.js.map} +0 -0
- /package/dist/{chunk-CRFTLUWT.js.map → chunk-Y5U35ZVS.js.map} +0 -0
- /package/dist/{chunk-UHGHTOR5.js.map → chunk-YGAOLHKN.js.map} +0 -0
- /package/dist/{chunk-QDCJNRKK.js.map → chunk-YXOAQAH2.js.map} +0 -0
- /package/dist/{forget-KHF7A44H.js.map → forget-CQCLB5LB.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "9.7.
|
|
3
|
+
"version": "9.7.9",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -1090,6 +1090,16 @@
|
|
|
1090
1090
|
"remnic-source": "./src/importance.ts",
|
|
1091
1091
|
"import": "./dist/importance.js"
|
|
1092
1092
|
},
|
|
1093
|
+
"./in-flight-reads": {
|
|
1094
|
+
"types": "./dist/in-flight-reads.d.ts",
|
|
1095
|
+
"remnic-source": "./src/in-flight-reads.ts",
|
|
1096
|
+
"import": "./dist/in-flight-reads.js"
|
|
1097
|
+
},
|
|
1098
|
+
"./in-flight-reads.js": {
|
|
1099
|
+
"types": "./dist/in-flight-reads.d.ts",
|
|
1100
|
+
"remnic-source": "./src/in-flight-reads.ts",
|
|
1101
|
+
"import": "./dist/in-flight-reads.js"
|
|
1102
|
+
},
|
|
1093
1103
|
"./index": {
|
|
1094
1104
|
"types": "./dist/index.d.ts",
|
|
1095
1105
|
"remnic-source": "./src/index.ts",
|
|
@@ -3000,7 +3010,7 @@
|
|
|
3000
3010
|
"core"
|
|
3001
3011
|
],
|
|
3002
3012
|
"peerDependencies": {
|
|
3003
|
-
"@remnic/coding-graph": "^9.7.
|
|
3013
|
+
"@remnic/coding-graph": "^9.7.9"
|
|
3004
3014
|
},
|
|
3005
3015
|
"peerDependenciesMeta": {
|
|
3006
3016
|
"@remnic/coding-graph": {
|
|
@@ -18,6 +18,21 @@ import type {
|
|
|
18
18
|
import type { BinaryStorageBackend } from "./backend.js";
|
|
19
19
|
import { readManifest, writeManifest } from "./manifest.js";
|
|
20
20
|
import { scanForBinaries } from "./scanner.js";
|
|
21
|
+
import { bumpMemoryCorpusVersionForDir } from "../memory-corpus-version.js";
|
|
22
|
+
import { RECALL_FALLBACK_DIRS } from "../utils/category-dir.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* True when `mdPath` is an active recall-corpus memory file under `memoryDir`
|
|
26
|
+
* (a top-level RECALL_FALLBACK_DIRS category), so a redirect rewrite of it must
|
|
27
|
+
* bump the corpus sentinel to self-heal a warm hot-memories cache (#1902).
|
|
28
|
+
* entities/, artifacts/, and other non-recall paths need no bump.
|
|
29
|
+
*/
|
|
30
|
+
function isRecallCorpusMarkdown(mdPath: string, memoryDir: string): boolean {
|
|
31
|
+
const rel = path.relative(memoryDir, mdPath);
|
|
32
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) return false;
|
|
33
|
+
const top = rel.split(path.sep)[0];
|
|
34
|
+
return (RECALL_FALLBACK_DIRS as readonly string[]).includes(top);
|
|
35
|
+
}
|
|
21
36
|
|
|
22
37
|
/** Minimal logger interface so we don't depend on the full logger module. */
|
|
23
38
|
interface PipelineLogger {
|
|
@@ -305,6 +320,13 @@ async function stageRedirect(
|
|
|
305
320
|
for (const update of updates) {
|
|
306
321
|
try {
|
|
307
322
|
await writeMarkdownFile(update.mdPath, update.content);
|
|
323
|
+
// The redirect rewrote an on-disk memory file out-of-band (not through a
|
|
324
|
+
// StorageManager mutation). Bump the corpus sentinel per write so a warm
|
|
325
|
+
// hot-memories cache rescans — mid-batch too, never serving pre-redirect
|
|
326
|
+
// content (#1902). Only recall-corpus files affect that cache.
|
|
327
|
+
if (isRecallCorpusMarkdown(update.mdPath, memoryDir)) {
|
|
328
|
+
bumpMemoryCorpusVersionForDir(memoryDir);
|
|
329
|
+
}
|
|
308
330
|
} catch (err) {
|
|
309
331
|
writeFailCount++;
|
|
310
332
|
const msg = `redirect write failed for ${update.mdPath}: ${err instanceof Error ? err.message : String(err)}`;
|
package/src/cli.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { resolveNamespaceCapabilities,
|
|
|
10
10
|
resolveIdentityContinuityCapabilities,
|
|
11
11
|
resolveSecurityCapabilities, resolveEvalCapabilities, resolveConsolidationCapabilities, resolveRecallAuxiliaryCapabilities } from "./capabilities.js";
|
|
12
12
|
import { ThreadingManager } from "./threading.js";
|
|
13
|
+
import { bumpMemoryCorpusVersionForDir } from "./memory-corpus-version.js";
|
|
13
14
|
import { utcDayRange } from "./transcript.js";
|
|
14
15
|
import { runWearablesCliCommand } from "./wearables/cli.js";
|
|
15
16
|
import { registerResearchStatusCommands } from "./cli/research-status-commands.js";
|
|
@@ -7495,6 +7496,11 @@ export function registerCli(
|
|
|
7495
7496
|
try {
|
|
7496
7497
|
await unlink(filePath);
|
|
7497
7498
|
deleted += 1;
|
|
7499
|
+
// Per-delete corpus bump (Cursor Medium, #1902): dedupe removes
|
|
7500
|
+
// active memory files directly (bypassing StorageManager), so bump
|
|
7501
|
+
// each delete to keep a same-process or peer readAllMemories from
|
|
7502
|
+
// recalling a duplicate that was already removed mid-batch.
|
|
7503
|
+
bumpMemoryCorpusVersionForDir(memoryDir);
|
|
7498
7504
|
} catch (err) {
|
|
7499
7505
|
console.log(` failed to delete ${filePath}: ${String(err)}`);
|
|
7500
7506
|
}
|
|
@@ -7556,6 +7562,8 @@ export function registerCli(
|
|
|
7556
7562
|
try {
|
|
7557
7563
|
await unlink(filePath);
|
|
7558
7564
|
deleted += 1;
|
|
7565
|
+
// Per-delete corpus bump (Cursor Medium, #1902) — see dedupe-exact.
|
|
7566
|
+
bumpMemoryCorpusVersionForDir(memoryDir);
|
|
7559
7567
|
} catch (err) {
|
|
7560
7568
|
console.log(` failed to delete ${filePath}: ${String(err)}`);
|
|
7561
7569
|
}
|
|
@@ -321,7 +321,15 @@ function normalizePromotionWhitespace(value: string): string {
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
function stripTrailingPromotionPunctuation(value: string): string {
|
|
324
|
-
|
|
324
|
+
// Linear trailing-punctuation strip (no `/[,:;]+$/` — CodeQL flags that as a
|
|
325
|
+
// polynomial-backtracking ReDoS on long runs of ',;:'). Walk back once, O(n).
|
|
326
|
+
let end = value.length;
|
|
327
|
+
while (end > 0) {
|
|
328
|
+
const ch = value[end - 1];
|
|
329
|
+
if (ch === "," || ch === ":" || ch === ";") end--;
|
|
330
|
+
else break;
|
|
331
|
+
}
|
|
332
|
+
return value.slice(0, end).trim();
|
|
325
333
|
}
|
|
326
334
|
|
|
327
335
|
function extractExplicitIfThenRule(value: string): string | null {
|
package/src/config.ts
CHANGED
|
@@ -2106,6 +2106,20 @@ export function parseConfig(
|
|
|
2106
2106
|
// opt out with recallMemoryWorthFilterEnabled=false.
|
|
2107
2107
|
recallMemoryWorthFilterEnabled:
|
|
2108
2108
|
coerceBool(cfg.recallMemoryWorthFilterEnabled) ?? true,
|
|
2109
|
+
// Hot-memories result cache (issue #1902). Default on: readAllMemories()
|
|
2110
|
+
// serves from the version-keyed in-process corpus cache. coerceBool
|
|
2111
|
+
// handles "false"/"0" string forms; set false to force disk scans.
|
|
2112
|
+
hotMemoriesCacheEnabled: coerceBool(cfg.hotMemoriesCacheEnabled) ?? true,
|
|
2113
|
+
// External-edit safety net (issue #1902): the version sentinel gives
|
|
2114
|
+
// immediate coherence for cooperating writers, but direct user/git/editor
|
|
2115
|
+
// writes to memory files (which the project supports) don't bump it. Bound
|
|
2116
|
+
// the hot cache's staleness with a TTL so such edits self-heal. Default 60s;
|
|
2117
|
+
// 0 disables the TTL (version invalidation only — max perf for pure-daemon
|
|
2118
|
+
// deployments with no external edits). coerceNumber handles string forms.
|
|
2119
|
+
hotMemoriesCacheTtlMs: (() => {
|
|
2120
|
+
const n = coerceNumber(cfg.hotMemoriesCacheTtlMs);
|
|
2121
|
+
return n !== undefined && n >= 0 ? n : 60_000;
|
|
2122
|
+
})(),
|
|
2109
2123
|
recallMemoryWorthHalfLifeMs: (() => {
|
|
2110
2124
|
const n = coerceNumber(cfg.recallMemoryWorthHalfLifeMs);
|
|
2111
2125
|
return n !== undefined && n >= 0 ? n : 0;
|
|
@@ -25,6 +25,7 @@ import { mkdir, writeFile, access, realpath, lstat } from "node:fs/promises";
|
|
|
25
25
|
import { constants as fsConstants } from "node:fs";
|
|
26
26
|
import type { StorageManager } from "./storage.js";
|
|
27
27
|
import type { VersioningConfig } from "./page-versioning.js";
|
|
28
|
+
import { bumpMemoryCorpusVersionForDir } from "./memory-corpus-version.js";
|
|
28
29
|
import { getVersion } from "./page-versioning.js";
|
|
29
30
|
|
|
30
31
|
/**
|
|
@@ -646,6 +647,13 @@ export async function runConsolidationUndo(options: {
|
|
|
646
647
|
// write fails with EEXIST instead of silently overwriting the new
|
|
647
648
|
// file (PR #637 round-11 review, codex P1).
|
|
648
649
|
await writeFile(p.sourcePath, p.content, { encoding: "utf-8", flag: "wx" });
|
|
650
|
+
// Restore writes an active memory file back out-of-band (direct wx write,
|
|
651
|
+
// not a StorageManager mutation). Bump the corpus sentinel per successful
|
|
652
|
+
// restore so a warm hot-memories cache rescans — a later restore failure
|
|
653
|
+
// returns before archiveMemory() runs, so without this the already-written
|
|
654
|
+
// restores would stay invisible to recall until an unrelated mutation or
|
|
655
|
+
// restart (esp. with hotMemoriesCacheTtlMs: 0). Issue #1902, Codex Medium.
|
|
656
|
+
bumpMemoryCorpusVersionForDir(memoryDir);
|
|
649
657
|
result.restores.push({
|
|
650
658
|
entry: p.entry,
|
|
651
659
|
sourcePath: p.sourcePath,
|
package/src/curation/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import fs from "node:fs";
|
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import crypto from "node:crypto";
|
|
11
11
|
import { getCategoryDir, ALL_CATEGORY_DIRS } from "../utils/category-dir.js";
|
|
12
|
+
import { bumpMemoryCorpusVersionForDir } from "../memory-corpus-version.js";
|
|
12
13
|
|
|
13
14
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
14
15
|
|
|
@@ -202,6 +203,10 @@ export async function curate(options: CurateOptions): Promise<CurateResult> {
|
|
|
202
203
|
}
|
|
203
204
|
}
|
|
204
205
|
|
|
206
|
+
// Per-statement corpus bumps happen in writeStatement (below) so a
|
|
207
|
+
// same-process readAllMemories mid-batch never serves a stale/partial corpus
|
|
208
|
+
// (Cursor Medium, #1902); no after-loop bump needed here.
|
|
209
|
+
|
|
205
210
|
return {
|
|
206
211
|
statements,
|
|
207
212
|
filesProcessed,
|
|
@@ -471,6 +476,10 @@ function writeStatement(stmt: CuratedStatement, memoryDir: string): string {
|
|
|
471
476
|
const body = `${frontmatter}\n\n${stmt.content}\n`;
|
|
472
477
|
|
|
473
478
|
fs.writeFileSync(filePath, body);
|
|
479
|
+
// Bump the corpus sentinel per write (out-of-band create) so a concurrent
|
|
480
|
+
// same-process readAllMemories rescans and sees this statement immediately,
|
|
481
|
+
// never a stale/partial corpus mid-batch (Cursor Medium, #1902).
|
|
482
|
+
bumpMemoryCorpusVersionForDir(memoryDir);
|
|
474
483
|
return filePath;
|
|
475
484
|
}
|
|
476
485
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { MemoryFile } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* In-flight readAllMemories() dedup registry (issue #1902).
|
|
5
|
+
*
|
|
6
|
+
* NOT a cache layer — it holds transient per-(directory, secure-store key) scan
|
|
7
|
+
* promises so temporally-overlapping reads collapse onto ONE disk scan; the slot
|
|
8
|
+
* is deleted when the promise settles. It lives in its own module (not
|
|
9
|
+
* memory-cache.ts) so it is not swept by the ALL_CACHE_LAYERS invalidation
|
|
10
|
+
* contract, yet remains importable by both StorageManager and the out-of-band
|
|
11
|
+
* corpus-bump helper (memory-corpus-version.ts).
|
|
12
|
+
*
|
|
13
|
+
* Keyed by baseDir AND secure-store key identity (Codex P1 / Cursor Medium):
|
|
14
|
+
* an unlocked manager's decrypted scan must NOT be handed to a locked or
|
|
15
|
+
* differently-keyed manager for the same dir — that would bypass the keyId
|
|
16
|
+
* isolation getCachedMemories enforces. Locked/plaintext stores use keyId "".
|
|
17
|
+
*/
|
|
18
|
+
const inFlightReadsByKey = new Map<string, Promise<MemoryFile[]>>();
|
|
19
|
+
|
|
20
|
+
const DIR_SEP = "\u0000";
|
|
21
|
+
|
|
22
|
+
function composeKey(baseDir: string, keyId: string): string {
|
|
23
|
+
return `${baseDir}${DIR_SEP}${keyId}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getInFlightRead(baseDir: string, keyId = ""): Promise<MemoryFile[]> | undefined {
|
|
27
|
+
return inFlightReadsByKey.get(composeKey(baseDir, keyId));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function setInFlightRead(baseDir: string, keyId: string, read: Promise<MemoryFile[]>): void {
|
|
31
|
+
inFlightReadsByKey.set(composeKey(baseDir, keyId), read);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Delete the in-flight slot for one (baseDir, keyId). When `expected` is given,
|
|
36
|
+
* only deletes if the slot still holds that exact promise (owner-only clear).
|
|
37
|
+
*/
|
|
38
|
+
export function deleteInFlightRead(
|
|
39
|
+
baseDir: string,
|
|
40
|
+
keyId = "",
|
|
41
|
+
expected?: Promise<MemoryFile[]>,
|
|
42
|
+
): void {
|
|
43
|
+
const key = composeKey(baseDir, keyId);
|
|
44
|
+
if (expected === undefined || inFlightReadsByKey.get(key) === expected) {
|
|
45
|
+
inFlightReadsByKey.delete(key);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Drop EVERY in-flight slot for `baseDir` across all secure-store identities.
|
|
51
|
+
* Used by mutations / invalidation / out-of-band corpus bumps: after the corpus
|
|
52
|
+
* changes, no reader of any key identity may attach to a pre-mutation scan.
|
|
53
|
+
*/
|
|
54
|
+
export function deleteInFlightReadsForDir(baseDir: string): void {
|
|
55
|
+
const prefix = `${baseDir}${DIR_SEP}`;
|
|
56
|
+
for (const key of inFlightReadsByKey.keys()) {
|
|
57
|
+
if (key.startsWith(prefix)) inFlightReadsByKey.delete(key);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function clearInFlightReads(): void {
|
|
62
|
+
inFlightReadsByKey.clear();
|
|
63
|
+
}
|
|
@@ -4,6 +4,7 @@ import { StorageManager } from "../storage.js";
|
|
|
4
4
|
import { decideLifecycleTransition } from "../lifecycle.js";
|
|
5
5
|
import type { MemoryFile, MemoryStatus } from "../types.js";
|
|
6
6
|
import { RECALL_FALLBACK_DIRS } from "../utils/category-dir.js";
|
|
7
|
+
import { bumpMemoryCorpusVersionForDir } from "../memory-corpus-version.js";
|
|
7
8
|
import { assertPathInsideRoot } from "../utils/path-containment.js";
|
|
8
9
|
|
|
9
10
|
export type MemoryGovernanceMode = "shadow" | "apply";
|
|
@@ -1015,6 +1016,11 @@ export async function restoreMemoryGovernanceRun(
|
|
|
1015
1016
|
await mkdir(path.dirname(entry.originalPath), { recursive: true });
|
|
1016
1017
|
await writeFile(entry.originalPath, entry.beforeRaw, "utf-8");
|
|
1017
1018
|
restoredActions += 1;
|
|
1019
|
+
// Restore writes/removes memory files directly (out-of-band — not through a
|
|
1020
|
+
// StorageManager mutation). Bump the corpus sentinel PER entry so a
|
|
1021
|
+
// same-process or peer readAllMemories rescans mid-batch and never serves a
|
|
1022
|
+
// partially-restored corpus (Cursor Medium, #1902).
|
|
1023
|
+
bumpMemoryCorpusVersionForDir(options.memoryDir);
|
|
1018
1024
|
}
|
|
1019
1025
|
|
|
1020
1026
|
return {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1902 — unit tests for the hot-memories result cache layer.
|
|
3
|
+
*
|
|
4
|
+
* These pin the low-level cache contract that StorageManager.readAllMemories()
|
|
5
|
+
* relies on: version-keyed round-trip, the version-0 and version-mismatch null
|
|
6
|
+
* guards, in-place patch-on-write / patch-on-delete, and the two invalidation
|
|
7
|
+
* shapes (wholesale drop vs derived/global-only keep).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import test from "node:test";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
clearMemoryCache,
|
|
15
|
+
getCachedMemories,
|
|
16
|
+
invalidateAllForDir,
|
|
17
|
+
invalidateDerivedAndGlobalForDir,
|
|
18
|
+
setCachedMemories,
|
|
19
|
+
updateCacheOnDelete,
|
|
20
|
+
updateCacheOnWrite,
|
|
21
|
+
} from "./memory-cache.js";
|
|
22
|
+
import type { MemoryFile } from "./types.js";
|
|
23
|
+
|
|
24
|
+
function mem(p: string, content = "x"): MemoryFile {
|
|
25
|
+
return {
|
|
26
|
+
path: p,
|
|
27
|
+
frontmatter: { id: p, category: "fact" },
|
|
28
|
+
content,
|
|
29
|
+
} as unknown as MemoryFile;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test("hot cache: round-trips at a matching version", () => {
|
|
33
|
+
const dir = "/tmp/remnic-hot-unit-a";
|
|
34
|
+
clearMemoryCache();
|
|
35
|
+
setCachedMemories(dir, [mem("a.md"), mem("b.md")], 3);
|
|
36
|
+
const got = getCachedMemories(dir, 3);
|
|
37
|
+
assert.ok(got, "expected a cache hit at the matching version");
|
|
38
|
+
assert.deepEqual(got!.map((m) => m.path).sort(), ["a.md", "b.md"]);
|
|
39
|
+
clearMemoryCache();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("hot cache: version 0 always misses (fresh/test dirs)", () => {
|
|
43
|
+
const dir = "/tmp/remnic-hot-unit-b";
|
|
44
|
+
clearMemoryCache();
|
|
45
|
+
setCachedMemories(dir, [mem("a.md")], 0);
|
|
46
|
+
assert.equal(getCachedMemories(dir, 0), null, "version 0 must never serve a cache hit");
|
|
47
|
+
clearMemoryCache();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("hot cache: version mismatch misses (never serves stale)", () => {
|
|
51
|
+
const dir = "/tmp/remnic-hot-unit-c";
|
|
52
|
+
clearMemoryCache();
|
|
53
|
+
setCachedMemories(dir, [mem("a.md")], 5);
|
|
54
|
+
assert.ok(getCachedMemories(dir, 5), "same version hits");
|
|
55
|
+
assert.equal(getCachedMemories(dir, 6), null, "a bumped version must miss");
|
|
56
|
+
clearMemoryCache();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("hot cache: updateCacheOnWrite / updateCacheOnDelete mutate the entry in place", () => {
|
|
60
|
+
const dir = "/tmp/remnic-hot-unit-d";
|
|
61
|
+
clearMemoryCache();
|
|
62
|
+
setCachedMemories(dir, [mem("a.md")], 2);
|
|
63
|
+
updateCacheOnWrite(dir, mem("b.md", "new"));
|
|
64
|
+
const afterWrite = getCachedMemories(dir, 2);
|
|
65
|
+
assert.deepEqual(afterWrite!.map((m) => m.path).sort(), ["a.md", "b.md"], "write adds in place");
|
|
66
|
+
updateCacheOnWrite(dir, mem("a.md", "updated"));
|
|
67
|
+
assert.equal(
|
|
68
|
+
getCachedMemories(dir, 2)!.find((m) => m.path === "a.md")!.content,
|
|
69
|
+
"updated",
|
|
70
|
+
"write to an existing path replaces in place",
|
|
71
|
+
);
|
|
72
|
+
updateCacheOnDelete(dir, "a.md");
|
|
73
|
+
assert.deepEqual(getCachedMemories(dir, 2)!.map((m) => m.path), ["b.md"], "delete removes in place");
|
|
74
|
+
clearMemoryCache();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("hot cache: wholesale invalidateAllForDir drops the hot layer; invalidateDerivedAndGlobalForDir keeps it", () => {
|
|
78
|
+
const dir = "/tmp/remnic-hot-unit-e";
|
|
79
|
+
clearMemoryCache();
|
|
80
|
+
setCachedMemories(dir, [mem("a.md")], 4);
|
|
81
|
+
invalidateDerivedAndGlobalForDir(dir);
|
|
82
|
+
assert.ok(
|
|
83
|
+
getCachedMemories(dir, 4),
|
|
84
|
+
"invalidateDerivedAndGlobalForDir must NOT drop the hot layer (patch-on-write keeps it warm)",
|
|
85
|
+
);
|
|
86
|
+
invalidateAllForDir(dir);
|
|
87
|
+
assert.equal(getCachedMemories(dir, 4), null, "invalidateAllForDir must drop the hot layer");
|
|
88
|
+
clearMemoryCache();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("hot cache: keyId scopes entries — a mismatched secure-store key misses (#1902 Codex P1)", () => {
|
|
92
|
+
const dir = "/tmp/remnic-hot-unit-keyid";
|
|
93
|
+
clearMemoryCache();
|
|
94
|
+
// A keyed (unlocked encrypted) manager warms the cache with decrypted content.
|
|
95
|
+
setCachedMemories(dir, [mem("secret.md", "decrypted")], 5, "secure-store:key:1");
|
|
96
|
+
// A locked/unkeyed manager for the SAME dir must NOT read the decrypted entry.
|
|
97
|
+
assert.equal(
|
|
98
|
+
getCachedMemories(dir, 5, ""),
|
|
99
|
+
null,
|
|
100
|
+
"a manager without the matching key must miss the decrypted cache entry",
|
|
101
|
+
);
|
|
102
|
+
// A different key id also misses.
|
|
103
|
+
assert.equal(getCachedMemories(dir, 5, "secure-store:key:2"), null, "a different key id misses");
|
|
104
|
+
// The matching key id hits.
|
|
105
|
+
const got = getCachedMemories(dir, 5, "secure-store:key:1");
|
|
106
|
+
assert.ok(got, "the matching key id serves the cached corpus");
|
|
107
|
+
assert.deepEqual(got!.map((m) => m.path), ["secret.md"]);
|
|
108
|
+
clearMemoryCache();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("hot cache: TTL expires an entry so external edits self-heal (#1902 Codex P1)", (t) => {
|
|
112
|
+
const dir = "/tmp/remnic-hot-unit-ttl";
|
|
113
|
+
clearMemoryCache();
|
|
114
|
+
// Deterministic time control (no real timers): the entry's loadedAt is stamped
|
|
115
|
+
// from the mocked clock at set time; advancing it past the TTL forces a miss.
|
|
116
|
+
t.mock.timers.enable({ apis: ["Date"] });
|
|
117
|
+
try {
|
|
118
|
+
setCachedMemories(dir, [mem("a.md")], 9, "");
|
|
119
|
+
// ttl=0 disables the TTL: always a hit at the matching version.
|
|
120
|
+
assert.ok(getCachedMemories(dir, 9, "", 0), "ttl=0 disables expiry");
|
|
121
|
+
// Fresh entry within a generous TTL still hits.
|
|
122
|
+
assert.ok(getCachedMemories(dir, 9, "", 60_000), "fresh entry within TTL hits");
|
|
123
|
+
// Advance past the TTL: the entry is now a miss, forcing a rescan that would
|
|
124
|
+
// pick up any external filesystem edit that bypassed the version sentinel.
|
|
125
|
+
t.mock.timers.tick(120_000);
|
|
126
|
+
assert.equal(getCachedMemories(dir, 9, "", 60_000), null, "entry older than ttl is a miss");
|
|
127
|
+
} finally {
|
|
128
|
+
t.mock.timers.reset();
|
|
129
|
+
clearMemoryCache();
|
|
130
|
+
}
|
|
131
|
+
});
|
package/src/memory-cache.ts
CHANGED
|
@@ -5,49 +5,107 @@ interface CacheEntry {
|
|
|
5
5
|
memories: Map<string, MemoryFile>; // keyed by file path
|
|
6
6
|
version: number;
|
|
7
7
|
loadedAt: number;
|
|
8
|
+
// Secure-store key identity that decrypted these memories (issue #1902, Codex
|
|
9
|
+
// P1). "" for plaintext/locked stores. A manager only reads an entry whose
|
|
10
|
+
// keyId matches its own, so a locked/unkeyed instance never sees content
|
|
11
|
+
// another instance decrypted under a key for the same baseDir.
|
|
12
|
+
keyId: string;
|
|
13
|
+
// Effective TTL (ms) this entry was stored under (issue #1902, Codex Medium),
|
|
14
|
+
// so the opportunistic sweep can evict abandoned corpora that are never
|
|
15
|
+
// revisited. 0 = no TTL (never swept by age).
|
|
16
|
+
ttlMs: number;
|
|
8
17
|
}
|
|
9
18
|
|
|
10
19
|
// Module-level singleton — shared across all StorageManager instances and sessions
|
|
11
20
|
const hotCacheByDir = new Map<string, CacheEntry>();
|
|
12
21
|
const archiveCacheByDir = new Map<string, CacheEntry>();
|
|
13
22
|
|
|
14
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Evict every hot entry whose own TTL has elapsed (issue #1902, Codex Medium).
|
|
25
|
+
* Called opportunistically on each set so a long-running process that reads many
|
|
26
|
+
* namespace/temporary roots and never revisits some of them still releases their
|
|
27
|
+
* (potentially hundreds-of-MB) corpora within the TTL, instead of holding them
|
|
28
|
+
* resident until an explicit global clear. O(entries) over a small map.
|
|
29
|
+
*/
|
|
30
|
+
function sweepExpiredHotEntries(now: number): void {
|
|
31
|
+
for (const [dir, entry] of hotCacheByDir) {
|
|
32
|
+
if (entry.ttlMs > 0 && now - entry.loadedAt > entry.ttlMs) hotCacheByDir.delete(dir);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getCachedMemories(
|
|
37
|
+
baseDir: string,
|
|
38
|
+
currentVersion: number,
|
|
39
|
+
keyId = "",
|
|
40
|
+
ttlMs = 0,
|
|
41
|
+
): MemoryFile[] | null {
|
|
15
42
|
// Don't serve from cache when version tracking is unavailable (version=0).
|
|
16
43
|
// This ensures tests and fresh installs without a version file always read disk.
|
|
17
44
|
if (currentVersion === 0) return null;
|
|
18
45
|
const entry = hotCacheByDir.get(baseDir);
|
|
19
|
-
if (!entry || entry.version !== currentVersion) return null;
|
|
46
|
+
if (!entry || entry.version !== currentVersion || entry.keyId !== keyId) return null;
|
|
47
|
+
// TTL safety net (issue #1902): bound staleness from external filesystem edits
|
|
48
|
+
// that bypass the version sentinel. ttlMs <= 0 disables the check. On expiry
|
|
49
|
+
// evict the entry (not just return null) so a revisited-but-stale dir releases
|
|
50
|
+
// its resident corpus instead of lingering until an explicit clear.
|
|
51
|
+
if (ttlMs > 0 && Date.now() - entry.loadedAt > ttlMs) {
|
|
52
|
+
hotCacheByDir.delete(baseDir);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
20
55
|
return [...entry.memories.values()];
|
|
21
56
|
}
|
|
22
57
|
|
|
23
|
-
export function setCachedMemories(
|
|
58
|
+
export function setCachedMemories(
|
|
59
|
+
baseDir: string,
|
|
60
|
+
memories: MemoryFile[],
|
|
61
|
+
version: number,
|
|
62
|
+
keyId = "",
|
|
63
|
+
ttlMs = 0,
|
|
64
|
+
): void {
|
|
24
65
|
const map = new Map<string, MemoryFile>();
|
|
25
66
|
for (const m of memories) map.set(m.path, m);
|
|
26
|
-
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
hotCacheByDir.set(baseDir, { memories: map, version, loadedAt: now, keyId, ttlMs });
|
|
69
|
+
// Release abandoned corpora that expired but were never revisited (Codex Medium).
|
|
70
|
+
sweepExpiredHotEntries(now);
|
|
27
71
|
}
|
|
28
72
|
|
|
29
|
-
export function updateCacheOnWrite(baseDir: string, memory: MemoryFile): void {
|
|
73
|
+
export function updateCacheOnWrite(baseDir: string, memory: MemoryFile, keyId = ""): void {
|
|
30
74
|
const entry = hotCacheByDir.get(baseDir);
|
|
31
|
-
|
|
75
|
+
// Only mutate an entry that belongs to THIS key identity (issue #1902, Codex
|
|
76
|
+
// Medium). A concurrent differently-keyed manager may have replaced the
|
|
77
|
+
// single hotCacheByDir slot during an awaited parse; inserting our decrypted
|
|
78
|
+
// MemoryFile into its entry would leak plaintext across the key boundary.
|
|
79
|
+
if (entry && entry.keyId === keyId) entry.memories.set(memory.path, memory);
|
|
32
80
|
}
|
|
33
81
|
|
|
34
|
-
export function updateCacheOnDelete(baseDir: string, filePath: string): void {
|
|
82
|
+
export function updateCacheOnDelete(baseDir: string, filePath: string, keyId = ""): void {
|
|
35
83
|
const entry = hotCacheByDir.get(baseDir);
|
|
36
|
-
if (entry) entry.memories.delete(filePath);
|
|
84
|
+
if (entry && entry.keyId === keyId) entry.memories.delete(filePath);
|
|
37
85
|
}
|
|
38
|
-
|
|
39
86
|
// Archive cache — same pattern, separate store
|
|
40
|
-
export function getCachedArchivedMemories(
|
|
87
|
+
export function getCachedArchivedMemories(
|
|
88
|
+
baseDir: string,
|
|
89
|
+
currentVersion: number,
|
|
90
|
+
keyId = "",
|
|
91
|
+
): MemoryFile[] | null {
|
|
41
92
|
if (currentVersion === 0) return null;
|
|
42
93
|
const entry = archiveCacheByDir.get(baseDir);
|
|
43
|
-
if (!entry || entry.version !== currentVersion) return null;
|
|
94
|
+
if (!entry || entry.version !== currentVersion || entry.keyId !== keyId) return null;
|
|
44
95
|
return [...entry.memories.values()];
|
|
45
96
|
}
|
|
46
97
|
|
|
47
|
-
export function setCachedArchivedMemories(
|
|
98
|
+
export function setCachedArchivedMemories(
|
|
99
|
+
baseDir: string,
|
|
100
|
+
memories: MemoryFile[],
|
|
101
|
+
version: number,
|
|
102
|
+
keyId = "",
|
|
103
|
+
): void {
|
|
48
104
|
const map = new Map<string, MemoryFile>();
|
|
49
105
|
for (const m of memories) map.set(m.path, m);
|
|
50
|
-
|
|
106
|
+
// ttlMs: 0 — the archive cache has no age TTL; it is version-keyed and cleared
|
|
107
|
+
// via invalidation. The field satisfies the shared CacheEntry shape (#1902).
|
|
108
|
+
archiveCacheByDir.set(baseDir, { memories: map, version, loadedAt: Date.now(), keyId, ttlMs: 0 });
|
|
51
109
|
}
|
|
52
110
|
|
|
53
111
|
// Entity cache — same pattern as memory cache, but keyed by schema-aware parse inputs.
|
|
@@ -108,41 +166,77 @@ export function invalidateCachedEntities(baseDir: string): void {
|
|
|
108
166
|
interface DerivedCacheEntry<T> {
|
|
109
167
|
data: T;
|
|
110
168
|
sourceVersion: number; // matches the hot cache version it was derived from
|
|
169
|
+
keyId: string; // secure-store key identity of the corpus it was derived from (#1902)
|
|
170
|
+
loadedAt: number; // build time — bounds staleness from external edits via ttlMs (#1902)
|
|
111
171
|
}
|
|
112
172
|
|
|
113
173
|
const episodeMapByDir = new Map<string, DerivedCacheEntry<Map<string, MemoryFile>>>();
|
|
114
174
|
const ruleMemoriesByDir = new Map<string, DerivedCacheEntry<{ all: MemoryFile[]; byId: Map<string, MemoryFile> }>>();
|
|
115
175
|
|
|
116
|
-
|
|
117
|
-
|
|
176
|
+
export function getCachedEpisodeMap(
|
|
177
|
+
baseDir: string,
|
|
178
|
+
currentVersion: number,
|
|
179
|
+
keyId = "",
|
|
180
|
+
ttlMs = 0,
|
|
181
|
+
): Map<string, MemoryFile> | null {
|
|
118
182
|
if (currentVersion === 0) return null;
|
|
119
183
|
const entry = episodeMapByDir.get(baseDir);
|
|
120
|
-
if (!entry || entry.sourceVersion !== currentVersion) return null;
|
|
184
|
+
if (!entry || entry.sourceVersion !== currentVersion || entry.keyId !== keyId) return null;
|
|
185
|
+
// TTL safety net (issue #1902): the version sentinel doesn't move on a direct
|
|
186
|
+
// external edit, so bound how long a derived view is served before a rebuild
|
|
187
|
+
// (which re-reads via readAllMemories, itself TTL-bounded). ttlMs <= 0 disables.
|
|
188
|
+
if (ttlMs > 0 && Date.now() - entry.loadedAt > ttlMs) {
|
|
189
|
+
episodeMapByDir.delete(baseDir);
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
121
192
|
return entry.data;
|
|
122
193
|
}
|
|
123
194
|
|
|
124
195
|
/** Build and cache the episode memory map from the full memory list. */
|
|
125
|
-
export function setCachedEpisodeMap(
|
|
196
|
+
export function setCachedEpisodeMap(
|
|
197
|
+
baseDir: string,
|
|
198
|
+
memories: MemoryFile[],
|
|
199
|
+
version: number,
|
|
200
|
+
cache = true,
|
|
201
|
+
keyId = "",
|
|
202
|
+
): Map<string, MemoryFile> {
|
|
126
203
|
const map = new Map<string, MemoryFile>();
|
|
127
204
|
for (const m of memories) {
|
|
128
205
|
if (m.frontmatter.status === "archived" || m.frontmatter.status === "forgotten") continue;
|
|
129
206
|
if (m.frontmatter.memoryKind !== "episode") continue;
|
|
130
207
|
map.set(m.frontmatter.id, m);
|
|
131
208
|
}
|
|
132
|
-
episodeMapByDir.set(baseDir, { data: map, sourceVersion: version });
|
|
209
|
+
if (cache) episodeMapByDir.set(baseDir, { data: map, sourceVersion: version, keyId, loadedAt: Date.now() });
|
|
133
210
|
return map;
|
|
134
211
|
}
|
|
135
212
|
|
|
136
213
|
/** Get pre-filtered rule memories. Derived from hot cache. */
|
|
137
|
-
export function getCachedRuleMemories(
|
|
214
|
+
export function getCachedRuleMemories(
|
|
215
|
+
baseDir: string,
|
|
216
|
+
currentVersion: number,
|
|
217
|
+
keyId = "",
|
|
218
|
+
ttlMs = 0,
|
|
219
|
+
): { all: MemoryFile[]; byId: Map<string, MemoryFile> } | null {
|
|
138
220
|
if (currentVersion === 0) return null;
|
|
139
221
|
const entry = ruleMemoriesByDir.get(baseDir);
|
|
140
|
-
if (!entry || entry.sourceVersion !== currentVersion) return null;
|
|
222
|
+
if (!entry || entry.sourceVersion !== currentVersion || entry.keyId !== keyId) return null;
|
|
223
|
+
// TTL safety net (issue #1902): bound staleness from direct external edits
|
|
224
|
+
// that don't move the version sentinel. ttlMs <= 0 disables.
|
|
225
|
+
if (ttlMs > 0 && Date.now() - entry.loadedAt > ttlMs) {
|
|
226
|
+
ruleMemoriesByDir.delete(baseDir);
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
141
229
|
return entry.data;
|
|
142
230
|
}
|
|
143
231
|
|
|
144
232
|
/** Build and cache the rule memories from the full memory list. */
|
|
145
|
-
export function setCachedRuleMemories(
|
|
233
|
+
export function setCachedRuleMemories(
|
|
234
|
+
baseDir: string,
|
|
235
|
+
memories: MemoryFile[],
|
|
236
|
+
version: number,
|
|
237
|
+
cache = true,
|
|
238
|
+
keyId = "",
|
|
239
|
+
): { all: MemoryFile[]; byId: Map<string, MemoryFile> } {
|
|
146
240
|
const byId = new Map<string, MemoryFile>();
|
|
147
241
|
const all: MemoryFile[] = [];
|
|
148
242
|
for (const m of memories) {
|
|
@@ -156,7 +250,7 @@ export function setCachedRuleMemories(baseDir: string, memories: MemoryFile[], v
|
|
|
156
250
|
}
|
|
157
251
|
}
|
|
158
252
|
const result = { all, byId };
|
|
159
|
-
ruleMemoriesByDir.set(baseDir, { data: result, sourceVersion: version });
|
|
253
|
+
if (cache) ruleMemoriesByDir.set(baseDir, { data: result, sourceVersion: version, keyId, loadedAt: Date.now() });
|
|
160
254
|
return result;
|
|
161
255
|
}
|
|
162
256
|
|
|
@@ -295,6 +389,36 @@ export function invalidateAllForDir(baseDir: string): void {
|
|
|
295
389
|
}
|
|
296
390
|
}
|
|
297
391
|
|
|
392
|
+
/**
|
|
393
|
+
* Invalidate every cache layer for `baseDir` EXCEPT the hot-memories layer
|
|
394
|
+
* (issue #1902). Single-file write paths use this instead of
|
|
395
|
+
* `invalidateAllForDir` because they patch the hot-memories entry in place
|
|
396
|
+
* (via `updateCacheOnWrite`/`updateCacheOnDelete`) and re-key it to the bumped
|
|
397
|
+
* version — so dropping it here would defeat the whole warm-cache fast path.
|
|
398
|
+
* The derived episode/rule views and the global QMD caches are cheap to
|
|
399
|
+
* rebuild and are not patched, so they are still evicted here. Every other
|
|
400
|
+
* layer remains reachable for the wholesale `invalidateAllForDir` /
|
|
401
|
+
* `clearMemoryCache` chokepoint used by bulk/ambiguous mutations.
|
|
402
|
+
*/
|
|
403
|
+
export function invalidateDerivedAndGlobalForDir(baseDir: string): void {
|
|
404
|
+
// Preserve the layers a single-file write intentionally keeps warm (issue
|
|
405
|
+
// #1902, Codex Medium): hot-memories is patched + re-keyed in place; the
|
|
406
|
+
// entities layer is keyed on memory-status, which plain create/content writes
|
|
407
|
+
// deliberately do NOT bump (so entity retrieval stays warm); and the
|
|
408
|
+
// archive-memories layer is version-keyed on the archive tier, untouched by a
|
|
409
|
+
// hot-tier write. Evict ONLY the derived (episode/rule) and global (QMD) views.
|
|
410
|
+
for (const layer of ALL_CACHE_LAYERS) {
|
|
411
|
+
if (
|
|
412
|
+
layer.name === "hot-memories" ||
|
|
413
|
+
layer.name === "entities" ||
|
|
414
|
+
layer.name === "archive-memories"
|
|
415
|
+
) {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
layer.invalidateForDir(baseDir);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
298
422
|
export function clearMemoryCache(baseDir?: string): void {
|
|
299
423
|
if (baseDir) {
|
|
300
424
|
invalidateAllForDir(baseDir);
|