@remnic/core 9.29.0 → 9.30.0
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 +11 -11
- package/dist/access-admin-ops-surface.js +19 -18
- package/dist/access-authorization-probe.d.ts +11 -11
- package/dist/access-authorization-probe.js +20 -19
- package/dist/access-boundary.d.ts +11 -11
- package/dist/access-boundary.js +19 -18
- package/dist/access-citation-namespace.js +2 -2
- package/dist/access-cli.js +45 -44
- package/dist/access-cli.js.map +1 -1
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-lcm-compaction.d.ts +11 -11
- package/dist/access-http.d.ts +11 -11
- package/dist/access-http.js +25 -24
- package/dist/access-identity-continuity-surface.d.ts +8 -8
- package/dist/access-identity-continuity-surface.js +19 -18
- package/dist/access-lcm-surface.d.ts +10 -10
- package/dist/access-lcm-surface.js +19 -18
- package/dist/access-mcp.d.ts +11 -11
- package/dist/access-mcp.js +23 -22
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-DCXBkDp0.d.ts → access-namespace-preflight-VfXssvoI.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +19 -18
- package/dist/access-observe-write-surface.d.ts +10 -10
- package/dist/access-observe-write-surface.js +19 -18
- package/dist/access-operations-batch.js +20 -19
- package/dist/access-operations.d.ts +11 -11
- package/dist/access-operations.js +22 -21
- package/dist/access-recall-concurrency.d.ts +11 -11
- package/dist/access-recall-concurrency.js +19 -18
- package/dist/access-recall-response.d.ts +10 -10
- package/dist/access-recall-response.js +19 -18
- package/dist/access-recall-surface.d.ts +10 -10
- package/dist/access-recall-surface.js +19 -18
- package/dist/access-schema.d.ts +7 -7
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-DvPW_M5j.d.ts → access-service-B5RwVsTV.d.ts} +7 -7
- package/dist/access-service.d.ts +11 -11
- package/dist/access-service.js +19 -18
- package/dist/access-surface-catalog.d.ts +11 -11
- package/dist/access-wearables-meetings-surface.d.ts +4 -3
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +3 -3
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +8 -8
- package/dist/briefing.d.ts +3 -2
- package/dist/briefing.js +10 -9
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +3 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-BIVgftWu.d.ts → catalog-V7inUQdp.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 +11 -10
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-KUJQHXPH.js → chunk-2EUVEDF5.js} +2 -2
- package/dist/{chunk-2XC73SHY.js → chunk-4WKA6I65.js} +20 -20
- package/dist/{chunk-2XC73SHY.js.map → chunk-4WKA6I65.js.map} +1 -1
- package/dist/{chunk-AIK5W3IW.js → chunk-5TFP4CUL.js} +68 -2
- package/dist/chunk-5TFP4CUL.js.map +1 -0
- package/dist/{chunk-EVX44I7C.js → chunk-5W4ZLBEZ.js} +9 -9
- package/dist/{chunk-P3MH72CI.js → chunk-62U4J2GN.js} +2 -2
- package/dist/{chunk-YGHMSDLK.js → chunk-66772WBP.js} +2 -2
- package/dist/{chunk-EP5BVCRI.js → chunk-6BNE62PM.js} +2 -2
- package/dist/{chunk-AAP5T6QG.js → chunk-6DNMUYEE.js} +2 -2
- package/dist/{chunk-SG265CVF.js → chunk-6FUPCJUL.js} +7 -7
- package/dist/{chunk-WZ7UB7XQ.js → chunk-6U7F52DE.js} +19 -19
- package/dist/{chunk-E4OCKU67.js → chunk-7XC2HX75.js} +2 -2
- package/dist/{chunk-OVRQLAYR.js → chunk-AZ5BTEKX.js} +2 -2
- package/dist/{chunk-2HNUMF5V.js → chunk-CZRYLSSW.js} +3 -3
- package/dist/chunk-FBUE6PD3.js +1769 -0
- package/dist/chunk-FBUE6PD3.js.map +1 -0
- package/dist/{chunk-WONS4RZ2.js → chunk-FDAMS4PK.js} +2 -2
- package/dist/{chunk-WL5EBRDH.js → chunk-FFAZ2XFU.js} +9 -8
- package/dist/chunk-FFAZ2XFU.js.map +1 -0
- package/dist/{chunk-Q6VFVAKZ.js → chunk-GIBMDSJ2.js} +2 -2
- package/dist/{chunk-ZXAGJFHJ.js → chunk-H5CPS5JP.js} +3 -3
- package/dist/{chunk-NBDCZWP4.js → chunk-I5NLEENJ.js} +2 -2
- package/dist/{chunk-DRBXHRXE.js → chunk-IB26HVAJ.js} +2 -2
- package/dist/{chunk-XEQ7OT7U.js → chunk-IC6CBHCA.js} +3 -3
- package/dist/{chunk-CMRQ34J5.js → chunk-JCBWCYEJ.js} +2 -2
- package/dist/{chunk-3PDOVUHC.js → chunk-KXB4VXHY.js} +2 -2
- package/dist/{chunk-G6LBWZRF.js → chunk-LBOD4U5B.js} +7 -7
- package/dist/{chunk-ZGOQTOYA.js → chunk-LGHKJABJ.js} +3 -3
- package/dist/{chunk-T3GCXULH.js → chunk-M3C2V3PV.js} +10 -10
- package/dist/{chunk-OFHFH655.js → chunk-MJCGLM25.js} +8 -8
- package/dist/{chunk-JDZRYH2N.js → chunk-NHWB6HG2.js} +2 -2
- package/dist/{chunk-TC3VNKGX.js → chunk-NM2WP3B7.js} +4 -4
- package/dist/{chunk-IMA2EWLV.js → chunk-P5PXWTW7.js} +2 -2
- package/dist/chunk-PH3ZWL3W.js +786 -0
- package/dist/chunk-PH3ZWL3W.js.map +1 -0
- package/dist/{chunk-QMBNMPQM.js → chunk-PUS63Q3G.js} +24 -22
- package/dist/{chunk-QMBNMPQM.js.map → chunk-PUS63Q3G.js.map} +1 -1
- package/dist/{chunk-2QMKMVHH.js → chunk-QNWXCJ76.js} +2 -2
- package/dist/{chunk-FCDWWCLX.js → chunk-RGIWINDN.js} +427 -795
- package/dist/chunk-RGIWINDN.js.map +1 -0
- package/dist/{chunk-CSMCNOPH.js → chunk-SPJXKAVF.js} +3 -3
- package/dist/{chunk-4PBVXX7L.js → chunk-TH2LSRMG.js} +2 -2
- package/dist/{chunk-ESBQWC3U.js → chunk-VI2JQPNQ.js} +2 -2
- package/dist/{chunk-GGUBDXRR.js → chunk-W5EG6LE3.js} +2 -2
- package/dist/{chunk-DEBHXHJR.js → chunk-WIPEXG6R.js} +2 -2
- package/dist/{chunk-SH6YO3LT.js → chunk-WVO4G37W.js} +2 -2
- package/dist/{chunk-VVIW3PTA.js → chunk-YTQNQ4KB.js} +14 -14
- package/dist/{chunk-PKRIQL3V.js → chunk-YTSNAUXN.js} +2 -2
- package/dist/{chunk-QAOGGNE3.js → chunk-ZPSVBSOF.js} +4 -4
- package/dist/{cli-CxoSOiG0.d.ts → cli-C4GbETPC.d.ts} +5 -5
- package/dist/cli.d.ts +13 -13
- package/dist/cli.js +36 -35
- package/dist/compounding/engine.d.ts +3 -2
- package/dist/compounding/engine.js +10 -9
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +3 -3
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +10 -9
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +10 -9
- package/dist/consolidation-provenance-check.d.ts +3 -2
- package/dist/consolidation-undo.d.ts +3 -2
- package/dist/contradiction/index.d.ts +3 -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/corpus-watermark.d.ts +1 -1
- package/dist/corpus-watermark.js +12 -11
- 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 +3 -2
- package/dist/entity-retrieval.js +10 -9
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +52 -10
- package/dist/explicit-capture.js +13 -1
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction-source-grounding-rules.d.ts +1 -1
- package/dist/extraction-source-grounding.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{forget-HRPLVB3W.js → forget-4UY2EOKH.js} +3 -3
- 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/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/index.d.ts +21 -21
- package/dist/index.js +47 -45
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- 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 +10 -9
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -9
- package/dist/maintenance/rebuild-memory-projection.d.ts +3 -2
- package/dist/maintenance/rebuild-memory-projection.js +11 -10
- package/dist/{maintenance-Cb-PjxQd.d.ts → maintenance-CL30NWtc.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +11 -11
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +3 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +4 -3
- package/dist/namespaces/migrate.js +11 -10
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +4 -3
- package/dist/namespaces/storage.js +10 -9
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/offline-sync-impression-drain.js +5 -5
- package/dist/offline-sync.js +2 -2
- package/dist/operator-doctor-corpus.d.ts +1 -1
- package/dist/operator-doctor-corpus.js +13 -12
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +4 -3
- package/dist/operator-toolkit.js +18 -17
- package/dist/orchestration/compression-guideline-coordinator.d.ts +3 -2
- package/dist/orchestration/maintenance.d.ts +5 -4
- package/dist/orchestration/maintenance.js +15 -14
- package/dist/{orchestrator-5GKqb_HL.d.ts → orchestrator-CKTCRwWV.d.ts} +7 -7
- package/dist/orchestrator.d.ts +8 -8
- package/dist/orchestrator.js +45 -44
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-3Qi1TpRo.d.ts → pipeline-B7wj4rRZ.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{purge-YSGX5BOG.js → purge-533FZLS4.js} +16 -9
- package/dist/purge-533FZLS4.js.map +1 -0
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/{qmd-D9JqzzRX.d.ts → qmd-q9tg4dHp.d.ts} +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-state.js +2 -2
- 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-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- 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/replica-divergence.d.ts +1 -1
- package/dist/replica-peers-config.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +4 -4
- 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/salvage-envelope.d.ts +1 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/{scope-profiles-C4g6_UdP.d.ts → scope-profiles-CWDyglPM.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- 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/secure-store/index.js +2 -2
- package/dist/{semantic-consolidation-BrxqnzTW.d.ts → semantic-consolidation-BUE3deLn.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +11 -10
- package/dist/semantic-rule-promotion.js +10 -9
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +10 -9
- package/dist/{service-Bi1JgxdP.d.ts → service-CXgM0bQG.d.ts} +2 -2
- 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-CcH6yPk2.d.ts → storage-DysJq-XH.d.ts} +163 -27
- package/dist/storage.d.ts +3 -2
- package/dist/storage.js +12 -10
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +3 -3
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/summary-snapshot.js +2 -2
- package/dist/temporal-supersession.d.ts +3 -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 +3 -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/types.d.ts +12 -12
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-Dg0w9ADr.d.ts → types-aPPzhnjT.d.ts} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/utils/serialize-mutations.d.ts +11 -7
- package/dist/utils/serialize-mutations.js +1 -1
- package/dist/verified-recall.js +10 -9
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/access-observe-write-surface.ts +1 -1
- package/src/access-service-coding-write.test.ts +40 -0
- package/src/explicit-capture.test.ts +1110 -1
- package/src/explicit-capture.ts +534 -135
- package/src/maintenance/purge.test.ts +157 -1
- package/src/maintenance/purge.ts +15 -9
- package/src/secure-store/secure-fs.ts +78 -1
- package/src/storage/content-hash-index.ts +43 -17
- package/src/storage/entity-canonical-id-migration.ts +151 -23
- package/src/storage/tombstone-blocked-capture-index.ts +562 -0
- package/src/storage/tombstone-blocked-capture-mutation.ts +284 -0
- package/src/storage/tombstone-blocked-capture-sync.ts +872 -0
- package/src/storage-fact-hash-index-defer.test.ts +2900 -57
- package/src/storage.ts +266 -265
- package/src/testing/subjects/tombstone-blocked-capture.test.ts +181 -0
- package/src/utils/serialize-mutations.test.ts +4 -1
- package/src/utils/serialize-mutations.ts +26 -27
- package/dist/chunk-55MDBZLC.js +0 -490
- package/dist/chunk-55MDBZLC.js.map +0 -1
- package/dist/chunk-AIK5W3IW.js.map +0 -1
- package/dist/chunk-FCDWWCLX.js.map +0 -1
- package/dist/chunk-WL5EBRDH.js.map +0 -1
- package/dist/purge-YSGX5BOG.js.map +0 -1
- /package/dist/{chunk-KUJQHXPH.js.map → chunk-2EUVEDF5.js.map} +0 -0
- /package/dist/{chunk-EVX44I7C.js.map → chunk-5W4ZLBEZ.js.map} +0 -0
- /package/dist/{chunk-P3MH72CI.js.map → chunk-62U4J2GN.js.map} +0 -0
- /package/dist/{chunk-YGHMSDLK.js.map → chunk-66772WBP.js.map} +0 -0
- /package/dist/{chunk-EP5BVCRI.js.map → chunk-6BNE62PM.js.map} +0 -0
- /package/dist/{chunk-AAP5T6QG.js.map → chunk-6DNMUYEE.js.map} +0 -0
- /package/dist/{chunk-SG265CVF.js.map → chunk-6FUPCJUL.js.map} +0 -0
- /package/dist/{chunk-WZ7UB7XQ.js.map → chunk-6U7F52DE.js.map} +0 -0
- /package/dist/{chunk-E4OCKU67.js.map → chunk-7XC2HX75.js.map} +0 -0
- /package/dist/{chunk-OVRQLAYR.js.map → chunk-AZ5BTEKX.js.map} +0 -0
- /package/dist/{chunk-2HNUMF5V.js.map → chunk-CZRYLSSW.js.map} +0 -0
- /package/dist/{chunk-WONS4RZ2.js.map → chunk-FDAMS4PK.js.map} +0 -0
- /package/dist/{chunk-Q6VFVAKZ.js.map → chunk-GIBMDSJ2.js.map} +0 -0
- /package/dist/{chunk-ZXAGJFHJ.js.map → chunk-H5CPS5JP.js.map} +0 -0
- /package/dist/{chunk-NBDCZWP4.js.map → chunk-I5NLEENJ.js.map} +0 -0
- /package/dist/{chunk-DRBXHRXE.js.map → chunk-IB26HVAJ.js.map} +0 -0
- /package/dist/{chunk-XEQ7OT7U.js.map → chunk-IC6CBHCA.js.map} +0 -0
- /package/dist/{chunk-CMRQ34J5.js.map → chunk-JCBWCYEJ.js.map} +0 -0
- /package/dist/{chunk-3PDOVUHC.js.map → chunk-KXB4VXHY.js.map} +0 -0
- /package/dist/{chunk-G6LBWZRF.js.map → chunk-LBOD4U5B.js.map} +0 -0
- /package/dist/{chunk-ZGOQTOYA.js.map → chunk-LGHKJABJ.js.map} +0 -0
- /package/dist/{chunk-T3GCXULH.js.map → chunk-M3C2V3PV.js.map} +0 -0
- /package/dist/{chunk-OFHFH655.js.map → chunk-MJCGLM25.js.map} +0 -0
- /package/dist/{chunk-JDZRYH2N.js.map → chunk-NHWB6HG2.js.map} +0 -0
- /package/dist/{chunk-TC3VNKGX.js.map → chunk-NM2WP3B7.js.map} +0 -0
- /package/dist/{chunk-IMA2EWLV.js.map → chunk-P5PXWTW7.js.map} +0 -0
- /package/dist/{chunk-2QMKMVHH.js.map → chunk-QNWXCJ76.js.map} +0 -0
- /package/dist/{chunk-CSMCNOPH.js.map → chunk-SPJXKAVF.js.map} +0 -0
- /package/dist/{chunk-4PBVXX7L.js.map → chunk-TH2LSRMG.js.map} +0 -0
- /package/dist/{chunk-ESBQWC3U.js.map → chunk-VI2JQPNQ.js.map} +0 -0
- /package/dist/{chunk-GGUBDXRR.js.map → chunk-W5EG6LE3.js.map} +0 -0
- /package/dist/{chunk-DEBHXHJR.js.map → chunk-WIPEXG6R.js.map} +0 -0
- /package/dist/{chunk-SH6YO3LT.js.map → chunk-WVO4G37W.js.map} +0 -0
- /package/dist/{chunk-VVIW3PTA.js.map → chunk-YTQNQ4KB.js.map} +0 -0
- /package/dist/{chunk-PKRIQL3V.js.map → chunk-YTSNAUXN.js.map} +0 -0
- /package/dist/{chunk-QAOGGNE3.js.map → chunk-ZPSVBSOF.js.map} +0 -0
- /package/dist/{forget-HRPLVB3W.js.map → forget-4UY2EOKH.js.map} +0 -0
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
TombstoneStore,
|
|
14
14
|
buildRetiredFactTombstoneInputs,
|
|
15
15
|
collectRetiredMemoriesForRebuild
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-QNWXCJ76.js";
|
|
17
17
|
import {
|
|
18
18
|
computeSupersessionKey,
|
|
19
19
|
normalizeSupersessionKey,
|
|
@@ -42,13 +42,10 @@ import {
|
|
|
42
42
|
readMaybeEncryptedLines,
|
|
43
43
|
readMemoryActionEventRowsFromLines,
|
|
44
44
|
serializeLifecycleAppendPayload
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-MJCGLM25.js";
|
|
46
46
|
import {
|
|
47
47
|
namespaceIdentityFromToken
|
|
48
48
|
} from "./chunk-EGGE52UU.js";
|
|
49
|
-
import {
|
|
50
|
-
withHeldFileLock
|
|
51
|
-
} from "./chunk-WL5EBRDH.js";
|
|
52
49
|
import {
|
|
53
50
|
rewriteProjectedEntityReferences,
|
|
54
51
|
rewriteProjectedMemoryEntityReference
|
|
@@ -57,15 +54,6 @@ import {
|
|
|
57
54
|
assertPathInsideRoot,
|
|
58
55
|
pathIsInside
|
|
59
56
|
} from "./chunk-GCASGYIO.js";
|
|
60
|
-
import {
|
|
61
|
-
markProjectedMemoryPathInvalid,
|
|
62
|
-
openProjectionReadonly,
|
|
63
|
-
readProjectedGovernanceRecord,
|
|
64
|
-
readProjectedMemoryBrowse,
|
|
65
|
-
readProjectedMemoryState,
|
|
66
|
-
readProjectedMemoryTimeline,
|
|
67
|
-
updateProjectedMemoryPath
|
|
68
|
-
} from "./chunk-3CSX3ETE.js";
|
|
69
57
|
import {
|
|
70
58
|
inferMemoryStatus,
|
|
71
59
|
isArchivedMemoryPath,
|
|
@@ -91,6 +79,26 @@ import {
|
|
|
91
79
|
parseFaithfulnessField,
|
|
92
80
|
serializeFaithfulnessFields
|
|
93
81
|
} from "./chunk-JYIJNNN3.js";
|
|
82
|
+
import {
|
|
83
|
+
CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS,
|
|
84
|
+
ContentHashIndex,
|
|
85
|
+
FactHashIndexNotAuthoritativeError,
|
|
86
|
+
TombstoneBlockedCaptureIndexHost,
|
|
87
|
+
buildExplicitCaptureDedupKey,
|
|
88
|
+
parseTombstoneBlockedOfflineSyncMemory
|
|
89
|
+
} from "./chunk-FBUE6PD3.js";
|
|
90
|
+
import {
|
|
91
|
+
withHeldFileLock
|
|
92
|
+
} from "./chunk-FFAZ2XFU.js";
|
|
93
|
+
import {
|
|
94
|
+
markProjectedMemoryPathInvalid,
|
|
95
|
+
openProjectionReadonly,
|
|
96
|
+
readProjectedGovernanceRecord,
|
|
97
|
+
readProjectedMemoryBrowse,
|
|
98
|
+
readProjectedMemoryState,
|
|
99
|
+
readProjectedMemoryTimeline,
|
|
100
|
+
updateProjectedMemoryPath
|
|
101
|
+
} from "./chunk-3CSX3ETE.js";
|
|
94
102
|
import {
|
|
95
103
|
composeMemoryEnvelope,
|
|
96
104
|
isSealedMemoryEnvelope,
|
|
@@ -126,10 +134,6 @@ import {
|
|
|
126
134
|
getInFlightRead,
|
|
127
135
|
setInFlightRead
|
|
128
136
|
} from "./chunk-PDJTHRAW.js";
|
|
129
|
-
import {
|
|
130
|
-
computeContentHash,
|
|
131
|
-
normalizeContent
|
|
132
|
-
} from "./chunk-W5H6ZRPT.js";
|
|
133
137
|
import {
|
|
134
138
|
isConsolidationOperator,
|
|
135
139
|
isValidDerivedFromEntry
|
|
@@ -164,9 +168,8 @@ import {
|
|
|
164
168
|
probeEncryptedRegularFileHeader,
|
|
165
169
|
readMaybeEncryptedFile,
|
|
166
170
|
readMaybeEncryptedFileBuffer,
|
|
167
|
-
writeMaybeEncryptedFile
|
|
168
|
-
|
|
169
|
-
} from "./chunk-AIK5W3IW.js";
|
|
171
|
+
writeMaybeEncryptedFile
|
|
172
|
+
} from "./chunk-5TFP4CUL.js";
|
|
170
173
|
import {
|
|
171
174
|
isErrnoCode
|
|
172
175
|
} from "./chunk-5UZXUTVO.js";
|
|
@@ -208,16 +211,16 @@ import {
|
|
|
208
211
|
readdir as readdir5,
|
|
209
212
|
readFile as readFile2,
|
|
210
213
|
realpath as realpath3,
|
|
211
|
-
stat as
|
|
214
|
+
stat as stat2,
|
|
212
215
|
writeFile as writeFile2,
|
|
213
|
-
mkdir
|
|
216
|
+
mkdir,
|
|
214
217
|
unlink as unlink3,
|
|
215
218
|
appendFile,
|
|
216
219
|
open
|
|
217
220
|
} from "fs/promises";
|
|
218
221
|
import { appendFileSync, createReadStream, mkdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
219
222
|
import { createHash as createHash5 } from "crypto";
|
|
220
|
-
import
|
|
223
|
+
import path9 from "path";
|
|
221
224
|
|
|
222
225
|
// src/storage/memory-read-store.ts
|
|
223
226
|
import { lstat, readdir, realpath, stat } from "fs/promises";
|
|
@@ -1478,7 +1481,15 @@ function resolveHistoricalEntityCanonicalId(normalized, mappings) {
|
|
|
1478
1481
|
var EntityCanonicalIdMigrationStateSchema = z.object({
|
|
1479
1482
|
version: z.literal(ENTITY_CANONICAL_ID_MIGRATION_VERSION),
|
|
1480
1483
|
complete: z.boolean(),
|
|
1481
|
-
mappings: z.record(z.string().min(1))
|
|
1484
|
+
mappings: z.record(z.string().min(1)),
|
|
1485
|
+
/**
|
|
1486
|
+
* Pairs the migration refused to resolve, PARKED rather than dropped.
|
|
1487
|
+
* Deleting the record would strand the collision: once an operator removes
|
|
1488
|
+
* the legacy file, no later scan can rediscover the mapping, so references
|
|
1489
|
+
* still naming the legacy id would never be rewritten. Optional so a journal
|
|
1490
|
+
* written by an older build still parses.
|
|
1491
|
+
*/
|
|
1492
|
+
blocked: z.record(z.string().min(1)).optional()
|
|
1482
1493
|
});
|
|
1483
1494
|
async function fingerprintPath(filePath) {
|
|
1484
1495
|
let fileStat;
|
|
@@ -1642,26 +1653,26 @@ function assertSafeEntityId(entityId) {
|
|
|
1642
1653
|
}
|
|
1643
1654
|
}
|
|
1644
1655
|
async function assertNotSymlink(filePath, label) {
|
|
1645
|
-
let
|
|
1656
|
+
let stat3;
|
|
1646
1657
|
try {
|
|
1647
|
-
|
|
1658
|
+
stat3 = await lstat2(filePath);
|
|
1648
1659
|
} catch (error) {
|
|
1649
1660
|
if (isErrnoCode(error, "ENOENT")) return;
|
|
1650
1661
|
throw error;
|
|
1651
1662
|
}
|
|
1652
|
-
if (
|
|
1663
|
+
if (stat3.isSymbolicLink()) {
|
|
1653
1664
|
throw new Error(`Refusing entity canonical-id migration through symlink: ${label}.`);
|
|
1654
1665
|
}
|
|
1655
1666
|
}
|
|
1656
1667
|
async function assertNotMemorySymlink(filePath) {
|
|
1657
|
-
let
|
|
1668
|
+
let stat3;
|
|
1658
1669
|
try {
|
|
1659
|
-
|
|
1670
|
+
stat3 = await lstat2(filePath);
|
|
1660
1671
|
} catch (error) {
|
|
1661
1672
|
if (isErrnoCode(error, "ENOENT")) return;
|
|
1662
1673
|
throw error;
|
|
1663
1674
|
}
|
|
1664
|
-
if (
|
|
1675
|
+
if (stat3.isSymbolicLink()) {
|
|
1665
1676
|
throw new Error(`Refusing entity canonical-id migration through symlinked memory: ${filePath}.`);
|
|
1666
1677
|
}
|
|
1667
1678
|
}
|
|
@@ -1682,9 +1693,10 @@ async function readSafeEntityEntries(entitiesDir) {
|
|
|
1682
1693
|
}
|
|
1683
1694
|
return entries;
|
|
1684
1695
|
}
|
|
1685
|
-
async function discoverMappings(deps, knownMappings = {}) {
|
|
1696
|
+
async function discoverMappings(deps, knownMappings = {}, blocked = /* @__PURE__ */ new Map()) {
|
|
1686
1697
|
const mappings = {};
|
|
1687
1698
|
const canonicalOwners = /* @__PURE__ */ new Map();
|
|
1699
|
+
blocked.clear();
|
|
1688
1700
|
const entityEntries = await readSafeEntityEntries(deps.entitiesDir);
|
|
1689
1701
|
for (const entry of entityEntries) {
|
|
1690
1702
|
if (!entry.endsWith(".md")) continue;
|
|
@@ -1710,18 +1722,20 @@ async function discoverMappings(deps, knownMappings = {}) {
|
|
|
1710
1722
|
if (canonicalExists) {
|
|
1711
1723
|
const canonicalContent2 = await deps.readStorageSecureFile(canonicalPath);
|
|
1712
1724
|
if (entityContent !== canonicalContent2) {
|
|
1713
|
-
|
|
1725
|
+
blocked.set(legacyId, canonicalId);
|
|
1726
|
+
continue;
|
|
1714
1727
|
}
|
|
1715
1728
|
}
|
|
1716
|
-
|
|
1717
|
-
if (previousOwner !== void 0 && previousOwner !== legacyId) {
|
|
1718
|
-
throw new Error(
|
|
1719
|
-
`Cannot migrate legacy entity ids ${previousOwner} and ${legacyId}: both normalize to ${canonicalId}.`
|
|
1720
|
-
);
|
|
1721
|
-
}
|
|
1722
|
-
canonicalOwners.set(canonicalId, legacyId);
|
|
1729
|
+
canonicalOwners.set(canonicalId, [...canonicalOwners.get(canonicalId) ?? [], legacyId]);
|
|
1723
1730
|
mappings[legacyId] = canonicalId;
|
|
1724
1731
|
}
|
|
1732
|
+
for (const [canonicalId, claimants] of canonicalOwners) {
|
|
1733
|
+
if (claimants.length < 2) continue;
|
|
1734
|
+
for (const legacyId of claimants) {
|
|
1735
|
+
blocked.set(legacyId, canonicalId);
|
|
1736
|
+
delete mappings[legacyId];
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1725
1739
|
return mappings;
|
|
1726
1740
|
}
|
|
1727
1741
|
async function rewriteReferences(deps, mappings, refreshLock) {
|
|
@@ -1923,7 +1937,16 @@ function mergeDiscoveredMappings(existing, discovered) {
|
|
|
1923
1937
|
for (const [legacyId, canonicalId] of Object.entries(discovered)) {
|
|
1924
1938
|
const previousMapping = merged[legacyId];
|
|
1925
1939
|
if (previousMapping !== void 0 && previousMapping !== canonicalId) {
|
|
1926
|
-
|
|
1940
|
+
let hop = canonicalId;
|
|
1941
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1942
|
+
while (hop !== void 0 && hop !== previousMapping && !seen.has(hop)) {
|
|
1943
|
+
seen.add(hop);
|
|
1944
|
+
hop = merged[hop];
|
|
1945
|
+
}
|
|
1946
|
+
if (hop !== previousMapping) {
|
|
1947
|
+
throw new Error(`Legacy entity id ${legacyId} changed canonical target during migration.`);
|
|
1948
|
+
}
|
|
1949
|
+
continue;
|
|
1927
1950
|
}
|
|
1928
1951
|
if (previousMapping === void 0 && wouldCreateMappingCycle(merged, legacyId, canonicalId)) continue;
|
|
1929
1952
|
const previousOwner = canonicalOwners.get(canonicalId);
|
|
@@ -1978,7 +2001,7 @@ async function migrateEntityFilePair(deps, legacyId, canonicalId, refreshMigrati
|
|
|
1978
2001
|
deps.isEncryptedStorageFile(canonicalPath)
|
|
1979
2002
|
]);
|
|
1980
2003
|
if (legacyContent !== canonicalContent2) {
|
|
1981
|
-
|
|
2004
|
+
return { deferred: false, progressed: false, blocked: true };
|
|
1982
2005
|
}
|
|
1983
2006
|
if (legacyEncrypted && !canonicalEncrypted) {
|
|
1984
2007
|
if (!await refreshMigrationLock() || !await refreshEntityLock()) {
|
|
@@ -2055,6 +2078,46 @@ async function migrateLegacyEntityCanonicalIds(deps) {
|
|
|
2055
2078
|
mappings: {}
|
|
2056
2079
|
};
|
|
2057
2080
|
}
|
|
2081
|
+
const blocked = /* @__PURE__ */ new Map();
|
|
2082
|
+
const finish = () => {
|
|
2083
|
+
if (blocked.size > 0) {
|
|
2084
|
+
const pairs = [...blocked].map(([legacyId, canonicalId]) => `${legacyId} -> ${canonicalId}`).join(", ");
|
|
2085
|
+
log.warn(
|
|
2086
|
+
`entity canonical-id migration skipped ${blocked.size} unresolvable pair(s): ${pairs}. Both files were kept and the legacy id still resolves; merge or delete one side to migrate it.`
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
return deps.readMigrationFingerprint?.();
|
|
2090
|
+
};
|
|
2091
|
+
const pruneBlocked = async () => {
|
|
2092
|
+
const current = state;
|
|
2093
|
+
if (!current) return false;
|
|
2094
|
+
let revived = false;
|
|
2095
|
+
const parked = { ...current.blocked ?? {} };
|
|
2096
|
+
const active = {};
|
|
2097
|
+
for (const [legacyId, canonicalId] of Object.entries(current.mappings)) {
|
|
2098
|
+
if (!blocked.has(legacyId)) active[legacyId] = canonicalId;
|
|
2099
|
+
}
|
|
2100
|
+
for (const [legacyId, canonicalId] of blocked) parked[legacyId] = canonicalId;
|
|
2101
|
+
for (const [legacyId, canonicalId] of Object.entries(parked)) {
|
|
2102
|
+
if (blocked.has(legacyId)) continue;
|
|
2103
|
+
delete parked[legacyId];
|
|
2104
|
+
if (active[legacyId] !== void 0) continue;
|
|
2105
|
+
const legacyPath = deps.resolveEntityFilePath(legacyId);
|
|
2106
|
+
if (legacyPath !== null && await fileExists(legacyPath)) continue;
|
|
2107
|
+
active[legacyId] = canonicalId;
|
|
2108
|
+
revived = true;
|
|
2109
|
+
}
|
|
2110
|
+
const collapsed = collapseMappings(active);
|
|
2111
|
+
for (const [legacyId, canonicalId] of Object.entries(parked)) {
|
|
2112
|
+
const moved = collapsed[canonicalId];
|
|
2113
|
+
if (moved !== void 0 && moved !== legacyId) parked[legacyId] = moved;
|
|
2114
|
+
}
|
|
2115
|
+
const same = (a, b) => Object.keys(a).length === Object.keys(b).length && Object.entries(a).every(([key, value]) => b[key] === value);
|
|
2116
|
+
if (same(collapsed, current.mappings) && same(parked, current.blocked ?? {})) return revived;
|
|
2117
|
+
state = { ...current, mappings: collapsed, blocked: parked };
|
|
2118
|
+
await writeState(deps, state);
|
|
2119
|
+
return revived;
|
|
2120
|
+
};
|
|
2058
2121
|
const previousMappings = state.mappings;
|
|
2059
2122
|
const collapsedMappings = collapseMappings(previousMappings);
|
|
2060
2123
|
const mappingsChanged = Object.keys(collapsedMappings).length !== Object.keys(previousMappings).length || Object.entries(collapsedMappings).some(
|
|
@@ -2065,11 +2128,12 @@ async function migrateLegacyEntityCanonicalIds(deps) {
|
|
|
2065
2128
|
if (!await lock.refresh()) throw new Error("Lost entity canonical-id migration lock.");
|
|
2066
2129
|
const discoveredBefore = rejectMappingCycles(
|
|
2067
2130
|
state.mappings,
|
|
2068
|
-
await discoverMappings(deps, state.mappings)
|
|
2131
|
+
await discoverMappings(deps, state.mappings, blocked)
|
|
2069
2132
|
);
|
|
2133
|
+
await pruneBlocked();
|
|
2070
2134
|
const mergedBefore = collapseMappings(mergeDiscoveredMappings(state.mappings, discoveredBefore));
|
|
2071
2135
|
if (state.complete && Object.keys(discoveredBefore).length === 0 && !mappingsChanged) {
|
|
2072
|
-
if (Object.keys(state.mappings).length === 0) return
|
|
2136
|
+
if (Object.keys(state.mappings).length === 0) return finish();
|
|
2073
2137
|
const rewroteColdMemory2 = await rewriteKnownReferences(
|
|
2074
2138
|
deps,
|
|
2075
2139
|
state.mappings,
|
|
@@ -2079,7 +2143,7 @@ async function migrateLegacyEntityCanonicalIds(deps) {
|
|
|
2079
2143
|
deps.invalidateAllMemoriesCache();
|
|
2080
2144
|
if (rewroteColdMemory2) deps.invalidateColdMemoriesCache();
|
|
2081
2145
|
deps.bumpMemoryStatusVersion();
|
|
2082
|
-
return
|
|
2146
|
+
return finish();
|
|
2083
2147
|
}
|
|
2084
2148
|
if (state.complete || Object.keys(discoveredBefore).length > 0 || Object.keys(mergedBefore).length !== Object.keys(state.mappings).length) {
|
|
2085
2149
|
state = { ...state, complete: false, mappings: mergedBefore };
|
|
@@ -2089,11 +2153,12 @@ async function migrateLegacyEntityCanonicalIds(deps) {
|
|
|
2089
2153
|
if (!await lock.refresh()) throw new Error("Lost entity canonical-id migration lock.");
|
|
2090
2154
|
const discoveredAfter2 = rejectMappingCycles(
|
|
2091
2155
|
state.mappings,
|
|
2092
|
-
await discoverMappings(deps, state.mappings)
|
|
2156
|
+
await discoverMappings(deps, state.mappings, blocked)
|
|
2093
2157
|
);
|
|
2094
|
-
|
|
2158
|
+
const revivedInRescan = await pruneBlocked();
|
|
2159
|
+
if (Object.keys(discoveredAfter2).length === 0 && !revivedInRescan) {
|
|
2095
2160
|
await writeState(deps, { ...state, complete: true });
|
|
2096
|
-
return
|
|
2161
|
+
return finish();
|
|
2097
2162
|
}
|
|
2098
2163
|
state = {
|
|
2099
2164
|
...state,
|
|
@@ -2114,9 +2179,8 @@ async function migrateLegacyEntityCanonicalIds(deps) {
|
|
|
2114
2179
|
canonicalId,
|
|
2115
2180
|
() => lock.refresh()
|
|
2116
2181
|
);
|
|
2117
|
-
if (result.
|
|
2118
|
-
|
|
2119
|
-
}
|
|
2182
|
+
if (result.blocked) blocked.set(legacyId, canonicalId);
|
|
2183
|
+
else if (result.deferred) deferredMappings.push([legacyId, canonicalId]);
|
|
2120
2184
|
progressed ||= result.progressed;
|
|
2121
2185
|
}
|
|
2122
2186
|
if (deferredMappings.length === 0) break;
|
|
@@ -2128,6 +2192,7 @@ async function migrateLegacyEntityCanonicalIds(deps) {
|
|
|
2128
2192
|
}
|
|
2129
2193
|
pendingMappings = deferredMappings;
|
|
2130
2194
|
}
|
|
2195
|
+
await pruneBlocked();
|
|
2131
2196
|
const rewroteColdMemory = await rewriteKnownReferences(
|
|
2132
2197
|
deps,
|
|
2133
2198
|
state.mappings,
|
|
@@ -2140,11 +2205,12 @@ async function migrateLegacyEntityCanonicalIds(deps) {
|
|
|
2140
2205
|
if (!await lock.refresh()) throw new Error("Lost entity canonical-id migration lock.");
|
|
2141
2206
|
const discoveredAfter = rejectMappingCycles(
|
|
2142
2207
|
state.mappings,
|
|
2143
|
-
await discoverMappings(deps, state.mappings)
|
|
2208
|
+
await discoverMappings(deps, state.mappings, blocked)
|
|
2144
2209
|
);
|
|
2145
|
-
|
|
2210
|
+
const revivedAfterRewrite = await pruneBlocked();
|
|
2211
|
+
if (Object.keys(discoveredAfter).length === 0 && !revivedAfterRewrite) {
|
|
2146
2212
|
await writeState(deps, { ...state, complete: true });
|
|
2147
|
-
return
|
|
2213
|
+
return finish();
|
|
2148
2214
|
}
|
|
2149
2215
|
state = {
|
|
2150
2216
|
...state,
|
|
@@ -4112,14 +4178,14 @@ var FusionArtifactStore = class {
|
|
|
4112
4178
|
*/
|
|
4113
4179
|
async assertFusionDirNotSymlinked() {
|
|
4114
4180
|
const fusionDir = path7.join(this.wearablesDir, FUSION_DIR_NAME);
|
|
4115
|
-
let
|
|
4181
|
+
let stat3;
|
|
4116
4182
|
try {
|
|
4117
|
-
|
|
4183
|
+
stat3 = await this.io.lstat(fusionDir);
|
|
4118
4184
|
} catch (err) {
|
|
4119
4185
|
if (err.code === "ENOENT") return;
|
|
4120
4186
|
throw err;
|
|
4121
4187
|
}
|
|
4122
|
-
if (
|
|
4188
|
+
if (stat3.isSymbolicLink) {
|
|
4123
4189
|
throw new Error(
|
|
4124
4190
|
"wearable fusion dir '_fusion' is a symbolic link \u2014 refusing to follow it even when it resolves inside the memory dir (AGENTS.md pattern #3)"
|
|
4125
4191
|
);
|
|
@@ -4551,14 +4617,14 @@ var MeetingRecordStore = class {
|
|
|
4551
4617
|
return path8.join(this.meetingsDir, first);
|
|
4552
4618
|
}
|
|
4553
4619
|
async assertNotSymlink(target, kind) {
|
|
4554
|
-
let
|
|
4620
|
+
let stat3;
|
|
4555
4621
|
try {
|
|
4556
|
-
|
|
4622
|
+
stat3 = await this.io.lstat(target);
|
|
4557
4623
|
} catch (err) {
|
|
4558
4624
|
if (err.code === "ENOENT") return;
|
|
4559
4625
|
throw err;
|
|
4560
4626
|
}
|
|
4561
|
-
if (
|
|
4627
|
+
if (stat3.isSymbolicLink) {
|
|
4562
4628
|
const label = path8.relative(this.memoryDir, target) || MEETINGS_DIR_NAME;
|
|
4563
4629
|
throw new Error(
|
|
4564
4630
|
`meetings ${kind} '${label}' is a symbolic link \u2014 refusing to follow it even when it resolves inside the memory dir (AGENTS.md pattern #3)`
|
|
@@ -4975,471 +5041,6 @@ function isEntitySynthesisStale(entity) {
|
|
|
4975
5041
|
return entity.timeline.length > entity.synthesisTimelineCount || structuredFactCount > (entity.synthesisStructuredFactCount ?? 0) || structuredFactDigest !== storedStructuredFactDigest;
|
|
4976
5042
|
}
|
|
4977
5043
|
|
|
4978
|
-
// src/storage/content-hash-index.ts
|
|
4979
|
-
import { mkdir, stat as stat2 } from "fs/promises";
|
|
4980
|
-
import path9 from "path";
|
|
4981
|
-
var CONTENT_HASH_INDEX_LOCK_STALE_MS = 3e4;
|
|
4982
|
-
var CONTENT_HASH_INDEX_RETRY_MAX_ATTEMPTS = 5;
|
|
4983
|
-
var CONTENT_HASH_INDEX_RETRY_BASE_MS = 500;
|
|
4984
|
-
var CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS = 5e3;
|
|
4985
|
-
var FactHashIndexNotAuthoritativeError = class extends Error {
|
|
4986
|
-
constructor(stateDir) {
|
|
4987
|
-
super(
|
|
4988
|
-
`fact-hash index is not authoritative: the cross-process rebuild lock for ${stateDir} could not be acquired within the bounded retry budget`
|
|
4989
|
-
);
|
|
4990
|
-
this.name = "FactHashIndexNotAuthoritativeError";
|
|
4991
|
-
}
|
|
4992
|
-
};
|
|
4993
|
-
var ContentHashIndex = class _ContentHashIndex {
|
|
4994
|
-
hashes = /* @__PURE__ */ new Set();
|
|
4995
|
-
dirty = false;
|
|
4996
|
-
/**
|
|
4997
|
-
* Hashes explicitly removed since the last successful save (issue #1909 review
|
|
4998
|
-
* round 8 thread 3). Tracked separately so `saveMergingWithDisk` can be
|
|
4999
|
-
* removal-AWARE: under the file lock it reads the latest on-disk set, DROPS
|
|
5000
|
-
* these, then unions our additions — so a removal batch cannot resurrect a
|
|
5001
|
-
* hash (union alone would) and cannot clobber a concurrent extraction's
|
|
5002
|
-
* appended hash (blind overwrite would). Consumed + cleared by every save.
|
|
5003
|
-
*/
|
|
5004
|
-
removed = /* @__PURE__ */ new Set();
|
|
5005
|
-
/**
|
|
5006
|
-
* Hashes added by THIS instance since the last successful save (issue #1909
|
|
5007
|
-
* review round 9 finding 3). Tracked separately from the loaded snapshot so a
|
|
5008
|
-
* reconciling save publishes `(on-disk \ removed) ∪ added` — it re-applies only
|
|
5009
|
-
* OUR additions, never the stale loaded entries. Without this, seeding the
|
|
5010
|
-
* merge from `this.hashes` (loaded ∪ added) would resurrect a hash a peer
|
|
5011
|
-
* removed on disk while this instance held a stale snapshot.
|
|
5012
|
-
*/
|
|
5013
|
-
added = /* @__PURE__ */ new Set();
|
|
5014
|
-
filePath;
|
|
5015
|
-
secureStoreKeyProvider;
|
|
5016
|
-
secureStoreWriteKeyProvider;
|
|
5017
|
-
memoryDir;
|
|
5018
|
-
lockOptions;
|
|
5019
|
-
/**
|
|
5020
|
-
* Deferred reconcile-save durable-retry state (issue #1909 / PR #2016). At
|
|
5021
|
-
* most ONE retry is ever armed (`reconcileRetryTimer` is the reentrancy guard)
|
|
5022
|
-
* so successive timed-out saves cannot stack duplicate/parallel retries.
|
|
5023
|
-
* `reconcileRetryBarrier` resolves when the chain settles (published or gave
|
|
5024
|
-
* up) — awaited by tests and any caller wanting the eventual result.
|
|
5025
|
-
*/
|
|
5026
|
-
reconcileRetryTimer = null;
|
|
5027
|
-
reconcileRetryAttempts = 0;
|
|
5028
|
-
reconcileRetryBarrier = null;
|
|
5029
|
-
reconcileRetryResolve = null;
|
|
5030
|
-
/**
|
|
5031
|
-
* On-disk fingerprint (mtime + byte size) of `fact-hashes.txt` captured at the
|
|
5032
|
-
* last point THIS instance's in-memory set matched disk — after `load()`,
|
|
5033
|
-
* `save()`, or a reconcile publish (PR #2016 review). A peer process that
|
|
5034
|
-
* advances the durable index changes this fingerprint;
|
|
5035
|
-
* {@link isDiskFingerprintCurrent} compares a single cheap `stat` against it so
|
|
5036
|
-
* a stale-but-"authoritative" snapshot is detected without an O(file-size)
|
|
5037
|
-
* re-read on the hot path.
|
|
5038
|
-
*/
|
|
5039
|
-
lastSyncedFingerprint = null;
|
|
5040
|
-
constructor(stateDir, secureStoreKeyProvider = () => null, secureStoreWriteKeyProvider = secureStoreKeyProvider, memoryDir = path9.dirname(stateDir), lockOptions = {}) {
|
|
5041
|
-
this.filePath = path9.join(stateDir, "fact-hashes.txt");
|
|
5042
|
-
this.secureStoreKeyProvider = secureStoreKeyProvider;
|
|
5043
|
-
this.secureStoreWriteKeyProvider = secureStoreWriteKeyProvider;
|
|
5044
|
-
this.memoryDir = memoryDir;
|
|
5045
|
-
this.lockOptions = lockOptions;
|
|
5046
|
-
}
|
|
5047
|
-
/** Load existing hashes from disk. Safe to call multiple times. */
|
|
5048
|
-
async load() {
|
|
5049
|
-
try {
|
|
5050
|
-
const raw = await readMaybeEncryptedFile(this.filePath, this.secureStoreKeyProvider(), this.memoryDir);
|
|
5051
|
-
for (const line of raw.split("\n")) {
|
|
5052
|
-
const trimmed = line.trim();
|
|
5053
|
-
if (trimmed.length > 0) {
|
|
5054
|
-
this.hashes.add(trimmed);
|
|
5055
|
-
}
|
|
5056
|
-
}
|
|
5057
|
-
log.debug(`content-hash index: loaded ${this.hashes.size} hashes`);
|
|
5058
|
-
} catch (err) {
|
|
5059
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
5060
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5061
|
-
log.debug("content-hash index: no existing index \u2014 starting fresh");
|
|
5062
|
-
}
|
|
5063
|
-
await this.captureSyncedFingerprint();
|
|
5064
|
-
}
|
|
5065
|
-
/** Cheap `stat` of the durable index file; null when it does not exist yet. */
|
|
5066
|
-
async statIndexFile() {
|
|
5067
|
-
try {
|
|
5068
|
-
const s = await stat2(this.filePath);
|
|
5069
|
-
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
5070
|
-
} catch (err) {
|
|
5071
|
-
if (isErrnoCode(err, "ENOENT")) return null;
|
|
5072
|
-
throw err;
|
|
5073
|
-
}
|
|
5074
|
-
}
|
|
5075
|
-
/** Record the current on-disk fingerprint as this instance's synced baseline. */
|
|
5076
|
-
async captureSyncedFingerprint() {
|
|
5077
|
-
try {
|
|
5078
|
-
this.lastSyncedFingerprint = await this.statIndexFile();
|
|
5079
|
-
} catch (err) {
|
|
5080
|
-
this.lastSyncedFingerprint = null;
|
|
5081
|
-
log.debug(`content-hash index: could not fingerprint ${this.filePath}: ${err}`);
|
|
5082
|
-
}
|
|
5083
|
-
}
|
|
5084
|
-
/**
|
|
5085
|
-
* True when the durable index file is unchanged since this instance last
|
|
5086
|
-
* synced it (load / save / reconcile). False when a peer advanced it, when the
|
|
5087
|
-
* file appeared or vanished, or when freshness cannot be established (stat
|
|
5088
|
-
* error) — the caller then treats the in-memory snapshot as non-authoritative
|
|
5089
|
-
* and confirms a dedup miss against the durable corpus (PR #2016 review). One
|
|
5090
|
-
* `stat`, never an O(file-size) read, so the hot path stays cheap.
|
|
5091
|
-
*/
|
|
5092
|
-
async isDiskFingerprintCurrent() {
|
|
5093
|
-
let current;
|
|
5094
|
-
try {
|
|
5095
|
-
current = await this.statIndexFile();
|
|
5096
|
-
} catch {
|
|
5097
|
-
return false;
|
|
5098
|
-
}
|
|
5099
|
-
const last = this.lastSyncedFingerprint;
|
|
5100
|
-
if (last === null || current === null) {
|
|
5101
|
-
return last === null && current === null;
|
|
5102
|
-
}
|
|
5103
|
-
return current.mtimeMs === last.mtimeMs && current.size === last.size;
|
|
5104
|
-
}
|
|
5105
|
-
/** Check if content already exists in the index. */
|
|
5106
|
-
has(content) {
|
|
5107
|
-
return this.hashes.has(_ContentHashIndex.computeHash(content));
|
|
5108
|
-
}
|
|
5109
|
-
/** Add content hash to the index. */
|
|
5110
|
-
add(content) {
|
|
5111
|
-
const hash = _ContentHashIndex.computeHash(content);
|
|
5112
|
-
this.removed.delete(hash);
|
|
5113
|
-
this.hashes.add(hash);
|
|
5114
|
-
this.added.add(hash);
|
|
5115
|
-
this.dirty = true;
|
|
5116
|
-
}
|
|
5117
|
-
get size() {
|
|
5118
|
-
return this.hashes.size;
|
|
5119
|
-
}
|
|
5120
|
-
/** Clear all loaded hashes so the next save rewrites the index from scratch. */
|
|
5121
|
-
clear() {
|
|
5122
|
-
if (this.hashes.size > 0) {
|
|
5123
|
-
this.hashes.clear();
|
|
5124
|
-
}
|
|
5125
|
-
this.added.clear();
|
|
5126
|
-
this.removed.clear();
|
|
5127
|
-
this.cancelReconcileRetry();
|
|
5128
|
-
this.dirty = true;
|
|
5129
|
-
}
|
|
5130
|
-
/** Persist index to disk if changed (plain whole-file overwrite). */
|
|
5131
|
-
async save() {
|
|
5132
|
-
if (!this.dirty) return;
|
|
5133
|
-
await mkdir(path9.dirname(this.filePath), { recursive: true });
|
|
5134
|
-
const publishedAdded = new Set(this.added);
|
|
5135
|
-
const publishedRemoved = new Set(this.removed);
|
|
5136
|
-
const serialized = [...this.hashes].join("\n") + "\n";
|
|
5137
|
-
await writeMaybeEncryptedFile(
|
|
5138
|
-
this.filePath,
|
|
5139
|
-
serialized,
|
|
5140
|
-
this.secureStoreWriteKeyProvider(),
|
|
5141
|
-
{},
|
|
5142
|
-
this.memoryDir
|
|
5143
|
-
);
|
|
5144
|
-
for (const h of publishedAdded) this.added.delete(h);
|
|
5145
|
-
for (const h of publishedRemoved) this.removed.delete(h);
|
|
5146
|
-
this.dirty = this.added.size > 0 || this.removed.size > 0;
|
|
5147
|
-
await this.captureSyncedFingerprint();
|
|
5148
|
-
if (this.dirty) {
|
|
5149
|
-
this.scheduleReconcileRetry();
|
|
5150
|
-
} else {
|
|
5151
|
-
this.cancelReconcileRetry();
|
|
5152
|
-
}
|
|
5153
|
-
log.debug(`content-hash index: saved ${this.hashes.size} hashes`);
|
|
5154
|
-
}
|
|
5155
|
-
/**
|
|
5156
|
-
* Persist the index by RECONCILING with the latest on-disk state under a
|
|
5157
|
-
* cross-process lock (issue #1909). Used by the extraction-persist batch save
|
|
5158
|
-
* for BOTH append and removal batches, so appends and removals to the same
|
|
5159
|
-
* index serialize against each other:
|
|
5160
|
-
* final = (on-disk \ this.removed) ∪ this.added
|
|
5161
|
-
* Note it re-applies only OUR local additions (`added`), NOT the loaded
|
|
5162
|
-
* snapshot (review round 9 finding 3): seeding from `this.hashes` would
|
|
5163
|
-
* resurrect a hash a peer removed on disk while we held a stale snapshot. This
|
|
5164
|
-
* preserves a concurrent writer's appended hashes (a blind overwrite would
|
|
5165
|
-
* drop them), never resurrects a removed hash, and never loses our additions.
|
|
5166
|
-
*
|
|
5167
|
-
* Hardening:
|
|
5168
|
-
* - Dirty short-circuit (round 7 finding 3): when this instance neither added
|
|
5169
|
-
* nor removed anything this run, skip the O(file) read+rewrite entirely.
|
|
5170
|
-
* - Cross-process lock (round 7 finding 2): serialize the read→reconcile→write
|
|
5171
|
-
* window on a per-file advisory lock. If the lock is NOT acquired (timeout),
|
|
5172
|
-
* do NOT publish (round 9 finding 2): keep `dirty` set AND schedule a bounded
|
|
5173
|
-
* durable background retry (PR #2016) that keeps re-attempting the locked
|
|
5174
|
-
* publish until the deferred additions land — never an unlocked write that
|
|
5175
|
-
* could clobber a peer, and never a silent "batch save complete" that leaves
|
|
5176
|
-
* the addition in this process's memory only.
|
|
5177
|
-
*/
|
|
5178
|
-
async saveMergingWithDisk() {
|
|
5179
|
-
if (!this.dirty) return;
|
|
5180
|
-
await mkdir(path9.dirname(this.filePath), { recursive: true });
|
|
5181
|
-
const publish = async () => {
|
|
5182
|
-
const publishedAdded = new Set(this.added);
|
|
5183
|
-
const publishedRemoved = new Set(this.removed);
|
|
5184
|
-
const merged = new Set(publishedAdded);
|
|
5185
|
-
try {
|
|
5186
|
-
const raw = await readMaybeEncryptedFile(
|
|
5187
|
-
this.filePath,
|
|
5188
|
-
this.secureStoreKeyProvider(),
|
|
5189
|
-
this.memoryDir
|
|
5190
|
-
);
|
|
5191
|
-
for (const line of raw.split("\n")) {
|
|
5192
|
-
const trimmed = line.trim();
|
|
5193
|
-
if (trimmed.length > 0 && !publishedRemoved.has(trimmed)) merged.add(trimmed);
|
|
5194
|
-
}
|
|
5195
|
-
} catch (err) {
|
|
5196
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
5197
|
-
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5198
|
-
}
|
|
5199
|
-
await mkdir(path9.dirname(this.filePath), { recursive: true });
|
|
5200
|
-
await writeMaybeEncryptedFile(
|
|
5201
|
-
this.filePath,
|
|
5202
|
-
[...merged].join("\n") + "\n",
|
|
5203
|
-
this.secureStoreWriteKeyProvider(),
|
|
5204
|
-
{},
|
|
5205
|
-
this.memoryDir
|
|
5206
|
-
);
|
|
5207
|
-
for (const h of publishedAdded) this.added.delete(h);
|
|
5208
|
-
for (const h of publishedRemoved) this.removed.delete(h);
|
|
5209
|
-
for (const h of this.added) merged.add(h);
|
|
5210
|
-
for (const h of this.removed) merged.delete(h);
|
|
5211
|
-
this.hashes = merged;
|
|
5212
|
-
this.dirty = this.added.size > 0 || this.removed.size > 0;
|
|
5213
|
-
await this.captureSyncedFingerprint();
|
|
5214
|
-
};
|
|
5215
|
-
await withHeldFileLock(
|
|
5216
|
-
`${this.filePath}.lock`,
|
|
5217
|
-
{
|
|
5218
|
-
staleMs: CONTENT_HASH_INDEX_LOCK_STALE_MS,
|
|
5219
|
-
...this.lockOptions.maxWaitMs !== void 0 ? { maxWaitMs: this.lockOptions.maxWaitMs } : {},
|
|
5220
|
-
...this.lockOptions.pollMs !== void 0 ? { pollMs: this.lockOptions.pollMs } : {}
|
|
5221
|
-
},
|
|
5222
|
-
async (acquired) => {
|
|
5223
|
-
if (!acquired) {
|
|
5224
|
-
log.warn(
|
|
5225
|
-
`content-hash index: lock not acquired for ${this.filePath}; deferring reconcile save (dirty retained, scheduling durable retry)`
|
|
5226
|
-
);
|
|
5227
|
-
this.scheduleReconcileRetry();
|
|
5228
|
-
return;
|
|
5229
|
-
}
|
|
5230
|
-
await publish();
|
|
5231
|
-
if (this.dirty) {
|
|
5232
|
-
this.scheduleReconcileRetry();
|
|
5233
|
-
} else {
|
|
5234
|
-
this.cancelReconcileRetry();
|
|
5235
|
-
}
|
|
5236
|
-
}
|
|
5237
|
-
);
|
|
5238
|
-
log.debug(`content-hash index: reconcile-saved ${this.hashes.size} hashes`);
|
|
5239
|
-
}
|
|
5240
|
-
/**
|
|
5241
|
-
* Rebuild-and-publish the index from an authoritative source (the durable
|
|
5242
|
-
* corpus) under the SAME per-file cross-process lock the reconciling
|
|
5243
|
-
* append/removal saves (`saveMergingWithDisk`) use (issue #1909 / PR #2016).
|
|
5244
|
-
* The `populate` callback runs WHILE the lock is held and MUST repopulate this
|
|
5245
|
-
* index from the corpus (`clear()` then `addByHash(...)` for every corpus
|
|
5246
|
-
* hash); the rebuilt set is then published with a plain overwrite that is now
|
|
5247
|
-
* serialized against every locked writer.
|
|
5248
|
-
*
|
|
5249
|
-
* This closes the finding where the previous unlocked clear→scan→save() could
|
|
5250
|
-
* overwrite a peer's newer lock-merged or deferred additions: a locked writer
|
|
5251
|
-
* that committed BEFORE the rebuild acquired the lock has its memory `.md` on
|
|
5252
|
-
* disk and is re-scanned; one that commits AFTER the rebuild releases
|
|
5253
|
-
* reconciles its additions on top of the freshly-published set. No union with
|
|
5254
|
-
* the raw on-disk file is performed, so the rebuild keeps its
|
|
5255
|
-
* garbage-collection property (orphaned hashes for deleted memories drop).
|
|
5256
|
-
*
|
|
5257
|
-
* Lock-acquisition failure is explicit and non-destructive: `populate` is NOT
|
|
5258
|
-
* run and nothing is written (never an unlocked overwrite). Returns true when
|
|
5259
|
-
* the rebuild was published under the lock, false when the lock timed out —
|
|
5260
|
-
* the caller keeps the index non-authoritative and retries on next use. Uses
|
|
5261
|
-
* the same non-reentrant file lock as the reconcile path, so it can never
|
|
5262
|
-
* deadlock with the deferred reconcile-retry: a concurrent locked writer (or a
|
|
5263
|
-
* fired retry) simply yields `acquired=false` after the bounded wait.
|
|
5264
|
-
*/
|
|
5265
|
-
async rebuildUnderLock(populate) {
|
|
5266
|
-
await mkdir(path9.dirname(this.filePath), { recursive: true });
|
|
5267
|
-
let published = false;
|
|
5268
|
-
await withHeldFileLock(
|
|
5269
|
-
`${this.filePath}.lock`,
|
|
5270
|
-
{
|
|
5271
|
-
staleMs: CONTENT_HASH_INDEX_LOCK_STALE_MS,
|
|
5272
|
-
...this.lockOptions.maxWaitMs !== void 0 ? { maxWaitMs: this.lockOptions.maxWaitMs } : {},
|
|
5273
|
-
...this.lockOptions.pollMs !== void 0 ? { pollMs: this.lockOptions.pollMs } : {}
|
|
5274
|
-
},
|
|
5275
|
-
async (acquired) => {
|
|
5276
|
-
if (!acquired) {
|
|
5277
|
-
log.warn(
|
|
5278
|
-
`content-hash index: lock not acquired for ${this.filePath}; deferring authoritative rebuild (index left non-authoritative, retried on next use)`
|
|
5279
|
-
);
|
|
5280
|
-
return;
|
|
5281
|
-
}
|
|
5282
|
-
await populate();
|
|
5283
|
-
await this.save();
|
|
5284
|
-
published = true;
|
|
5285
|
-
}
|
|
5286
|
-
);
|
|
5287
|
-
return published;
|
|
5288
|
-
}
|
|
5289
|
-
/**
|
|
5290
|
-
* True while a background durable reconcile-save retry is armed (PR #2016).
|
|
5291
|
-
* Test/introspection hook.
|
|
5292
|
-
*/
|
|
5293
|
-
get hasPendingReconcileRetry() {
|
|
5294
|
-
return this.reconcileRetryTimer !== null;
|
|
5295
|
-
}
|
|
5296
|
-
/**
|
|
5297
|
-
* Resolve once any armed background reconcile-save retry chain has settled —
|
|
5298
|
-
* either it published the deferred additions, or it exhausted its bounded
|
|
5299
|
-
* attempts and fell back to the corpus-rebuild safety net. Resolves
|
|
5300
|
-
* immediately when nothing is armed. Lets a caller (or a test) await the
|
|
5301
|
-
* eventual durability outcome of a lock-timed-out save.
|
|
5302
|
-
*/
|
|
5303
|
-
async whenReconcileRetrySettled() {
|
|
5304
|
-
await (this.reconcileRetryBarrier ?? Promise.resolve());
|
|
5305
|
-
}
|
|
5306
|
-
/**
|
|
5307
|
-
* Drive any deferred lock-timeout reconcile save to completion INLINE at a
|
|
5308
|
-
* lifecycle boundary (PR #2016 finding 3). The background retry timer is
|
|
5309
|
-
* `unref`'d so it never keeps a long-lived daemon alive — but a short-lived
|
|
5310
|
-
* writer (a one-shot CLI) can exit before it fires, leaving a durable fact
|
|
5311
|
-
* `.md` whose hash never reached `fact-hashes.txt` for peers that already
|
|
5312
|
-
* built their in-memory index. `orchestrator.destroy()` calls this so the
|
|
5313
|
-
* addition publishes before the process exits; long-lived hosts only reach it
|
|
5314
|
-
* at their own shutdown, so their in-flight retries keep running in the
|
|
5315
|
-
* background until then. Bounded by the same attempt ceiling as the background
|
|
5316
|
-
* retry; a permanently contended lock falls back to the corpus-rebuild-on-
|
|
5317
|
-
* restart safety net (the fact `.md` is already durable). No deadlock: each
|
|
5318
|
-
* attempt is the same non-reentrant, bounded-wait file lock.
|
|
5319
|
-
*/
|
|
5320
|
-
async flushReconcileRetry() {
|
|
5321
|
-
if (!this.dirty) return;
|
|
5322
|
-
const maxAttempts = this.lockOptions.retryMaxAttempts ?? CONTENT_HASH_INDEX_RETRY_MAX_ATTEMPTS;
|
|
5323
|
-
const baseMs = this.lockOptions.retryBaseMs ?? CONTENT_HASH_INDEX_RETRY_BASE_MS;
|
|
5324
|
-
for (let attempt = 0; this.dirty && attempt < maxAttempts; attempt += 1) {
|
|
5325
|
-
if (this.reconcileRetryTimer) {
|
|
5326
|
-
clearTimeout(this.reconcileRetryTimer);
|
|
5327
|
-
this.reconcileRetryTimer = null;
|
|
5328
|
-
}
|
|
5329
|
-
await this.saveMergingWithDisk();
|
|
5330
|
-
if (!this.dirty) break;
|
|
5331
|
-
if (attempt < maxAttempts - 1) {
|
|
5332
|
-
const wait = Math.min(baseMs * 2 ** attempt, CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS);
|
|
5333
|
-
await new Promise((resolve3) => setTimeout(resolve3, wait));
|
|
5334
|
-
}
|
|
5335
|
-
}
|
|
5336
|
-
if (this.reconcileRetryTimer) {
|
|
5337
|
-
clearTimeout(this.reconcileRetryTimer);
|
|
5338
|
-
this.reconcileRetryTimer = null;
|
|
5339
|
-
}
|
|
5340
|
-
this.reconcileRetryAttempts = 0;
|
|
5341
|
-
this.settleReconcileRetry();
|
|
5342
|
-
}
|
|
5343
|
-
/**
|
|
5344
|
-
* Schedule ONE bounded, exponentially-backed-off background retry of the
|
|
5345
|
-
* deferred reconcile save (PR #2016). Reentrant-safe: an already-armed timer
|
|
5346
|
-
* short-circuits, so successive timed-out saves never stack duplicate retries.
|
|
5347
|
-
* When attempts are exhausted the chain settles without publishing — the
|
|
5348
|
-
* durable fact `.md` is on disk and a process restart rebuilds the index from
|
|
5349
|
-
* the corpus, so this degrades to that safety net, never a fatal failure.
|
|
5350
|
-
*/
|
|
5351
|
-
scheduleReconcileRetry() {
|
|
5352
|
-
if (this.reconcileRetryTimer) return;
|
|
5353
|
-
const maxAttempts = this.lockOptions.retryMaxAttempts ?? CONTENT_HASH_INDEX_RETRY_MAX_ATTEMPTS;
|
|
5354
|
-
if (this.reconcileRetryAttempts >= maxAttempts) {
|
|
5355
|
-
log.warn(
|
|
5356
|
-
`content-hash index: exhausted ${maxAttempts} deferred reconcile-save retries for ${this.filePath}; ${this.added.size} addition(s) remain dirty in-memory (a process restart rebuilds the index from the durable corpus)`
|
|
5357
|
-
);
|
|
5358
|
-
this.reconcileRetryAttempts = 0;
|
|
5359
|
-
this.settleReconcileRetry();
|
|
5360
|
-
return;
|
|
5361
|
-
}
|
|
5362
|
-
if (!this.reconcileRetryBarrier) {
|
|
5363
|
-
this.reconcileRetryBarrier = new Promise((resolve3) => {
|
|
5364
|
-
this.reconcileRetryResolve = resolve3;
|
|
5365
|
-
});
|
|
5366
|
-
}
|
|
5367
|
-
const attempt = this.reconcileRetryAttempts + 1;
|
|
5368
|
-
const baseMs = this.lockOptions.retryBaseMs ?? CONTENT_HASH_INDEX_RETRY_BASE_MS;
|
|
5369
|
-
const delay = Math.min(baseMs * 2 ** (attempt - 1), CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS);
|
|
5370
|
-
this.reconcileRetryTimer = setTimeout(() => {
|
|
5371
|
-
this.reconcileRetryTimer = null;
|
|
5372
|
-
this.reconcileRetryAttempts = attempt;
|
|
5373
|
-
void this.saveMergingWithDisk().catch((err) => {
|
|
5374
|
-
log.warn(`content-hash index: deferred reconcile-save retry failed for ${this.filePath}: ${err}`);
|
|
5375
|
-
}).finally(() => {
|
|
5376
|
-
if (this.dirty && !this.reconcileRetryTimer) this.settleReconcileRetry();
|
|
5377
|
-
});
|
|
5378
|
-
}, delay);
|
|
5379
|
-
this.reconcileRetryTimer.unref?.();
|
|
5380
|
-
}
|
|
5381
|
-
/** Cancel any armed reconcile-save retry and settle the barrier (a publish landed). */
|
|
5382
|
-
cancelReconcileRetry() {
|
|
5383
|
-
if (this.reconcileRetryTimer) {
|
|
5384
|
-
clearTimeout(this.reconcileRetryTimer);
|
|
5385
|
-
this.reconcileRetryTimer = null;
|
|
5386
|
-
}
|
|
5387
|
-
this.reconcileRetryAttempts = 0;
|
|
5388
|
-
this.settleReconcileRetry();
|
|
5389
|
-
}
|
|
5390
|
-
/** Resolve and clear the retry barrier if one is outstanding. */
|
|
5391
|
-
settleReconcileRetry() {
|
|
5392
|
-
const resolve3 = this.reconcileRetryResolve;
|
|
5393
|
-
this.reconcileRetryBarrier = null;
|
|
5394
|
-
this.reconcileRetryResolve = null;
|
|
5395
|
-
resolve3?.();
|
|
5396
|
-
}
|
|
5397
|
-
/** Remove a hash from the index (used when archiving/deleting). */
|
|
5398
|
-
remove(content) {
|
|
5399
|
-
const hash = _ContentHashIndex.computeHash(content);
|
|
5400
|
-
this.added.delete(hash);
|
|
5401
|
-
this.removed.add(hash);
|
|
5402
|
-
this.hashes.delete(hash);
|
|
5403
|
-
this.dirty = true;
|
|
5404
|
-
}
|
|
5405
|
-
/**
|
|
5406
|
-
* Remove a pre-computed SHA-256 hash directly from the index without
|
|
5407
|
-
* re-hashing. Use this when the caller already holds the stored hash
|
|
5408
|
-
* (e.g. `memory.frontmatter.contentHash`) to avoid the double-hash bug
|
|
5409
|
-
* where `remove(hash)` would compute `hash(hash)` and never match the
|
|
5410
|
-
* entry.
|
|
5411
|
-
*/
|
|
5412
|
-
removeByHash(hash) {
|
|
5413
|
-
this.added.delete(hash);
|
|
5414
|
-
this.removed.add(hash);
|
|
5415
|
-
this.hashes.delete(hash);
|
|
5416
|
-
this.dirty = true;
|
|
5417
|
-
}
|
|
5418
|
-
/**
|
|
5419
|
-
* Add a pre-computed SHA-256 hash directly to the index without re-hashing.
|
|
5420
|
-
* Use this when the caller already holds the stored hash
|
|
5421
|
-
* (e.g. `memory.frontmatter.contentHash`) so that the index records the raw
|
|
5422
|
-
* content hash rather than re-hashing the citation-annotated body.
|
|
5423
|
-
*
|
|
5424
|
-
* @internal Only called from `StorageManager.ensureFactHashIndexAuthoritative`.
|
|
5425
|
-
* Not part of the public API — prefer `add(content)` for external callers.
|
|
5426
|
-
*/
|
|
5427
|
-
addByHash(hash) {
|
|
5428
|
-
this.removed.delete(hash);
|
|
5429
|
-
this.hashes.add(hash);
|
|
5430
|
-
this.added.add(hash);
|
|
5431
|
-
this.dirty = true;
|
|
5432
|
-
}
|
|
5433
|
-
/** Normalize content (delegates to content-hash.ts for a single source of truth). */
|
|
5434
|
-
static normalizeContent(content) {
|
|
5435
|
-
return normalizeContent(content);
|
|
5436
|
-
}
|
|
5437
|
-
/** Compute SHA-256 hash (delegates to content-hash.ts for a single source of truth). */
|
|
5438
|
-
static computeHash(content) {
|
|
5439
|
-
return computeContentHash(content);
|
|
5440
|
-
}
|
|
5441
|
-
};
|
|
5442
|
-
|
|
5443
5044
|
// src/storage.ts
|
|
5444
5045
|
var ARTIFACT_SEARCH_STOPWORDS = /* @__PURE__ */ new Set([
|
|
5445
5046
|
"a",
|
|
@@ -6064,17 +5665,17 @@ function inferEntityTypeFromContent(content) {
|
|
|
6064
5665
|
}
|
|
6065
5666
|
var KNOWN_ENTITY_FILENAME_PREFIXES = /* @__PURE__ */ new Set(["company", "other", "person", "place", "project", "tool", "topic"]);
|
|
6066
5667
|
function inferEntityTypeFromFilename(pathRel) {
|
|
6067
|
-
const basename =
|
|
5668
|
+
const basename = path9.basename(pathRel, ".md").toLowerCase();
|
|
6068
5669
|
const separator = basename.indexOf("-");
|
|
6069
5670
|
if (separator <= 0) return void 0;
|
|
6070
5671
|
const candidate = basename.slice(0, separator);
|
|
6071
5672
|
return KNOWN_ENTITY_FILENAME_PREFIXES.has(candidate) ? candidate : void 0;
|
|
6072
5673
|
}
|
|
6073
5674
|
function normalizeFrontmatterForPath(frontmatter, pathRel, content = "") {
|
|
6074
|
-
const normalizedPath = pathRel.split(
|
|
5675
|
+
const normalizedPath = pathRel.split(path9.sep).join("/");
|
|
6075
5676
|
let normalizedFrontmatter = frontmatter;
|
|
6076
5677
|
if (normalizedPath === "entities" || normalizedPath.startsWith("entities/") || normalizedPath.includes("/entities/")) {
|
|
6077
|
-
const basename =
|
|
5678
|
+
const basename = path9.basename(pathRel, ".md");
|
|
6078
5679
|
const inferredType = inferEntityTypeFromContent(content) || inferEntityTypeFromFilename(pathRel) || "entity";
|
|
6079
5680
|
const existingTags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [];
|
|
6080
5681
|
normalizedFrontmatter = {
|
|
@@ -6534,8 +6135,9 @@ function parseExtractionRetryStateEntries(raw) {
|
|
|
6534
6135
|
}
|
|
6535
6136
|
return valid;
|
|
6536
6137
|
}
|
|
6537
|
-
var StorageManager3 = class _StorageManager {
|
|
6138
|
+
var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndexHost {
|
|
6538
6139
|
constructor(baseDir, entitySchemas, hotMemoriesCacheEnabledOverride) {
|
|
6140
|
+
super();
|
|
6539
6141
|
this.baseDir = baseDir;
|
|
6540
6142
|
this.entitySchemas = entitySchemas;
|
|
6541
6143
|
this.hotMemoriesCacheEnabled = hotMemoriesCacheEnabledOverride ?? _StorageManager.hotMemoriesCacheDefaultByDir.get(baseDir) ?? _StorageManager.hotMemoriesCacheDefault;
|
|
@@ -6720,8 +6322,8 @@ var StorageManager3 = class _StorageManager {
|
|
|
6720
6322
|
}
|
|
6721
6323
|
/** Whether `filePath` resolves to `stateDir` itself or a path contained in it. */
|
|
6722
6324
|
isStateFilePath(filePath) {
|
|
6723
|
-
const rel =
|
|
6724
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
6325
|
+
const rel = path9.relative(this.stateDir, path9.resolve(filePath));
|
|
6326
|
+
return rel === "" || !rel.startsWith("..") && !path9.isAbsolute(rel);
|
|
6725
6327
|
}
|
|
6726
6328
|
// ── Tombstone store (issue #1579) ───────────────────────────────────────
|
|
6727
6329
|
// Instance-scoped (rule 11) + namespace-scoped (rule 42) tombstone index.
|
|
@@ -6838,8 +6440,8 @@ var StorageManager3 = class _StorageManager {
|
|
|
6838
6440
|
* /srv/cold/remnic), skipping the patch and stranding a stale hot entry.
|
|
6839
6441
|
*/
|
|
6840
6442
|
isColdOrArchiveTierPath(p) {
|
|
6841
|
-
const rel =
|
|
6842
|
-
return rel === "cold" || rel === "archive" || rel.startsWith(`cold${
|
|
6443
|
+
const rel = path9.relative(this.baseDir, p);
|
|
6444
|
+
return rel === "cold" || rel === "archive" || rel.startsWith(`cold${path9.sep}`) || rel.startsWith(`archive${path9.sep}`);
|
|
6843
6445
|
}
|
|
6844
6446
|
/**
|
|
6845
6447
|
* Mark the secure-store as required for this storage instance.
|
|
@@ -6926,7 +6528,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
6926
6528
|
void 0,
|
|
6927
6529
|
this.baseDir
|
|
6928
6530
|
);
|
|
6929
|
-
const rel =
|
|
6531
|
+
const rel = path9.relative(this.baseDir, filePath).split(path9.sep).join("/");
|
|
6930
6532
|
return `${rel}:${version.versionId}`;
|
|
6931
6533
|
} catch (err) {
|
|
6932
6534
|
log.warn(
|
|
@@ -7006,13 +6608,13 @@ var StorageManager3 = class _StorageManager {
|
|
|
7006
6608
|
}
|
|
7007
6609
|
identityFilePath(workspaceDir, namespace) {
|
|
7008
6610
|
const rawNamespace = typeof namespace === "string" ? namespace.trim() : "";
|
|
7009
|
-
if (!rawNamespace) return
|
|
6611
|
+
if (!rawNamespace) return path9.join(workspaceDir, "IDENTITY.md");
|
|
7010
6612
|
const safeNamespace = rawNamespace.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
7011
|
-
return
|
|
6613
|
+
return path9.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
|
|
7012
6614
|
}
|
|
7013
6615
|
versionFilePath(kind) {
|
|
7014
6616
|
const fileName = kind === "memory-status" ? ".memory-status-version.log" : kind === "artifact-write" ? ".artifact-write-version.log" : kind === "cold-write" ? ".cold-write-version.log" : ".memory-corpus-version.log";
|
|
7015
|
-
return
|
|
6617
|
+
return path9.join(this.stateDir, fileName);
|
|
7016
6618
|
}
|
|
7017
6619
|
bumpSharedVersion(kind, fallbackMap) {
|
|
7018
6620
|
const filePath = this.versionFilePath(kind);
|
|
@@ -7104,7 +6706,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
7104
6706
|
// memories.
|
|
7105
6707
|
// -------------------------------------------------------------------------
|
|
7106
6708
|
get wearablesDir() {
|
|
7107
|
-
return
|
|
6709
|
+
return path9.join(this.baseDir, WEARABLES_DIR_NAME);
|
|
7108
6710
|
}
|
|
7109
6711
|
/**
|
|
7110
6712
|
* Resolve the on-disk path for a source/day transcript. Throws on
|
|
@@ -7120,7 +6722,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
7120
6722
|
if (!isValidTranscriptDate(date)) {
|
|
7121
6723
|
throw new Error(`invalid wearable transcript date '${String(date)}' \u2014 expected YYYY-MM-DD`);
|
|
7122
6724
|
}
|
|
7123
|
-
return
|
|
6725
|
+
return path9.join(this.wearablesDir, sourceId, `${date}.md`);
|
|
7124
6726
|
}
|
|
7125
6727
|
async writeWearableDayTranscript(sourceId, date, serialized) {
|
|
7126
6728
|
const targetPath = this.wearableTranscriptPath(sourceId, date);
|
|
@@ -7236,25 +6838,25 @@ var StorageManager3 = class _StorageManager {
|
|
|
7236
6838
|
});
|
|
7237
6839
|
}
|
|
7238
6840
|
get factsDir() {
|
|
7239
|
-
return
|
|
6841
|
+
return path9.join(this.baseDir, "facts");
|
|
7240
6842
|
}
|
|
7241
6843
|
get correctionsDir() {
|
|
7242
|
-
return
|
|
6844
|
+
return path9.join(this.baseDir, "corrections");
|
|
7243
6845
|
}
|
|
7244
6846
|
get proceduresDir() {
|
|
7245
|
-
return
|
|
6847
|
+
return path9.join(this.baseDir, "procedures");
|
|
7246
6848
|
}
|
|
7247
6849
|
get reasoningTracesDir() {
|
|
7248
|
-
return
|
|
6850
|
+
return path9.join(this.baseDir, "reasoning-traces");
|
|
7249
6851
|
}
|
|
7250
6852
|
get entitiesDir() {
|
|
7251
|
-
return
|
|
6853
|
+
return path9.join(this.baseDir, "entities");
|
|
7252
6854
|
}
|
|
7253
6855
|
resolveEntityFilePath(name) {
|
|
7254
6856
|
if (typeof name !== "string") return null;
|
|
7255
|
-
const filePath =
|
|
7256
|
-
const relative2 =
|
|
7257
|
-
if (relative2 === "" || relative2.startsWith("..") ||
|
|
6857
|
+
const filePath = path9.resolve(this.entitiesDir, `${name}.md`);
|
|
6858
|
+
const relative2 = path9.relative(this.entitiesDir, filePath);
|
|
6859
|
+
if (relative2 === "" || relative2.startsWith("..") || path9.isAbsolute(relative2)) {
|
|
7258
6860
|
return null;
|
|
7259
6861
|
}
|
|
7260
6862
|
return filePath;
|
|
@@ -7268,17 +6870,11 @@ var StorageManager3 = class _StorageManager {
|
|
|
7268
6870
|
this.notifyCatalogWriteForPath(filePath);
|
|
7269
6871
|
});
|
|
7270
6872
|
}
|
|
7271
|
-
writeStorageSecureFileChunks(filePath, chunks) {
|
|
7272
|
-
const writeKey = this.resolveWriteKey();
|
|
7273
|
-
return writeMaybeEncryptedFileFromChunks(filePath, chunks, writeKey, {}, this.baseDir).then(() => {
|
|
7274
|
-
this.notifyCatalogWriteForPath(filePath);
|
|
7275
|
-
});
|
|
7276
|
-
}
|
|
7277
6873
|
assertManagedStoragePath(filePath, method) {
|
|
7278
|
-
const resolved =
|
|
7279
|
-
const base =
|
|
7280
|
-
const rel =
|
|
7281
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${
|
|
6874
|
+
const resolved = path9.resolve(filePath);
|
|
6875
|
+
const base = path9.resolve(this.baseDir);
|
|
6876
|
+
const rel = path9.relative(base, resolved);
|
|
6877
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${path9.sep}`) || path9.isAbsolute(rel)) {
|
|
7282
6878
|
throw new Error(`${method}: file path escapes memory dir`);
|
|
7283
6879
|
}
|
|
7284
6880
|
return resolved;
|
|
@@ -7289,8 +6885,8 @@ var StorageManager3 = class _StorageManager {
|
|
|
7289
6885
|
}
|
|
7290
6886
|
async digestOfflineSyncFile(filePath) {
|
|
7291
6887
|
const target = this.assertManagedStoragePath(filePath, "storage.digestOfflineSyncFile");
|
|
7292
|
-
const st = await
|
|
7293
|
-
const relPath =
|
|
6888
|
+
const st = await stat2(target);
|
|
6889
|
+
const relPath = path9.relative(this.baseDir, target).split(path9.sep).join("/");
|
|
7294
6890
|
const cache = await this.loadOfflineSyncDigestCache();
|
|
7295
6891
|
const cached = cache.get(relPath);
|
|
7296
6892
|
if (cached && cached.statBytes === st.size && cached.mtimeMs === st.mtimeMs && cached.ctimeMs === st.ctimeMs && !cached.encrypted) {
|
|
@@ -7351,7 +6947,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
7351
6947
|
const bytes = typeof record.bytes === "number" ? record.bytes : NaN;
|
|
7352
6948
|
const sha256 = typeof record.sha256 === "string" ? record.sha256 : "";
|
|
7353
6949
|
const encrypted = record.encrypted === true;
|
|
7354
|
-
if (cachePath.length === 0 || cachePath === ".." || cachePath.startsWith("../") ||
|
|
6950
|
+
if (cachePath.length === 0 || cachePath === ".." || cachePath.startsWith("../") || path9.isAbsolute(cachePath) || !Number.isFinite(statBytes) || !Number.isFinite(mtimeMs) || !Number.isFinite(ctimeMs) || !Number.isFinite(bytes) || !/^[a-f0-9]{64}$/i.test(sha256)) {
|
|
7355
6951
|
continue;
|
|
7356
6952
|
}
|
|
7357
6953
|
cache.set(cachePath, { statBytes, mtimeMs, ctimeMs, encrypted, sha256, bytes });
|
|
@@ -7393,7 +6989,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
7393
6989
|
const cache = this.offlineSyncDigestCache;
|
|
7394
6990
|
if (!cache) return;
|
|
7395
6991
|
const entries = [...cache.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([entryPath, entry]) => ({ path: entryPath, ...entry }));
|
|
7396
|
-
await
|
|
6992
|
+
await mkdir(path9.dirname(this.offlineSyncDigestCachePath), { recursive: true });
|
|
7397
6993
|
await writeFile2(this.offlineSyncDigestCachePath, `${JSON.stringify({ version: 1, entries })}
|
|
7398
6994
|
`, "utf-8");
|
|
7399
6995
|
}).catch((err) => {
|
|
@@ -7412,40 +7008,10 @@ var StorageManager3 = class _StorageManager {
|
|
|
7412
7008
|
await handle.close();
|
|
7413
7009
|
}
|
|
7414
7010
|
}
|
|
7415
|
-
async writeOfflineSyncFile(filePath, content) {
|
|
7416
|
-
const target = this.assertManagedStoragePath(filePath, "storage.writeOfflineSyncFile");
|
|
7417
|
-
await this.writeStorageSecureFile(target, content);
|
|
7418
|
-
await this.invalidateAfterOfflineSyncMutation(target);
|
|
7419
|
-
}
|
|
7420
7011
|
async writeOfflineSyncStagingFile(filePath, content) {
|
|
7421
7012
|
const target = this.assertManagedStoragePath(filePath, "storage.writeOfflineSyncStagingFile");
|
|
7422
7013
|
await this.writeStorageSecureFile(target, content);
|
|
7423
7014
|
}
|
|
7424
|
-
async writeOfflineSyncFileChunks(filePath, chunks) {
|
|
7425
|
-
const target = this.assertManagedStoragePath(filePath, "storage.writeOfflineSyncFileChunks");
|
|
7426
|
-
await this.writeStorageSecureFileChunks(target, chunks);
|
|
7427
|
-
await this.invalidateAfterOfflineSyncMutation(target);
|
|
7428
|
-
}
|
|
7429
|
-
async deleteOfflineSyncFile(filePath) {
|
|
7430
|
-
const target = this.assertManagedStoragePath(filePath, "storage.deleteOfflineSyncFile");
|
|
7431
|
-
await unlink3(target).catch((error) => {
|
|
7432
|
-
if (isErrnoCode(error, "ENOENT")) return;
|
|
7433
|
-
throw error;
|
|
7434
|
-
});
|
|
7435
|
-
await this.invalidateAfterOfflineSyncMutation(target);
|
|
7436
|
-
}
|
|
7437
|
-
async invalidateAfterOfflineSyncMutation(filePath) {
|
|
7438
|
-
this.invalidateAllMemoriesCache();
|
|
7439
|
-
this.invalidateKnowledgeIndexCache();
|
|
7440
|
-
this.factHashIndexAuthoritative = false;
|
|
7441
|
-
if (filePath.includes(`${path10.sep}cold${path10.sep}`)) {
|
|
7442
|
-
this.invalidateColdMemoriesCache();
|
|
7443
|
-
}
|
|
7444
|
-
if (filePath.includes(`${path10.sep}artifacts${path10.sep}`)) {
|
|
7445
|
-
this.bumpArtifactWriteVersion();
|
|
7446
|
-
}
|
|
7447
|
-
this.bumpMemoryStatusVersion();
|
|
7448
|
-
}
|
|
7449
7015
|
createContentHashIndex() {
|
|
7450
7016
|
return new ContentHashIndex(
|
|
7451
7017
|
this.stateDir,
|
|
@@ -7481,7 +7047,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
7481
7047
|
async isEncryptedFileHeader(filePath) {
|
|
7482
7048
|
let st;
|
|
7483
7049
|
try {
|
|
7484
|
-
st = await
|
|
7050
|
+
st = await stat2(filePath);
|
|
7485
7051
|
} catch (err) {
|
|
7486
7052
|
if (isErrnoCode(err, "ENOENT")) return false;
|
|
7487
7053
|
throw err;
|
|
@@ -7512,7 +7078,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
7512
7078
|
}
|
|
7513
7079
|
async appendStorageSecureFileUnlocked(filePath, content) {
|
|
7514
7080
|
const writeKey = this.resolveWriteKey();
|
|
7515
|
-
await
|
|
7081
|
+
await mkdir(path9.dirname(filePath), { recursive: true });
|
|
7516
7082
|
if (writeKey === null) {
|
|
7517
7083
|
try {
|
|
7518
7084
|
if (await this.isEncryptedFileHeader(filePath)) {
|
|
@@ -7538,13 +7104,13 @@ var StorageManager3 = class _StorageManager {
|
|
|
7538
7104
|
this.notifyCatalogWriteForPath(filePath);
|
|
7539
7105
|
}
|
|
7540
7106
|
get stateDir() {
|
|
7541
|
-
return
|
|
7107
|
+
return path9.join(this.baseDir, "state");
|
|
7542
7108
|
}
|
|
7543
7109
|
get offlineSyncDigestCachePath() {
|
|
7544
|
-
return
|
|
7110
|
+
return path9.join(this.baseDir, ".offline-sync", "digest-cache.v1.json");
|
|
7545
7111
|
}
|
|
7546
7112
|
get entitySynthesisQueuePath() {
|
|
7547
|
-
return
|
|
7113
|
+
return path9.join(this.stateDir, "entity-synthesis-queue.json");
|
|
7548
7114
|
}
|
|
7549
7115
|
// ── Tombstone store access (issue #1579) ─────────────────────────────────
|
|
7550
7116
|
/**
|
|
@@ -7554,7 +7120,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
7554
7120
|
* helpers are injected into the store).
|
|
7555
7121
|
*/
|
|
7556
7122
|
get tombstonesPath() {
|
|
7557
|
-
return
|
|
7123
|
+
return path9.join(this.stateDir, "tombstones.jsonl");
|
|
7558
7124
|
}
|
|
7559
7125
|
/**
|
|
7560
7126
|
* Configure the tombstone invariant for this storage instance. Installed
|
|
@@ -7683,6 +7249,28 @@ var StorageManager3 = class _StorageManager {
|
|
|
7683
7249
|
});
|
|
7684
7250
|
return await store.rebuild(retired);
|
|
7685
7251
|
}
|
|
7252
|
+
markFactHashIndexNotAuthoritative() {
|
|
7253
|
+
this.factHashIndexAuthoritative = false;
|
|
7254
|
+
}
|
|
7255
|
+
tombstoneBlockedCaptureIndexOptions() {
|
|
7256
|
+
return {
|
|
7257
|
+
stateDir: this.stateDir,
|
|
7258
|
+
memoryDir: this.baseDir,
|
|
7259
|
+
secureStoreKeyProvider: () => this._secureStoreKey,
|
|
7260
|
+
secureStoreWriteKeyProvider: () => this.resolveWriteKey(),
|
|
7261
|
+
lockOptions: () => this.factHashIndexLockOptions,
|
|
7262
|
+
parseMemory: (filePath, content) => parseTombstoneBlockedOfflineSyncMemory(
|
|
7263
|
+
filePath,
|
|
7264
|
+
content,
|
|
7265
|
+
this.baseDir,
|
|
7266
|
+
parseFrontmatter,
|
|
7267
|
+
normalizeFrontmatterForPath,
|
|
7268
|
+
toMemoryPathRel
|
|
7269
|
+
),
|
|
7270
|
+
readAllMemories: () => this.readAllMemories(),
|
|
7271
|
+
readAllColdMemories: () => this.readAllColdMemories()
|
|
7272
|
+
};
|
|
7273
|
+
}
|
|
7686
7274
|
async getFactHashIndex() {
|
|
7687
7275
|
if (this.factHashIndex) {
|
|
7688
7276
|
return this.factHashIndex;
|
|
@@ -7744,9 +7332,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
7744
7332
|
const baseMs = this.factHashIndexLockOptions.retryBaseMs ?? FACT_HASH_INDEX_REBUILD_RETRY_BASE_MS;
|
|
7745
7333
|
let published = false;
|
|
7746
7334
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
7747
|
-
published = await factHashIndex.rebuildUnderLock(
|
|
7748
|
-
() => this.rebuildFactHashIndexFromCorpus(factHashIndex)
|
|
7749
|
-
);
|
|
7335
|
+
published = await factHashIndex.rebuildUnderLock(() => this.rebuildFactHashIndexFromCorpus(factHashIndex));
|
|
7750
7336
|
if (published || attempt === maxAttempts - 1) break;
|
|
7751
7337
|
const wait = Math.min(baseMs * 2 ** attempt, CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS);
|
|
7752
7338
|
await new Promise((resolve3) => setTimeout(resolve3, wait));
|
|
@@ -7772,10 +7358,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
7772
7358
|
async rebuildFactHashIndexFromCorpus(factHashIndex) {
|
|
7773
7359
|
factHashIndex.clear();
|
|
7774
7360
|
this.factOnlyHashes.clear();
|
|
7775
|
-
const existing = [
|
|
7776
|
-
...await this.readAllMemories(),
|
|
7777
|
-
...await this.readAllColdMemories()
|
|
7778
|
-
];
|
|
7361
|
+
const existing = [...await this.readAllMemories(), ...await this.readAllColdMemories()];
|
|
7779
7362
|
let legacyRecovered = 0;
|
|
7780
7363
|
for (const memory of existing) {
|
|
7781
7364
|
if (inferMemoryStatus(memory.frontmatter, memory.path) !== "active") continue;
|
|
@@ -7827,55 +7410,55 @@ var StorageManager3 = class _StorageManager {
|
|
|
7827
7410
|
return null;
|
|
7828
7411
|
}
|
|
7829
7412
|
get questionsDir() {
|
|
7830
|
-
return
|
|
7413
|
+
return path9.join(this.baseDir, "questions");
|
|
7831
7414
|
}
|
|
7832
7415
|
get artifactsDir() {
|
|
7833
|
-
return
|
|
7416
|
+
return path9.join(this.baseDir, "artifacts");
|
|
7834
7417
|
}
|
|
7835
7418
|
get identityDir() {
|
|
7836
|
-
return
|
|
7419
|
+
return path9.join(this.baseDir, "identity");
|
|
7837
7420
|
}
|
|
7838
7421
|
get identityAnchorPath() {
|
|
7839
|
-
return
|
|
7422
|
+
return path9.join(this.identityDir, "identity-anchor.md");
|
|
7840
7423
|
}
|
|
7841
7424
|
get identityIncidentsDir() {
|
|
7842
|
-
return
|
|
7425
|
+
return path9.join(this.identityDir, "incidents");
|
|
7843
7426
|
}
|
|
7844
7427
|
get identityAuditsWeeklyDir() {
|
|
7845
|
-
return
|
|
7428
|
+
return path9.join(this.identityDir, "audits", "weekly");
|
|
7846
7429
|
}
|
|
7847
7430
|
get identityAuditsMonthlyDir() {
|
|
7848
|
-
return
|
|
7431
|
+
return path9.join(this.identityDir, "audits", "monthly");
|
|
7849
7432
|
}
|
|
7850
7433
|
get identityImprovementLoopsPath() {
|
|
7851
|
-
return
|
|
7434
|
+
return path9.join(this.identityDir, "improvement-loops.md");
|
|
7852
7435
|
}
|
|
7853
7436
|
get identityReflectionsPath() {
|
|
7854
|
-
return
|
|
7437
|
+
return path9.join(this.identityDir, "reflections.md");
|
|
7855
7438
|
}
|
|
7856
7439
|
get profilePath() {
|
|
7857
|
-
return
|
|
7440
|
+
return path9.join(this.baseDir, "profile.md");
|
|
7858
7441
|
}
|
|
7859
7442
|
get memoryActionsPath() {
|
|
7860
|
-
return
|
|
7443
|
+
return path9.join(this.stateDir, "memory-actions.jsonl");
|
|
7861
7444
|
}
|
|
7862
7445
|
get memoryLifecycleLedgerPath() {
|
|
7863
|
-
return
|
|
7446
|
+
return path9.join(this.stateDir, "memory-lifecycle-ledger.jsonl");
|
|
7864
7447
|
}
|
|
7865
7448
|
get compressionGuidelinesPath() {
|
|
7866
|
-
return
|
|
7449
|
+
return path9.join(this.stateDir, "compression-guidelines.md");
|
|
7867
7450
|
}
|
|
7868
7451
|
get compressionGuidelineDraftPath() {
|
|
7869
|
-
return
|
|
7452
|
+
return path9.join(this.stateDir, "compression-guidelines.draft.md");
|
|
7870
7453
|
}
|
|
7871
7454
|
get compressionGuidelineStatePath() {
|
|
7872
|
-
return
|
|
7455
|
+
return path9.join(this.stateDir, "compression-guideline-state.json");
|
|
7873
7456
|
}
|
|
7874
7457
|
get compressionGuidelineDraftStatePath() {
|
|
7875
|
-
return
|
|
7458
|
+
return path9.join(this.stateDir, "compression-guideline-draft-state.json");
|
|
7876
7459
|
}
|
|
7877
7460
|
get behaviorSignalsPath() {
|
|
7878
|
-
return
|
|
7461
|
+
return path9.join(this.stateDir, "behavior-signals.jsonl");
|
|
7879
7462
|
}
|
|
7880
7463
|
/**
|
|
7881
7464
|
* In-memory dedup key set for behavior-signals appends (issue #1909),
|
|
@@ -7907,7 +7490,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
7907
7490
|
* governance sweeps can treat it uniformly.
|
|
7908
7491
|
*/
|
|
7909
7492
|
get bufferSurpriseLedgerPath() {
|
|
7910
|
-
return
|
|
7493
|
+
return path9.join(this.stateDir, "buffer-surprise-ledger.jsonl");
|
|
7911
7494
|
}
|
|
7912
7495
|
/**
|
|
7913
7496
|
* Entity alias table loaded from THIS store's config/aliases.json.
|
|
@@ -7947,7 +7530,7 @@ var StorageManager3 = class _StorageManager {
|
|
|
7947
7530
|
this.loadAliasesSync();
|
|
7948
7531
|
}
|
|
7949
7532
|
loadAliasesSync() {
|
|
7950
|
-
const aliasPath =
|
|
7533
|
+
const aliasPath = path9.join(this.baseDir, "config", "aliases.json");
|
|
7951
7534
|
this.userAliases = {};
|
|
7952
7535
|
try {
|
|
7953
7536
|
const raw = readFileSync2(aliasPath, "utf-8");
|
|
@@ -7982,22 +7565,22 @@ var StorageManager3 = class _StorageManager {
|
|
|
7982
7565
|
return completionFingerprint;
|
|
7983
7566
|
}
|
|
7984
7567
|
async ensureDirectories() {
|
|
7985
|
-
await
|
|
7568
|
+
await mkdir(this.baseDir, { recursive: true });
|
|
7986
7569
|
await validateRoots(this.baseDir, this.entitiesDir, this.stateDir, this.archiveDir);
|
|
7987
7570
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7988
|
-
await
|
|
7989
|
-
await
|
|
7990
|
-
await
|
|
7991
|
-
await
|
|
7992
|
-
await
|
|
7993
|
-
await
|
|
7994
|
-
await
|
|
7995
|
-
await
|
|
7996
|
-
await
|
|
7997
|
-
await
|
|
7998
|
-
await
|
|
7999
|
-
await
|
|
8000
|
-
await
|
|
7571
|
+
await mkdir(path9.join(this.factsDir, today), { recursive: true });
|
|
7572
|
+
await mkdir(path9.join(this.proceduresDir, today), { recursive: true });
|
|
7573
|
+
await mkdir(path9.join(this.reasoningTracesDir, today), { recursive: true });
|
|
7574
|
+
await mkdir(this.correctionsDir, { recursive: true });
|
|
7575
|
+
await mkdir(this.entitiesDir, { recursive: true });
|
|
7576
|
+
await mkdir(this.stateDir, { recursive: true });
|
|
7577
|
+
await mkdir(this.questionsDir, { recursive: true });
|
|
7578
|
+
await mkdir(this.artifactsDir, { recursive: true });
|
|
7579
|
+
await mkdir(this.identityDir, { recursive: true });
|
|
7580
|
+
await mkdir(this.identityIncidentsDir, { recursive: true });
|
|
7581
|
+
await mkdir(this.identityAuditsWeeklyDir, { recursive: true });
|
|
7582
|
+
await mkdir(this.identityAuditsMonthlyDir, { recursive: true });
|
|
7583
|
+
await mkdir(path9.join(this.baseDir, "config"), { recursive: true });
|
|
8001
7584
|
await this.entityCanonicalIdMigration.ensure();
|
|
8002
7585
|
if (this.hotMemoriesCacheEnabled && this.getMemoryCorpusVersion() === 0) {
|
|
8003
7586
|
this.bumpMemoryCorpusVersion();
|
|
@@ -8018,12 +7601,24 @@ var StorageManager3 = class _StorageManager {
|
|
|
8018
7601
|
*/
|
|
8019
7602
|
async resolveCategoryWritePath(category, id, today) {
|
|
8020
7603
|
if (category === "correction") {
|
|
8021
|
-
await
|
|
8022
|
-
return
|
|
8023
|
-
}
|
|
8024
|
-
const datedDir =
|
|
8025
|
-
await
|
|
8026
|
-
return
|
|
7604
|
+
await mkdir(this.correctionsDir, { recursive: true });
|
|
7605
|
+
return path9.join(this.correctionsDir, `${id}.md`);
|
|
7606
|
+
}
|
|
7607
|
+
const datedDir = path9.join(getCategoryDir(this.baseDir, category), today);
|
|
7608
|
+
await mkdir(datedDir, { recursive: true });
|
|
7609
|
+
return path9.join(datedDir, `${id}.md`);
|
|
7610
|
+
}
|
|
7611
|
+
async findExistingTombstoneBlockedMemory(content, category, sourceConnector) {
|
|
7612
|
+
const identity = buildExplicitCaptureDedupKey(content, category, sourceConnector);
|
|
7613
|
+
const memories = [...await this.readAllMemories(), ...await this.readAllColdMemories()];
|
|
7614
|
+
return memories.find((memory) => {
|
|
7615
|
+
if (memory.frontmatter.status !== "pending_review" || !memory.frontmatter.blockedBy) return false;
|
|
7616
|
+
return buildExplicitCaptureDedupKey(
|
|
7617
|
+
memory.content,
|
|
7618
|
+
memory.frontmatter.category,
|
|
7619
|
+
memory.frontmatter.sourceConnector
|
|
7620
|
+
) === identity;
|
|
7621
|
+
}) ?? null;
|
|
8027
7622
|
}
|
|
8028
7623
|
async writeMemory(category, content, options = {}) {
|
|
8029
7624
|
await this.ensureDirectories();
|
|
@@ -8139,8 +7734,29 @@ var StorageManager3 = class _StorageManager {
|
|
|
8139
7734
|
${sanitized.text}
|
|
8140
7735
|
`;
|
|
8141
7736
|
const filePath = await this.resolveCategoryWritePath(category, id, today);
|
|
8142
|
-
|
|
8143
|
-
|
|
7737
|
+
const persistFile = async () => {
|
|
7738
|
+
if (tombstoneBlocked) {
|
|
7739
|
+
const duplicate = await this.findExistingTombstoneBlockedMemory(sanitized.text, category, fm.sourceConnector);
|
|
7740
|
+
if (duplicate) return duplicate;
|
|
7741
|
+
}
|
|
7742
|
+
await this.snapshotBeforeWrite(filePath, "write");
|
|
7743
|
+
await this.writeTombstoneBlockedMemory(filePath, fileContent, fm, sanitized.text, async () => {
|
|
7744
|
+
this.invalidateAllMemoriesCache();
|
|
7745
|
+
});
|
|
7746
|
+
return null;
|
|
7747
|
+
};
|
|
7748
|
+
const duplicateBlocked = tombstoneBlocked ? await this.withTombstoneBlockedCaptureWriteLock(
|
|
7749
|
+
persistFile,
|
|
7750
|
+
buildExplicitCaptureDedupKey(sanitized.text, category, fm.sourceConnector)
|
|
7751
|
+
) : await persistFile();
|
|
7752
|
+
if (duplicateBlocked) {
|
|
7753
|
+
return {
|
|
7754
|
+
id: duplicateBlocked.frontmatter.id,
|
|
7755
|
+
tombstoneBlocked: true,
|
|
7756
|
+
blockedBy: duplicateBlocked.frontmatter.blockedBy,
|
|
7757
|
+
duplicateOf: duplicateBlocked.frontmatter.id
|
|
7758
|
+
};
|
|
7759
|
+
}
|
|
8144
7760
|
await this.patchHotMemoriesCache({ addedPath: filePath }, "memory-create");
|
|
8145
7761
|
this.notifyMemoryWrite(filePath);
|
|
8146
7762
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.writeMemory", {
|
|
@@ -8209,10 +7825,7 @@ ${sanitized.text}
|
|
|
8209
7825
|
return await this.factContentHashPresentInCorpus(hash);
|
|
8210
7826
|
}
|
|
8211
7827
|
async factContentHashPresentInCorpus(targetHash) {
|
|
8212
|
-
const existing = [
|
|
8213
|
-
...await this.readAllMemories(),
|
|
8214
|
-
...await this.readAllColdMemories()
|
|
8215
|
-
];
|
|
7828
|
+
const existing = [...await this.readAllMemories(), ...await this.readAllColdMemories()];
|
|
8216
7829
|
for (const memory of existing) {
|
|
8217
7830
|
if (memory.frontmatter.category !== "fact") continue;
|
|
8218
7831
|
if (inferMemoryStatus(memory.frontmatter, memory.path) !== "active") continue;
|
|
@@ -8328,8 +7941,8 @@ ${sanitized.text}
|
|
|
8328
7941
|
await this.ensureDirectories();
|
|
8329
7942
|
const now = /* @__PURE__ */ new Date();
|
|
8330
7943
|
const day = now.toISOString().slice(0, 10);
|
|
8331
|
-
const dir =
|
|
8332
|
-
await
|
|
7944
|
+
const dir = path9.join(this.artifactsDir, day);
|
|
7945
|
+
await mkdir(dir, { recursive: true });
|
|
8333
7946
|
const id = `artifact-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
8334
7947
|
const fm = {
|
|
8335
7948
|
id,
|
|
@@ -8353,7 +7966,7 @@ ${sanitized.text}
|
|
|
8353
7966
|
log.warn(`artifact content rejected for ${id}; violations=${sanitized.violations.join(", ")}`);
|
|
8354
7967
|
return "";
|
|
8355
7968
|
}
|
|
8356
|
-
const filePath =
|
|
7969
|
+
const filePath = path9.join(dir, `${id}.md`);
|
|
8357
7970
|
await this.writeStorageSecureFile(filePath, `${serializeFrontmatter(fm)}
|
|
8358
7971
|
|
|
8359
7972
|
${sanitized.text}
|
|
@@ -8624,7 +8237,7 @@ ${sanitized.text}
|
|
|
8624
8237
|
* per-process in-memory cache safe across process boundaries.
|
|
8625
8238
|
*/
|
|
8626
8239
|
invalidateColdMemoriesCache() {
|
|
8627
|
-
const coldRoot =
|
|
8240
|
+
const coldRoot = path9.join(this.baseDir, "cold");
|
|
8628
8241
|
_StorageManager.coldMemoriesCache.delete(coldRoot);
|
|
8629
8242
|
this.bumpColdWriteVersion();
|
|
8630
8243
|
invalidateAllForDir(this.baseDir);
|
|
@@ -8726,7 +8339,7 @@ ${sanitized.text}
|
|
|
8726
8339
|
return updatedMs >= updatedAfterMs ? filePath : null;
|
|
8727
8340
|
}
|
|
8728
8341
|
try {
|
|
8729
|
-
const fileStat = await
|
|
8342
|
+
const fileStat = await stat2(filePath);
|
|
8730
8343
|
return fileStat.mtimeMs >= updatedAfterMs ? filePath : null;
|
|
8731
8344
|
} catch {
|
|
8732
8345
|
return filePath;
|
|
@@ -8739,7 +8352,7 @@ ${sanitized.text}
|
|
|
8739
8352
|
const correctionPaths = [];
|
|
8740
8353
|
const factPaths = [];
|
|
8741
8354
|
for (const filePath of filePaths) {
|
|
8742
|
-
if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${
|
|
8355
|
+
if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${path9.sep}`)) {
|
|
8743
8356
|
correctionPaths.push(filePath);
|
|
8744
8357
|
} else {
|
|
8745
8358
|
factPaths.push(filePath);
|
|
@@ -8802,7 +8415,7 @@ ${sanitized.text}
|
|
|
8802
8415
|
try {
|
|
8803
8416
|
const entries = await readdir5(dir, { withFileTypes: true });
|
|
8804
8417
|
for (const entry of entries) {
|
|
8805
|
-
const fullPath =
|
|
8418
|
+
const fullPath = path9.join(dir, entry.name);
|
|
8806
8419
|
if (entry.isDirectory()) {
|
|
8807
8420
|
await readDir(fullPath);
|
|
8808
8421
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -8835,7 +8448,7 @@ ${sanitized.text}
|
|
|
8835
8448
|
return this.memoryReadStore.readMemoryByPath(filePath);
|
|
8836
8449
|
}
|
|
8837
8450
|
resolveTierRootDir(tier) {
|
|
8838
|
-
return tier === "cold" ?
|
|
8451
|
+
return tier === "cold" ? path9.join(this.baseDir, "cold") : this.baseDir;
|
|
8839
8452
|
}
|
|
8840
8453
|
resolveMemoryDateDir(memory) {
|
|
8841
8454
|
const preferred = memory.frontmatter.created || memory.frontmatter.updated;
|
|
@@ -8850,13 +8463,13 @@ ${sanitized.text}
|
|
|
8850
8463
|
buildTierMemoryPath(memory, tier) {
|
|
8851
8464
|
const root = this.resolveTierRootDir(tier);
|
|
8852
8465
|
if (this.isArtifactMemory(memory)) {
|
|
8853
|
-
return
|
|
8466
|
+
return path9.join(root, "artifacts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
8854
8467
|
}
|
|
8855
8468
|
if (memory.frontmatter.category === "correction") {
|
|
8856
|
-
return
|
|
8469
|
+
return path9.join(root, "corrections", `${memory.frontmatter.id}.md`);
|
|
8857
8470
|
}
|
|
8858
8471
|
const dir = categoryDirName(memory.frontmatter.category);
|
|
8859
|
-
return
|
|
8472
|
+
return path9.join(root, dir, this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
8860
8473
|
}
|
|
8861
8474
|
async writeMemoryFileAtomic(targetPath, memory) {
|
|
8862
8475
|
const fileContent = `${serializeFrontmatter(memory.frontmatter)}
|
|
@@ -8869,8 +8482,8 @@ ${memory.content}
|
|
|
8869
8482
|
}
|
|
8870
8483
|
async moveMemoryToPath(memory, targetPath) {
|
|
8871
8484
|
await this.writeMemoryFileAtomic(targetPath, memory);
|
|
8872
|
-
const sourcePath =
|
|
8873
|
-
const destPath =
|
|
8485
|
+
const sourcePath = path9.resolve(memory.path);
|
|
8486
|
+
const destPath = path9.resolve(targetPath);
|
|
8874
8487
|
if (sourcePath !== destPath) {
|
|
8875
8488
|
try {
|
|
8876
8489
|
await unlink3(memory.path);
|
|
@@ -8886,8 +8499,8 @@ ${memory.content}
|
|
|
8886
8499
|
}
|
|
8887
8500
|
async migrateMemoryToTier(memory, targetTier) {
|
|
8888
8501
|
const targetPath = this.buildTierMemoryPath(memory, targetTier);
|
|
8889
|
-
const sourcePath =
|
|
8890
|
-
const destPath =
|
|
8502
|
+
const sourcePath = path9.resolve(memory.path);
|
|
8503
|
+
const destPath = path9.resolve(targetPath);
|
|
8891
8504
|
if (sourcePath === destPath) {
|
|
8892
8505
|
return { changed: false, targetPath };
|
|
8893
8506
|
}
|
|
@@ -8914,7 +8527,7 @@ ${memory.content}
|
|
|
8914
8527
|
return { changed: true, targetPath };
|
|
8915
8528
|
}
|
|
8916
8529
|
get archiveDir() {
|
|
8917
|
-
return
|
|
8530
|
+
return path9.join(this.baseDir, "archive");
|
|
8918
8531
|
}
|
|
8919
8532
|
/**
|
|
8920
8533
|
* Archive a memory by moving it from facts/ to archive/YYYY-MM-DD/.
|
|
@@ -8922,48 +8535,52 @@ ${memory.content}
|
|
|
8922
8535
|
* Returns the new file path on success, null on failure.
|
|
8923
8536
|
*/
|
|
8924
8537
|
async archiveMemory(memory, lifecycle) {
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8538
|
+
const archiveCurrent = async (current, markDurable) => {
|
|
8539
|
+
try {
|
|
8540
|
+
const now = lifecycle?.at ?? /* @__PURE__ */ new Date();
|
|
8541
|
+
const today = now.toISOString().slice(0, 10);
|
|
8542
|
+
const destDir = path9.join(this.archiveDir, today);
|
|
8543
|
+
await mkdir(destDir, { recursive: true });
|
|
8544
|
+
const updatedFm = {
|
|
8545
|
+
...current.frontmatter,
|
|
8546
|
+
status: "archived",
|
|
8547
|
+
archivedAt: now.toISOString(),
|
|
8548
|
+
updated: now.toISOString()
|
|
8549
|
+
};
|
|
8550
|
+
const fileContent = `${serializeFrontmatter(updatedFm)}
|
|
8937
8551
|
|
|
8938
|
-
${
|
|
8552
|
+
${current.content}
|
|
8939
8553
|
`;
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8554
|
+
const destPath = path9.join(destDir, path9.basename(current.path));
|
|
8555
|
+
await this.writeStorageSecureFile(destPath, fileContent);
|
|
8556
|
+
await unlink3(current.path);
|
|
8557
|
+
markDurable();
|
|
8558
|
+
markProjectedMemoryPathInvalid(this.baseDir, current.frontmatter.id);
|
|
8559
|
+
this.invalidateAllMemoriesCache();
|
|
8560
|
+
await this.appendGeneratedMemoryLifecycleEventFailOpen(
|
|
8561
|
+
"storage.archiveMemory",
|
|
8562
|
+
{
|
|
8563
|
+
memoryId: current.frontmatter.id,
|
|
8564
|
+
eventType: "archived",
|
|
8565
|
+
timestamp: updatedFm.archivedAt ?? updatedFm.updated,
|
|
8566
|
+
actor: lifecycle?.actor ?? "storage.archiveMemory",
|
|
8567
|
+
reasonCode: lifecycle?.reasonCode,
|
|
8568
|
+
before: this.summarizeLifecycleState(current.frontmatter, current.path),
|
|
8569
|
+
after: this.summarizeLifecycleState(updatedFm, destPath),
|
|
8570
|
+
relatedMemoryIds: lifecycle?.relatedMemoryIds,
|
|
8571
|
+
correlationId: lifecycle?.correlationId
|
|
8572
|
+
},
|
|
8573
|
+
lifecycle?.ruleVersion
|
|
8574
|
+
);
|
|
8575
|
+
this.bumpMemoryStatusVersion();
|
|
8576
|
+
log.debug(`archived memory ${current.frontmatter.id} \u2192 ${destPath}`);
|
|
8577
|
+
return destPath;
|
|
8578
|
+
} catch (err) {
|
|
8579
|
+
log.warn(`failed to archive memory ${current.frontmatter.id}: ${err}`);
|
|
8580
|
+
return null;
|
|
8581
|
+
}
|
|
8582
|
+
};
|
|
8583
|
+
return await this.runTombstoneBlockedArchive(memory, archiveCurrent);
|
|
8967
8584
|
}
|
|
8968
8585
|
async readEntities() {
|
|
8969
8586
|
try {
|
|
@@ -9016,18 +8633,21 @@ ${memory.content}
|
|
|
9016
8633
|
}
|
|
9017
8634
|
async invalidateMemory(id) {
|
|
9018
8635
|
const memories = await this.readAllMemories();
|
|
9019
|
-
const memory = memories.find((
|
|
8636
|
+
const memory = memories.find((candidate) => candidate.frontmatter.id === id);
|
|
9020
8637
|
if (!memory) return false;
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
8638
|
+
return await this.runTombstoneBlockedInvalidation(
|
|
8639
|
+
memory,
|
|
8640
|
+
async (current, rebuildMarker, markDurable) => {
|
|
8641
|
+
await unlink3(current.path);
|
|
8642
|
+
markDurable();
|
|
8643
|
+
markProjectedMemoryPathInvalid(this.baseDir, id);
|
|
8644
|
+
this.invalidateAllMemoriesCache();
|
|
8645
|
+
await this.rebuildTombstoneBlockedCaptureAfterInvalidation(rebuildMarker);
|
|
8646
|
+
this.bumpMemoryStatusVersion();
|
|
8647
|
+
log.debug(`invalidated memory ${id}`);
|
|
8648
|
+
return true;
|
|
8649
|
+
}
|
|
8650
|
+
);
|
|
9031
8651
|
}
|
|
9032
8652
|
async updateMemory(id, newContent, options) {
|
|
9033
8653
|
const memories = await this.readAllMemories();
|
|
@@ -9051,7 +8671,9 @@ ${memory.content}
|
|
|
9051
8671
|
|
|
9052
8672
|
${sanitized.text}
|
|
9053
8673
|
`;
|
|
9054
|
-
await this.
|
|
8674
|
+
await this.writeTombstoneBlockedUpdate(memory, fileContent, updated, sanitized.text, async () => {
|
|
8675
|
+
this.invalidateAllMemoriesCache();
|
|
8676
|
+
});
|
|
9055
8677
|
await this.patchHotMemoriesCache({ addedPath: memory.path });
|
|
9056
8678
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.updateMemory", {
|
|
9057
8679
|
memoryId: id,
|
|
@@ -9083,9 +8705,14 @@ ${sanitized.text}
|
|
|
9083
8705
|
|
|
9084
8706
|
${memory.content}
|
|
9085
8707
|
`;
|
|
9086
|
-
await this.
|
|
8708
|
+
await this.writeTombstoneBlockedFrontmatter(memory, fileContent, updated, async () => {
|
|
8709
|
+
this.invalidateAllMemoriesCache();
|
|
8710
|
+
if (memory.path.includes(`${path9.sep}cold${path9.sep}`)) {
|
|
8711
|
+
this.invalidateColdMemoriesCache();
|
|
8712
|
+
}
|
|
8713
|
+
});
|
|
9087
8714
|
await this.patchHotMemoriesCache({ addedPath: memory.path });
|
|
9088
|
-
if (memory.path.includes(`${
|
|
8715
|
+
if (memory.path.includes(`${path9.sep}cold${path9.sep}`)) {
|
|
9089
8716
|
this.invalidateColdMemoriesCache();
|
|
9090
8717
|
}
|
|
9091
8718
|
try {
|
|
@@ -9138,15 +8765,14 @@ ${memory.content}
|
|
|
9138
8765
|
for (const m of memories) {
|
|
9139
8766
|
if (!m.frontmatter.expiresAt) continue;
|
|
9140
8767
|
const expiresAt = new Date(m.frontmatter.expiresAt).getTime();
|
|
9141
|
-
if (expiresAt
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
}
|
|
9149
|
-
}
|
|
8768
|
+
if (expiresAt >= now) continue;
|
|
8769
|
+
const removed = await this.deleteMemoryForMaintenance(m, (current) => {
|
|
8770
|
+
const currentExpiresAt = current.frontmatter.expiresAt ? new Date(current.frontmatter.expiresAt).getTime() : Number.NaN;
|
|
8771
|
+
return Number.isFinite(currentExpiresAt) && currentExpiresAt < now;
|
|
8772
|
+
});
|
|
8773
|
+
if (removed) {
|
|
8774
|
+
deleted.push(removed);
|
|
8775
|
+
log.debug(`cleaned expired memory ${removed.frontmatter.id} (TTL expired)`);
|
|
9150
8776
|
}
|
|
9151
8777
|
}
|
|
9152
8778
|
if (deleted.length > 0) {
|
|
@@ -9156,7 +8782,7 @@ ${memory.content}
|
|
|
9156
8782
|
return deleted;
|
|
9157
8783
|
}
|
|
9158
8784
|
async loadBuffer() {
|
|
9159
|
-
const bufferPath =
|
|
8785
|
+
const bufferPath = path9.join(this.stateDir, "buffer.json");
|
|
9160
8786
|
try {
|
|
9161
8787
|
const raw = await this.readStorageSecureFile(bufferPath);
|
|
9162
8788
|
return JSON.parse(raw);
|
|
@@ -9168,11 +8794,11 @@ ${memory.content}
|
|
|
9168
8794
|
}
|
|
9169
8795
|
async saveBuffer(state) {
|
|
9170
8796
|
await this.ensureDirectories();
|
|
9171
|
-
const bufferPath =
|
|
8797
|
+
const bufferPath = path9.join(this.stateDir, "buffer.json");
|
|
9172
8798
|
await this.writeStorageSecureFile(bufferPath, JSON.stringify(state, null, 2));
|
|
9173
8799
|
}
|
|
9174
8800
|
async loadMeta() {
|
|
9175
|
-
const metaPath =
|
|
8801
|
+
const metaPath = path9.join(this.stateDir, "meta.json");
|
|
9176
8802
|
try {
|
|
9177
8803
|
const raw = await this.readStorageSecureFile(metaPath);
|
|
9178
8804
|
const parsed = JSON.parse(raw);
|
|
@@ -9205,7 +8831,7 @@ ${memory.content}
|
|
|
9205
8831
|
}
|
|
9206
8832
|
async saveMeta(state) {
|
|
9207
8833
|
await this.ensureDirectories();
|
|
9208
|
-
const metaPath =
|
|
8834
|
+
const metaPath = path9.join(this.stateDir, "meta.json");
|
|
9209
8835
|
await this.writeStorageSecureFile(metaPath, JSON.stringify(state, null, 2));
|
|
9210
8836
|
}
|
|
9211
8837
|
async appendMemoryActionEvents(events) {
|
|
@@ -9260,10 +8886,7 @@ ${memory.content}
|
|
|
9260
8886
|
* The offline CLI uses this path-aware variant for per-namespace ledgers so
|
|
9261
8887
|
* secure-store encryption and path-bound authentication remain consistent. */
|
|
9262
8888
|
async drainPendingMemoryLifecycleEventsForSyncAt(ledgerPath) {
|
|
9263
|
-
const target = this.assertManagedStoragePath(
|
|
9264
|
-
ledgerPath,
|
|
9265
|
-
"storage.drainPendingMemoryLifecycleEventsForSyncAt"
|
|
9266
|
-
);
|
|
8889
|
+
const target = this.assertManagedStoragePath(ledgerPath, "storage.drainPendingMemoryLifecycleEventsForSyncAt");
|
|
9267
8890
|
return drainPendingLifecycleLedgerForSync(
|
|
9268
8891
|
target,
|
|
9269
8892
|
{
|
|
@@ -9272,7 +8895,7 @@ ${memory.content}
|
|
|
9272
8895
|
},
|
|
9273
8896
|
(payload) => this.appendStorageSecureFile(target, payload),
|
|
9274
8897
|
async () => {
|
|
9275
|
-
await
|
|
8898
|
+
await mkdir(path9.dirname(target), { recursive: true });
|
|
9276
8899
|
}
|
|
9277
8900
|
);
|
|
9278
8901
|
}
|
|
@@ -9304,7 +8927,7 @@ ${memory.content}
|
|
|
9304
8927
|
let existingKeys;
|
|
9305
8928
|
let identity = null;
|
|
9306
8929
|
try {
|
|
9307
|
-
const st = await
|
|
8930
|
+
const st = await stat2(this.behaviorSignalsPath);
|
|
9308
8931
|
identity = { size: st.size, mtimeMs: st.mtimeMs };
|
|
9309
8932
|
if (this.behaviorSignalsKeyCache && this.behaviorSignalsKeyCache.identity.size === identity.size && this.behaviorSignalsKeyCache.identity.mtimeMs === identity.mtimeMs) {
|
|
9310
8933
|
existingKeys = this.behaviorSignalsKeyCache.keys;
|
|
@@ -9352,7 +8975,7 @@ ${memory.content}
|
|
|
9352
8975
|
await this.appendStorageSecureFile(this.behaviorSignalsPath, payload);
|
|
9353
8976
|
for (const key of pending) existingKeys.add(key);
|
|
9354
8977
|
try {
|
|
9355
|
-
const st = await
|
|
8978
|
+
const st = await stat2(this.behaviorSignalsPath);
|
|
9356
8979
|
const expectedSize = (identity?.size ?? 0) + Buffer.byteLength(payload, "utf-8");
|
|
9357
8980
|
if (st.size === expectedSize) {
|
|
9358
8981
|
this.behaviorSignalsKeyCache = {
|
|
@@ -9370,7 +8993,7 @@ ${memory.content}
|
|
|
9370
8993
|
async appendReextractJobs(events) {
|
|
9371
8994
|
if (events.length === 0) return 0;
|
|
9372
8995
|
await this.ensureDirectories();
|
|
9373
|
-
const filePath =
|
|
8996
|
+
const filePath = path9.join(this.stateDir, "reextract-jobs.jsonl");
|
|
9374
8997
|
const lines = events.map((event) => JSON.stringify(event)).join("\n") + "\n";
|
|
9375
8998
|
try {
|
|
9376
8999
|
await this.appendStorageSecureFile(filePath, lines);
|
|
@@ -9383,7 +9006,7 @@ ${memory.content}
|
|
|
9383
9006
|
}
|
|
9384
9007
|
async readReextractJobs(limit = 200) {
|
|
9385
9008
|
const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.min(1e3, Math.floor(limit))) : 200;
|
|
9386
|
-
const filePath =
|
|
9009
|
+
const filePath = path9.join(this.stateDir, "reextract-jobs.jsonl");
|
|
9387
9010
|
try {
|
|
9388
9011
|
const raw = await this.readStorageSecureFile(filePath);
|
|
9389
9012
|
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
@@ -9463,10 +9086,7 @@ ${memory.content}
|
|
|
9463
9086
|
}
|
|
9464
9087
|
async readAllMemoryLifecycleEventsForCompaction() {
|
|
9465
9088
|
if (!await probeEncryptedRegularFileHeader(this.memoryLifecycleLedgerPath)) {
|
|
9466
|
-
return readAllLifecycleEventsFromLedger(
|
|
9467
|
-
this.memoryLifecycleLedgerPath,
|
|
9468
|
-
(p) => this.readStorageSecureFile(p)
|
|
9469
|
-
);
|
|
9089
|
+
return readAllLifecycleEventsFromLedger(this.memoryLifecycleLedgerPath, (p) => this.readStorageSecureFile(p));
|
|
9470
9090
|
}
|
|
9471
9091
|
return readAllLifecycleEventsFromLedgerBuffer(
|
|
9472
9092
|
this.memoryLifecycleLedgerPath,
|
|
@@ -9597,7 +9217,7 @@ ${memory.content}
|
|
|
9597
9217
|
return `${prefix}-${ts}-${rand}`;
|
|
9598
9218
|
}
|
|
9599
9219
|
async writeQuestion(question, context, priority) {
|
|
9600
|
-
await
|
|
9220
|
+
await mkdir(this.questionsDir, { recursive: true });
|
|
9601
9221
|
const id = this.generateId("q");
|
|
9602
9222
|
const frontmatter = {
|
|
9603
9223
|
id,
|
|
@@ -9613,7 +9233,7 @@ ${question}
|
|
|
9613
9233
|
|
|
9614
9234
|
**Context:** ${context}
|
|
9615
9235
|
`;
|
|
9616
|
-
const filePath =
|
|
9236
|
+
const filePath = path9.join(this.questionsDir, `${id}.md`);
|
|
9617
9237
|
await this.writeStorageSecureFile(filePath, content);
|
|
9618
9238
|
log.debug(`wrote question ${id} to ${filePath}`);
|
|
9619
9239
|
this.invalidateQuestionsCache();
|
|
@@ -9631,7 +9251,7 @@ ${question}
|
|
|
9631
9251
|
if (!match) return null;
|
|
9632
9252
|
const frontmatterStr = match[1];
|
|
9633
9253
|
const body = match[2].trim();
|
|
9634
|
-
const id = this.extractFrontmatterValue(frontmatterStr, "id") ??
|
|
9254
|
+
const id = this.extractFrontmatterValue(frontmatterStr, "id") ?? path9.basename(filePath, ".md");
|
|
9635
9255
|
const created = this.extractFrontmatterValue(frontmatterStr, "created") ?? "";
|
|
9636
9256
|
const priority = parseFloat(this.extractFrontmatterValue(frontmatterStr, "priority") ?? "0.5");
|
|
9637
9257
|
const resolved = this.extractFrontmatterValue(frontmatterStr, "resolved") === "true";
|
|
@@ -9687,11 +9307,11 @@ ${question}
|
|
|
9687
9307
|
} catch {
|
|
9688
9308
|
}
|
|
9689
9309
|
const hygiene = opts?.hygiene;
|
|
9690
|
-
const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(
|
|
9310
|
+
const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(path9.basename(identityPath));
|
|
9691
9311
|
if (rotateEnabled) {
|
|
9692
9312
|
const maxBytes = hygiene.rotateMaxBytes;
|
|
9693
9313
|
if (existing.length > maxBytes) {
|
|
9694
|
-
const archiveDir =
|
|
9314
|
+
const archiveDir = path9.join(workspaceDir, hygiene.archiveDir);
|
|
9695
9315
|
const { newContent } = await rotateMarkdownFileToArchive({
|
|
9696
9316
|
filePath: identityPath,
|
|
9697
9317
|
archiveDir,
|
|
@@ -9743,7 +9363,7 @@ ${reflection}
|
|
|
9743
9363
|
}
|
|
9744
9364
|
}
|
|
9745
9365
|
async writeIdentityReflections(content) {
|
|
9746
|
-
await
|
|
9366
|
+
await mkdir(this.identityDir, { recursive: true });
|
|
9747
9367
|
await this.writeStorageSecureFile(this.identityReflectionsPath, content);
|
|
9748
9368
|
}
|
|
9749
9369
|
async appendIdentityReflection(reflection) {
|
|
@@ -9775,7 +9395,7 @@ ${reflection}
|
|
|
9775
9395
|
|
|
9776
9396
|
${reflection}
|
|
9777
9397
|
`;
|
|
9778
|
-
await
|
|
9398
|
+
await mkdir(this.identityDir, { recursive: true });
|
|
9779
9399
|
await this.writeStorageSecureFile(this.identityReflectionsPath, `${existing.trimEnd()}${section}`);
|
|
9780
9400
|
log.debug(`appended namespace-local reflection to ${this.identityReflectionsPath}`);
|
|
9781
9401
|
}
|
|
@@ -9787,7 +9407,7 @@ ${reflection}
|
|
|
9787
9407
|
* Deduplicates by target+label.
|
|
9788
9408
|
*/
|
|
9789
9409
|
async addEntityRelationship(name, rel) {
|
|
9790
|
-
const filePath =
|
|
9410
|
+
const filePath = path9.join(this.entitiesDir, `${name}.md`);
|
|
9791
9411
|
let entity;
|
|
9792
9412
|
try {
|
|
9793
9413
|
const content = await this.readStorageSecureFile(filePath);
|
|
@@ -9810,7 +9430,7 @@ ${reflection}
|
|
|
9810
9430
|
* Prepends to the beginning, prunes oldest entries beyond maxEntries.
|
|
9811
9431
|
*/
|
|
9812
9432
|
async addEntityActivity(name, entry, maxEntries) {
|
|
9813
|
-
const filePath =
|
|
9433
|
+
const filePath = path9.join(this.entitiesDir, `${name}.md`);
|
|
9814
9434
|
let entity;
|
|
9815
9435
|
try {
|
|
9816
9436
|
const content = await this.readStorageSecureFile(filePath);
|
|
@@ -9830,7 +9450,7 @@ ${reflection}
|
|
|
9830
9450
|
this.invalidateKnowledgeIndexCache();
|
|
9831
9451
|
}
|
|
9832
9452
|
async addEntityAlias(name, alias) {
|
|
9833
|
-
const filePath =
|
|
9453
|
+
const filePath = path9.join(this.entitiesDir, `${name}.md`);
|
|
9834
9454
|
let entity;
|
|
9835
9455
|
try {
|
|
9836
9456
|
const content = await this.readStorageSecureFile(filePath);
|
|
@@ -9858,7 +9478,7 @@ ${reflection}
|
|
|
9858
9478
|
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9859
9479
|
let synthesisTimelineCount;
|
|
9860
9480
|
try {
|
|
9861
|
-
const filePath =
|
|
9481
|
+
const filePath = path9.join(this.entitiesDir, `${name}.md`);
|
|
9862
9482
|
const content = await this.readStorageSecureFile(filePath);
|
|
9863
9483
|
synthesisTimelineCount = parseEntityFile(content, this.entitySchemas).timeline.length;
|
|
9864
9484
|
} catch (err) {
|
|
@@ -9900,7 +9520,7 @@ ${reflection}
|
|
|
9900
9520
|
const rightTs = latestEntityTimelineTimestamp(right.entity) ?? "";
|
|
9901
9521
|
return compareEntityTimestamps(rightTs, leftTs);
|
|
9902
9522
|
}).map(({ entityName }) => entityName);
|
|
9903
|
-
await
|
|
9523
|
+
await mkdir(this.stateDir, { recursive: true });
|
|
9904
9524
|
await this.writeStorageSecureFile(
|
|
9905
9525
|
this.entitySynthesisQueuePath,
|
|
9906
9526
|
JSON.stringify(
|
|
@@ -9920,7 +9540,7 @@ ${reflection}
|
|
|
9920
9540
|
if (queue.length === 0) return;
|
|
9921
9541
|
const removals = new Set(entityNames);
|
|
9922
9542
|
const nextQueue = queue.filter((name) => !removals.has(name));
|
|
9923
|
-
await
|
|
9543
|
+
await mkdir(this.stateDir, { recursive: true });
|
|
9924
9544
|
await this.writeStorageSecureFile(
|
|
9925
9545
|
this.entitySynthesisQueuePath,
|
|
9926
9546
|
JSON.stringify(
|
|
@@ -9941,7 +9561,7 @@ ${reflection}
|
|
|
9941
9561
|
if (!raw) continue;
|
|
9942
9562
|
const serialized = serializeEntityFile(parseEntityFile(raw, this.entitySchemas), this.entitySchemas);
|
|
9943
9563
|
if (raw.trimEnd() === serialized.trimEnd()) continue;
|
|
9944
|
-
await this.writeStorageSecureFile(
|
|
9564
|
+
await this.writeStorageSecureFile(path9.join(this.entitiesDir, `${entityName}.md`), serialized);
|
|
9945
9565
|
migrated += 1;
|
|
9946
9566
|
}
|
|
9947
9567
|
if (migrated > 0) {
|
|
@@ -10007,15 +9627,18 @@ ${reflection}
|
|
|
10007
9627
|
const isResolved = m.frontmatter.tags.some((t) => t === "fulfilled" || t === "expired");
|
|
10008
9628
|
if (!isResolved) continue;
|
|
10009
9629
|
const updatedAt = new Date(m.frontmatter.updated).getTime();
|
|
10010
|
-
if (updatedAt
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
9630
|
+
if (updatedAt >= cutoff) continue;
|
|
9631
|
+
try {
|
|
9632
|
+
const removed = await this.deleteMemoryForMaintenance(m, (current) => {
|
|
9633
|
+
const currentResolved = current.frontmatter.tags.some((tag) => tag === "fulfilled" || tag === "expired");
|
|
9634
|
+
const currentUpdatedAt = new Date(current.frontmatter.updated).getTime();
|
|
9635
|
+
return currentResolved && currentUpdatedAt < cutoff;
|
|
9636
|
+
});
|
|
9637
|
+
if (removed) {
|
|
9638
|
+
deleted.push(removed);
|
|
9639
|
+
log.debug(`cleaned expired commitment ${removed.frontmatter.id}`);
|
|
10018
9640
|
}
|
|
9641
|
+
} catch {
|
|
10019
9642
|
}
|
|
10020
9643
|
}
|
|
10021
9644
|
if (deleted.length > 0) {
|
|
@@ -10034,14 +9657,14 @@ ${reflection}
|
|
|
10034
9657
|
if (entries.length === 0) return 0;
|
|
10035
9658
|
const memories = await this.readAllMemories();
|
|
10036
9659
|
const memoryMap = new Map(memories.map((m) => [m.frontmatter.id, m]));
|
|
10037
|
-
const memoryPathMap = new Map(memories.map((m) => [
|
|
9660
|
+
const memoryPathMap = new Map(memories.map((m) => [path9.resolve(m.path), m]));
|
|
10038
9661
|
let updated = 0;
|
|
10039
9662
|
const prevVersion = this.getMemoryCorpusVersion();
|
|
10040
9663
|
const keyId = this.hotCacheKeyId();
|
|
10041
9664
|
const warm = this.hotMemoriesCacheEnabled && getCachedMemories(this.baseDir, prevVersion, keyId, this.hotCacheTtlMs()) !== null;
|
|
10042
9665
|
const appliedPatches = /* @__PURE__ */ new Map();
|
|
10043
9666
|
for (const entry of entries) {
|
|
10044
|
-
const memory = entry.memoryPath ? memoryPathMap.get(
|
|
9667
|
+
const memory = entry.memoryPath ? memoryPathMap.get(path9.resolve(entry.memoryPath)) : memoryMap.get(entry.memoryId);
|
|
10045
9668
|
if (!memory) continue;
|
|
10046
9669
|
const newFm = {
|
|
10047
9670
|
...memory.frontmatter,
|
|
@@ -10057,7 +9680,7 @@ ${memory.content}
|
|
|
10057
9680
|
if (warm) {
|
|
10058
9681
|
updateCacheOnWrite(this.baseDir, { ...memory, frontmatter: newFm }, keyId);
|
|
10059
9682
|
}
|
|
10060
|
-
appliedPatches.set(
|
|
9683
|
+
appliedPatches.set(path9.resolve(memory.path), {
|
|
10061
9684
|
accessCount: entry.newCount,
|
|
10062
9685
|
lastAccessed: entry.lastAccessed
|
|
10063
9686
|
});
|
|
@@ -10073,8 +9696,11 @@ ${memory.content}
|
|
|
10073
9696
|
const cur = getCachedMemories(this.baseDir, prevVersion, keyId);
|
|
10074
9697
|
if (cur) {
|
|
10075
9698
|
const reapplied = cur.map((m) => {
|
|
10076
|
-
const patch = appliedPatches.get(
|
|
10077
|
-
return patch ? {
|
|
9699
|
+
const patch = appliedPatches.get(path9.resolve(m.path));
|
|
9700
|
+
return patch ? {
|
|
9701
|
+
...m,
|
|
9702
|
+
frontmatter: { ...m.frontmatter, accessCount: patch.accessCount, lastAccessed: patch.lastAccessed }
|
|
9703
|
+
} : m;
|
|
10078
9704
|
});
|
|
10079
9705
|
setCachedMemories(this.baseDir, reapplied, produced, keyId, this.hotCacheTtlMs());
|
|
10080
9706
|
}
|
|
@@ -10105,7 +9731,7 @@ ${memory.content}
|
|
|
10105
9731
|
const pathsById = /* @__PURE__ */ new Map();
|
|
10106
9732
|
const filePathsById = /* @__PURE__ */ new Map();
|
|
10107
9733
|
for (const filePath of filePaths) {
|
|
10108
|
-
const memoryId =
|
|
9734
|
+
const memoryId = path9.basename(filePath, ".md");
|
|
10109
9735
|
if (!wantedIds.has(memoryId)) continue;
|
|
10110
9736
|
const paths = filePathsById.get(memoryId) ?? [];
|
|
10111
9737
|
paths.push(filePath);
|
|
@@ -10116,12 +9742,8 @@ ${memory.content}
|
|
|
10116
9742
|
const preferred = preferredPaths.get(id) ?? [];
|
|
10117
9743
|
const preferredMatches = [];
|
|
10118
9744
|
for (const preferredPath of preferred) {
|
|
10119
|
-
const directCandidates = new Set(
|
|
10120
|
-
|
|
10121
|
-
);
|
|
10122
|
-
const directMatch = existingPaths.find(
|
|
10123
|
-
(filePath) => directCandidates.has(path10.resolve(filePath))
|
|
10124
|
-
);
|
|
9745
|
+
const directCandidates = new Set(qmdResultPathCandidates(this.baseDir, preferredPath));
|
|
9746
|
+
const directMatch = existingPaths.find((filePath) => directCandidates.has(path9.resolve(filePath)));
|
|
10125
9747
|
if (directMatch) {
|
|
10126
9748
|
preferredMatches.push(directMatch);
|
|
10127
9749
|
continue;
|
|
@@ -10133,7 +9755,7 @@ ${memory.content}
|
|
|
10133
9755
|
candidates.add(candidate);
|
|
10134
9756
|
}
|
|
10135
9757
|
}
|
|
10136
|
-
const match = existingPaths.find((filePath) => candidates.has(
|
|
9758
|
+
const match = existingPaths.find((filePath) => candidates.has(path9.resolve(filePath)));
|
|
10137
9759
|
if (match) preferredMatches.push(match);
|
|
10138
9760
|
}
|
|
10139
9761
|
if (preferredMatches.length > 0) {
|
|
@@ -10266,10 +9888,17 @@ ${memory.content}
|
|
|
10266
9888
|
${sanitized.text}
|
|
10267
9889
|
`;
|
|
10268
9890
|
const filePath = await this.resolveCategoryWritePath(category, id, today);
|
|
10269
|
-
await this.
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
9891
|
+
return await this.writeTombstoneBlockedChunk(
|
|
9892
|
+
filePath,
|
|
9893
|
+
fileContent,
|
|
9894
|
+
fm,
|
|
9895
|
+
sanitized.text,
|
|
9896
|
+
() => this.findExistingTombstoneBlockedMemory(sanitized.text, category, fm.sourceConnector),
|
|
9897
|
+
async () => {
|
|
9898
|
+
await this.patchHotMemoriesCache({ addedPath: filePath }, "memory-create");
|
|
9899
|
+
log.debug(`wrote chunk ${id} (${chunkIndex + 1}/${chunkTotal}) to ${filePath}`);
|
|
9900
|
+
}
|
|
9901
|
+
);
|
|
10273
9902
|
}
|
|
10274
9903
|
/**
|
|
10275
9904
|
* Get all chunks for a given parent memory ID.
|
|
@@ -10339,7 +9968,12 @@ ${oldMemory.content}
|
|
|
10339
9968
|
const auditBody = `Superseded: ${oldMemory.content}
|
|
10340
9969
|
|
|
10341
9970
|
Reason: ${reason}`;
|
|
10342
|
-
const auditInput = {
|
|
9971
|
+
const auditInput = {
|
|
9972
|
+
content: auditBody,
|
|
9973
|
+
category: "correction",
|
|
9974
|
+
confidence: 1,
|
|
9975
|
+
tags: ["supersession", "auto-resolved"]
|
|
9976
|
+
};
|
|
10343
9977
|
const auditEnvelope = composeMemoryEnvelope(auditInput, { source: "contradiction-detection" });
|
|
10344
9978
|
await this.writeSealedMemory(auditEnvelope, { lineage: [oldMemoryId, newMemoryId] });
|
|
10345
9979
|
return true;
|
|
@@ -10352,14 +9986,14 @@ Reason: ${reason}`;
|
|
|
10352
9986
|
// Memory Summarization (Phase 4A)
|
|
10353
9987
|
// ---------------------------------------------------------------------------
|
|
10354
9988
|
get summariesDir() {
|
|
10355
|
-
return
|
|
9989
|
+
return path9.join(this.baseDir, "summaries");
|
|
10356
9990
|
}
|
|
10357
9991
|
/**
|
|
10358
9992
|
* Write a memory summary.
|
|
10359
9993
|
*/
|
|
10360
9994
|
async writeSummary(summary) {
|
|
10361
|
-
await
|
|
10362
|
-
const filePath =
|
|
9995
|
+
await mkdir(this.summariesDir, { recursive: true });
|
|
9996
|
+
const filePath = path9.join(this.summariesDir, `${summary.id}.json`);
|
|
10363
9997
|
await this.writeStorageSecureFile(filePath, JSON.stringify(summary, null, 2));
|
|
10364
9998
|
log.debug(`wrote summary ${summary.id}`);
|
|
10365
9999
|
}
|
|
@@ -10372,7 +10006,7 @@ Reason: ${reason}`;
|
|
|
10372
10006
|
const summaries = [];
|
|
10373
10007
|
for (const file of files) {
|
|
10374
10008
|
if (!file.endsWith(".json")) continue;
|
|
10375
|
-
const filePath =
|
|
10009
|
+
const filePath = path9.join(this.summariesDir, file);
|
|
10376
10010
|
const raw = await this.readStorageSecureFile(filePath);
|
|
10377
10011
|
summaries.push(JSON.parse(raw));
|
|
10378
10012
|
}
|
|
@@ -10434,8 +10068,8 @@ ${memory.content}
|
|
|
10434
10068
|
* Save topic scores to meta.json.
|
|
10435
10069
|
*/
|
|
10436
10070
|
async saveTopics(topics) {
|
|
10437
|
-
const metaPath =
|
|
10438
|
-
await
|
|
10071
|
+
const metaPath = path9.join(this.stateDir, "topics.json");
|
|
10072
|
+
await mkdir(this.stateDir, { recursive: true });
|
|
10439
10073
|
await this.writeStorageSecureFile(
|
|
10440
10074
|
metaPath,
|
|
10441
10075
|
JSON.stringify({ topics, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
@@ -10446,7 +10080,7 @@ ${memory.content}
|
|
|
10446
10080
|
* Load topic scores from meta.json.
|
|
10447
10081
|
*/
|
|
10448
10082
|
async loadTopics() {
|
|
10449
|
-
const metaPath =
|
|
10083
|
+
const metaPath = path9.join(this.stateDir, "topics.json");
|
|
10450
10084
|
try {
|
|
10451
10085
|
const raw = await this.readStorageSecureFile(metaPath);
|
|
10452
10086
|
return JSON.parse(raw);
|
|
@@ -10557,8 +10191,6 @@ export {
|
|
|
10557
10191
|
countEntityStructuredFacts,
|
|
10558
10192
|
fingerprintEntityStructuredFacts,
|
|
10559
10193
|
isEntitySynthesisStale,
|
|
10560
|
-
FactHashIndexNotAuthoritativeError,
|
|
10561
|
-
ContentHashIndex,
|
|
10562
10194
|
parseFrontmatterStringValue,
|
|
10563
10195
|
parseFrontmatter,
|
|
10564
10196
|
normalizeFrontmatterForPath,
|
|
@@ -10568,4 +10200,4 @@ export {
|
|
|
10568
10200
|
isValidBufferSurpriseEvent,
|
|
10569
10201
|
StorageManager3 as StorageManager
|
|
10570
10202
|
};
|
|
10571
|
-
//# sourceMappingURL=chunk-
|
|
10203
|
+
//# sourceMappingURL=chunk-RGIWINDN.js.map
|