@remnic/core 9.17.0 → 9.18.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 +8 -8
- package/dist/access-admin-ops-surface.js +18 -18
- package/dist/access-boundary.d.ts +8 -8
- package/dist/access-boundary.js +18 -18
- package/dist/access-cli.js +52 -52
- package/dist/access-http.d.ts +8 -8
- package/dist/access-http.js +23 -23
- package/dist/access-identity-continuity-surface.d.ts +6 -6
- package/dist/access-identity-continuity-surface.js +18 -18
- package/dist/access-lcm-surface.d.ts +8 -8
- package/dist/access-lcm-surface.js +18 -18
- package/dist/access-mcp.d.ts +8 -8
- package/dist/access-mcp.js +22 -22
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/access-memory-search-fanout.js +3 -3
- package/dist/{access-namespace-preflight-_yxq3PYy.d.ts → access-namespace-preflight-IbQ0O8Kf.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +18 -18
- package/dist/access-observe-write-surface.d.ts +8 -8
- package/dist/access-observe-write-surface.js +18 -18
- package/dist/access-operations-batch.js +20 -20
- package/dist/access-operations.d.ts +8 -8
- package/dist/access-operations.js +21 -21
- package/dist/access-recall-concurrency.d.ts +8 -8
- package/dist/access-recall-concurrency.js +18 -18
- package/dist/access-recall-response.d.ts +8 -8
- package/dist/access-recall-response.js +18 -18
- package/dist/access-recall-surface.d.ts +8 -8
- package/dist/access-recall-surface.js +18 -18
- package/dist/access-schema.d.ts +2 -2
- package/dist/{access-service-CSm242ks.d.ts → access-service-BqjsJRn_.d.ts} +6 -6
- package/dist/access-service.d.ts +8 -8
- package/dist/access-service.js +18 -18
- package/dist/access-surface-catalog.d.ts +8 -8
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-memory-bridge.js +3 -3
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +6 -6
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +6 -6
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/buffer.js +2 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +4 -3
- package/dist/capabilities.js +1 -1
- package/dist/{catalog-9qLG_2OV.d.ts → catalog-D_ToaAZM.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 +7 -7
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-BV5WMSIJ.js → chunk-2AJ72VVS.js} +3 -3
- package/dist/{chunk-755Q3DGF.js → chunk-2H7OEVPO.js} +2 -2
- package/dist/{chunk-TG6IHVE2.js → chunk-354T54RR.js} +2 -2
- package/dist/{chunk-347WD2LE.js → chunk-3CSX3ETE.js} +2 -1
- package/dist/chunk-3CSX3ETE.js.map +1 -0
- package/dist/{chunk-EA2H5LML.js → chunk-44DUJETW.js} +2 -2
- package/dist/{chunk-SESF64QR.js → chunk-52H7OB7Y.js} +4 -4
- package/dist/{chunk-TXXP7WAM.js → chunk-6P6ZR6JR.js} +22 -22
- package/dist/{chunk-C2RMQRPN.js → chunk-6YJLLDWO.js} +3 -3
- package/dist/{chunk-QDTEH6BS.js → chunk-7JXXI3GW.js} +2 -2
- package/dist/{chunk-OXT5TQDS.js → chunk-7KXE7KN5.js} +2 -2
- package/dist/{chunk-2OH3ZRYH.js → chunk-7NHIHUBW.js} +7 -6
- package/dist/chunk-7NHIHUBW.js.map +1 -0
- package/dist/{chunk-LMVAIZR5.js → chunk-A2KGXUOU.js} +5 -9
- package/dist/chunk-A2KGXUOU.js.map +1 -0
- package/dist/{chunk-UH7VNQ7O.js → chunk-AKWX5E6R.js} +30 -30
- package/dist/{chunk-ANGUOE2N.js → chunk-ALPBE5EC.js} +15 -15
- package/dist/{chunk-M6TZQDTX.js → chunk-B3R5CC35.js} +2 -2
- package/dist/{chunk-JECBQBEX.js → chunk-B7GROTXD.js} +2 -2
- package/dist/{chunk-6WAFYNVT.js → chunk-BJJAX3IZ.js} +2 -2
- package/dist/{chunk-GEDBTSYU.js → chunk-CNNQVI2A.js} +7 -7
- package/dist/{chunk-S2FSBU4H.js → chunk-FCCMFVS6.js} +2 -2
- package/dist/{chunk-F7BWFIDK.js → chunk-FO3WM76R.js} +65 -4
- package/dist/chunk-FO3WM76R.js.map +1 -0
- package/dist/{chunk-V57NLO5P.js → chunk-HKF5VASQ.js} +11 -4
- package/dist/chunk-HKF5VASQ.js.map +1 -0
- package/dist/{chunk-7ON2JW73.js → chunk-IKMAO55B.js} +2 -2
- package/dist/{chunk-B57BIBXI.js → chunk-IU3XJAQM.js} +3 -3
- package/dist/{chunk-WQ2I6ODB.js → chunk-JEMLK7ED.js} +3 -3
- package/dist/{chunk-RFDD3B7W.js → chunk-JGUXPJUQ.js} +2 -2
- package/dist/{chunk-TUNI4GB3.js → chunk-JHIEQCMG.js} +6 -6
- package/dist/{chunk-N7DK67GZ.js → chunk-KPEIWAZY.js} +1 -1
- package/dist/{chunk-N7DK67GZ.js.map → chunk-KPEIWAZY.js.map} +1 -1
- package/dist/{chunk-I6ODVWQB.js → chunk-LTGSOACX.js} +2 -2
- package/dist/{chunk-AWT7SCWQ.js → chunk-LU22QKFA.js} +2 -2
- package/dist/{chunk-QCLQ4BGA.js → chunk-MJYYXODM.js} +3 -3
- package/dist/{chunk-7VZWZVYG.js → chunk-MQVINOJQ.js} +3 -3
- package/dist/{chunk-7YJLCNPF.js → chunk-NEFMSUC2.js} +204 -170
- package/dist/chunk-NEFMSUC2.js.map +1 -0
- package/dist/{chunk-K2B2TMF3.js → chunk-NGO4QKMY.js} +2 -2
- package/dist/{chunk-7LXSPIVG.js → chunk-OR3OQHHS.js} +4 -4
- package/dist/{chunk-ANEUPF6O.js → chunk-QH6CS6XJ.js} +2 -2
- package/dist/{chunk-SCCO46KZ.js → chunk-RDQGLJRR.js} +3 -2
- package/dist/chunk-RDQGLJRR.js.map +1 -0
- package/dist/{chunk-QDNE35FU.js → chunk-SB4OKS6C.js} +3 -3
- package/dist/{chunk-IZHXSLO7.js → chunk-SGCWBLHN.js} +2 -2
- package/dist/{chunk-B6NCHYAK.js → chunk-SQEDDWAM.js} +4 -4
- package/dist/{chunk-DETNBTZS.js → chunk-UGH6VPPO.js} +21 -5
- package/dist/chunk-UGH6VPPO.js.map +1 -0
- package/dist/{chunk-GBT4TFV7.js → chunk-UHOUNNY3.js} +9 -9
- package/dist/{chunk-7PK4W4RN.js → chunk-UL6WTQN4.js} +3 -3
- package/dist/{chunk-KGKYTCDO.js → chunk-UQXIW7OZ.js} +3 -3
- package/dist/{chunk-WIS5JTJR.js → chunk-UVCY7VDX.js} +2 -2
- package/dist/{chunk-BIRPR5N7.js → chunk-VMALXCR5.js} +2 -2
- package/dist/{chunk-L3HAUO7K.js → chunk-WIP3MQZY.js} +2 -2
- package/dist/{chunk-AE7WMV4V.js → chunk-WMLHJ5UW.js} +2 -2
- package/dist/{chunk-G2WCVFDC.js → chunk-WMMT6W7C.js} +4 -4
- package/dist/{chunk-AFH7PIFU.js → chunk-XGYCI23S.js} +2 -2
- package/dist/{chunk-6QYST5XN.js → chunk-XY7QPJDA.js} +2 -2
- package/dist/{chunk-BZ52BJ6C.js → chunk-YWJBAHTY.js} +2 -2
- package/dist/{chunk-BCNVNUQG.js → chunk-ZHWEUG7Y.js} +10 -1
- package/dist/{chunk-BCNVNUQG.js.map → chunk-ZHWEUG7Y.js.map} +1 -1
- package/dist/{cli-BLZHYY2A.d.ts → cli-D2g5mRgf.d.ts} +5 -5
- package/dist/cli.d.ts +10 -10
- package/dist/cli.js +36 -36
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +7 -7
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +6 -6
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +7 -7
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/contradiction/index.js +2 -2
- package/dist/{contradiction-scan-RJBABNRH.js → contradiction-scan-HMF7CNKD.js} +3 -3
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/day-summary.js +2 -2
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/embedding-fallback.js +2 -2
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +6 -6
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +6 -6
- package/dist/explicit-capture.js +2 -2
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +4 -4
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-CSRHCJIQ.js → first-start-migration-LEXL3XYL.js} +2 -2
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/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 +18 -18
- package/dist/index.js +52 -52
- 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-llm.js +2 -2
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +6 -6
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +6 -6
- package/dist/maintenance/rebuild-memory-projection.d.ts +26 -8
- package/dist/maintenance/rebuild-memory-projection.js +7 -7
- package/dist/mcp-memory-inspector-app.d.ts +8 -8
- 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 +3 -2
- package/dist/memory-projection-store.js +3 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/migrate.js +10 -10
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/principal.js +2 -2
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +6 -6
- 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 +4 -4
- package/dist/operator-toolkit.d.ts +2 -2
- package/dist/operator-toolkit.js +16 -16
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/compression-guideline-coordinator.js +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -3
- package/dist/orchestration/maintenance.js +15 -11
- package/dist/{orchestrator-CplS9U3U.d.ts → orchestrator-DrILRi-n.d.ts} +5 -5
- package/dist/orchestrator.d.ts +6 -6
- package/dist/orchestrator.js +52 -52
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-DB4CbFqo.d.ts → pipeline-BYo4b_y9.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-IRF47ZTX.js → purge-YSGX5BOG.js} +2 -2
- package/dist/{qmd-BxZmOLHq.d.ts → qmd-BIrL8e69.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/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-explain-renderer.js +3 -3
- 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-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/{scope-profiles-NsQQo1Lt.d.ts → scope-profiles-BiQ9yXP4.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/embed-helper.js +2 -2
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +3 -3
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +3 -3
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-DW85h30n.d.ts → semantic-consolidation-C-cSATcR.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +7 -7
- package/dist/semantic-rule-promotion.js +6 -6
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +6 -6
- 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 +5 -5
- package/dist/shared-context/manager.js +2 -2
- package/dist/signal.d.ts +1 -1
- package/dist/{storage-o5BTAa8d.d.ts → storage-ByTv05qM.d.ts} +1 -1
- package/dist/storage-guards.d.ts +6 -2
- package/dist/storage-guards.js +3 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +5 -5
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +3 -3
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/{tier-stats-QEJSPFV2.js → tier-stats-XKD7SXYY.js} +2 -2
- 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-V3dT-pen.d.ts → types-e0_MI3Iq.d.ts} +16 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +6 -6
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/capabilities.test.ts +1 -0
- package/src/capabilities.ts +4 -1
- package/src/config.test.ts +22 -0
- package/src/config.ts +5 -0
- package/src/maintenance/projection-support.ts +56 -0
- package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +2 -7
- package/src/maintenance/rebuild-memory-projection.ts +7 -10
- package/src/memory-projection-store.ts +1 -1
- package/src/offline-sync-impression-drain.test.ts +54 -0
- package/src/offline-sync-impression-drain.ts +35 -3
- package/src/orchestration/maintenance.test.ts +116 -0
- package/src/orchestration/maintenance.ts +16 -0
- package/src/orchestration/projection-rebuild-schedule.ts +115 -0
- package/src/storage-guards.ts +19 -2
- package/src/storage-memory-lifecycle-ledger.test.ts +32 -0
- package/src/storage.ts +15 -1
- package/src/testing/subjects/memory-projection.test.ts +78 -0
- package/src/types.ts +18 -0
- package/dist/chunk-2OH3ZRYH.js.map +0 -1
- package/dist/chunk-347WD2LE.js.map +0 -1
- package/dist/chunk-7YJLCNPF.js.map +0 -1
- package/dist/chunk-DETNBTZS.js.map +0 -1
- package/dist/chunk-F7BWFIDK.js.map +0 -1
- package/dist/chunk-LMVAIZR5.js.map +0 -1
- package/dist/chunk-SCCO46KZ.js.map +0 -1
- package/dist/chunk-V57NLO5P.js.map +0 -1
- /package/dist/{chunk-BV5WMSIJ.js.map → chunk-2AJ72VVS.js.map} +0 -0
- /package/dist/{chunk-755Q3DGF.js.map → chunk-2H7OEVPO.js.map} +0 -0
- /package/dist/{chunk-TG6IHVE2.js.map → chunk-354T54RR.js.map} +0 -0
- /package/dist/{chunk-EA2H5LML.js.map → chunk-44DUJETW.js.map} +0 -0
- /package/dist/{chunk-SESF64QR.js.map → chunk-52H7OB7Y.js.map} +0 -0
- /package/dist/{chunk-TXXP7WAM.js.map → chunk-6P6ZR6JR.js.map} +0 -0
- /package/dist/{chunk-C2RMQRPN.js.map → chunk-6YJLLDWO.js.map} +0 -0
- /package/dist/{chunk-QDTEH6BS.js.map → chunk-7JXXI3GW.js.map} +0 -0
- /package/dist/{chunk-OXT5TQDS.js.map → chunk-7KXE7KN5.js.map} +0 -0
- /package/dist/{chunk-UH7VNQ7O.js.map → chunk-AKWX5E6R.js.map} +0 -0
- /package/dist/{chunk-ANGUOE2N.js.map → chunk-ALPBE5EC.js.map} +0 -0
- /package/dist/{chunk-M6TZQDTX.js.map → chunk-B3R5CC35.js.map} +0 -0
- /package/dist/{chunk-JECBQBEX.js.map → chunk-B7GROTXD.js.map} +0 -0
- /package/dist/{chunk-6WAFYNVT.js.map → chunk-BJJAX3IZ.js.map} +0 -0
- /package/dist/{chunk-GEDBTSYU.js.map → chunk-CNNQVI2A.js.map} +0 -0
- /package/dist/{chunk-S2FSBU4H.js.map → chunk-FCCMFVS6.js.map} +0 -0
- /package/dist/{chunk-7ON2JW73.js.map → chunk-IKMAO55B.js.map} +0 -0
- /package/dist/{chunk-B57BIBXI.js.map → chunk-IU3XJAQM.js.map} +0 -0
- /package/dist/{chunk-WQ2I6ODB.js.map → chunk-JEMLK7ED.js.map} +0 -0
- /package/dist/{chunk-RFDD3B7W.js.map → chunk-JGUXPJUQ.js.map} +0 -0
- /package/dist/{chunk-TUNI4GB3.js.map → chunk-JHIEQCMG.js.map} +0 -0
- /package/dist/{chunk-I6ODVWQB.js.map → chunk-LTGSOACX.js.map} +0 -0
- /package/dist/{chunk-AWT7SCWQ.js.map → chunk-LU22QKFA.js.map} +0 -0
- /package/dist/{chunk-QCLQ4BGA.js.map → chunk-MJYYXODM.js.map} +0 -0
- /package/dist/{chunk-7VZWZVYG.js.map → chunk-MQVINOJQ.js.map} +0 -0
- /package/dist/{chunk-K2B2TMF3.js.map → chunk-NGO4QKMY.js.map} +0 -0
- /package/dist/{chunk-7LXSPIVG.js.map → chunk-OR3OQHHS.js.map} +0 -0
- /package/dist/{chunk-ANEUPF6O.js.map → chunk-QH6CS6XJ.js.map} +0 -0
- /package/dist/{chunk-QDNE35FU.js.map → chunk-SB4OKS6C.js.map} +0 -0
- /package/dist/{chunk-IZHXSLO7.js.map → chunk-SGCWBLHN.js.map} +0 -0
- /package/dist/{chunk-B6NCHYAK.js.map → chunk-SQEDDWAM.js.map} +0 -0
- /package/dist/{chunk-GBT4TFV7.js.map → chunk-UHOUNNY3.js.map} +0 -0
- /package/dist/{chunk-7PK4W4RN.js.map → chunk-UL6WTQN4.js.map} +0 -0
- /package/dist/{chunk-KGKYTCDO.js.map → chunk-UQXIW7OZ.js.map} +0 -0
- /package/dist/{chunk-WIS5JTJR.js.map → chunk-UVCY7VDX.js.map} +0 -0
- /package/dist/{chunk-BIRPR5N7.js.map → chunk-VMALXCR5.js.map} +0 -0
- /package/dist/{chunk-L3HAUO7K.js.map → chunk-WIP3MQZY.js.map} +0 -0
- /package/dist/{chunk-AE7WMV4V.js.map → chunk-WMLHJ5UW.js.map} +0 -0
- /package/dist/{chunk-G2WCVFDC.js.map → chunk-WMMT6W7C.js.map} +0 -0
- /package/dist/{chunk-AFH7PIFU.js.map → chunk-XGYCI23S.js.map} +0 -0
- /package/dist/{chunk-6QYST5XN.js.map → chunk-XY7QPJDA.js.map} +0 -0
- /package/dist/{chunk-BZ52BJ6C.js.map → chunk-YWJBAHTY.js.map} +0 -0
- /package/dist/{contradiction-scan-RJBABNRH.js.map → contradiction-scan-HMF7CNKD.js.map} +0 -0
- /package/dist/{first-start-migration-CSRHCJIQ.js.map → first-start-migration-LEXL3XYL.js.map} +0 -0
- /package/dist/{purge-IRF47ZTX.js.map → purge-YSGX5BOG.js.map} +0 -0
- /package/dist/{tier-stats-QEJSPFV2.js.map → tier-stats-XKD7SXYY.js.map} +0 -0
|
@@ -366,7 +366,7 @@ function logProjectionOpenFailure(memoryDir: string, error: unknown): void {
|
|
|
366
366
|
);
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
function openProjectionReadonly(memoryDir: string): BetterSqlite3Database | null {
|
|
369
|
+
export function openProjectionReadonly(memoryDir: string): BetterSqlite3Database | null {
|
|
370
370
|
const dbPath = getMemoryProjectionPath(memoryDir);
|
|
371
371
|
// A MISSING file is a normal fallback (cold install, never built). A file
|
|
372
372
|
// that EXISTS but cannot open (wrong ABI / corrupt / bad perms) is a real
|
|
@@ -8,7 +8,9 @@ import {
|
|
|
8
8
|
drainPendingImpressionsForOfflineSync,
|
|
9
9
|
drainPendingLifecycleForOfflineSync,
|
|
10
10
|
getOfflineSyncStorage,
|
|
11
|
+
listOfflineSyncNamespaces,
|
|
11
12
|
} from "./offline-sync-impression-drain.js";
|
|
13
|
+
import { namespaceIdentityToken } from "./namespaces/identity.js";
|
|
12
14
|
import { isEncryptedFile } from "./secure-store/secure-fs.js";
|
|
13
15
|
import { StorageManager } from "./storage.js";
|
|
14
16
|
|
|
@@ -314,3 +316,55 @@ test("getOfflineSyncStorage never double-folds the root ledger when it is listed
|
|
|
314
316
|
|
|
315
317
|
assert.deepEqual(lifecycleDrains, ["root", "team"], "root ledger is folded exactly once");
|
|
316
318
|
});
|
|
319
|
+
|
|
320
|
+
test("listOfflineSyncNamespaces decodes canonical token dirs and skips names the router would reject", async () => {
|
|
321
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-offline-ns-enum-"));
|
|
322
|
+
try {
|
|
323
|
+
// A project namespace whose canonical token dir `ns-<hex>` exceeds the
|
|
324
|
+
// 64-char route-namespace cap: `getStorage` on the RAW basename throws
|
|
325
|
+
// `unsafe namespace` and 500s the whole root snapshot-stream (the regression).
|
|
326
|
+
const longName = "generalist-project-origin-6d1ad1f2";
|
|
327
|
+
const longToken = namespaceIdentityToken(longName); // ns-<hex>, 71 chars
|
|
328
|
+
assert.ok(longToken.length > 64, "token dir must exceed the route-namespace cap");
|
|
329
|
+
// A namespace whose DECODED name itself exceeds the 64-char route cap:
|
|
330
|
+
// decoding succeeds but isSafeRouteNamespace must still reject it.
|
|
331
|
+
const overCapName = `generalist-project-tag-${"x".repeat(60)}`;
|
|
332
|
+
const overCapToken = namespaceIdentityToken(overCapName);
|
|
333
|
+
assert.ok(overCapName.length > 64, "decoded name must exceed the route cap");
|
|
334
|
+
for (const dir of [longToken, overCapToken, "blend", "ns-default"]) {
|
|
335
|
+
await mkdir(path.join(root, "namespaces", dir, "state"), { recursive: true });
|
|
336
|
+
await writeFile(
|
|
337
|
+
path.join(root, "namespaces", dir, LIFECYCLE_LEDGER_REL),
|
|
338
|
+
"",
|
|
339
|
+
"utf-8",
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
const host = {
|
|
343
|
+
config: {
|
|
344
|
+
memoryDir: root,
|
|
345
|
+
defaultNamespace: "generalist",
|
|
346
|
+
sharedNamespace: "shared",
|
|
347
|
+
namespacePolicies: [],
|
|
348
|
+
},
|
|
349
|
+
namespaceCatalog: { listNamespaces: async () => [] },
|
|
350
|
+
} as unknown as Parameters<typeof listOfflineSyncNamespaces>[0];
|
|
351
|
+
|
|
352
|
+
const names = new Set(await listOfflineSyncNamespaces(host));
|
|
353
|
+
|
|
354
|
+
// The canonical token dir is decoded back to its namespace NAME...
|
|
355
|
+
assert.ok(names.has(longName), "canonical token dir must be decoded to its namespace name");
|
|
356
|
+
// ...never surfaced as the raw >64-char token that getStorage would reject.
|
|
357
|
+
assert.ok(!names.has(longToken), "raw over-cap token must never be enumerated as a namespace");
|
|
358
|
+
// A decoded name that is ITSELF over the route cap is skipped entirely
|
|
359
|
+
// (the isSafeRouteNamespace rejection branch).
|
|
360
|
+
assert.ok(!names.has(overCapName), "over-cap decoded name must be skipped");
|
|
361
|
+
assert.ok(!names.has(overCapToken), "over-cap raw token must be skipped");
|
|
362
|
+
// A raw plain-named dir is used verbatim.
|
|
363
|
+
assert.ok(names.has("blend"), "plain namespace dir must be enumerated verbatim");
|
|
364
|
+
// The literal ns-default token decodes to the EMPTY default identity and
|
|
365
|
+
// must map to the configured default namespace, not be skipped as "".
|
|
366
|
+
assert.ok(names.has("generalist"), "ns-default dir maps to the configured default namespace");
|
|
367
|
+
} finally {
|
|
368
|
+
await rm(root, { recursive: true, force: true });
|
|
369
|
+
}
|
|
370
|
+
});
|
|
@@ -12,6 +12,8 @@ import { isErrnoCode } from "./utils/errno.js";
|
|
|
12
12
|
import { assertPathInsideRoot, pathIsInside } from "./utils/path-containment.js";
|
|
13
13
|
import { displayErrorDetail } from "./runtime/better-sqlite.js";
|
|
14
14
|
import { getConfiguredNamespaces } from "./scopes/scope-plan.js";
|
|
15
|
+
import { isSafeRouteNamespace } from "./routing/engine.js";
|
|
16
|
+
import { namespaceIdentityFromToken } from "./namespaces/identity.js";
|
|
15
17
|
import type { PluginConfig } from "./types.js";
|
|
16
18
|
|
|
17
19
|
type PendingImpressionDrain = () => Promise<{ pendingDeferred: boolean }>;
|
|
@@ -89,7 +91,17 @@ export async function getOfflineSyncStorage<T extends PendingLifecycleDrain & {
|
|
|
89
91
|
if (path.resolve(storage.dir) === memoryRoot && orchestrator.listOfflineSyncNamespaces) {
|
|
90
92
|
const drained = new Set<string>([memoryRoot]);
|
|
91
93
|
for (const ns of await orchestrator.listOfflineSyncNamespaces()) {
|
|
92
|
-
|
|
94
|
+
// A single unresolvable namespace (e.g. a legacy canonical token dir whose
|
|
95
|
+
// name exceeds the route-namespace length cap) must not abort the whole root
|
|
96
|
+
// snapshot — degrade to draining the rest. listOfflineSyncNamespaces already
|
|
97
|
+
// normalizes token dirs, so this is defense-in-depth.
|
|
98
|
+
let nsStorage: T;
|
|
99
|
+
try {
|
|
100
|
+
nsStorage = await orchestrator.getStorage(ns);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
log.debug(`offline-sync root drain: skipping namespace ${ns} (non-fatal): ${err}`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
93
105
|
const nsDir = path.resolve(nsStorage.dir);
|
|
94
106
|
if (drained.has(nsDir)) continue;
|
|
95
107
|
drained.add(nsDir);
|
|
@@ -114,8 +126,28 @@ export async function listOfflineSyncNamespaces(host: OfflineSyncNamespaceHost):
|
|
|
114
126
|
try {
|
|
115
127
|
for (const ledgerPath of await offlineSyncLifecycleLedgerPaths(host.config.memoryDir)) {
|
|
116
128
|
const namespaceDir = path.dirname(path.dirname(ledgerPath));
|
|
117
|
-
if (path.basename(path.dirname(namespaceDir))
|
|
118
|
-
|
|
129
|
+
if (path.basename(path.dirname(namespaceDir)) !== "namespaces") continue;
|
|
130
|
+
// A namespace is stored under its canonical token dir `ns-<hex>`. Decode the
|
|
131
|
+
// token back to the namespace NAME so the drain fanout resolves the SAME
|
|
132
|
+
// storage the token dir belongs to (the catalog rebuild scanner decodes the
|
|
133
|
+
// same way, catalog.ts finishRebuild). A raw dir already named as a plain
|
|
134
|
+
// namespace is used verbatim. Skip any name the router would reject — e.g. a
|
|
135
|
+
// canonical token dir whose decoded-or-raw name exceeds isSafeRouteNamespace's
|
|
136
|
+
// 64-char cap — because getStorage() would throw `unsafe namespace` and 500
|
|
137
|
+
// the whole root snapshot-stream (the offline-sync regression this fixes).
|
|
138
|
+
const dirName = path.basename(namespaceDir);
|
|
139
|
+
// namespaceIdentityFromToken returns "" for the ns-default token (the
|
|
140
|
+
// default-namespace identity) — map that to the configured default
|
|
141
|
+
// namespace instead of skipping the directory as an unsafe empty name.
|
|
142
|
+
const decoded = namespaceIdentityFromToken(dirName);
|
|
143
|
+
const name =
|
|
144
|
+
decoded === null
|
|
145
|
+
? dirName
|
|
146
|
+
: decoded.length > 0
|
|
147
|
+
? decoded
|
|
148
|
+
: host.config.defaultNamespace;
|
|
149
|
+
if (isSafeRouteNamespace(name)) {
|
|
150
|
+
names.add(name);
|
|
119
151
|
}
|
|
120
152
|
}
|
|
121
153
|
} catch {
|
|
@@ -18,10 +18,22 @@ import test from "node:test";
|
|
|
18
18
|
import assert from "node:assert/strict";
|
|
19
19
|
import os from "node:os";
|
|
20
20
|
import path from "node:path";
|
|
21
|
+
import { readProjectionRebuiltAt } from "../maintenance/projection-support.js";
|
|
21
22
|
import { lstat, mkdir, mkdtemp, readdir, readFile, rm, stat, symlink, writeFile } from "node:fs/promises";
|
|
22
23
|
import { execFileSync } from "node:child_process";
|
|
23
24
|
|
|
24
25
|
import { MaintenanceScheduler } from "./maintenance.js";
|
|
26
|
+
import {
|
|
27
|
+
createProjectionRebuildScheduleState,
|
|
28
|
+
maybeRebuildMemoryProjectionScheduled,
|
|
29
|
+
type ProjectionRebuildScheduleState,
|
|
30
|
+
} from "./projection-rebuild-schedule.js";
|
|
31
|
+
import { rebuildMemoryProjection } from "../maintenance/rebuild-memory-projection.js";
|
|
32
|
+
import {
|
|
33
|
+
probeProjectionHealth,
|
|
34
|
+
readProjectedMemoryBrowse,
|
|
35
|
+
|
|
36
|
+
} from "../memory-projection-store.js";
|
|
25
37
|
import type { PluginConfig } from "../types.js";
|
|
26
38
|
import type { SearchBackend } from "../search/port.js";
|
|
27
39
|
import type {
|
|
@@ -2836,3 +2848,107 @@ test("catalog-disabled fallback pending drain skips unsafe spill entries (symlin
|
|
|
2836
2848
|
await rm(outside, { recursive: true, force: true });
|
|
2837
2849
|
}
|
|
2838
2850
|
});
|
|
2851
|
+
|
|
2852
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
2853
|
+
// Memory-projection scheduled rebuild (issue #2119)
|
|
2854
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
2855
|
+
|
|
2856
|
+
|
|
2857
|
+
async function seedMemoryDirWithOneFact(): Promise<string> {
|
|
2858
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-projrebuild-"));
|
|
2859
|
+
await mkdir(path.join(memoryDir, "facts", "2026-07-01"), { recursive: true });
|
|
2860
|
+
await writeFile(
|
|
2861
|
+
path.join(memoryDir, "facts", "2026-07-01", "fact-1.md"),
|
|
2862
|
+
`---
|
|
2863
|
+
id: fact-1
|
|
2864
|
+
category: fact
|
|
2865
|
+
created: 2026-07-01T00:00:00.000Z
|
|
2866
|
+
updated: 2026-07-01T01:00:00.000Z
|
|
2867
|
+
source: test
|
|
2868
|
+
confidence: 0.8
|
|
2869
|
+
confidenceTier: implied
|
|
2870
|
+
tags: ["alpha"]
|
|
2871
|
+
---
|
|
2872
|
+
|
|
2873
|
+
alpha
|
|
2874
|
+
`,
|
|
2875
|
+
"utf-8",
|
|
2876
|
+
);
|
|
2877
|
+
return memoryDir;
|
|
2878
|
+
}
|
|
2879
|
+
/** Build the pure-function state the extracted scheduled rebuild owns. */
|
|
2880
|
+
function projectionScheduleState(): ProjectionRebuildScheduleState {
|
|
2881
|
+
return createProjectionRebuildScheduleState();
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
test("scheduled projection rebuild runs when enabled and writes a populated projection (#2119)", async () => {
|
|
2885
|
+
const memoryDir = await seedMemoryDirWithOneFact();
|
|
2886
|
+
try {
|
|
2887
|
+
// No projection built yet — the exact issue-#2119 cold state where timeline
|
|
2888
|
+
// consumers would fall back to full-corpus scans.
|
|
2889
|
+
assert.equal(probeProjectionHealth(memoryDir).state, "absent");
|
|
2890
|
+
await maybeRebuildMemoryProjectionScheduled({
|
|
2891
|
+
config: fixtureConfig({
|
|
2892
|
+
memoryDir,
|
|
2893
|
+
projectionRebuildEnabled: true,
|
|
2894
|
+
projectionRebuildIntervalMs: 6 * 60 * 60 * 1000,
|
|
2895
|
+
}),
|
|
2896
|
+
state: projectionScheduleState(),
|
|
2897
|
+
});
|
|
2898
|
+
// The projection now exists, is openable, and carries the seeded memory.
|
|
2899
|
+
assert.equal(probeProjectionHealth(memoryDir).state, "openable");
|
|
2900
|
+
assert.equal(readProjectedMemoryBrowse(memoryDir, { limit: 5, offset: 0 })?.total, 1);
|
|
2901
|
+
assert.ok(readProjectionRebuiltAt(memoryDir), "rebuiltAt meta must be stamped");
|
|
2902
|
+
} finally {
|
|
2903
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2904
|
+
}
|
|
2905
|
+
});
|
|
2906
|
+
|
|
2907
|
+
test("scheduled projection rebuild is a no-op when disabled by config (#2119)", async () => {
|
|
2908
|
+
const memoryDir = await seedMemoryDirWithOneFact();
|
|
2909
|
+
try {
|
|
2910
|
+
await maybeRebuildMemoryProjectionScheduled({
|
|
2911
|
+
config: fixtureConfig({
|
|
2912
|
+
memoryDir,
|
|
2913
|
+
projectionRebuildEnabled: false,
|
|
2914
|
+
projectionRebuildIntervalMs: 6 * 60 * 60 * 1000,
|
|
2915
|
+
}),
|
|
2916
|
+
state: projectionScheduleState(),
|
|
2917
|
+
});
|
|
2918
|
+
// Gate off: no projection is created, so the CLI/cron path remains the only
|
|
2919
|
+
// way to build it.
|
|
2920
|
+
assert.equal(probeProjectionHealth(memoryDir).state, "absent");
|
|
2921
|
+
} finally {
|
|
2922
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2923
|
+
}
|
|
2924
|
+
});
|
|
2925
|
+
|
|
2926
|
+
test("scheduled projection rebuild skips when the projection was rebuilt within the interval (#2119)", async () => {
|
|
2927
|
+
const memoryDir = await seedMemoryDirWithOneFact();
|
|
2928
|
+
try {
|
|
2929
|
+
// Seed a projection whose rebuiltAt is one minute ago — inside the 6h
|
|
2930
|
+
// interval, so a scheduled pass must NOT rebuild it again.
|
|
2931
|
+
const oneMinuteAgo = new Date(Date.now() - 60_000);
|
|
2932
|
+
await rebuildMemoryProjection({ memoryDir, dryRun: false, now: oneMinuteAgo });
|
|
2933
|
+
const before = readProjectionRebuiltAt(memoryDir);
|
|
2934
|
+
assert.equal(before, oneMinuteAgo.toISOString());
|
|
2935
|
+
|
|
2936
|
+
await maybeRebuildMemoryProjectionScheduled({
|
|
2937
|
+
config: fixtureConfig({
|
|
2938
|
+
memoryDir,
|
|
2939
|
+
projectionRebuildEnabled: true,
|
|
2940
|
+
projectionRebuildIntervalMs: 6 * 60 * 60 * 1000,
|
|
2941
|
+
}),
|
|
2942
|
+
state: projectionScheduleState(),
|
|
2943
|
+
});
|
|
2944
|
+
// Fresh-within-interval: rebuiltAt is unchanged (no rebuild happened) and no
|
|
2945
|
+
// backup was written (a rebuild would have archived the prior projection).
|
|
2946
|
+
assert.equal(readProjectionRebuiltAt(memoryDir), before, "projection must not be rebuilt when fresh");
|
|
2947
|
+
await assert.rejects(
|
|
2948
|
+
() => readdir(path.join(memoryDir, "archive", "memory-projection")),
|
|
2949
|
+
"no backup means no rebuild occurred",
|
|
2950
|
+
);
|
|
2951
|
+
} finally {
|
|
2952
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2953
|
+
}
|
|
2954
|
+
});
|
|
@@ -51,6 +51,10 @@ import {
|
|
|
51
51
|
graphEdgeDecayCadenceToCronExpr,
|
|
52
52
|
} from "../maintenance/memory-governance-cron.js";
|
|
53
53
|
import { rebuildMemoryLifecycleLedger } from "../maintenance/rebuild-memory-lifecycle-ledger.js";
|
|
54
|
+
import {
|
|
55
|
+
createProjectionRebuildScheduleState,
|
|
56
|
+
maybeRebuildMemoryProjectionScheduled,
|
|
57
|
+
} from "./projection-rebuild-schedule.js";
|
|
54
58
|
import {
|
|
55
59
|
drainPendingLifecycleLedgerIfAny,
|
|
56
60
|
pendingLifecycleLedgerDir,
|
|
@@ -134,6 +138,9 @@ export class MaintenanceScheduler {
|
|
|
134
138
|
// fixture.
|
|
135
139
|
private lifecycleLedgerMaxBytes = STATE_FILE_MAX_DECRYPT_BYTES;
|
|
136
140
|
|
|
141
|
+
// ── Memory-projection scheduled-rebuild state (issue #2119) ──
|
|
142
|
+
private readonly projectionRebuild = createProjectionRebuildScheduleState();
|
|
143
|
+
|
|
137
144
|
// ── Activity (screen-capture) sync scheduler (issue #1900) ──
|
|
138
145
|
// The full arm/re-arm/teardown lifecycle lives in ActivitySyncRegistrar.
|
|
139
146
|
private readonly activityRegistrar: ActivitySyncRegistrar;
|
|
@@ -500,6 +507,15 @@ export class MaintenanceScheduler {
|
|
|
500
507
|
void this.maybeCompactMemoryLifecycleLedger().catch((err) =>
|
|
501
508
|
log.debug(`lifecycle ledger auto-compaction check failed (non-fatal): ${err}`),
|
|
502
509
|
);
|
|
510
|
+
// Memory-projection scheduled rebuild (#2119): interval-throttled,
|
|
511
|
+
// single-flighted, never awaited, never throws — see the module docblock.
|
|
512
|
+
void maybeRebuildMemoryProjectionScheduled({
|
|
513
|
+
config: this.deps.config,
|
|
514
|
+
getStorage: this.deps.getStorage,
|
|
515
|
+
state: this.projectionRebuild,
|
|
516
|
+
}).catch((err) =>
|
|
517
|
+
log.debug(`memory projection scheduled rebuild check failed (non-fatal): ${err}`),
|
|
518
|
+
);
|
|
503
519
|
}
|
|
504
520
|
|
|
505
521
|
/** Internal: run a single QMD maintenance pass under singleflight guard. */
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduled rebuild of the root memory projection (issue #2119).
|
|
3
|
+
*
|
|
4
|
+
* The projection (`state/memory-projection.sqlite`) is otherwise rebuild-only
|
|
5
|
+
* (manual CLI / tests), so it goes stale as the lifecycle ledger grows and
|
|
6
|
+
* timeline/browse consumers silently fall back to full-corpus scans. The
|
|
7
|
+
* MaintenanceScheduler fires this alongside ledger compaction on every
|
|
8
|
+
* maintenance request — interval-throttled, single-flighted, never awaited,
|
|
9
|
+
* never throws.
|
|
10
|
+
*
|
|
11
|
+
* `projectionRebuildEnabled` gates it (via the lifecycle capabilities
|
|
12
|
+
* resolver, default true); `projectionRebuildIntervalMs` is the cadence.
|
|
13
|
+
*
|
|
14
|
+
* Skipped when the projection was rebuilt within the interval. The freshness
|
|
15
|
+
* check reads the on-disk `rebuiltAt` meta (not just an in-process timestamp)
|
|
16
|
+
* so a daemon restart or an operator's cron `remnic rebuild-memory-projection`
|
|
17
|
+
* both suppress a redundant rebuild. On a real rebuild it logs the lag it just
|
|
18
|
+
* cured (age of the projection before the rebuild) plus the fresh row counts,
|
|
19
|
+
* so operators can see the projection was drifting. Failures are non-fatal
|
|
20
|
+
* and leave the throttle un-advanced to retry.
|
|
21
|
+
*
|
|
22
|
+
* Scope: the root memoryDir projection — the one `getMemoryTimeline` /
|
|
23
|
+
* browse / current-state serve from `baseDir`. Per-namespace projections are
|
|
24
|
+
* not yet scheduled here (they are still served by their own rebuild paths);
|
|
25
|
+
* the fallback WARN surfaces staleness for any namespace that lags.
|
|
26
|
+
*/
|
|
27
|
+
import { log } from "../logger.js";
|
|
28
|
+
import type { PluginConfig } from "../types.js";
|
|
29
|
+
import { resolveMemoryLifecycleCapabilities } from "../capabilities.js";
|
|
30
|
+
import {
|
|
31
|
+
rebuildMemoryProjection,
|
|
32
|
+
type RebuildMemoryProjectionOptions,
|
|
33
|
+
} from "../maintenance/rebuild-memory-projection.js";
|
|
34
|
+
import { readProjectionRebuiltAt } from "../maintenance/projection-support.js";
|
|
35
|
+
|
|
36
|
+
type InjectedStorage = NonNullable<RebuildMemoryProjectionOptions["storage"]>;
|
|
37
|
+
|
|
38
|
+
export interface ProjectionRebuildScheduleState {
|
|
39
|
+
inFlight: boolean;
|
|
40
|
+
lastRebuildAtMs: number;
|
|
41
|
+
/** Timestamp of the most recent FAILURE; gates a short retry backoff that is
|
|
42
|
+
* separate from the success interval so a transient cause (e.g. a locked
|
|
43
|
+
* secure store) retries promptly once cleared instead of waiting a full
|
|
44
|
+
* interval. */
|
|
45
|
+
lastFailureAtMs: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Bounded retry cadence after a failure — short enough to self-heal promptly
|
|
49
|
+
* once a transient cause clears, long enough to keep a persistently-failing
|
|
50
|
+
* rebuild off every maintenance tick. */
|
|
51
|
+
const FAILURE_RETRY_BACKOFF_MS = 5 * 60 * 1000;
|
|
52
|
+
|
|
53
|
+
export function createProjectionRebuildScheduleState(): ProjectionRebuildScheduleState {
|
|
54
|
+
return { inFlight: false, lastRebuildAtMs: 0, lastFailureAtMs: 0 };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function maybeRebuildMemoryProjectionScheduled(opts: {
|
|
58
|
+
config: PluginConfig;
|
|
59
|
+
/** The daemon's live root storage, when available (secure-store unlock). */
|
|
60
|
+
getStorage?: () => InjectedStorage | undefined;
|
|
61
|
+
state: ProjectionRebuildScheduleState;
|
|
62
|
+
}): Promise<void> {
|
|
63
|
+
const { config, state } = opts;
|
|
64
|
+
if (!resolveMemoryLifecycleCapabilities(config).projectionRebuild) return;
|
|
65
|
+
if (state.inFlight) return;
|
|
66
|
+
const intervalMs = config.projectionRebuildIntervalMs;
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
if (now - state.lastRebuildAtMs < intervalMs) return;
|
|
69
|
+
if (now - state.lastFailureAtMs < FAILURE_RETRY_BACKOFF_MS) return;
|
|
70
|
+
|
|
71
|
+
const rootStorage = opts.getStorage?.();
|
|
72
|
+
const memoryDir = rootStorage ? rootStorage.dir : config.memoryDir;
|
|
73
|
+
|
|
74
|
+
// On-disk freshness check: survives restarts and cross-process CLI rebuilds.
|
|
75
|
+
const rebuiltAt = readProjectionRebuiltAt(memoryDir);
|
|
76
|
+
const rebuiltAtMs = rebuiltAt ? Date.parse(rebuiltAt) : Number.NaN;
|
|
77
|
+
if (Number.isFinite(rebuiltAtMs) && now - rebuiltAtMs < intervalMs) {
|
|
78
|
+
// Fresh already — adopt its timestamp so the next pass short-circuits on
|
|
79
|
+
// the cheap in-process guard without re-opening the projection each time.
|
|
80
|
+
state.lastRebuildAtMs = rebuiltAtMs;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
state.inFlight = true;
|
|
85
|
+
try {
|
|
86
|
+
const result = await rebuildMemoryProjection({
|
|
87
|
+
memoryDir,
|
|
88
|
+
// Reuse the daemon's live storage so secure-store deployments rebuild
|
|
89
|
+
// through the unlocked instance instead of a fresh locked one.
|
|
90
|
+
...(rootStorage ? { storage: rootStorage } : {}),
|
|
91
|
+
defaultNamespace: config.defaultNamespace,
|
|
92
|
+
dryRun: false,
|
|
93
|
+
now: new Date(now),
|
|
94
|
+
});
|
|
95
|
+
state.lastRebuildAtMs = Date.now();
|
|
96
|
+
const lag = Number.isFinite(rebuiltAtMs)
|
|
97
|
+
? `${Math.max(0, Math.round((now - rebuiltAtMs) / 60_000))}m stale`
|
|
98
|
+
: "never previously built";
|
|
99
|
+
log.info(
|
|
100
|
+
`memory projection rebuilt (scheduled) for ${memoryDir}: was ${lag}; `
|
|
101
|
+
+ `${result.currentRows} current, ${result.timelineRows} timeline, `
|
|
102
|
+
+ `${result.entityMentionRows} entity-mention rows`,
|
|
103
|
+
);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
// Non-fatal. Arm a SHORT backoff (not the full success interval) so a
|
|
106
|
+
// transient cause (locked secure store, momentarily-unavailable storage)
|
|
107
|
+
// retries promptly once it clears, while a persistently-failing rebuild
|
|
108
|
+
// still stays off every maintenance tick. lastRebuildAtMs is left as-is so
|
|
109
|
+
// the success interval is not falsely advanced.
|
|
110
|
+
state.lastFailureAtMs = Date.now();
|
|
111
|
+
log.warn(`memory projection scheduled rebuild failed (non-fatal) for ${memoryDir}: ${err}`);
|
|
112
|
+
} finally {
|
|
113
|
+
state.inFlight = false;
|
|
114
|
+
}
|
|
115
|
+
}
|
package/src/storage-guards.ts
CHANGED
|
@@ -18,13 +18,30 @@ export function assertMemoryFrontmatterId(
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export function warnProjectionFallback(
|
|
21
|
+
export function warnProjectionFallback(
|
|
22
|
+
memoryDir: string,
|
|
23
|
+
consumer: string,
|
|
24
|
+
// Lazy so the (potentially I/O-bound) lag computation runs only when this
|
|
25
|
+
// call actually logs, not on every rate-limited-suppressed fallback (#2119).
|
|
26
|
+
detail?: () => string | undefined,
|
|
27
|
+
): null {
|
|
22
28
|
const key = `${memoryDir}\0${consumer}`;
|
|
23
29
|
const now = Date.now();
|
|
24
30
|
const warnedAt = projectionFallbackWarnedAt.get(key) ?? 0;
|
|
25
31
|
if (now - warnedAt >= PROJECTION_FALLBACK_WARN_INTERVAL_MS) {
|
|
26
32
|
projectionFallbackWarnedAt.set(key, now);
|
|
27
|
-
|
|
33
|
+
const suffix = detail?.();
|
|
34
|
+
log.warn(
|
|
35
|
+
`storage.${consumer}: memory projection absent or empty; falling back to full corpus`
|
|
36
|
+
+ (suffix ? ` (${suffix})` : ""),
|
|
37
|
+
);
|
|
28
38
|
}
|
|
29
39
|
return null;
|
|
30
40
|
}
|
|
41
|
+
|
|
42
|
+
/** @internal Test seam (#2119): clear the fallback-warn rate-limit dedup map so
|
|
43
|
+
* a focused test can prove the once-per-interval spam suppression from a clean
|
|
44
|
+
* slate regardless of prior tests in the same process. */
|
|
45
|
+
export function __resetProjectionFallbackWarnSuppressionForTest(): void {
|
|
46
|
+
projectionFallbackWarnedAt.clear();
|
|
47
|
+
}
|
|
@@ -6,6 +6,8 @@ import test from "node:test";
|
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
|
|
8
8
|
import { StorageManager } from "./storage.js";
|
|
9
|
+
import { initLogger, resetLogger } from "./logger.js";
|
|
10
|
+
import { __resetProjectionFallbackWarnSuppressionForTest } from "./storage-guards.js";
|
|
9
11
|
import { encryptFileBody, filePathAad, isEncryptedFile, readMaybeEncryptedFile, readMaybeEncryptedFileBuffer } from "./secure-store/secure-fs.js";
|
|
10
12
|
import type { MemoryLifecycleEvent } from "./types.js";
|
|
11
13
|
import {
|
|
@@ -1271,3 +1273,33 @@ test("compaction buffer reader skips an oversized unterminated row instead of th
|
|
|
1271
1273
|
"valid rows returned; the oversized row is skipped without throwing",
|
|
1272
1274
|
);
|
|
1273
1275
|
});
|
|
1276
|
+
|
|
1277
|
+
test("getMemoryTimeline fallback WARN is loud with lag and rate-limited when the projection is missing (#2119)", async () => {
|
|
1278
|
+
const warns: string[] = [];
|
|
1279
|
+
initLogger({ info() {}, warn: (m) => warns.push(m), error() {}, debug() {} }, false);
|
|
1280
|
+
__resetProjectionFallbackWarnSuppressionForTest();
|
|
1281
|
+
try {
|
|
1282
|
+
const rows = [
|
|
1283
|
+
JSON.stringify(lifecycleEvent("t1", "memory-target", "2026-01-01T00:01:00.000Z")),
|
|
1284
|
+
JSON.stringify(lifecycleEvent("t2", "memory-target", "2026-01-01T00:02:00.000Z")),
|
|
1285
|
+
];
|
|
1286
|
+
await withLifecycleLedger(rows, async (storage) => {
|
|
1287
|
+
// No projection sqlite exists in this temp dir, so both calls take the
|
|
1288
|
+
// ledger fallback — the exact issue-#2119 regression path.
|
|
1289
|
+
const first = await storage.getMemoryTimeline("memory-target", 5);
|
|
1290
|
+
const second = await storage.getMemoryTimeline("memory-target", 5);
|
|
1291
|
+
assert.equal(first.length, 2);
|
|
1292
|
+
assert.equal(second.length, 2);
|
|
1293
|
+
|
|
1294
|
+
const timelineWarns = warns.filter((w) => w.includes("storage.getMemoryTimeline"));
|
|
1295
|
+
// Rate-limited: two fallbacks, but only ONE WARN in the interval (no spam).
|
|
1296
|
+
assert.equal(timelineWarns.length, 1, "fallback WARN must fire once, not per call");
|
|
1297
|
+
// Loud: the single WARN names the fallback AND the projection lag.
|
|
1298
|
+
assert.match(timelineWarns[0]!, /falling back to full corpus/);
|
|
1299
|
+
assert.match(timelineWarns[0]!, /projection never rebuilt/);
|
|
1300
|
+
});
|
|
1301
|
+
} finally {
|
|
1302
|
+
resetLogger();
|
|
1303
|
+
__resetProjectionFallbackWarnSuppressionForTest();
|
|
1304
|
+
}
|
|
1305
|
+
});
|
package/src/storage.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readProjectionRebuiltAt } from "./maintenance/projection-support.js";
|
|
1
2
|
import {
|
|
2
3
|
access,
|
|
3
4
|
lstat,
|
|
@@ -6584,7 +6585,20 @@ export class StorageManager {
|
|
|
6584
6585
|
|
|
6585
6586
|
const projected = readProjectedMemoryTimeline(this.baseDir, memoryId, cappedLimit);
|
|
6586
6587
|
if (projected && projected.length > 0) return projected;
|
|
6587
|
-
|
|
6588
|
+
// Loud, rate-limited staleness telemetry (#2119): a fallback here means the
|
|
6589
|
+
// projection is missing/empty/stale, so surface HOW stale it is. The lag is
|
|
6590
|
+
// computed lazily — only when warnProjectionFallback actually logs (once per
|
|
6591
|
+
// interval) — so a hot fallback path pays for a projection meta read at most
|
|
6592
|
+
// once per suppression window, not per call.
|
|
6593
|
+
warnProjectionFallback(this.baseDir, "getMemoryTimeline", () => {
|
|
6594
|
+
const rebuiltAt = readProjectionRebuiltAt(this.baseDir);
|
|
6595
|
+
if (!rebuiltAt) return "projection never rebuilt";
|
|
6596
|
+
const ageMs = Date.now() - Date.parse(rebuiltAt);
|
|
6597
|
+
if (!Number.isFinite(ageMs)) return `projection rebuiltAt=${rebuiltAt}`;
|
|
6598
|
+
const ageMin = Math.max(0, Math.round(ageMs / 60_000));
|
|
6599
|
+
const age = ageMin >= 120 ? `${Math.round(ageMin / 60)}h` : `${ageMin}m`;
|
|
6600
|
+
return `projection ${age} stale, last rebuilt ${rebuiltAt}`;
|
|
6601
|
+
});
|
|
6588
6602
|
return readBoundedLifecycleEventsFromLedger(
|
|
6589
6603
|
this.memoryLifecycleLedgerPath, (p) => this.readStorageSecureFile(p), cappedLimit, memoryId);
|
|
6590
6604
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory-projection rebuild subject for the scenario-matrix harness
|
|
3
|
+
* (issue #2119). Each canonical row runs the REAL rebuild path — memory scan,
|
|
4
|
+
* sqlite build, atomic install, rebuiltAt meta — plus the scheduled-rebuild
|
|
5
|
+
* wrapper's freshness short-circuit. The matrix dimensions are orthogonal to
|
|
6
|
+
* this rebuild-only subsystem, so the same durable projection invariant
|
|
7
|
+
* applies to every row.
|
|
8
|
+
*/
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import os from "node:os";
|
|
14
|
+
import { rebuildMemoryProjection } from "../../maintenance/rebuild-memory-projection.js";
|
|
15
|
+
import { readProjectionRebuiltAt } from "../../maintenance/projection-support.js";
|
|
16
|
+
import {
|
|
17
|
+
type LifecycleSubject,
|
|
18
|
+
type MatrixRow,
|
|
19
|
+
runLifecycleMatrix,
|
|
20
|
+
} from "../lifecycle-matrix.js";
|
|
21
|
+
|
|
22
|
+
interface MemoryProjectionState {
|
|
23
|
+
memoryDir: string;
|
|
24
|
+
memoryId: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function memoryFixture(row: MatrixRow, memoryId: string): string {
|
|
28
|
+
return [
|
|
29
|
+
"---",
|
|
30
|
+
`id: ${memoryId}`,
|
|
31
|
+
"category: fact",
|
|
32
|
+
"confidence: 0.9",
|
|
33
|
+
"created: 2026-01-01T00:00:00.000Z",
|
|
34
|
+
"updated: 2026-01-01T00:00:00.000Z",
|
|
35
|
+
"status: active",
|
|
36
|
+
"---",
|
|
37
|
+
"",
|
|
38
|
+
`Matrix fixture memory for lifecycle row ${row.id}.`,
|
|
39
|
+
"",
|
|
40
|
+
].join("\n");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const subject: LifecycleSubject<MemoryProjectionState> = {
|
|
44
|
+
async setup(row): Promise<MemoryProjectionState> {
|
|
45
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-projection-subject-"));
|
|
46
|
+
const memoryId = `matrix-projection-${row.id}`;
|
|
47
|
+
const factDir = path.join(memoryDir, "facts", "2026-01-01");
|
|
48
|
+
await mkdir(factDir, { recursive: true });
|
|
49
|
+
await writeFile(path.join(factDir, `${memoryId}.md`), memoryFixture(row, memoryId), "utf8");
|
|
50
|
+
return { memoryDir, memoryId };
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async exercise(state): Promise<void> {
|
|
54
|
+
const result = await rebuildMemoryProjection({
|
|
55
|
+
memoryDir: state.memoryDir,
|
|
56
|
+
dryRun: false,
|
|
57
|
+
now: new Date("2026-01-01T12:00:00.000Z"),
|
|
58
|
+
});
|
|
59
|
+
assert.equal(result.dryRun, false);
|
|
60
|
+
assert.equal(result.scannedMemories, 1);
|
|
61
|
+
assert.equal(result.currentRows, 1);
|
|
62
|
+
assert.ok(existsSync(result.outputPath), "projection sqlite installed");
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async invariants(state): Promise<void> {
|
|
66
|
+
// The on-disk rebuiltAt meta is the scheduled rebuild's cross-process
|
|
67
|
+
// freshness signal (daemon restart / operator CLI both rely on it).
|
|
68
|
+
const rebuiltAt = readProjectionRebuiltAt(state.memoryDir);
|
|
69
|
+
assert.ok(rebuiltAt, "rebuiltAt meta written by the rebuild");
|
|
70
|
+
assert.ok(Number.isFinite(Date.parse(rebuiltAt)), "rebuiltAt parses as a timestamp");
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
async teardown(state): Promise<void> {
|
|
74
|
+
await rm(state.memoryDir, { recursive: true, force: true });
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
runLifecycleMatrix("memory-projection", subject);
|
package/src/types.ts
CHANGED
|
@@ -2121,6 +2121,24 @@ export interface PluginConfig extends BoundedJsonlStateConfig {
|
|
|
2121
2121
|
/** Confidence threshold for the "below visibility" telemetry counter. Default 0.2. */
|
|
2122
2122
|
graphEdgeDecayVisibilityThreshold: number;
|
|
2123
2123
|
|
|
2124
|
+
// Issue #2119 — maintenance-scheduled memory-projection rebuild.
|
|
2125
|
+
/**
|
|
2126
|
+
* Enable the maintenance-scheduled rebuild of `state/memory-projection.sqlite`.
|
|
2127
|
+
* The projection is otherwise rebuild-only (manual CLI / tests), so it rots as
|
|
2128
|
+
* the lifecycle ledger grows and timeline/browse consumers silently regress to
|
|
2129
|
+
* full-corpus fallback scans. Default true — mirrors lifecycle-ledger
|
|
2130
|
+
* auto-compaction, which is likewise enabled-by-default to keep a state file
|
|
2131
|
+
* healthy. Set false to disable (operators can still cron the CLI rebuild).
|
|
2132
|
+
*/
|
|
2133
|
+
projectionRebuildEnabled: boolean;
|
|
2134
|
+
/**
|
|
2135
|
+
* Minimum interval between scheduled projection rebuilds. A rebuild is skipped
|
|
2136
|
+
* when the projection's `rebuiltAt` meta is younger than this, so a burst of
|
|
2137
|
+
* maintenance requests cannot fan into back-to-back rebuilds. Floored at 60s.
|
|
2138
|
+
* Default 6h (matches memoryLifecycleLedgerCompactMinIntervalMs).
|
|
2139
|
+
*/
|
|
2140
|
+
projectionRebuildIntervalMs: number;
|
|
2141
|
+
|
|
2124
2142
|
/**
|
|
2125
2143
|
* Issue #681 PR 3/3 — minimum edge confidence to traverse during spreading
|
|
2126
2144
|
* activation. Edges below are pruned (no activation, no downstream neighbors);
|