@remnic/core 9.7.8 → 9.7.10
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 +32 -30
- package/dist/access-audit.js +2 -2
- package/dist/access-boundary.d.ts +6 -6
- package/dist/access-boundary.js +33 -31
- package/dist/access-cli.js +92 -90
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +6 -6
- package/dist/access-http.js +40 -38
- package/dist/access-identity-continuity-surface.d.ts +5 -5
- package/dist/access-identity-continuity-surface.js +32 -30
- package/dist/access-lcm-surface.d.ts +6 -6
- package/dist/access-lcm-surface.js +32 -30
- package/dist/access-mcp.d.ts +6 -6
- package/dist/access-mcp.js +37 -35
- package/dist/access-observe-write-surface.d.ts +6 -6
- package/dist/access-observe-write-surface.js +32 -30
- package/dist/access-operations-batch.js +35 -33
- package/dist/access-operations.d.ts +9 -9
- package/dist/access-operations.js +36 -34
- package/dist/access-recall-surface.d.ts +6 -6
- package/dist/access-recall-surface.js +32 -30
- package/dist/access-schema.d.ts +64 -64
- 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 +32 -30
- 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 +15 -13
- 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 +16 -14
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-HHAA35A3.js → chunk-2VN4JAX7.js} +548 -161
- package/dist/chunk-2VN4JAX7.js.map +1 -0
- package/dist/{chunk-CNWC274T.js → chunk-3OQ4IZJN.js} +28 -28
- package/dist/{chunk-JO5AT6QT.js → chunk-3ZVFLMFQ.js} +1 -1
- package/dist/chunk-3ZVFLMFQ.js.map +1 -0
- package/dist/{chunk-7XH7VJN4.js → chunk-5S7Z5SHA.js} +2 -2
- package/dist/{chunk-WKE4VXSZ.js → chunk-673NPXE3.js} +2 -2
- package/dist/{chunk-Q6JPMCPO.js → chunk-7CKS4AXY.js} +63 -58
- package/dist/chunk-7CKS4AXY.js.map +1 -0
- package/dist/{chunk-UHGHTOR5.js → chunk-7EX4YOKH.js} +7 -7
- 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-6C35R7P4.js → chunk-DJCKXI4M.js} +2 -2
- package/dist/{chunk-AD2KK5MT.js → chunk-DLWYAN6A.js} +9 -3
- package/dist/chunk-DLWYAN6A.js.map +1 -0
- package/dist/{chunk-TVRPRFUB.js → chunk-EQF5763Z.js} +9 -7
- package/dist/chunk-EQF5763Z.js.map +1 -0
- package/dist/{chunk-FYKIEOG6.js → chunk-FNJX2O6G.js} +3 -3
- package/dist/{chunk-DQPPRXFO.js → chunk-HQ7QZJM5.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-O3BZ6PFY.js → chunk-JZM5QUTE.js} +2 -2
- package/dist/{chunk-KTOH7EJE.js → chunk-KXLRMM35.js} +312 -48
- package/dist/chunk-KXLRMM35.js.map +1 -0
- package/dist/{chunk-ZUFJ7ZTR.js → chunk-L4H6MQ7I.js} +6 -2
- package/dist/chunk-L4H6MQ7I.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-ZW6OKHVX.js → chunk-LQKITFGB.js} +2 -2
- package/dist/{chunk-TZJVTH3F.js → chunk-MXRMNKSY.js} +14 -14
- package/dist/{chunk-GL3SMLMZ.js → chunk-N3LWQJ6Y.js} +150 -119
- package/dist/chunk-N3LWQJ6Y.js.map +1 -0
- 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-LRGSLV7M.js → chunk-PYHOAXTI.js} +4 -4
- package/dist/{chunk-NMMKRVUF.js → chunk-QVALQE4P.js} +6 -6
- package/dist/{chunk-FXGC5UUH.js → chunk-RT7IP3D3.js} +2 -2
- package/dist/{chunk-AH3M6F4X.js → chunk-SBFDL2EQ.js} +2 -2
- package/dist/{chunk-UYZUJ4EB.js → chunk-SECMZACR.js} +2 -2
- package/dist/{chunk-HXDKC3BI.js → chunk-TGBJ2OMK.js} +2 -2
- package/dist/{chunk-DE6ENYLG.js → chunk-TXLGHDHS.js} +2 -2
- package/dist/{chunk-74MJH7I3.js → chunk-U4KHQTJ6.js} +12 -3
- package/dist/chunk-U4KHQTJ6.js.map +1 -0
- package/dist/{chunk-CRFTLUWT.js → chunk-UTDA43TB.js} +2 -2
- package/dist/{chunk-GKCCHHZK.js → chunk-UXKMMNJD.js} +4 -4
- package/dist/{chunk-57INMZ6F.js → chunk-UZRIKUFS.js} +2 -2
- package/dist/{chunk-2FSHROYK.js → chunk-V53GJUSL.js} +8 -8
- package/dist/{chunk-LKT755LP.js → chunk-VA4WSW54.js} +2 -2
- package/dist/{chunk-ZHKD4EZM.js → chunk-VCEH5UYS.js} +3 -3
- package/dist/{chunk-CHVU4RE5.js → chunk-VDLMAJXS.js} +3 -3
- package/dist/{chunk-XFTPUXCE.js → chunk-VRAD2FGO.js} +10 -10
- package/dist/{chunk-VF4XKTX3.js → chunk-VZ5OJE4X.js} +10 -2
- package/dist/chunk-VZ5OJE4X.js.map +1 -0
- package/dist/{chunk-UVIQHSS7.js → chunk-WUDBMLC6.js} +9 -3
- package/dist/chunk-WUDBMLC6.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-XF4KOVDK.js} +8 -8
- package/dist/{chunk-VMGMY2AI.js.map → chunk-XF4KOVDK.js.map} +1 -1
- package/dist/{chunk-T7VNN3ZM.js → chunk-XVLEGMJI.js} +2 -2
- package/dist/{chunk-R2SJDIPO.js → chunk-YLTG6BO4.js} +9 -7
- package/dist/chunk-YLTG6BO4.js.map +1 -0
- 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 +66 -64
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +15 -13
- 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 +15 -13
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +15 -13
- 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 +15 -13
- 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 +116 -114
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +4 -4
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +13 -13
- 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 +15 -13
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +15 -13
- package/dist/maintenance/rebuild-memory-projection.js +16 -14
- 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 +22 -20
- 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 +15 -13
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +2 -2
- package/dist/operator-toolkit.js +27 -25
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +17 -15
- package/dist/{orchestrator-37Cx7yyw.d.ts → orchestrator-D83AvrPf.d.ts} +4 -4
- package/dist/orchestrator.d.ts +5 -5
- package/dist/orchestrator.js +92 -90
- 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-agents.js +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/schemas.d.ts +52 -52
- 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 +16 -14
- package/dist/semantic-rule-promotion.js +15 -13
- package/dist/semantic-rule-verifier.d.ts +5 -1
- package/dist/semantic-rule-verifier.js +15 -13
- 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 +9 -9
- 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 +14 -12
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +3 -3
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-index-compat.d.ts +1 -0
- package/dist/temporal-index-compat.js +16 -0
- package/dist/temporal-index-compat.js.map +1 -0
- package/dist/temporal-index.d.ts +58 -7
- package/dist/temporal-index.js +23 -3
- 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/transfer/types.d.ts +66 -66
- 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 +15 -13
- 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/consolidation-run.ts +5 -5
- package/src/orchestration/contradiction-linking-coordinator.ts +2 -2
- package/src/orchestration/lifecycle-policy-coordinator.ts +2 -2
- package/src/orchestration/persistence-index.ts +6 -6
- package/src/orchestration/recall-internal.ts +6 -3
- package/src/orchestration/recall-search-pipeline.ts +36 -21
- package/src/orchestration/semantic-consolidation-coordinator.ts +2 -2
- package/src/orchestrator.ts +25 -5
- 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/temporal-index-compat.ts +311 -0
- package/src/temporal-index.test.ts +273 -22
- package/src/temporal-index.ts +427 -178
- 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-HHAA35A3.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-CNWC274T.js.map → chunk-3OQ4IZJN.js.map} +0 -0
- /package/dist/{chunk-7XH7VJN4.js.map → chunk-5S7Z5SHA.js.map} +0 -0
- /package/dist/{chunk-WKE4VXSZ.js.map → chunk-673NPXE3.js.map} +0 -0
- /package/dist/{chunk-UHGHTOR5.js.map → chunk-7EX4YOKH.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-6C35R7P4.js.map → chunk-DJCKXI4M.js.map} +0 -0
- /package/dist/{chunk-FYKIEOG6.js.map → chunk-FNJX2O6G.js.map} +0 -0
- /package/dist/{chunk-DQPPRXFO.js.map → chunk-HQ7QZJM5.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-O3BZ6PFY.js.map → chunk-JZM5QUTE.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-ZW6OKHVX.js.map → chunk-LQKITFGB.js.map} +0 -0
- /package/dist/{chunk-TZJVTH3F.js.map → chunk-MXRMNKSY.js.map} +0 -0
- /package/dist/{chunk-PKGWJFNR.js.map → chunk-OVMS3DCB.js.map} +0 -0
- /package/dist/{chunk-LRGSLV7M.js.map → chunk-PYHOAXTI.js.map} +0 -0
- /package/dist/{chunk-NMMKRVUF.js.map → chunk-QVALQE4P.js.map} +0 -0
- /package/dist/{chunk-FXGC5UUH.js.map → chunk-RT7IP3D3.js.map} +0 -0
- /package/dist/{chunk-AH3M6F4X.js.map → chunk-SBFDL2EQ.js.map} +0 -0
- /package/dist/{chunk-UYZUJ4EB.js.map → chunk-SECMZACR.js.map} +0 -0
- /package/dist/{chunk-HXDKC3BI.js.map → chunk-TGBJ2OMK.js.map} +0 -0
- /package/dist/{chunk-DE6ENYLG.js.map → chunk-TXLGHDHS.js.map} +0 -0
- /package/dist/{chunk-CRFTLUWT.js.map → chunk-UTDA43TB.js.map} +0 -0
- /package/dist/{chunk-GKCCHHZK.js.map → chunk-UXKMMNJD.js.map} +0 -0
- /package/dist/{chunk-57INMZ6F.js.map → chunk-UZRIKUFS.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-ZHKD4EZM.js.map → chunk-VCEH5UYS.js.map} +0 -0
- /package/dist/{chunk-CHVU4RE5.js.map → chunk-VDLMAJXS.js.map} +0 -0
- /package/dist/{chunk-XFTPUXCE.js.map → chunk-VRAD2FGO.js.map} +0 -0
- /package/dist/{chunk-T7VNN3ZM.js.map → chunk-XVLEGMJI.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/src/storage.ts
CHANGED
|
@@ -27,7 +27,24 @@ import { isErrnoCode } from "./utils/errno.js";
|
|
|
27
27
|
import { RECALL_FALLBACK_DIRS, getCategoryDir, categoryDirName } from "./utils/category-dir.js";
|
|
28
28
|
import { assertPathInsideRoot } from "./utils/path-containment.js";
|
|
29
29
|
import { decodeYamlScalar } from "./utils/yaml-scalar.js";
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
clearMemoryCache,
|
|
32
|
+
getCachedEntities,
|
|
33
|
+
getCachedMemories,
|
|
34
|
+
invalidateAllForDir,
|
|
35
|
+
invalidateDerivedAndGlobalForDir,
|
|
36
|
+
setCachedEntities,
|
|
37
|
+
setCachedMemories,
|
|
38
|
+
updateCacheOnDelete,
|
|
39
|
+
updateCacheOnWrite,
|
|
40
|
+
} from "./memory-cache.js";
|
|
41
|
+
import {
|
|
42
|
+
getInFlightRead,
|
|
43
|
+
setInFlightRead,
|
|
44
|
+
deleteInFlightRead,
|
|
45
|
+
deleteInFlightReadsForDir,
|
|
46
|
+
clearInFlightReads,
|
|
47
|
+
} from "./in-flight-reads.js";
|
|
31
48
|
import { rotateMarkdownFileToArchive } from "./hygiene.js";
|
|
32
49
|
import { sanitizeMemoryContent } from "./sanitize.js";
|
|
33
50
|
import {
|
|
@@ -2305,23 +2322,72 @@ export class StorageManager {
|
|
|
2305
2322
|
static readonly ARTIFACT_INDEX_CACHE_TTL_MS = 60_000; // 1 minute
|
|
2306
2323
|
private static readonly artifactWriteVersionByDir = new Map<string, number>();
|
|
2307
2324
|
private static readonly memoryStatusVersionByDir = new Map<string, number>();
|
|
2325
|
+
// Corpus version sentinel (issue #1902): bumped on every memory-FILE mutation
|
|
2326
|
+
// so the version-keyed hot-memories cache stays coherent across processes,
|
|
2327
|
+
// without touching memory-status (which would invalidate the entity cache on
|
|
2328
|
+
// every plain create). In-process fallback when the on-disk sentinel is
|
|
2329
|
+
// unavailable; canonical source is state/.memory-corpus-version.log.
|
|
2330
|
+
private static readonly memoryCorpusVersionByDir = new Map<string, number>();
|
|
2308
2331
|
private static readonly secureStoreEntityCacheKeyIds = new WeakMap<Buffer, number>();
|
|
2309
2332
|
private static nextSecureStoreEntityCacheKeyId = 1;
|
|
2310
2333
|
// In-process fallback for the cold-write sentinel (used when the disk file
|
|
2311
2334
|
// is not accessible). The canonical source of truth is state/cold-write.log.
|
|
2312
2335
|
private static readonly coldWriteVersionByDir = new Map<string, number>();
|
|
2313
2336
|
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2337
|
+
/**
|
|
2338
|
+
* Process-wide default for the hot-memories result cache gate (issue #1902).
|
|
2339
|
+
* Set once from config by the orchestrator/access layer via
|
|
2340
|
+
* setHotMemoriesCacheDefault() so that EVERY StorageManager — including the
|
|
2341
|
+
* ephemeral instances recall sub-stages (verified-recall, semantic-rule
|
|
2342
|
+
* verification, ...) construct over the same dir — honors the operator's
|
|
2343
|
+
* hotMemoriesCacheEnabled setting. The constructor option overrides this
|
|
2344
|
+
* per-instance (tests). Defaults true (cache on).
|
|
2345
|
+
*/
|
|
2346
|
+
private static hotMemoriesCacheDefault = true;
|
|
2347
|
+
|
|
2348
|
+
/**
|
|
2349
|
+
* Per-memory-directory override of the hot-cache gate (issue #1902, Codex
|
|
2350
|
+
* P2). setHotMemoriesCacheDefault(memoryDir, enabled) registers the
|
|
2351
|
+
* operator's setting keyed by dir, so a StorageManager constructed over that
|
|
2352
|
+
* dir honors ITS owning orchestrator's config — not merely the last
|
|
2353
|
+
* orchestrator constructed in the process. Unregistered dirs fall back to
|
|
2354
|
+
* the process-wide default above.
|
|
2355
|
+
*/
|
|
2356
|
+
private static readonly hotMemoriesCacheDefaultByDir = new Map<string, boolean>();
|
|
2357
|
+
|
|
2358
|
+
/**
|
|
2359
|
+
* Process-wide TTL (ms) safety net for the hot cache (issue #1902). Bounds
|
|
2360
|
+
* how long a version-keyed entry is served before a fresh disk scan, so
|
|
2361
|
+
* external filesystem edits (manual/git/editor) that don't bump the sentinel
|
|
2362
|
+
* self-heal. Set from config by the orchestrator via setHotMemoriesCacheDefault.
|
|
2363
|
+
* 0 disables the TTL (version invalidation only). Default 60s.
|
|
2364
|
+
*/
|
|
2365
|
+
private static hotMemoriesCacheTtlMs = 60_000;
|
|
2366
|
+
|
|
2367
|
+
/**
|
|
2368
|
+
* Per-memory-directory override of the hot-cache TTL (issue #1902, Codex P2),
|
|
2369
|
+
* mirroring hotMemoriesCacheDefaultByDir. Registered by
|
|
2370
|
+
* setHotMemoriesCacheDefault so two orchestrators in one process with
|
|
2371
|
+
* different TTLs don't clobber each other's setting. Unregistered dirs fall
|
|
2372
|
+
* back to the process-wide default above.
|
|
2373
|
+
*/
|
|
2374
|
+
private static readonly hotMemoriesCacheTtlByDir = new Map<string, number>();
|
|
2375
|
+
|
|
2376
|
+
/**
|
|
2377
|
+
* Whether the process-wide hot-cache fallbacks (hotMemoriesCacheDefault /
|
|
2378
|
+
* hotMemoriesCacheTtlMs) have been seeded by the first
|
|
2379
|
+
* setHotMemoriesCacheDefault call (issue #1902, Cursor Low). Only the first
|
|
2380
|
+
* registration seeds them; later registrations touch ONLY their per-dir map
|
|
2381
|
+
* entry. This makes the process-wide fallback deterministic (first-writer,
|
|
2382
|
+
* by init order) instead of last-writer-wins, so a second orchestrator with
|
|
2383
|
+
* a divergent config cannot silently flip the fallback that unregistered
|
|
2384
|
+
* dirs resolve to. All REGISTERED dirs are isolated by the per-dir maps.
|
|
2385
|
+
*/
|
|
2386
|
+
private static hotMemoriesCacheProcessDefaultSeeded = false;
|
|
2387
|
+
|
|
2388
|
+
// Module-level maps for readAllMemories() keyed by base directory, shared
|
|
2389
|
+
// across all StorageManager instances (static) so concurrent callers over
|
|
2390
|
+
// the same dir cooperate.
|
|
2325
2391
|
|
|
2326
2392
|
// Cache for readAllColdMemories() — keyed by cold root directory path.
|
|
2327
2393
|
// Prevents an uncached full-tree directory scan on every structured-attribute
|
|
@@ -2336,7 +2402,7 @@ export class StorageManager {
|
|
|
2336
2402
|
/** Read by storage/memory-read-store.ts (decomposition). */
|
|
2337
2403
|
static readonly coldMemoriesCache = new Map<
|
|
2338
2404
|
string,
|
|
2339
|
-
{ memories: MemoryFile[]; loadedAt: number; coldVersion: number }
|
|
2405
|
+
{ memories: MemoryFile[]; loadedAt: number; coldVersion: number; keyId: string }
|
|
2340
2406
|
>();
|
|
2341
2407
|
|
|
2342
2408
|
// Cache for readQuestions() — avoids serially re-reading tens of thousands of
|
|
@@ -2457,6 +2523,54 @@ export class StorageManager {
|
|
|
2457
2523
|
return `secure-store:key:${id}`;
|
|
2458
2524
|
}
|
|
2459
2525
|
|
|
2526
|
+
/**
|
|
2527
|
+
* Secure-store key identity for the version-keyed hot/archive memory caches
|
|
2528
|
+
* (issue #1902, Codex P1). Scopes cached DECRYPTED corpora by the key that
|
|
2529
|
+
* decrypted them, so a locked/unkeyed StorageManager for the same baseDir
|
|
2530
|
+
* never reads content another instance decrypted under a key (which would
|
|
2531
|
+
* bypass the locked-store error). Plaintext and locked stores share the ""
|
|
2532
|
+
* namespace; an unlocked encrypted store uses its per-key id.
|
|
2533
|
+
*/
|
|
2534
|
+
hotCacheKeyId(): string {
|
|
2535
|
+
return this._secureStoreKey === null ? "" : this.getEntityCacheSecureStoreKey();
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
/**
|
|
2539
|
+
* Resolved hot-cache gate for THIS store (issue #1902, Cursor Medium): the
|
|
2540
|
+
* per-instance override if one was passed, else the per-dir/process default.
|
|
2541
|
+
* Recall sub-stages (verified-recall, semantic-rule) read this to honor the
|
|
2542
|
+
* operator's opt-out for their derived caches even when the caller omits the
|
|
2543
|
+
* flag in options.
|
|
2544
|
+
*/
|
|
2545
|
+
isHotCacheEnabled(): boolean {
|
|
2546
|
+
return this.hotMemoriesCacheEnabled;
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
/**
|
|
2550
|
+
* Effective hot-cache TTL (ms) for this store's dir (issue #1902): the per-dir
|
|
2551
|
+
* registration if present, else the process-wide default. 0 disables the TTL.
|
|
2552
|
+
*/
|
|
2553
|
+
hotCacheTtlMs(): number {
|
|
2554
|
+
return StorageManager.hotMemoriesCacheTtlByDir.get(this.baseDir) ?? StorageManager.hotMemoriesCacheTtlMs;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
/**
|
|
2558
|
+
* True if `p` lives in this store's cold/ or archive/ tier (issue #1902,
|
|
2559
|
+
* Codex P2). Determined RELATIVE to baseDir — a substring scan of the
|
|
2560
|
+
* absolute path would misclassify every active path when memoryDir itself is
|
|
2561
|
+
* nested under a dir literally named "cold" or "archive" (e.g.
|
|
2562
|
+
* /srv/cold/remnic), skipping the patch and stranding a stale hot entry.
|
|
2563
|
+
*/
|
|
2564
|
+
private isColdOrArchiveTierPath(p: string): boolean {
|
|
2565
|
+
const rel = path.relative(this.baseDir, p);
|
|
2566
|
+
return (
|
|
2567
|
+
rel === "cold" ||
|
|
2568
|
+
rel === "archive" ||
|
|
2569
|
+
rel.startsWith(`cold${path.sep}`) ||
|
|
2570
|
+
rel.startsWith(`archive${path.sep}`)
|
|
2571
|
+
);
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2460
2574
|
/**
|
|
2461
2575
|
* Mark the secure-store as required for this storage instance.
|
|
2462
2576
|
* When required and locked, writes throw SecureStoreLockedError
|
|
@@ -2553,10 +2667,23 @@ export class StorageManager {
|
|
|
2553
2667
|
}
|
|
2554
2668
|
}
|
|
2555
2669
|
|
|
2670
|
+
private readonly hotMemoriesCacheEnabled: boolean;
|
|
2671
|
+
|
|
2556
2672
|
constructor(
|
|
2557
2673
|
private readonly baseDir: string,
|
|
2558
|
-
private readonly entitySchemas?: PluginConfig["entitySchemas"]
|
|
2674
|
+
private readonly entitySchemas?: PluginConfig["entitySchemas"],
|
|
2675
|
+
/**
|
|
2676
|
+
* Hot-memories result cache gate (issue #1902). When omitted, resolves to
|
|
2677
|
+
* the per-directory default registered by setHotMemoriesCacheDefault()
|
|
2678
|
+
* (falling back to the process-wide default); pass an explicit boolean to
|
|
2679
|
+
* override per-instance (tests, memory-constrained readers).
|
|
2680
|
+
*/
|
|
2681
|
+
hotMemoriesCacheEnabledOverride?: boolean,
|
|
2559
2682
|
) {
|
|
2683
|
+
this.hotMemoriesCacheEnabled =
|
|
2684
|
+
hotMemoriesCacheEnabledOverride ??
|
|
2685
|
+
StorageManager.hotMemoriesCacheDefaultByDir.get(baseDir) ??
|
|
2686
|
+
StorageManager.hotMemoriesCacheDefault;
|
|
2560
2687
|
// Load this store's alias table at construction (#1534): StorageManager
|
|
2561
2688
|
// is created in a dozen places (namespace router, operator toolkit,
|
|
2562
2689
|
// compounding engine, cold storage, ...) and most never call
|
|
@@ -2565,6 +2692,40 @@ export class StorageManager {
|
|
|
2565
2692
|
this.loadAliasesSync();
|
|
2566
2693
|
}
|
|
2567
2694
|
|
|
2695
|
+
/** Set the process-wide hot-memories cache default (issue #1902). The
|
|
2696
|
+
* orchestrator/access layer calls this with the operator's
|
|
2697
|
+
* hotMemoriesCacheEnabled config so the escape hatch reaches every
|
|
2698
|
+
* StorageManager, including ephemeral recall-sub-stage instances. Only an
|
|
2699
|
+
* explicit `false` disables; `undefined` (e.g. a cast test config missing
|
|
2700
|
+
* the field) leaves the cache on. */
|
|
2701
|
+
static setHotMemoriesCacheDefault(
|
|
2702
|
+
memoryDir: string,
|
|
2703
|
+
enabled: boolean | undefined,
|
|
2704
|
+
ttlMs?: number,
|
|
2705
|
+
): void {
|
|
2706
|
+
// Register per-dir so each orchestrator's config reaches every
|
|
2707
|
+
// StorageManager built over its own memory dir (Codex P2). The per-dir map
|
|
2708
|
+
// is authoritative for every registered dir.
|
|
2709
|
+
StorageManager.hotMemoriesCacheDefaultByDir.set(memoryDir, enabled !== false);
|
|
2710
|
+
// TTL safety net: a finite, non-negative override wins (0 honored = disable).
|
|
2711
|
+
// Registered per-dir (Codex P2) so concurrent orchestrators with different
|
|
2712
|
+
// TTLs don't clobber each other.
|
|
2713
|
+
const ttlValid = typeof ttlMs === "number" && Number.isFinite(ttlMs) && ttlMs >= 0;
|
|
2714
|
+
if (ttlValid) {
|
|
2715
|
+
StorageManager.hotMemoriesCacheTtlByDir.set(memoryDir, ttlMs as number);
|
|
2716
|
+
}
|
|
2717
|
+
// Seed the process-wide fallbacks (for UNregistered dirs) ONCE, from the
|
|
2718
|
+
// first registration only (issue #1902, Cursor Low). A later orchestrator
|
|
2719
|
+
// with a divergent config must not flip the fallback out from under the
|
|
2720
|
+
// first — that was a last-writer-wins race. First-writer is deterministic
|
|
2721
|
+
// by init order, and every registered dir is isolated per-dir above.
|
|
2722
|
+
if (!StorageManager.hotMemoriesCacheProcessDefaultSeeded) {
|
|
2723
|
+
StorageManager.hotMemoriesCacheDefault = enabled !== false;
|
|
2724
|
+
if (ttlValid) StorageManager.hotMemoriesCacheTtlMs = ttlMs as number;
|
|
2725
|
+
StorageManager.hotMemoriesCacheProcessDefaultSeeded = true;
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2568
2729
|
/**
|
|
2569
2730
|
* The LIVE class object of this instance (issue #1809 review of the
|
|
2570
2731
|
* storage decomposition): extracted store modules must read/write the
|
|
@@ -2622,18 +2783,20 @@ export class StorageManager {
|
|
|
2622
2783
|
return path.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
|
|
2623
2784
|
}
|
|
2624
2785
|
|
|
2625
|
-
private versionFilePath(kind: "memory-status" | "artifact-write" | "cold-write"): string {
|
|
2786
|
+
private versionFilePath(kind: "memory-status" | "artifact-write" | "cold-write" | "memory-corpus"): string {
|
|
2626
2787
|
const fileName =
|
|
2627
2788
|
kind === "memory-status"
|
|
2628
2789
|
? ".memory-status-version.log"
|
|
2629
2790
|
: kind === "artifact-write"
|
|
2630
2791
|
? ".artifact-write-version.log"
|
|
2631
|
-
: "
|
|
2792
|
+
: kind === "cold-write"
|
|
2793
|
+
? ".cold-write-version.log"
|
|
2794
|
+
: ".memory-corpus-version.log";
|
|
2632
2795
|
return path.join(this.stateDir, fileName);
|
|
2633
2796
|
}
|
|
2634
2797
|
|
|
2635
2798
|
private bumpSharedVersion(
|
|
2636
|
-
kind: "memory-status" | "artifact-write" | "cold-write",
|
|
2799
|
+
kind: "memory-status" | "artifact-write" | "cold-write" | "memory-corpus",
|
|
2637
2800
|
fallbackMap: Map<string, number>
|
|
2638
2801
|
): number {
|
|
2639
2802
|
const filePath = this.versionFilePath(kind);
|
|
@@ -2651,7 +2814,7 @@ export class StorageManager {
|
|
|
2651
2814
|
}
|
|
2652
2815
|
|
|
2653
2816
|
private readSharedVersion(
|
|
2654
|
-
kind: "memory-status" | "artifact-write" | "cold-write",
|
|
2817
|
+
kind: "memory-status" | "artifact-write" | "cold-write" | "memory-corpus",
|
|
2655
2818
|
fallbackMap: Map<string, number>
|
|
2656
2819
|
): number {
|
|
2657
2820
|
const filePath = this.versionFilePath(kind);
|
|
@@ -2664,6 +2827,10 @@ export class StorageManager {
|
|
|
2664
2827
|
|
|
2665
2828
|
private bumpMemoryStatusVersion(): void {
|
|
2666
2829
|
this.bumpSharedVersion("memory-status", StorageManager.memoryStatusVersionByDir);
|
|
2830
|
+
// Also bump the corpus sentinel so peer processes rescan the hot-memories
|
|
2831
|
+
// cache after status/lifecycle/bulk mutations (issue #1902). invalidateAllForDir
|
|
2832
|
+
// below drops the hot layer locally; the corpus bump handles cross-process.
|
|
2833
|
+
this.bumpSharedVersion("memory-corpus", StorageManager.memoryCorpusVersionByDir);
|
|
2667
2834
|
// Invalidation chokepoint (issue #1535): status/entity mutations funnel
|
|
2668
2835
|
// through this bump — several of them (supersedeMemory, archiveMemories,
|
|
2669
2836
|
// writeEntity, cleanExpiredCommitments) do NOT call
|
|
@@ -2677,6 +2844,62 @@ export class StorageManager {
|
|
|
2677
2844
|
return this.readSharedVersion("memory-status", StorageManager.memoryStatusVersionByDir);
|
|
2678
2845
|
}
|
|
2679
2846
|
|
|
2847
|
+
/**
|
|
2848
|
+
* Corpus version sentinel for the hot-memories result cache (issue #1902).
|
|
2849
|
+
* Distinct from memory-status: it bumps on EVERY memory-file mutation
|
|
2850
|
+
* (create/update/delete/bulk) so the version-keyed hot cache stays coherent
|
|
2851
|
+
* across processes, WITHOUT bumping memory-status — which would needlessly
|
|
2852
|
+
* invalidate the version-keyed entity cache on every plain fact create (the
|
|
2853
|
+
* ki-spike regression the sibling perf work removes). memory-status keeps its
|
|
2854
|
+
* lifecycle/status/entity semantics unchanged.
|
|
2855
|
+
*/
|
|
2856
|
+
getMemoryCorpusVersion(): number {
|
|
2857
|
+
return this.readSharedVersion("memory-corpus", StorageManager.memoryCorpusVersionByDir);
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
private bumpMemoryCorpusVersion(): void {
|
|
2861
|
+
// Bump only — unlike bumpMemoryStatusVersion this must NOT invalidateAllForDir
|
|
2862
|
+
// (that would drop the very hot entry the write path just patched).
|
|
2863
|
+
this.bumpSharedVersion("memory-corpus", StorageManager.memoryCorpusVersionByDir);
|
|
2864
|
+
// Also drop the in-flight readAllMemories slot (Cursor Medium, #1902): after
|
|
2865
|
+
// the sentinel advances, a concurrent read must not attach to a scan that
|
|
2866
|
+
// began BEFORE this mutation and receive a pre-mutation corpus. Clearing the
|
|
2867
|
+
// slot forces such a read to start a fresh scan of current disk state. (The
|
|
2868
|
+
// patch path uses bumpMemoryCorpusVersionExclusive and clears the slot
|
|
2869
|
+
// itself; this covers the per-item supersede/archive/TTL-cleanup bumps.)
|
|
2870
|
+
deleteInFlightReadsForDir(this.baseDir);
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
/**
|
|
2874
|
+
* Corpus bump that reports whether THIS process's append was the only one
|
|
2875
|
+
* since the pre-bump size (issue #1902, Codex P1). appendFileSync is atomic
|
|
2876
|
+
* per write, but a peer process can append concurrently; the `exclusive`
|
|
2877
|
+
* flag lets patchHotMemoriesCache refuse to re-key its locally patched
|
|
2878
|
+
* (peer-incomplete) corpus at a version that already reflects a peer's
|
|
2879
|
+
* still-unread write.
|
|
2880
|
+
*/
|
|
2881
|
+
private bumpMemoryCorpusVersionExclusive(): { produced: number; exclusive: boolean } {
|
|
2882
|
+
const filePath = this.versionFilePath("memory-corpus");
|
|
2883
|
+
try {
|
|
2884
|
+
mkdirSync(this.stateDir, { recursive: true });
|
|
2885
|
+
let before = 0;
|
|
2886
|
+
try {
|
|
2887
|
+
before = statSync(filePath).size;
|
|
2888
|
+
} catch {
|
|
2889
|
+
before = 0;
|
|
2890
|
+
}
|
|
2891
|
+
appendFileSync(filePath, "x");
|
|
2892
|
+
const produced = statSync(filePath).size;
|
|
2893
|
+
StorageManager.memoryCorpusVersionByDir.set(this.baseDir, produced);
|
|
2894
|
+
// Exclusive iff exactly our single byte landed between the two stats.
|
|
2895
|
+
return { produced, exclusive: produced === before + 1 };
|
|
2896
|
+
} catch {
|
|
2897
|
+
const next = (StorageManager.memoryCorpusVersionByDir.get(this.baseDir) ?? 0) + 1;
|
|
2898
|
+
StorageManager.memoryCorpusVersionByDir.set(this.baseDir, next);
|
|
2899
|
+
return { produced: next, exclusive: true };
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2680
2903
|
private bumpArtifactWriteVersion(): number {
|
|
2681
2904
|
return this.bumpSharedVersion("artifact-write", StorageManager.artifactWriteVersionByDir);
|
|
2682
2905
|
}
|
|
@@ -3581,6 +3804,19 @@ export class StorageManager {
|
|
|
3581
3804
|
await mkdir(this.identityAuditsWeeklyDir, { recursive: true });
|
|
3582
3805
|
await mkdir(this.identityAuditsMonthlyDir, { recursive: true });
|
|
3583
3806
|
await mkdir(path.join(this.baseDir, "config"), { recursive: true });
|
|
3807
|
+
// Activate the corpus sentinel at setup (issue #1902, Cursor Medium): an
|
|
3808
|
+
// existing corpus predating this feature has a size-0/absent sentinel, which
|
|
3809
|
+
// getCachedMemories treats as a miss — so a read-heavy workload would never
|
|
3810
|
+
// engage the hot cache until the first mutation bumped it. The daemon calls
|
|
3811
|
+
// ensureDirectories() at startup (orchestration/orchestrator-init.ts) before
|
|
3812
|
+
// serving recall reads, so seeding a nonzero version here makes the cache
|
|
3813
|
+
// engage from the first read. Fires once, only while the version is still 0.
|
|
3814
|
+
// Deliberately NOT done on the readAllMemories() path: a write-side-effect
|
|
3815
|
+
// during a read perturbs concurrent hot/cold reads in the tier-migration
|
|
3816
|
+
// cycle. Fail-open: a failed bump falls back to the in-process counter.
|
|
3817
|
+
if (this.hotMemoriesCacheEnabled && this.getMemoryCorpusVersion() === 0) {
|
|
3818
|
+
this.bumpMemoryCorpusVersion();
|
|
3819
|
+
}
|
|
3584
3820
|
}
|
|
3585
3821
|
|
|
3586
3822
|
/**
|
|
@@ -3848,7 +4084,7 @@ export class StorageManager {
|
|
|
3848
4084
|
|
|
3849
4085
|
await this.snapshotBeforeWrite(filePath, "write");
|
|
3850
4086
|
await this.writeStorageSecureFile(filePath, fileContent);
|
|
3851
|
-
this.
|
|
4087
|
+
await this.patchHotMemoriesCache({ addedPath: filePath });
|
|
3852
4088
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.writeMemory", {
|
|
3853
4089
|
memoryId: id,
|
|
3854
4090
|
eventType: "created",
|
|
@@ -4190,21 +4426,65 @@ export class StorageManager {
|
|
|
4190
4426
|
}
|
|
4191
4427
|
|
|
4192
4428
|
async readAllMemories(): Promise<MemoryFile[]> {
|
|
4429
|
+
// Version-keyed hot-memories cache (issue #1902): once populated, serve the
|
|
4430
|
+
// full parsed corpus from memory within a version epoch. getCachedMemories
|
|
4431
|
+
// returns null when the sentinel is 0 (fresh/test dirs) or the stored
|
|
4432
|
+
// version differs from the current on-disk sentinel (a peer or local write
|
|
4433
|
+
// bumped it), so a stale entry is never served — the miss falls through to
|
|
4434
|
+
// a fresh disk scan below.
|
|
4435
|
+
if (this.hotMemoriesCacheEnabled) {
|
|
4436
|
+
const cached = getCachedMemories(
|
|
4437
|
+
this.baseDir,
|
|
4438
|
+
this.getMemoryCorpusVersion(),
|
|
4439
|
+
this.hotCacheKeyId(),
|
|
4440
|
+
this.hotCacheTtlMs(),
|
|
4441
|
+
);
|
|
4442
|
+
// Null-check, not truthiness (kilo WARNING): getCachedMemories returns []
|
|
4443
|
+
// for a cached EMPTY corpus, which is falsy — a truthiness guard would
|
|
4444
|
+
// force a full disk scan on every read of an empty/near-empty store.
|
|
4445
|
+
if (cached !== null) return cached;
|
|
4446
|
+
}
|
|
4193
4447
|
// Deduplicate concurrent reads for the same directory so multiple
|
|
4194
4448
|
// callers in the same recall share one disk scan.
|
|
4195
|
-
|
|
4449
|
+
// Snapshot the secure-store key identity ONCE (issue #1902, Codex Medium):
|
|
4450
|
+
// hotCacheKeyId() can change mid-scan if setSecureStoreKey(null/other) runs
|
|
4451
|
+
// during the await. Recomputing it at publish time would store K-decrypted
|
|
4452
|
+
// results under the new ""/other identity, letting a locked/differently-keyed
|
|
4453
|
+
// read match the entry and receive plaintext instead of SecureStoreLockedError.
|
|
4454
|
+
// The in-flight slot get/set/delete also key on this snapshot so a mid-scan
|
|
4455
|
+
// key change can't orphan the slot registered under the old identity.
|
|
4456
|
+
const keyId = this.hotCacheKeyId();
|
|
4457
|
+
const inFlight = getInFlightRead(this.baseDir, keyId);
|
|
4196
4458
|
if (inFlight) return inFlight;
|
|
4197
4459
|
|
|
4198
|
-
const readPromise =
|
|
4199
|
-
|
|
4460
|
+
const readPromise = (async (): Promise<MemoryFile[]> => {
|
|
4461
|
+
// Capture the version BEFORE the scan and store under it. If a concurrent
|
|
4462
|
+
// bump (peer or local write) lands during the scan, the stored entry's
|
|
4463
|
+
// version is older than the sentinel, so the next getCachedMemories()
|
|
4464
|
+
// rejects it (version mismatch → rescan) — correct, never stale.
|
|
4465
|
+
const version = this.getMemoryCorpusVersion();
|
|
4466
|
+
const memories = await this._readAllMemoriesFromDisk();
|
|
4467
|
+
// Publish only if neither the corpus version NOR the key identity changed
|
|
4468
|
+
// during the scan. The version guard prevents clobbering a newer patched +
|
|
4469
|
+
// re-keyed entry; the keyId guard prevents publishing this key's decrypted
|
|
4470
|
+
// corpus under a since-changed identity (Codex Medium, #1902).
|
|
4471
|
+
if (
|
|
4472
|
+
this.hotMemoriesCacheEnabled &&
|
|
4473
|
+
this.getMemoryCorpusVersion() === version &&
|
|
4474
|
+
this.hotCacheKeyId() === keyId
|
|
4475
|
+
) {
|
|
4476
|
+
setCachedMemories(this.baseDir, memories, version, keyId, this.hotCacheTtlMs());
|
|
4477
|
+
}
|
|
4478
|
+
return memories;
|
|
4479
|
+
})();
|
|
4480
|
+
setInFlightRead(this.baseDir, keyId, readPromise);
|
|
4200
4481
|
try {
|
|
4201
4482
|
return await readPromise;
|
|
4202
4483
|
} finally {
|
|
4203
|
-
// Only delete if we still own the slot —
|
|
4204
|
-
// may have already cleared it and a new read
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
}
|
|
4484
|
+
// Only delete if we still own the slot for our snapshot key identity —
|
|
4485
|
+
// invalidateAllMemoriesCache() may have already cleared it and a new read
|
|
4486
|
+
// may have claimed it (the expected-promise guard enforces owner-only).
|
|
4487
|
+
deleteInFlightRead(this.baseDir, keyId, readPromise);
|
|
4208
4488
|
}
|
|
4209
4489
|
}
|
|
4210
4490
|
|
|
@@ -4237,9 +4517,22 @@ export class StorageManager {
|
|
|
4237
4517
|
/** Clear ALL static caches. Use in tests that write files directly
|
|
4238
4518
|
* (bypassing StorageManager.writeMemory) to avoid stale reads. */
|
|
4239
4519
|
static clearAllStaticCaches(): void {
|
|
4240
|
-
|
|
4520
|
+
clearInFlightReads();
|
|
4241
4521
|
StorageManager.questionsCache.clear();
|
|
4242
4522
|
StorageManager.coldMemoriesCache.clear(); // also wipe the cold-scan TTL cache
|
|
4523
|
+
// Also clear the module-level memory-cache layers (hot-memories result cache
|
|
4524
|
+
// + entity/derived/QMD layers) so the reset seam is authoritative for tests
|
|
4525
|
+
// that write files directly (issue #1902).
|
|
4526
|
+
clearMemoryCache();
|
|
4527
|
+
// Reset the hot-cache config statics to construction defaults so each test
|
|
4528
|
+
// starts from a clean slate (issue #1902). resetStaticCaches() runs before
|
|
4529
|
+
// every contract test; without this the per-dir maps and the first-writer
|
|
4530
|
+
// process-wide seed would leak across tests.
|
|
4531
|
+
StorageManager.hotMemoriesCacheDefaultByDir.clear();
|
|
4532
|
+
StorageManager.hotMemoriesCacheTtlByDir.clear();
|
|
4533
|
+
StorageManager.hotMemoriesCacheDefault = true;
|
|
4534
|
+
StorageManager.hotMemoriesCacheTtlMs = 60_000;
|
|
4535
|
+
StorageManager.hotMemoriesCacheProcessDefaultSeeded = false;
|
|
4243
4536
|
}
|
|
4244
4537
|
|
|
4245
4538
|
/** Cancel any in-flight concurrent read so the next readAllMemories()
|
|
@@ -4255,7 +4548,11 @@ export class StorageManager {
|
|
|
4255
4548
|
* cold content actually changes (hot→cold demotions, writeMemoryFileAtomic
|
|
4256
4549
|
* inside cold/, archiveMemory, etc.). */
|
|
4257
4550
|
private invalidateAllMemoriesCache(): void {
|
|
4258
|
-
|
|
4551
|
+
deleteInFlightReadsForDir(this.baseDir);
|
|
4552
|
+
// Bulk/ambiguous mutations drop the hot layer wholesale (below); bump the
|
|
4553
|
+
// corpus sentinel too so PEER processes rescan instead of serving a warm
|
|
4554
|
+
// pre-mutation corpus entry (issue #1902 cross-process coherence).
|
|
4555
|
+
this.bumpMemoryCorpusVersion();
|
|
4259
4556
|
// Invalidation chokepoint (issue #1535): eagerly evict EVERY process-level
|
|
4260
4557
|
// cache layer that can serve memory-derived content for this dir — hot,
|
|
4261
4558
|
// archive, entity, derived episode/rule views, and both QMD result caches
|
|
@@ -4274,6 +4571,103 @@ export class StorageManager {
|
|
|
4274
4571
|
}
|
|
4275
4572
|
}
|
|
4276
4573
|
|
|
4574
|
+
/**
|
|
4575
|
+
* Single-file write/delete fast path for the hot-memories cache (issue
|
|
4576
|
+
* #1902). Keeps THIS process's version-keyed corpus entry warm by patching
|
|
4577
|
+
* the changed memory in place, bumps the dedicated `.memory-corpus-version.log`
|
|
4578
|
+
* sentinel so PEER processes rescan (memory-status is deliberately NOT bumped
|
|
4579
|
+
* here so plain creates/content edits don't invalidate the entity cache), and
|
|
4580
|
+
* evicts only the derived/global layers (which
|
|
4581
|
+
* are cheap to rebuild and are never patched). Mirrors the in-flight-dedup
|
|
4582
|
+
* and tombstone-store invalidation that invalidateAllMemoriesCache() does.
|
|
4583
|
+
*
|
|
4584
|
+
* `added` is the written MemoryFile (writes/overwrites); `removedPath` is a
|
|
4585
|
+
* deleted path. A path outside the active scan roots (under cold/ or
|
|
4586
|
+
* archive/) is NOT applied to the hot map — that cache holds active memories
|
|
4587
|
+
* only — but the version bump and derived/global eviction still run so
|
|
4588
|
+
* coherence is preserved. Bulk/ambiguous mutations keep the wholesale
|
|
4589
|
+
* invalidateAllMemoriesCache() drop instead (issue #1902 Step 5).
|
|
4590
|
+
*/
|
|
4591
|
+
private async patchHotMemoriesCache(opts: { addedPath?: string; removedPath?: string }): Promise<void> {
|
|
4592
|
+
const prevVersion = this.getMemoryCorpusVersion();
|
|
4593
|
+
// Snapshot the key identity once (issue #1902, Codex Medium): a mid-await
|
|
4594
|
+
// setSecureStoreKey change would otherwise let the post-await patch/re-key
|
|
4595
|
+
// store this key's decrypted memory under a since-changed identity.
|
|
4596
|
+
const keyId = this.hotCacheKeyId();
|
|
4597
|
+
const warm = this.hotMemoriesCacheEnabled && getCachedMemories(this.baseDir, prevVersion, keyId, this.hotCacheTtlMs()) !== null;
|
|
4598
|
+
if (
|
|
4599
|
+
warm &&
|
|
4600
|
+
opts.removedPath !== undefined &&
|
|
4601
|
+
!this.isColdOrArchiveTierPath(opts.removedPath)
|
|
4602
|
+
) {
|
|
4603
|
+
updateCacheOnDelete(this.baseDir, opts.removedPath, keyId);
|
|
4604
|
+
}
|
|
4605
|
+
// Bump the corpus sentinel AND drop the in-flight slot BEFORE the awaited
|
|
4606
|
+
// one-file re-parse below (Cursor Medium, #1902). The on-disk write already
|
|
4607
|
+
// landed in the caller, so advancing the version now makes any concurrent
|
|
4608
|
+
// readAllMemories() during the re-parse window miss the still-old hot entry
|
|
4609
|
+
// and start a FRESH scan of disk truth (which includes the write) instead of
|
|
4610
|
+
// serving the pre-write corpus or joining a pre-write in-flight scan.
|
|
4611
|
+
// memory-status is deliberately NOT bumped here so plain creates/content
|
|
4612
|
+
// edits don't invalidate the entity cache (issue #1902).
|
|
4613
|
+
const { produced, exclusive } = this.bumpMemoryCorpusVersionExclusive();
|
|
4614
|
+
deleteInFlightReadsForDir(this.baseDir);
|
|
4615
|
+
let patchedOk = warm;
|
|
4616
|
+
if (
|
|
4617
|
+
warm &&
|
|
4618
|
+
opts.addedPath !== undefined &&
|
|
4619
|
+
!this.isColdOrArchiveTierPath(opts.addedPath)
|
|
4620
|
+
) {
|
|
4621
|
+
// Re-read + parse the ONE just-written file so the cached object is
|
|
4622
|
+
// exactly what a fresh disk scan would yield — serialize→parse normalizes
|
|
4623
|
+
// fields (e.g. an empty array becomes undefined), and the in-memory write
|
|
4624
|
+
// object would otherwise diverge from disk truth. This is a single O(1)
|
|
4625
|
+
// read (the file is page-cache-hot), NOT the O(corpus) scan the cache
|
|
4626
|
+
// exists to avoid.
|
|
4627
|
+
// Best-effort re-parse: this cache patch runs AFTER the on-disk write and
|
|
4628
|
+
// corpus-version bump have already persisted, so it must never fail the
|
|
4629
|
+
// completed mutation. If the re-read throws (e.g. setSecureStoreKey(null)
|
|
4630
|
+
// locked the store mid-await → SecureStoreLockedError), treat the entry as
|
|
4631
|
+
// unpatchable — leave it version-invalidated so the next read rescans —
|
|
4632
|
+
// rather than propagating and making writeMemory/updateMemory reject an
|
|
4633
|
+
// already-durable write (which a caller would retry, duplicating memories).
|
|
4634
|
+
// Issue #1902, Codex Medium.
|
|
4635
|
+
let parsed: MemoryFile | undefined;
|
|
4636
|
+
try {
|
|
4637
|
+
[parsed] = await this.readParsedMemoriesFromPaths([opts.addedPath], 1);
|
|
4638
|
+
} catch {
|
|
4639
|
+
parsed = undefined;
|
|
4640
|
+
}
|
|
4641
|
+
if (parsed) {
|
|
4642
|
+
updateCacheOnWrite(this.baseDir, parsed, keyId);
|
|
4643
|
+
} else {
|
|
4644
|
+
// Could not re-parse the write — don't trust the patched entry; leaving
|
|
4645
|
+
// it tagged at prevVersion means the version-bumped read rejects it
|
|
4646
|
+
// (version mismatch) and rescans.
|
|
4647
|
+
patchedOk = false;
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
if (patchedOk && exclusive && produced === prevVersion + 1 && this.getMemoryCorpusVersion() === produced && this.hotCacheKeyId() === keyId) {
|
|
4651
|
+
// Re-key the patched entry (still tagged prevVersion) to the version THIS
|
|
4652
|
+
// mutation produced — ONLY if no peer appended between prevVersion capture
|
|
4653
|
+
// and our bump (produced === prevVersion + 1), our bump was exclusive, and
|
|
4654
|
+
// no further peer append landed during the re-parse (still === produced).
|
|
4655
|
+
// Otherwise a concurrent read has already republished disk truth at the
|
|
4656
|
+
// newer version, or our patched corpus lacks a peer's write — either way
|
|
4657
|
+
// leave it version-rejected so the next read rescans.
|
|
4658
|
+
const patched = getCachedMemories(this.baseDir, prevVersion, keyId);
|
|
4659
|
+
if (patched) setCachedMemories(this.baseDir, patched, produced, keyId, this.hotCacheTtlMs());
|
|
4660
|
+
}
|
|
4661
|
+
// Evict every OTHER layer; the hot layer is patched (or intentionally left
|
|
4662
|
+
// to be version-rejected) above, not dropped here.
|
|
4663
|
+
invalidateDerivedAndGlobalForDir(this.baseDir);
|
|
4664
|
+
if (this.tombstoneStore) {
|
|
4665
|
+
this.tombstoneStore.invalidate();
|
|
4666
|
+
this.tombstoneStore = null;
|
|
4667
|
+
this.tombstoneStoreLoadPromise = null;
|
|
4668
|
+
}
|
|
4669
|
+
}
|
|
4670
|
+
|
|
4277
4671
|
/**
|
|
4278
4672
|
* Invalidate the cold-scan cache for this storage root and bump the
|
|
4279
4673
|
* on-disk cold-version sentinel so that other processes (gateway, CLI) see
|
|
@@ -4792,7 +5186,7 @@ export class StorageManager {
|
|
|
4792
5186
|
}
|
|
4793
5187
|
const fileContent = `${serializeFrontmatter(updated)}\n\n${sanitized.text}\n`;
|
|
4794
5188
|
await this.writeStorageSecureFile(memory.path, fileContent);
|
|
4795
|
-
this.
|
|
5189
|
+
await this.patchHotMemoriesCache({ addedPath: memory.path });
|
|
4796
5190
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.updateMemory", {
|
|
4797
5191
|
memoryId: id,
|
|
4798
5192
|
eventType: "updated",
|
|
@@ -4827,7 +5221,7 @@ export class StorageManager {
|
|
|
4827
5221
|
|
|
4828
5222
|
const fileContent = `${serializeFrontmatter(updated)}\n\n${memory.content}\n`;
|
|
4829
5223
|
await this.writeStorageSecureFile(memory.path, fileContent);
|
|
4830
|
-
this.
|
|
5224
|
+
await this.patchHotMemoriesCache({ addedPath: memory.path });
|
|
4831
5225
|
// If the target file lives in cold/, bump the cold-version sentinel so
|
|
4832
5226
|
// other processes detect the change on their next readAllColdMemories()
|
|
4833
5227
|
// call (Finding UvUy fix).
|
|
@@ -4862,6 +5256,9 @@ export class StorageManager {
|
|
|
4862
5256
|
lifecycle?.ruleVersion
|
|
4863
5257
|
);
|
|
4864
5258
|
if (beforeStatus !== afterStatus) {
|
|
5259
|
+
// Status/lifecycle change must bump memory-status so the version-keyed
|
|
5260
|
+
// entity/derived caches and peer processes observe it (issue #1902:
|
|
5261
|
+
// restored — corpus bump alone doesn't cover status-derived views).
|
|
4865
5262
|
this.bumpMemoryStatusVersion();
|
|
4866
5263
|
}
|
|
4867
5264
|
return true;
|
|
@@ -4891,6 +5288,10 @@ export class StorageManager {
|
|
|
4891
5288
|
try {
|
|
4892
5289
|
await unlink(m.path);
|
|
4893
5290
|
markProjectedMemoryPathInvalid(this.baseDir, m.frontmatter.id);
|
|
5291
|
+
// Bump per deletion so a concurrent readAllMemories mid-loop rescans
|
|
5292
|
+
// and never re-caches a partially-cleaned corpus (Cursor Medium,
|
|
5293
|
+
// #1902); invalidateAllMemoriesCache below still runs at loop end.
|
|
5294
|
+
this.bumpMemoryCorpusVersion();
|
|
4894
5295
|
deleted.push(m);
|
|
4895
5296
|
log.debug(`cleaned expired memory ${m.frontmatter.id} (TTL expired)`);
|
|
4896
5297
|
} catch {
|
|
@@ -5906,6 +6307,10 @@ export class StorageManager {
|
|
|
5906
6307
|
try {
|
|
5907
6308
|
await unlink(m.path);
|
|
5908
6309
|
markProjectedMemoryPathInvalid(this.baseDir, m.frontmatter.id);
|
|
6310
|
+
// Bump per deletion so a concurrent readAllMemories mid-loop rescans
|
|
6311
|
+
// and never re-caches a partially-cleaned corpus (Cursor Medium,
|
|
6312
|
+
// #1902); bumpMemoryStatusVersion below still runs at loop end.
|
|
6313
|
+
this.bumpMemoryCorpusVersion();
|
|
5909
6314
|
deleted.push(m);
|
|
5910
6315
|
log.debug(`cleaned expired commitment ${m.frontmatter.id}`);
|
|
5911
6316
|
} catch {
|
|
@@ -5935,6 +6340,22 @@ export class StorageManager {
|
|
|
5935
6340
|
const memories = await this.readAllMemories();
|
|
5936
6341
|
const memoryMap = new Map(memories.map((m) => [m.frontmatter.id, m]));
|
|
5937
6342
|
let updated = 0;
|
|
6343
|
+
// Capture the corpus version + warmth BEFORE writing so we can patch the hot
|
|
6344
|
+
// entries in place and then re-key them to the version this flush produces
|
|
6345
|
+
// (issue #1902). Access-tracking flush is batched (consolidation / buffer
|
|
6346
|
+
// full), not per-recall, so the single corpus bump below is cheap.
|
|
6347
|
+
const prevVersion = this.getMemoryCorpusVersion();
|
|
6348
|
+
// Snapshot the secure-store key identity once (Cursor Medium #1902), mirroring
|
|
6349
|
+
// readAllMemories/patchHotMemoriesCache: a mid-flush setSecureStoreKey change
|
|
6350
|
+
// would otherwise let loop patches or the re-keyed entry be stored under a
|
|
6351
|
+
// different identity than the one that decrypted this corpus.
|
|
6352
|
+
const keyId = this.hotCacheKeyId();
|
|
6353
|
+
const warm =
|
|
6354
|
+
this.hotMemoriesCacheEnabled && getCachedMemories(this.baseDir, prevVersion, keyId, this.hotCacheTtlMs()) !== null;
|
|
6355
|
+
// Record each applied patch so the end-of-flush re-key can re-apply them on
|
|
6356
|
+
// top of whatever is cached at prevVersion — robust to a concurrent scan
|
|
6357
|
+
// that republished an UNpatched corpus mid-flush (Cursor Medium #1902).
|
|
6358
|
+
const appliedPatches = new Map<string, { accessCount: number; lastAccessed: string }>();
|
|
5938
6359
|
|
|
5939
6360
|
for (const entry of entries) {
|
|
5940
6361
|
const memory = memoryMap.get(entry.memoryId);
|
|
@@ -5949,6 +6370,14 @@ export class StorageManager {
|
|
|
5949
6370
|
try {
|
|
5950
6371
|
const fileContent = `${serializeFrontmatter(newFm)}\n\n${memory.content}\n`;
|
|
5951
6372
|
await this.writeStorageSecureFile(memory.path, fileContent);
|
|
6373
|
+
// Patch the hot corpus cache entry in place with the new accessCount/
|
|
6374
|
+
// lastAccessed (issue #1902). The shared sentinel is bumped once after
|
|
6375
|
+
// the loop for cross-process coherence; the re-key below keeps this
|
|
6376
|
+
// process's patched entries warm.
|
|
6377
|
+
if (warm) {
|
|
6378
|
+
updateCacheOnWrite(this.baseDir, { ...memory, frontmatter: newFm }, keyId);
|
|
6379
|
+
}
|
|
6380
|
+
appliedPatches.set(entry.memoryId, { accessCount: entry.newCount, lastAccessed: entry.lastAccessed });
|
|
5952
6381
|
updated++;
|
|
5953
6382
|
} catch (err) {
|
|
5954
6383
|
log.debug(`failed to update access tracking for ${entry.memoryId}: ${err}`);
|
|
@@ -5956,6 +6385,45 @@ export class StorageManager {
|
|
|
5956
6385
|
}
|
|
5957
6386
|
|
|
5958
6387
|
if (updated > 0) {
|
|
6388
|
+
// Advance the corpus sentinel so PEER processes rescan and don't overwrite
|
|
6389
|
+
// this process's increments (Codex P2): WorkspaceOpsCoordinator computes
|
|
6390
|
+
// existingCount + update.count from the cached value, so a peer serving a
|
|
6391
|
+
// stale count would undercount. Re-key the locally patched entries to the
|
|
6392
|
+
// produced version so this process stays warm — only when our bump was
|
|
6393
|
+
// exclusive and still the current sentinel; otherwise a peer also wrote
|
|
6394
|
+
// and we must let the next read rescan.
|
|
6395
|
+
const { produced, exclusive } = this.bumpMemoryCorpusVersionExclusive();
|
|
6396
|
+
// Drop the in-flight read slot after the bump (parity with
|
|
6397
|
+
// patchHotMemoriesCache, Cursor Medium #1902): a readAllMemories scan that
|
|
6398
|
+
// started before the flush would otherwise keep awaiting a pre-flush scan
|
|
6399
|
+
// and could republish it at the old version, clobbering our patches. New
|
|
6400
|
+
// readers now start a fresh scan at the bumped version.
|
|
6401
|
+
deleteInFlightReadsForDir(this.baseDir);
|
|
6402
|
+
if (warm && exclusive && produced === prevVersion + 1 && this.getMemoryCorpusVersion() === produced && this.hotCacheKeyId() === keyId) {
|
|
6403
|
+
const cur = getCachedMemories(this.baseDir, prevVersion, keyId);
|
|
6404
|
+
if (cur) {
|
|
6405
|
+
// Re-apply our patches on top of whatever is cached at prevVersion
|
|
6406
|
+
// before re-keying (Cursor Medium #1902): a concurrent readAllMemories
|
|
6407
|
+
// that finished in the prevVersion epoch may have republished a full
|
|
6408
|
+
// UNpatched corpus via its publish guard, clobbering our in-loop
|
|
6409
|
+
// updateCacheOnWrite patches. Re-applying is idempotent and makes the
|
|
6410
|
+
// re-key robust to that race; the publish guard blocks any scan
|
|
6411
|
+
// finishing AFTER our bump, so this is the only window.
|
|
6412
|
+
const reapplied = cur.map((m) => {
|
|
6413
|
+
const patch = appliedPatches.get(m.frontmatter.id);
|
|
6414
|
+
return patch
|
|
6415
|
+
? { ...m, frontmatter: { ...m.frontmatter, accessCount: patch.accessCount, lastAccessed: patch.lastAccessed } }
|
|
6416
|
+
: m;
|
|
6417
|
+
});
|
|
6418
|
+
setCachedMemories(this.baseDir, reapplied, produced, keyId, this.hotCacheTtlMs());
|
|
6419
|
+
}
|
|
6420
|
+
}
|
|
6421
|
+
// Evict the derived (episode/rule) + global (QMD recall/search) layers,
|
|
6422
|
+
// mirroring patchHotMemoriesCache (Cursor Medium, #1902). The hot layer is
|
|
6423
|
+
// patched + re-keyed above; without this the derived caches linger at
|
|
6424
|
+
// prevVersion and the QMD recall bundle keeps pre-flush accessCount-based
|
|
6425
|
+
// boost ordering until an unrelated mutation invalidates them.
|
|
6426
|
+
invalidateDerivedAndGlobalForDir(this.baseDir);
|
|
5959
6427
|
log.debug(`flushed access tracking for ${updated} memories`);
|
|
5960
6428
|
}
|
|
5961
6429
|
return updated;
|
|
@@ -6152,6 +6620,9 @@ export class StorageManager {
|
|
|
6152
6620
|
const filePath = await this.resolveCategoryWritePath(category, id, today);
|
|
6153
6621
|
|
|
6154
6622
|
await this.writeStorageSecureFile(filePath, fileContent);
|
|
6623
|
+
// Keep the version-keyed hot-memories cache coherent with the new chunk
|
|
6624
|
+
// file (issue #1902) — same single-file patch path writeMemory uses.
|
|
6625
|
+
await this.patchHotMemoriesCache({ addedPath: filePath });
|
|
6155
6626
|
log.debug(`wrote chunk ${id} (${chunkIndex + 1}/${chunkTotal}) to ${filePath}`);
|
|
6156
6627
|
return id;
|
|
6157
6628
|
}
|
|
@@ -6193,6 +6664,12 @@ export class StorageManager {
|
|
|
6193
6664
|
|
|
6194
6665
|
try {
|
|
6195
6666
|
await this.writeStorageSecureFile(oldMemory.path, fileContent);
|
|
6667
|
+
// Advance the corpus sentinel immediately after the on-disk write, BEFORE
|
|
6668
|
+
// the awaited lifecycle append (Cursor Medium, #1902). Otherwise a warm
|
|
6669
|
+
// hot-memories cache keeps serving the pre-supersede snapshot during the
|
|
6670
|
+
// await window; bumpMemoryStatusVersion() below additionally invalidates
|
|
6671
|
+
// the entity cache for the status change.
|
|
6672
|
+
this.bumpMemoryCorpusVersion();
|
|
6196
6673
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.supersedeMemory", {
|
|
6197
6674
|
memoryId: oldMemoryId,
|
|
6198
6675
|
eventType: "superseded",
|
|
@@ -6312,6 +6789,11 @@ export class StorageManager {
|
|
|
6312
6789
|
try {
|
|
6313
6790
|
const fileContent = `${serializeFrontmatter(updatedFm)}\n\n${memory.content}\n`;
|
|
6314
6791
|
await this.writeStorageSecureFile(memory.path, fileContent);
|
|
6792
|
+
// Corpus sentinel bump per file write, BEFORE the awaited lifecycle
|
|
6793
|
+
// append (Cursor Medium, #1902): the end-of-loop bumpMemoryStatusVersion
|
|
6794
|
+
// fires only after the whole batch, so without this a warm cache serves
|
|
6795
|
+
// pre-archive state for every file during the loop's await windows.
|
|
6796
|
+
this.bumpMemoryCorpusVersion();
|
|
6315
6797
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.archiveMemories", {
|
|
6316
6798
|
memoryId: id,
|
|
6317
6799
|
eventType: "archived",
|