@remnic/core 9.17.1 → 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 +11 -11
- 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 +76 -76
- 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-KQED7RCV.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-B6P3QDBF.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-52EFQMQC.js → chunk-AKWX5E6R.js} +30 -30
- package/dist/{chunk-WIUYMCPL.js → chunk-ALPBE5EC.js} +15 -15
- package/dist/{chunk-VZC7ORJE.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-7O2C2OOF.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-BGBPDAIN.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-XSBUROEB.js → chunk-UGH6VPPO.js} +2 -2
- 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 +418 -418
- 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 +54 -54
- 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 +11 -11
- 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 +66 -66
- 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/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-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-KQED7RCV.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-B6P3QDBF.js.map → chunk-7KXE7KN5.js.map} +0 -0
- /package/dist/{chunk-52EFQMQC.js.map → chunk-AKWX5E6R.js.map} +0 -0
- /package/dist/{chunk-WIUYMCPL.js.map → chunk-ALPBE5EC.js.map} +0 -0
- /package/dist/{chunk-VZC7ORJE.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-7O2C2OOF.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-BGBPDAIN.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-XSBUROEB.js.map → chunk-UGH6VPPO.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
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/orchestration/maintenance.ts","../src/activity/reindex.ts","../src/activity/store.ts","../src/activity/pipeline.ts","../src/activity/source-client.ts","../src/activity/runner.ts","../src/activity/scheduler.ts","../src/activity/sync-registration.ts"],"sourcesContent":["/**\n * Maintenance scheduler — extracted from the orchestrator (issue #1526 PR1).\n *\n * Owns the WHEN of memory maintenance:\n * - cron auto-registration (writes jobs.json entries the OpenClaw cron\n * daemon picks up)\n * - debounced + singleflight QMD index maintenance (re-index + embed\n * after writes)\n * - consolidation scheduling trigger (count + interval gated)\n *\n * Does NOT own the WHAT — the actual job runners (runSemanticConsolidation,\n * runPatternReinforcement, runDeepSleepGovernanceNow, runConsolidation)\n * remain on the orchestrator because they need the full LLM + extraction\n * dependency surface. The scheduler invokes consolidation through a\n * callback so the orchestrator stays the source of truth for that work.\n *\n * Behavior-preserving move from orchestrator.ts. No logic changes — the\n * orchestrator keeps thin delegating methods so existing tests that stub\n * `orchestrator.requestQmdMaintenance` etc. continue to work.\n */\n\nimport { resolveNamespaceCapabilities,\n resolveQmdCapabilities,resolveConsolidationCapabilities, resolveRecallAuxiliaryCapabilities } from \"../capabilities.js\";\nimport { existsSync, type Dirent } from \"node:fs\";\nimport { appendFile, lstat, mkdir, readFile, readdir, realpath, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { PluginConfig } from \"../types.js\";\nimport type { SearchBackend } from \"../search/port.js\";\nimport type { StorageManager } from \"../index.js\";\nimport {\n NamespaceSearchRouter,\n type NamespaceUpdateResult,\n} from \"../namespaces/search.js\";\nimport type { NamespaceCatalog, NamespaceRecord } from \"../namespaces/catalog.js\";\nimport { namespaceIdentityFromToken } from \"../namespaces/identity.js\";\nimport { isSafeRouteNamespace } from \"../routing/engine.js\";\nimport {\n planNamespaceMaintenance,\n runNamespaceMaintenanceBatchPlan,\n type NamespaceMaintenancePlan,\n type NamespaceMaintenanceSkipReason,\n} from \"../maintenance/namespace-planner.js\";\nimport {\n ensureContradictionScanCron,\n ensureDaySummaryCron,\n ensureGraphEdgeDecayCron,\n ensureNightlyGovernanceCron,\n ensurePatternReinforcementCron,\n ensureProceduralMiningCron,\n graphEdgeDecayCadenceToCronExpr,\n} from \"../maintenance/memory-governance-cron.js\";\nimport { rebuildMemoryLifecycleLedger } from \"../maintenance/rebuild-memory-lifecycle-ledger.js\";\nimport {\n drainPendingLifecycleLedgerIfAny,\n pendingLifecycleLedgerDir,\n type LifecyclePendingIo,\n} from \"../storage/memory-lifecycle-ledger-access.js\";\nimport { STATE_FILE_MAX_DECRYPT_BYTES } from \"../storage/secure-line-reader.js\";\nimport { ActivitySyncRegistrar } from \"../activity/sync-registration.js\";\nimport { resolveHomeDir } from \"../runtime/env.js\";\nimport { log } from \"../logger.js\";\nimport { isErrnoCode } from \"../utils/errno.js\";\nimport { assertPathInsideRoot, listContainedSpillFiles } from \"../utils/path-containment.js\";\nimport {\n probeEncryptedRegularFileHeader,\n SECURE_STORE_ENVELOPE_OVERHEAD_BYTES,\n} from \"../secure-store/secure-fs.js\";\n\n/** Reason a QMD maintenance pass was skipped, used for status recording. */\nfunction qmdMaintenanceSkipReasonForError(\n error: unknown,\n): NamespaceMaintenanceSkipReason | null {\n const message = error instanceof Error ? error.message : String(error);\n return /^QMD (?:update|embed) skipped by .*min-interval gate$/.test(message)\n ? \"throttled\"\n : null;\n}\n\n/** Dependencies injected by the orchestrator. All stable references. */\nexport interface MaintenanceSchedulerDeps {\n config: PluginConfig;\n /** Live accessor — the orchestrator can reassign its qmd backend after\n * construction (e.g. swap to NoopSearchBackend when the collection is\n * missing), and the scheduler must always observe the current backend. */\n getQmd: () => SearchBackend;\n namespaceSearchRouter: NamespaceSearchRouter;\n namespaceCatalog: NamespaceCatalog;\n /**\n * Root storage (secure-store configured). Threaded so lifecycle-ledger\n * auto-compaction (#1910) rebuilds through the live secure context: encrypted\n * memories stay decryptable and the rewritten ledger stays encrypted at rest.\n * Omitted in focused tests, where compaction falls back to a fresh plaintext\n * StorageManager (the CLI-equivalent path).\n */\n getStorage?: () => StorageManager;\n /**\n * Resolve a namespace's (secure-store aware) storage. Threaded so\n * auto-compaction also bounds per-namespace lifecycle ledgers under\n * `memoryDir/namespaces/<token>/state/`, not just the root state path\n * (#1910). Consulted only when namespaces are enabled.\n */\n storageForNamespace?: (namespace: string) => Promise<StorageManager>;\n}\n\n/**\n * Schedules and debounces memory maintenance work. Owns the cadence +\n * singleflight state that previously lived as private orchestrator fields.\n *\n * The orchestrator constructs one instance in its constructor and delegates\n * the public scheduling entrypoints to it. Job execution stays on the\n * orchestrator (LLM-heavy) and is invoked through callbacks.\n */\nexport class MaintenanceScheduler {\n // ── Consolidation scheduling state ──\n private nonZeroExtractionsSinceConsolidation = 0;\n private lastConsolidationRunAtMs = 0;\n private consolidationInFlight = false;\n\n // ── QMD maintenance scheduling state ──\n private qmdMaintenanceTimer: NodeJS.Timeout | null = null;\n private qmdMaintenancePending = false;\n private qmdMaintenanceInFlight = false;\n private lastQmdEmbedAtMs = 0;\n private lastQmdEmbedAtMsByNamespace = new Map<string, number>();\n\n // ── Lifecycle-ledger auto-compaction state (issue #1910) ──\n private lifecycleCompactionInFlight = false;\n private lastLifecycleCompactionAtMs = 0;\n // Upper bound on a rewritten ledger; a preserving compaction is bounded to\n // this so it can never leave the ledger over the whole-file read/decrypt cap\n // (#2033). Defaults to the decrypt cap; overridable in focused tests to drive\n // the bounding + post-write verification deterministically without a 400MB\n // fixture.\n private lifecycleLedgerMaxBytes = STATE_FILE_MAX_DECRYPT_BYTES;\n\n // ── Activity (screen-capture) sync scheduler (issue #1900) ──\n // The full arm/re-arm/teardown lifecycle lives in ActivitySyncRegistrar.\n private readonly activityRegistrar: ActivitySyncRegistrar;\n\n constructor(private readonly deps: MaintenanceSchedulerDeps) {\n this.activityRegistrar = new ActivitySyncRegistrar({\n config: deps.config.activity,\n memoryDir: deps.config.memoryDir,\n qmdCollection: deps.config.qmdCollection,\n secureStoreEnabled: resolveRecallAuxiliaryCapabilities(deps.config).secureStore,\n getQmd: () => deps.getQmd(),\n requestReindexRetry: () => this.requestQmdMaintenanceForTool(\"activity-reindex-retry\"),\n });\n }\n\n // ───────────────────────────────────────────────────────────────────────\n // Cron auto-registration\n // ───────────────────────────────────────────────────────────────────────\n\n /**\n * Register every cron job the daemon should run, gated by its config flag.\n * Each registration is independent and non-fatal — a failure in one does\n * not block the others (mirrors the orchestrator's prior sequential try/\n * catch behavior exactly).\n *\n * Returns when all eligible registrations have completed so callers that\n * `await deferredReady` can rely on jobs.json being current.\n */\n async autoRegisterCrons(signal: AbortSignal): Promise<void> {\n if (resolveRecallAuxiliaryCapabilities(this.deps.config).daySummary) {\n try {\n await this.autoRegisterDaySummaryCron();\n } catch (err) {\n log.debug(`day-summary cron auto-register failed (non-fatal): ${err}`);\n }\n }\n if (this.deps.config.nightlyGovernanceCronAutoRegister) {\n try {\n await this.autoRegisterNightlyGovernanceCron();\n } catch (err) {\n log.debug(`nightly governance cron auto-register failed (non-fatal): ${err}`);\n }\n }\n if (this.deps.config.procedural?.proceduralMiningCronAutoRegister) {\n try {\n await this.autoRegisterProceduralMiningCron();\n } catch (err) {\n log.debug(`procedural mining cron auto-register failed (non-fatal): ${err}`);\n }\n }\n if (this.deps.config.contradictionScan?.enabled) {\n try {\n await this.autoRegisterContradictionScanCron();\n } catch (err) {\n log.debug(`contradiction scan cron auto-register failed (non-fatal): ${err}`);\n }\n }\n if (resolveConsolidationCapabilities(this.deps.config).patternReinforcement) {\n try {\n await this.autoRegisterPatternReinforcementCron();\n } catch (err) {\n log.debug(`pattern reinforcement cron auto-register failed (non-fatal): ${err}`);\n }\n }\n if (resolveConsolidationCapabilities(this.deps.config).graphEdgeDecay) {\n try {\n await this.autoRegisterGraphEdgeDecayCron();\n } catch (err) {\n log.debug(`graph edge decay cron auto-register failed (non-fatal): ${err}`);\n }\n }\n\n // Activity (screen-capture) in-process sync scheduler (issue #1900),\n // master default-off. The registrar owns the arm/re-arm/teardown contract\n // (stop-prior, abort/teardown guards, secure-store refusal, retry latch).\n await this.activityRegistrar.register(signal);\n }\n\n async autoRegisterDaySummaryCron(): Promise<void> {\n const home = resolveHomeDir();\n const jobsPath = path.join(home, \".openclaw\", \"cron\", \"jobs.json\");\n\n try {\n if (!existsSync(jobsPath)) {\n log.debug(\"day-summary cron: jobs.json not found, skipping auto-register\");\n return;\n }\n\n // Resolve an OpenClaw cron-routing model only in gateway mode. In plugin\n // mode, summaryModel is a direct-client model id for Remnic's own LLM\n // calls and may be unroutable as an OpenClaw agentTurn model.\n const rawSummaryModel = this.deps.config.summaryModel;\n const taskPrimary = this.deps.config.taskModelChain?.primary;\n const isGateway = this.deps.config.modelSource === \"gateway\";\n const model = isGateway ? (rawSummaryModel || taskPrimary || undefined) : undefined;\n // Attach task-chain fallbacks only when the model matches the task-chain\n // primary. If summaryModel is a distinct override, its fallbacks would\n // be unrelated to the task chain. Also append gateway default models as\n // tail fallbacks (de-duped) so a task-chain outage doesn't stop the cron\n // before reaching the gateway default chain. Mirrors hourly cron pattern.\n const fallbacks: string[] = [];\n if (model && taskPrimary && model === taskPrimary) {\n const seen = new Set<string>(model ? [model] : []);\n const addUnique = (value: string | undefined) => {\n if (typeof value !== \"string\") return;\n const trimmed = value.trim();\n if (trimmed.length > 0 && !seen.has(trimmed)) {\n seen.add(trimmed);\n fallbacks.push(trimmed);\n }\n };\n for (const fb of this.deps.config.taskModelChain?.fallbacks ?? []) addUnique(fb);\n const gwDefaults = this.deps.config.gatewayConfig?.agents?.defaults?.model;\n addUnique(gwDefaults?.primary);\n if (Array.isArray(gwDefaults?.fallbacks)) {\n for (const fb of gwDefaults.fallbacks) addUnique(fb);\n }\n }\n\n const timezone =\n this.deps.config.daySummaryTimezone ||\n Intl.DateTimeFormat().resolvedOptions().timeZone;\n\n const result = await ensureDaySummaryCron(jobsPath, {\n timezone,\n ...(model ? { model } : {}),\n ...(fallbacks.length > 0 ? { fallbacks } : {}),\n });\n if (result.created) {\n log.info(\n `day-summary cron auto-registered (${result.jobId}, 23:47 ${timezone}${model ? `, model: ${model}` : \"\"})`,\n );\n } else if (result.updated) {\n log.info(\n `day-summary cron reconciled (${result.jobId}, timezone: ${timezone}${model ? `, model: ${model}` : \"\"})`,\n );\n } else {\n log.debug(\"day-summary cron already up to date\");\n }\n } catch (err) {\n log.debug(`day-summary cron auto-register error: ${err}`);\n }\n }\n\n async autoRegisterNightlyGovernanceCron(): Promise<void> {\n const home = resolveHomeDir();\n const jobsPath = path.join(home, \".openclaw\", \"cron\", \"jobs.json\");\n\n try {\n if (!existsSync(jobsPath)) {\n log.debug(\"nightly governance cron: jobs.json not found, skipping auto-register\");\n return;\n }\n\n const created = await ensureNightlyGovernanceCron(jobsPath, {\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n });\n if (created.created) {\n log.info(\n `nightly governance cron auto-registered (${created.jobId}, 02:23 ${Intl.DateTimeFormat().resolvedOptions().timeZone})`,\n );\n } else {\n log.debug(\"nightly governance cron already exists, skipping auto-register\");\n }\n } catch (err) {\n log.debug(`nightly governance cron auto-register error: ${err}`);\n }\n }\n\n async autoRegisterProceduralMiningCron(): Promise<void> {\n const home = resolveHomeDir();\n const jobsPath = path.join(home, \".openclaw\", \"cron\", \"jobs.json\");\n try {\n if (!existsSync(jobsPath)) {\n log.debug(\"procedural mining cron: jobs.json not found, skipping auto-register\");\n return;\n }\n const created = await ensureProceduralMiningCron(jobsPath, {\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n });\n if (created.created) {\n log.info(`procedural mining cron auto-registered (${created.jobId})`);\n } else {\n log.debug(\"procedural mining cron already exists, skipping auto-register\");\n }\n } catch (err) {\n log.debug(`procedural mining cron auto-register error: ${err}`);\n }\n }\n\n async autoRegisterContradictionScanCron(): Promise<void> {\n const home = resolveHomeDir();\n const jobsPath = path.join(home, \".openclaw\", \"cron\", \"jobs.json\");\n try {\n if (!existsSync(jobsPath)) {\n log.debug(\"contradiction scan cron: jobs.json not found, skipping auto-register\");\n return;\n }\n const created = await ensureContradictionScanCron(jobsPath, {\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n });\n if (created.created) {\n log.info(`contradiction scan cron auto-registered (${created.jobId})`);\n } else {\n log.debug(\"contradiction scan cron already exists, skipping auto-register\");\n }\n } catch (err) {\n log.debug(`contradiction scan cron auto-register error: ${err}`);\n }\n }\n\n async autoRegisterPatternReinforcementCron(): Promise<void> {\n const home = resolveHomeDir();\n const jobsPath = path.join(home, \".openclaw\", \"cron\", \"jobs.json\");\n try {\n if (!existsSync(jobsPath)) {\n log.debug(\"pattern reinforcement cron: jobs.json not found, skipping auto-register\");\n return;\n }\n const created = await ensurePatternReinforcementCron(jobsPath, {\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n });\n if (created.created) {\n log.info(`pattern reinforcement cron auto-registered (${created.jobId})`);\n } else {\n log.debug(\"pattern reinforcement cron already exists, skipping auto-register\");\n }\n } catch (err) {\n log.debug(`pattern reinforcement cron auto-register error: ${err}`);\n }\n }\n\n async autoRegisterGraphEdgeDecayCron(): Promise<void> {\n const home = resolveHomeDir();\n const jobsPath = path.join(home, \".openclaw\", \"cron\", \"jobs.json\");\n try {\n if (!existsSync(jobsPath)) {\n log.debug(\"graph edge decay cron: jobs.json not found, skipping auto-register\");\n return;\n }\n const scheduleExpr = graphEdgeDecayCadenceToCronExpr(\n this.deps.config.graphEdgeDecayCadenceMs,\n );\n const created = await ensureGraphEdgeDecayCron(jobsPath, {\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n scheduleExpr,\n });\n if (created.created) {\n log.info(`graph edge decay cron auto-registered (${created.jobId}, ${scheduleExpr})`);\n } else {\n log.debug(\"graph edge decay cron already exists, skipping auto-register\");\n }\n } catch (err) {\n log.debug(`graph edge decay cron auto-register error: ${err}`);\n }\n }\n\n // ───────────────────────────────────────────────────────────────────────\n // Consolidation scheduling\n // ───────────────────────────────────────────────────────────────────────\n\n /**\n * Maybe trigger a background consolidation pass after an extraction.\n * Counted + interval gated so a burst of extractions does not fan into a\n * burst of consolidation passes. The consolidation runner itself is\n * passed in as a callback because it lives on the orchestrator (LLM +\n * storage dependency surface) and is not owned by this module.\n */\n maybeScheduleConsolidation(\n nonZeroExtraction: boolean,\n runConsolidation: () => Promise<unknown>,\n ): void {\n if (nonZeroExtraction) this.nonZeroExtractionsSinceConsolidation += 1;\n if (\n this.deps.config.consolidationRequireNonZeroExtraction &&\n !nonZeroExtraction\n ) {\n return;\n }\n if (\n this.nonZeroExtractionsSinceConsolidation <\n this.deps.config.consolidateEveryN\n ) {\n return;\n }\n\n const now = Date.now();\n if (\n now - this.lastConsolidationRunAtMs <\n this.deps.config.consolidationMinIntervalMs\n ) {\n return;\n }\n if (this.consolidationInFlight) return;\n\n this.consolidationInFlight = true;\n this.lastConsolidationRunAtMs = now;\n this.nonZeroExtractionsSinceConsolidation = 0;\n runConsolidation()\n .catch((err) => log.error(\"background consolidation failed\", err))\n .finally(() => {\n this.consolidationInFlight = false;\n });\n }\n\n /** Whether a background consolidation pass is currently running. Exposed so\n * the orchestrator's waitForConsolidationIdle can poll idle state without\n * owning the cadence state (issue #1526 PR1). */\n isConsolidationInFlight(): boolean {\n return this.consolidationInFlight;\n }\n\n /** Whether a debounced QMD maintenance pass is armed and waiting for the\n * debounce timer. Exposed so drain/teardown diagnostics (e.g. the bench\n * adapter) can observe scheduler idle state after the #1526 PR1 extraction\n * moved this flag off the orchestrator. */\n isQmdMaintenancePending(): boolean {\n return this.qmdMaintenancePending;\n }\n\n /** Whether a QMD maintenance pass is currently running (singleflight guard).\n * Exposed for drain/teardown diagnostics (see isQmdMaintenancePending). */\n isQmdMaintenanceInFlight(): boolean {\n return this.qmdMaintenanceInFlight;\n }\n // ───────────────────────────────────────────────────────────────────────\n // QMD maintenance scheduling (debounced + singleflight)\n // ───────────────────────────────────────────────────────────────────────\n\n /** Internal: queue a debounced QMD maintenance pass. */\n private requestQmdMaintenance(): void {\n if (!this.deps.getQmd().isAvailable()) return;\n if (!resolveQmdCapabilities(this.deps.config).qmdMaintenance) return;\n\n this.qmdMaintenancePending = true;\n if (this.qmdMaintenanceTimer) return;\n\n this.qmdMaintenanceTimer = setTimeout(() => {\n this.qmdMaintenanceTimer = null;\n this.runQmdMaintenance().catch((err) =>\n log.debug(`background qmd maintenance failed: ${err}`),\n );\n }, this.deps.config.qmdMaintenanceDebounceMs);\n }\n\n /**\n * Public entrypoint for tool-driven QMD maintenance requests.\n * Routes through existing debounced/singleflight maintenance controls.\n */\n requestQmdMaintenanceForTool(reason: string): void {\n try {\n this.requestQmdMaintenance();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n log.warn(`qmd maintenance request failed (${reason}): ${msg}`);\n }\n // Lifecycle-ledger auto-compaction (#1910) must run independently of QMD.\n // When QMD is unavailable or its maintenance capability is off,\n // requestQmdMaintenance() short-circuits and runQmdMaintenance() never\n // fires — so a compaction check living only in that path would never run.\n // Fire it here on every maintenance request (throttled, size-gated,\n // single-flighted, never awaited, never throws) so a QMD-off deployment\n // still bounds the lifecycle ledger.\n void this.maybeCompactMemoryLifecycleLedger().catch((err) =>\n log.debug(`lifecycle ledger auto-compaction check failed (non-fatal): ${err}`),\n );\n }\n\n /** Internal: run a single QMD maintenance pass under singleflight guard. */\n async runQmdMaintenance(): Promise<void> {\n if (this.qmdMaintenanceInFlight) return;\n if (!this.qmdMaintenancePending) return;\n this.qmdMaintenanceInFlight = true;\n this.qmdMaintenancePending = false;\n\n try {\n if (resolveNamespaceCapabilities(this.deps.config).namespaces) {\n // Include cataloged dynamic namespaces, not just the configured set\n // (NGnei), but run through the namespace-aware maintenance planner so\n // each namespace is budgeted, lock-protected, and status-recorded\n // independently (issue #1500).\n const plan = await this.namespaceMaintenancePlan(\"qmd\");\n const now = Date.now();\n const lastEmbedAtByNamespace = this.lastQmdEmbedAtMsByNamespace;\n const dueEmbedNamespaces = (namespaces: string[]): string[] => {\n if (!resolveQmdCapabilities(this.deps.config).qmdAutoEmbed) return [];\n return namespaces.filter(\n (namespace) =>\n now - (lastEmbedAtByNamespace.get(namespace) ?? 0) >=\n this.deps.config.qmdEmbedMinIntervalMs,\n );\n };\n const markEmbedded = (namespaces: string[]): void => {\n if (namespaces.length === 0) return;\n for (const namespace of namespaces) {\n lastEmbedAtByNamespace.set(namespace, now);\n }\n this.lastQmdEmbedAtMs = now;\n };\n await runNamespaceMaintenanceBatchPlan(\n this.deps.config,\n plan,\n async (candidates) => {\n const namespaces = candidates.map((candidate) => candidate.namespace);\n const embedNamespaces = dueEmbedNamespaces(namespaces);\n let result: NamespaceUpdateResult;\n try {\n result = await this.deps.namespaceSearchRouter.updateNamespacesDetailed(\n namespaces,\n undefined,\n { strict: true },\n );\n } catch (error) {\n if (\n embedNamespaces.length > 0 &&\n qmdMaintenanceSkipReasonForError(error) === \"throttled\"\n ) {\n await this.deps.namespaceSearchRouter.embedNamespaces(embedNamespaces, {\n strict: true,\n });\n markEmbedded(embedNamespaces);\n }\n throw error;\n }\n if (result.backendCount <= 0) {\n throw new Error(\"no eligible QMD backend for selected namespaces\");\n }\n if (result.eligibleNamespaces.length !== namespaces.length) {\n const eligible = new Set(result.eligibleNamespaces);\n const missing = namespaces.filter(\n (namespace) => !eligible.has(namespace),\n );\n throw new Error(\n `QMD backend ineligible for selected namespaces (${missing.length})`,\n );\n }\n if (embedNamespaces.length > 0) {\n await this.deps.namespaceSearchRouter.embedNamespaces(embedNamespaces, {\n strict: true,\n });\n markEmbedded(embedNamespaces);\n }\n return { itemCount: result.backendCount };\n },\n this.deps.namespaceCatalog,\n {\n skipReasonForError: qmdMaintenanceSkipReasonForError,\n },\n );\n } else {\n await this.deps.getQmd().update();\n const now = Date.now();\n if (\n resolveQmdCapabilities(this.deps.config).qmdAutoEmbed &&\n now - this.lastQmdEmbedAtMs >= this.deps.config.qmdEmbedMinIntervalMs\n ) {\n await this.deps.getQmd().embed();\n this.lastQmdEmbedAtMs = now;\n }\n }\n // Note: a successful QMD update/embed clears the global QMD result caches\n // itself (QmdClient.runUpdateForCollection / runEmbedForCollection), so\n // this scheduler does not invalidate them here — every refresh path\n // (direct update, namespace router, wearable/OpenClaw sync) funnels through\n // the backend and clears centrally. A no-op pass (throttle/unavailable/\n // backoff) does not advance lastUpdateRanAtMs, so it leaves caches warm\n // (#1904, Codex/Cursor).\n } finally {\n this.qmdMaintenanceInFlight = false;\n if (this.qmdMaintenancePending) {\n this.requestQmdMaintenance();\n }\n // Lifecycle-ledger auto-compaction is triggered from\n // requestQmdMaintenanceForTool (every maintenance request, QMD or not),\n // so it does not need to be re-fired here (issue #1910).\n }\n }\n\n /**\n * Size-gated, throttled, single-flighted auto-compaction of the lifecycle\n * ledger (issue #1910). Compacts the root ledger AND every namespace ledger\n * (`memoryDir/namespaces/<token>/state/`) when namespaces are enabled, each\n * through the existing `rebuildMemoryLifecycleLedger` so the\n * archive-then-atomic-write discipline is reused verbatim. `0` disables.\n *\n * Before compacting, drain EVERY target's durable pending-append spill (the\n * root ledger AND each namespace ledger) so a lifecycle event that spilled\n * while a prior long rewrite held the lock is folded back into its ledger and\n * cannot be silently lost (issue #2033). Draining is independent of both the\n * compaction threshold and the min-interval throttle: targets are enumerated\n * through the same safe (symlink/containment-checked) resolver and every\n * eligible queue is drained BEFORE any throttle return, so a namespace spill\n * created after the previous pass never waits out the whole interval.\n * Encrypted-store safety is preserved — a keyed target drains through its\n * secure StorageManager, a filesystem-fallback target only through a plaintext\n * context that refuses encrypted ledgers/spills. Work stays bounded: one\n * target enumeration plus a bounded per-queue drain.\n *\n * The min-interval throttle timestamp advances ONLY after real, fully\n * successful compaction work: a failed OR deferred target leaves the throttle\n * un-advanced so it stays eligible on the next maintenance pass, while\n * `lifecycleCompactionInFlight` still prevents overlapping runs. A \"deferred\"\n * target is oversized but could not be compacted because its encrypted ledger\n * has no unlocked secure store — arming the throttle for it would suppress the\n * retry of that still-oversized ledger for the whole interval (#2033).\n *\n * The min-interval throttle is bypassed for one case: an encrypted ledger\n * already at/over the whole-file decrypt cap is unreadable, so it must be\n * clamped on the next pass rather than wait out the interval. Ordinary\n * below-cap (or plaintext) targets stay throttled (#2033).\n */\n private async maybeCompactMemoryLifecycleLedger(): Promise<void> {\n // Enumerate every lifecycle target (root + namespaces) through the safe,\n // containment-checked resolver, and fold each one's durable pending spill\n // back into its ledger BEFORE any threshold or throttle return (#2033). A\n // spill created after the previous pass must land promptly even while\n // compaction is throttled or disabled; draining is bounded per queue and\n // never bypasses encrypted-store safety.\n const targets = await this.resolveLifecycleCompactionTargets();\n for (const target of targets) {\n await this.drainPendingForTarget(target);\n }\n\n const threshold = this.deps.config.memoryLifecycleLedgerCompactBytes;\n if (!(threshold > 0)) return; // 0 / negative / non-numeric disables compaction.\n if (this.lifecycleCompactionInFlight) return;\n const now = Date.now();\n if (\n now - this.lastLifecycleCompactionAtMs <\n this.deps.config.memoryLifecycleLedgerCompactMinIntervalMs &&\n !(await this.hasOverCapEncryptedLifecycleLedger(targets))\n ) {\n // Throttled: no ordinary target is due yet. The exception is an encrypted\n // ledger already at/over the whole-file decrypt cap — it is unreadable, so\n // the global min-interval must not defer clamping it for a whole interval.\n // The guard above lets such a target through immediately (compaction then\n // clamps it via the cap-aware effective threshold) while ordinary\n // below-cap targets stay throttled (#2033).\n return;\n }\n // The over-cap probe above awaits, so a second maintenance request can pass\n // the early `lifecycleCompactionInFlight` guard and reach here during that\n // await. Recheck the guard immediately before claiming it — otherwise two\n // racing requests both see an over-cap ledger and run duplicate 400MB-class\n // compactions/backups back-to-back (#2033). The set below is synchronous\n // with this recheck (no await between), so exactly one caller wins.\n if (this.lifecycleCompactionInFlight) return;\n this.lifecycleCompactionInFlight = true;\n try {\n let compacted = 0;\n let failed = 0;\n let deferred = 0;\n for (const target of targets) {\n const outcome = await this.compactLifecycleLedgerTarget(target, threshold);\n if (outcome === \"compacted\") compacted += 1;\n else if (outcome === \"failed\") failed += 1;\n else if (outcome === \"deferred\") deferred += 1;\n }\n // Arm the throttle only when at least one ledger actually compacted AND\n // nothing failed AND nothing was deferred. A below-threshold no-op\n // (compacted === 0), any failure, or a deferred oversized ledger (locked\n // encrypted store, still pending) leaves the throttle where it was so the\n // next pass retries the untouched targets (#2033).\n if (compacted > 0 && failed === 0 && deferred === 0) {\n this.lastLifecycleCompactionAtMs = now;\n }\n } finally {\n this.lifecycleCompactionInFlight = false;\n }\n }\n\n /**\n * True when any lifecycle target's ledger is encrypted at rest AND its\n * on-disk size is at/over the whole-file decrypt cap. Such a ledger is already\n * unreadable (the secure reader refuses at/over the cap), so it must reach the\n * bounded compaction path on the next maintenance pass regardless of the\n * global min-interval throttle — otherwise the throttle could keep an\n * unreadable ledger un-clamped for a whole interval (#2033). Ordinary\n * below-cap or plaintext targets never match, so the throttle still governs\n * them. A stat/probe failure yields \"no forced target\" and is left to the\n * normal compaction path, which classifies it as failed and retries.\n */\n private async hasOverCapEncryptedLifecycleLedger(\n targets: Array<{ memoryDir: string; storage?: StorageManager }>,\n ): Promise<boolean> {\n for (const target of targets) {\n const ledgerPath = path.join(target.memoryDir, \"state\", \"memory-lifecycle-ledger.jsonl\");\n let size: number;\n try {\n size = (await stat(ledgerPath)).size;\n } catch {\n continue; // absent/unstattable — nothing to force here.\n }\n if (size < this.lifecycleLedgerMaxBytes) continue;\n try {\n if (await probeEncryptedRegularFileHeader(ledgerPath)) return true;\n } catch {\n continue; // probe failure — the normal compaction path reports it.\n }\n }\n return false;\n }\n\n /**\n * Drain one compaction target's durable pending spill. A catalog-backed\n * target folds through its secure StorageManager; a filesystem-fallback\n * target (no storage) folds through a path-scoped plaintext context that\n * refuses encrypted ledgers/spills so a keyless drain never downgrades an\n * encrypted ledger (issue #2033).\n */\n private async drainPendingForTarget(\n target: { memoryDir: string; storage?: StorageManager },\n ): Promise<void> {\n if (target.storage) {\n await this.drainPendingLifecycleAppends(target.storage);\n } else {\n await this.drainPendingLifecycleAppendsForPath(target.memoryDir);\n }\n }\n\n /** Drain a target's durable pending lifecycle-append spill into its ledger\n * (issue #2033); non-fatal and a fast no-op when nothing is pending. */\n private async drainPendingLifecycleAppends(storage: StorageManager | undefined): Promise<void> {\n if (!storage) return;\n try {\n await storage.drainPendingMemoryLifecycleEvents();\n } catch (err) {\n log.debug(`lifecycle pending drain failed (non-fatal): ${err}`);\n }\n }\n\n /**\n * Drain a filesystem-fallback target's pending lifecycle-append spill when no\n * secure StorageManager is available (catalog-disabled namespace; issue\n * #2033). Uses the safe path-scoped PLAINTEXT context this fallback already\n * operates in: a keyless drain can only fold plaintext spills into a plaintext\n * ledger, so it refuses when the ledger or ANY spill is encrypted at rest,\n * leaving those pending for a keyed (catalog-path) drain — exactly mirroring\n * the fallback compaction's encrypted-ledger deferral. Non-fatal; a fast\n * no-op when nothing is pending. The shared spill lister enforces the symlink/\n * containment guard on every spill file it reads or deletes.\n */\n private async drainPendingLifecycleAppendsForPath(memoryDir: string): Promise<void> {\n const ledgerPath = path.join(memoryDir, \"state\", \"memory-lifecycle-ledger.jsonl\");\n const spillDir = pendingLifecycleLedgerDir(ledgerPath);\n // Enumerate spills through the SAME guarded lister the actual drain uses\n // (listContainedSpillFiles): it rejects a symlinked/non-directory spill dir\n // and skips any symlink/FIFO/device/escaping entry, returning only regular\n // files contained in the dir. Never raw-readdir + open() an untrusted name:\n // opening a FIFO would block until a writer appears and a symlink would\n // redirect the probe outside the dir (#2033). Enumerate BOTH live `*.jsonl`\n // spills and crash-orphaned `*.jsonl.claimed` files: a drain that died\n // between claiming a spill (rename to `.claimed`) and committing it leaves\n // the ONLY copy of those rows as a claimed orphan. Gating solely on live\n // spills would early-return here whenever the pending dir holds nothing but\n // orphans, stranding those rows forever; proceeding lets\n // drainPendingLifecycleLedgerIfAny -> recoverOrphanedClaims restore and\n // re-commit them under the ledger lock (#2033).\n let spillFiles: string[];\n let claimedOrphans: string[];\n try {\n spillFiles = await listContainedSpillFiles(spillDir);\n claimedOrphans = await listContainedSpillFiles(spillDir, \".jsonl.claimed\");\n } catch (err) {\n log.debug(`fallback lifecycle pending scan failed (non-fatal) for ${memoryDir}: ${err}`);\n return;\n }\n if (spillFiles.length === 0 && claimedOrphans.length === 0) return;\n // The pending encryption state is authoritative only UNDER the ledger lock:\n // drainPendingLifecycleLedgerIfAny waits for the lock and re-enumerates the\n // pending dir, so a secure-store writer that spills an encrypted file — or a\n // keyed compaction that rewrites the ledger encrypted — while this fallback\n // waits would defeat any pre-lock probe (#2033). Probe each file at the\n // moment of use inside the held lock instead: readSecure refuses an\n // encrypted spill (the fold reads each spill lazily and propagates the throw\n // BEFORE claiming that spill, leaving it intact) and the append refuses a\n // ledger that became encrypted (the fold rolls the claims back). Either way\n // the plaintext IO\n // never reads or writes ciphertext; the keyed catalog-path drain handles the\n // deferred rows.\n const io: LifecyclePendingIo = {\n writeSecure: async (filePath, payload) => {\n await mkdir(path.dirname(filePath), { recursive: true });\n await writeFile(filePath, payload, \"utf-8\");\n },\n readSecure: async (filePath) => {\n if (await probeEncryptedRegularFileHeader(filePath)) {\n throw new Error(`fallback lifecycle drain deferred: encrypted spill at ${filePath}`);\n }\n return readFile(filePath, \"utf-8\");\n },\n };\n try {\n await drainPendingLifecycleLedgerIfAny(\n ledgerPath,\n io,\n async (payload) => {\n if (await probeEncryptedRegularFileHeader(ledgerPath)) {\n throw new Error(`fallback lifecycle drain deferred: ledger became encrypted at ${ledgerPath}`);\n }\n await appendFile(ledgerPath, payload, \"utf-8\");\n },\n async () => {\n await mkdir(path.dirname(ledgerPath), { recursive: true });\n },\n );\n } catch (err) {\n log.debug(`fallback lifecycle pending drain failed (non-fatal) for ${memoryDir}: ${err}`);\n }\n }\n\n /**\n * Enumerate the lifecycle ledgers to consider for compaction: the root\n * (through the secure-configured root storage when available) plus every\n * cataloged namespace ledger when namespaces are enabled. Deduplicated by\n * resolved storage dir so a namespace that collapses onto the root is not\n * compacted twice. Namespace resolution failures are non-fatal.\n */\n private async resolveLifecycleCompactionTargets(): Promise<\n Array<{ memoryDir: string; storage?: StorageManager }>\n > {\n const targets: Array<{ memoryDir: string; storage?: StorageManager }> = [];\n const seen = new Set<string>();\n const addTarget = (memoryDir: string, storage?: StorageManager): void => {\n const key = path.resolve(memoryDir);\n if (seen.has(key)) return;\n seen.add(key);\n targets.push({ memoryDir, storage });\n };\n\n const rootStorage = this.deps.getStorage?.();\n addTarget(rootStorage ? rootStorage.dir : this.deps.config.memoryDir, rootStorage);\n\n const resolveNamespaceStorage = this.deps.storageForNamespace;\n if (resolveNamespaceStorage && this.deps.namespaceCatalog?.enabled) {\n let records: NamespaceRecord[] = [];\n try {\n records = await this.deps.namespaceCatalog.listNamespaces();\n } catch (err) {\n log.debug(`lifecycle compaction: namespace enumeration failed (non-fatal): ${err}`);\n }\n for (const record of records) {\n const namespace = record.namespace.trim();\n if (!namespace) continue;\n try {\n const storage = await resolveNamespaceStorage(namespace);\n addTarget(storage.dir, storage);\n } catch (err) {\n log.debug(\n `lifecycle compaction: storage resolve failed for namespace ${namespace} (non-fatal): ${err}`,\n );\n }\n }\n }\n\n // Filesystem fallback (codex P2): when namespaces are enabled but the\n // catalog is disabled (namespaceCatalogEnabled=false) — or a namespace is\n // not yet cataloged — the catalog walk above finds nothing, yet per-namespace\n // ledgers still grow under <memoryDir>/namespaces/<token>/state/. Scan that\n // base directly so those ledgers are still bounded. Deduped by resolved dir,\n // so a namespace already added via the catalog is not compacted twice.\n if (resolveNamespaceCapabilities(this.deps.config).namespaces) {\n const namespacesBase = path.join(this.deps.config.memoryDir, \"namespaces\");\n // Symlink/traversal containment (issue #2033 codex P2): a symlinked\n // <memoryDir>/namespaces (or a symlinked child) must not redirect the scan\n // — and later backup/ledger rewrites — outside memoryDir. Resolve the\n // memory root and the scan base through realpath, reject a symlinked or\n // escaping base, and skip any symlinked/escaping child, mirroring the\n // memory-store walkers' hardening (utils/path-containment).\n let memoryDirReal: string;\n try {\n memoryDirReal = await realpath(this.deps.config.memoryDir);\n const baseStat = await lstat(namespacesBase);\n if (baseStat.isSymbolicLink() || !baseStat.isDirectory()) {\n throw new Error(\"namespaces base is a symlink or not a directory\");\n }\n assertPathInsideRoot(memoryDirReal, await realpath(namespacesBase), namespacesBase);\n } catch (err) {\n if (!isErrnoCode(err, \"ENOENT\")) {\n log.debug(`lifecycle compaction: namespaces base rejected (non-fatal): ${err}`);\n }\n return targets;\n }\n let entries: Dirent[] = [];\n try {\n entries = await readdir(namespacesBase, { withFileTypes: true });\n } catch (err) {\n if (!isErrnoCode(err, \"ENOENT\")) {\n log.debug(`lifecycle compaction: namespaces dir scan failed (non-fatal): ${err}`);\n }\n }\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n if (!entry.isDirectory() || entry.isSymbolicLink()) continue;\n const childPath = path.join(namespacesBase, entry.name);\n try {\n assertPathInsideRoot(memoryDirReal, await realpath(childPath), childPath);\n } catch (err) {\n log.debug(`lifecycle compaction: skipping out-of-root namespace dir ${childPath}: ${err}`);\n continue;\n }\n // Resolve the namespace's secure-store-aware storage so an encrypted\n // fallback ledger compacts through the correct root key/context instead\n // of deferring forever behind a keyless plaintext StorageManager (#2033\n // thread PRRT_kwDORJXyws6SE5fv). The on-disk dir name is EITHER a\n // namespace identity TOKEN (ns-<hex>) OR a legacy raw namespace dir name\n // written before tokenization (namespaces/<name>/). Decode a token back\n // to its canonical namespace; treat a non-token dir as a raw namespace\n // only when it is a safe route namespace (isSafeRouteNamespace), so a\n // garbage/traversal dir name is never handed to the resolver. Passing the\n // raw token to the resolver would re-tokenize an already-tokenized value\n // and (for namespaces longer than 30 bytes) exceed the router's 64-char\n // safe-route limit, so the resolver throws and the encrypted over-cap\n // ledger defers forever — hence decode first. Resolve the route namespace\n // and accept the keyed storage only when it roots at THIS dir: the secure\n // store keys one master key per memory ROOT (not per namespace), the\n // router routes a token to namespaces/<token>/ and an existing legacy raw\n // name to its namespaces/<name>/ root, so a correct resolve returns THIS\n // childPath; anything else is an unexpected re-route we refuse (keeping\n // namespace isolation and never compacting the wrong ledger). A dir that\n // neither decodes nor is a safe raw namespace, or no resolver wired\n // (focused tests / plaintext deployments), leaves the keyless target\n // standing: the rebuild chokepoint still refuses to downgrade an\n // encrypted ledger, so plaintext behavior holds.\n const decodedNamespace = namespaceIdentityFromToken(entry.name);\n const routeNamespace =\n decodedNamespace !== null\n ? decodedNamespace\n : isSafeRouteNamespace(entry.name)\n ? entry.name\n : null;\n if (resolveNamespaceStorage && routeNamespace !== null) {\n try {\n const storage = await resolveNamespaceStorage(routeNamespace);\n if (path.resolve(storage.dir) === path.resolve(childPath)) {\n addTarget(storage.dir, storage);\n continue;\n }\n log.debug(\n `lifecycle compaction: fallback resolver routed ${childPath} to ${storage.dir}; `\n + \"using keyless target\",\n );\n } catch (err) {\n log.debug(\n `lifecycle compaction: fallback storage resolve failed for ${childPath} (non-fatal): ${err}`,\n );\n }\n }\n addTarget(childPath);\n }\n }\n return targets;\n }\n\n /**\n * Compact one lifecycle ledger when it is at/over `threshold`. Returns\n * `\"skipped\"` (absent or below threshold — nothing to do), `\"compacted\"`\n * (rewritten and verified under the read/decrypt cap), `\"failed\"`\n * (rebuild/probe threw, or the rewritten ledger is STILL over the cap — an\n * ineffective compaction — non-fatal, retried next pass), or `\"deferred\"`\n * (oversized but its encrypted ledger cannot be rewritten because no unlocked\n * secure store is available — real work still pending, so the caller must NOT\n * arm the throttle). #2033: a deferred/failed target is distinct from a genuine\n * no-op so one namespace compacting cannot suppress retries for another, and\n * an over-cap rewrite never arms the throttle.\n */\n private async compactLifecycleLedgerTarget(\n target: { memoryDir: string; storage?: StorageManager },\n threshold: number,\n ): Promise<\"skipped\" | \"compacted\" | \"failed\" | \"deferred\"> {\n const ledgerPath = path.join(target.memoryDir, \"state\", \"memory-lifecycle-ledger.jsonl\");\n let size = 0;\n try {\n size = (await stat(ledgerPath)).size;\n } catch (err) {\n if (isErrnoCode(err, \"ENOENT\")) return \"skipped\"; // absent — nothing to compact yet.\n log.warn(\n `lifecycle ledger size check failed (non-fatal) for ${target.memoryDir}: ${err}`,\n );\n return \"failed\";\n }\n // #2033 finding (1): an encrypted ledger becomes unreadable once its on-disk\n // size reaches the whole-file decrypt cap (the reader refuses at/over it), so\n // the effective compaction trigger for an encrypted target is clamped to that\n // cap even when the configured threshold is LARGER — otherwise a ledger that\n // grows past the cap but stays below a bigger threshold would never compact\n // and stay permanently unreadable. Plaintext streaming is unaffected at any\n // size, so a plaintext target keeps the configured threshold. The cheap\n // pre-check skips anything below the smaller of the two before paying for the\n // extra lstat/open the encryption probe costs.\n if (size < Math.min(threshold, this.lifecycleLedgerMaxBytes)) return \"skipped\";\n let encrypted: boolean;\n try {\n encrypted = await probeEncryptedRegularFileHeader(ledgerPath);\n } catch (err) {\n log.warn(`lifecycle ledger encryption probe failed (non-fatal) for ${ledgerPath}: ${err}`);\n return \"failed\";\n }\n const effectiveThreshold = encrypted\n ? Math.min(threshold, this.lifecycleLedgerMaxBytes)\n : threshold;\n if (size < effectiveThreshold) return \"skipped\";\n // No plaintext rewrite of an encrypted-at-rest ledger (#2033): rebuilding an\n // encrypted ledger without an unlocked secure StorageManager would either\n // fail the preserve-read (locked store) or downgrade the ledger to plaintext.\n // Report \"deferred\" (NOT \"skipped\"): the ledger is over threshold and still\n // needs compaction, so the throttle must stay un-armed and retry once a key\n // is available — a \"skipped\" here would let another target's success arm the\n // throttle and suppress this pending work for the whole interval (#2033).\n if (encrypted && !(target.storage?.isSecureStoreUnlocked() ?? false)) {\n log.warn(\n `lifecycle ledger at ${ledgerPath} is encrypted at rest but no unlocked secure `\n + `storage is available; deferring auto-compaction to avoid a plaintext rewrite. `\n + `Run 'remnic rebuild-memory-lifecycle-ledger --write' after unlocking.`,\n );\n return \"deferred\";\n }\n // #2033 findings (2)+(4): the rewrite budget bounds the PLAINTEXT payload, but\n // an encrypted-at-rest target adds a fixed secure-store envelope on disk.\n // Reserve that envelope (plus one byte) from the plaintext budget so the\n // encrypted file lands STRICTLY below the reader's refusal cap and the\n // post-write check — which stats the on-disk (encrypted) size against the cap\n // — cannot fail forever on a plaintext budget that equals the cap (the\n // endless-retry bug). Base this on whether the REPLACEMENT will be encrypted\n // (the storage write mode), NOT only on the current file header: a\n // plaintext ledger rewritten under `secureStoreEncryptOnWrite` with the key\n // set becomes encrypted, so it needs the reserve even though its current\n // header is plaintext (#2033 write-mode finding).\n const replacementEncrypted =\n encrypted || (target.storage?.willEncryptStateWrites() ?? false);\n const plaintextBudget = replacementEncrypted\n ? this.lifecycleLedgerMaxBytes - SECURE_STORE_ENVELOPE_OVERHEAD_BYTES - 1\n : this.lifecycleLedgerMaxBytes - 1;\n try {\n const result = await rebuildMemoryLifecycleLedger({\n memoryDir: target.memoryDir,\n dryRun: false,\n storage: target.storage,\n // Background compaction must not lose append-only history that\n // frontmatter cannot reconstruct (issue #1910); the manual CLI repair\n // rebuild now preserves and bounds identically (#2033).\n preserveExistingEvents: true,\n // Bound the PLAINTEXT rewrite to a budget that reserves the secure-store\n // envelope, so the on-disk ledger (plaintext, or plaintext+envelope when\n // encrypted) is strictly below the read/decrypt cap; overflow lands in\n // the verbatim backup (#2033 findings 2+4).\n maxLedgerBytes: plaintextBudget,\n });\n // Verify the rewritten ledger is actually bounded. A compaction that left\n // the ledger at/over the read/decrypt cap is ineffective (the ledger would\n // be unreadable) — report \"failed\" so the throttle stays un-armed and the\n // next pass retries rather than declaring success (#2033).\n let rewrittenSize = 0;\n try {\n rewrittenSize = (await stat(ledgerPath)).size;\n } catch (err) {\n log.warn(`lifecycle ledger post-compaction size check failed (non-fatal) for ${target.memoryDir}: ${err}`);\n return \"failed\";\n }\n if (rewrittenSize >= this.lifecycleLedgerMaxBytes) {\n log.warn(\n `lifecycle ledger auto-compaction ineffective for ${target.memoryDir}: rewritten `\n + `ledger is ${rewrittenSize}B, still at/over the ${this.lifecycleLedgerMaxBytes}-byte `\n + `read/decrypt cap; leaving the throttle un-armed to retry.`,\n );\n return \"failed\";\n }\n if (result.rewritten === false) {\n // No-op pass (#2033 thread PRRT_kwDORJXyws6SExst): a preserving rebuild\n // would reproduce the current ledger byte-for-byte, so it skipped the\n // backup+rewrite. The ledger stays over the trigger but under the cap;\n // report \"compacted\" so the throttle still arms (nothing more can be\n // done until the ledger grows), which stops the periodic re-archive/\n // rewrite churn the review flagged — the archive no longer grows.\n log.debug(\n `lifecycle ledger already compact for ${target.memoryDir}: ${rewrittenSize}B, `\n + `no rewrite needed (throttle armed to avoid re-archiving an unchanged ledger).`,\n );\n } else {\n log.info(\n `lifecycle ledger auto-compacted (${target.memoryDir}): ${size}B -> `\n + `${rewrittenSize}B, ${result.rebuiltRows} rows `\n + `(${result.preservedAppendOnlyRows ?? 0} append-only preserved, `\n + `${result.archivedOverflowRows ?? 0} overflow archived), `\n + `backup=${result.backupPath ?? \"none\"}`,\n );\n }\n return \"compacted\";\n } catch (err) {\n log.warn(\n `lifecycle ledger auto-compaction failed (non-fatal) for ${target.memoryDir}: ${err}`,\n );\n return \"failed\";\n }\n }\n\n /** Build the namespace-aware maintenance plan for one job. */\n private async namespaceMaintenancePlan(\n jobName: string,\n ): Promise<NamespaceMaintenancePlan> {\n return planNamespaceMaintenance(this.deps.config, {\n jobName,\n catalog: this.deps.namespaceCatalog,\n });\n }\n\n // ───────────────────────────────────────────────────────────────────────\n // Lifecycle\n // ───────────────────────────────────────────────────────────────────────\n\n /**\n * Clear any pending debounce timer and drain the activity sync scheduler.\n * Called from the orchestrator's destroy() so a late tick does not fire on a\n * torn-down instance and no in-flight activity tick keeps writing after\n * teardown. Async: awaits the scheduler's abort+drain before resolving.\n */\n async dispose(): Promise<void> {\n // The registrar latches teardown first (so a draining tick's onRun cannot\n // re-arm QMD maintenance) and aborts+drains the sync. Capture its drain and\n // await it last so the rest of teardown runs while the aborted tick unwinds.\n const activityDrain = this.activityRegistrar.dispose();\n if (this.qmdMaintenanceTimer) {\n clearTimeout(this.qmdMaintenanceTimer);\n this.qmdMaintenanceTimer = null;\n }\n this.qmdMaintenancePending = false;\n await activityDrain;\n // Belt-and-suspenders: clear anything a late tick armed before the latch.\n if (this.qmdMaintenanceTimer) {\n clearTimeout(this.qmdMaintenanceTimer);\n this.qmdMaintenanceTimer = null;\n }\n this.qmdMaintenancePending = false;\n }\n}\n","/**\n * Forced, non-fake search-index refresh for direct activity digest writes\n * (issue #1899; AGENTS.md rule 31). The digest is written straight to the QMD\n * collection root, bypassing the extraction->persist->index pipeline, so it must\n * explicitly trigger a refresh — and that refresh must actually run.\n *\n * Ordinary `SearchBackend.update()` is fail-open: it silently returns when the\n * backend is unavailable, in failure backoff, or inside its min-interval gate.\n * A freshly written digest hit by that gate would stay unsearchable until an\n * unrelated reindex, and the per-day digest-unchanged guard means the next tick\n * won't retry. So prefer the forced/strict contracts: `updateCollectionStrict`\n * bypasses the min-interval gate (force) AND throws on a genuine failure instead\n * of reporting a fake success, then `updateStrict`, then plain `update`.\n */\n\nimport type { SearchExecutionOptions } from \"../search/port.js\";\n\n/** The subset of the search backend the activity reindex needs. */\nexport interface ActivityIndexRefresher {\n update(execution?: SearchExecutionOptions): Promise<void>;\n updateStrict?(execution?: SearchExecutionOptions): Promise<void>;\n updateCollectionStrict?(collection: string, execution?: SearchExecutionOptions): Promise<void>;\n}\n\nexport async function refreshActivityIndex(\n qmd: ActivityIndexRefresher,\n collection: string,\n signal?: AbortSignal,\n): Promise<void> {\n const execution = signal === undefined ? undefined : { signal };\n if (typeof qmd.updateCollectionStrict === \"function\" && collection.trim().length > 0) {\n await qmd.updateCollectionStrict(collection, execution);\n return;\n }\n if (typeof qmd.updateStrict === \"function\") {\n await qmd.updateStrict(execution);\n return;\n }\n await qmd.update(execution);\n}\n","/**\n * Screen-activity SQLite store (issue #1899).\n *\n * Durable, capture-machine-agnostic store for on-screen text snapshots plus a\n * per-machine sync cursor. Mirrors the LCM store conventions\n * (packages/remnic-core/src/lcm/schema.ts): WAL, a `<name>_meta` schema-version\n * row, `CREATE TABLE IF NOT EXISTS`, and an FTS5 virtual table created\n * separately. better-sqlite3 is synchronous.\n *\n * Snapshots dedup on `(machine, content_hash)` so a re-sync is idempotent, and\n * day queries use half-open [start, end) UTC bounds (AGENTS.md §23).\n */\n\nimport { mkdirSync } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { openBetterSqlite3, type BetterSqlite3Database } from \"../runtime/better-sqlite.js\";\nimport type { ActivitySnapshot } from \"./types.js\";\n\nconst ACTIVITY_SCHEMA_VERSION = 1;\nconst MAX_SEARCH_RESULTS = 100;\n\nexport function activityDatabasePath(memoryDir: string): string {\n return path.join(memoryDir, \"state\", \"activity.sqlite\");\n}\n\nexport async function ensureActivityStateDir(memoryDir: string): Promise<void> {\n await mkdir(path.join(memoryDir, \"state\"), { recursive: true });\n}\n\nexport function openActivityDatabase(memoryDir: string): BetterSqlite3Database {\n // Create the state/ dir synchronously first: better-sqlite3 can't open a file\n // in a missing directory, and open() is the sync public entry point (callers\n // don't await ensureActivityStateDir). Idempotent.\n mkdirSync(path.join(memoryDir, \"state\"), { recursive: true });\n const db = openBetterSqlite3(activityDatabasePath(memoryDir));\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"busy_timeout = 5000\");\n db.pragma(\"synchronous = NORMAL\");\n applySchema(db);\n return db;\n}\n\n/** Apply the activity schema on an already-open handle (test/in-memory use). */\nexport function applyActivitySchema(db: BetterSqlite3Database): void {\n applySchema(db);\n}\n\nfunction applySchema(db: BetterSqlite3Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS activity_meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS activity_snapshots (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n machine TEXT NOT NULL,\n captured_at_utc TEXT NOT NULL,\n app_name TEXT NOT NULL,\n window_title TEXT NOT NULL,\n browser_url TEXT,\n text TEXT NOT NULL,\n text_source TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n simhash TEXT\n );\n CREATE UNIQUE INDEX IF NOT EXISTS idx_activity_snapshots_dedup\n ON activity_snapshots(machine, captured_at_utc, content_hash);\n CREATE INDEX IF NOT EXISTS idx_activity_snapshots_time\n ON activity_snapshots(captured_at_utc, id);\n\n CREATE TABLE IF NOT EXISTS activity_sync_state (\n machine TEXT PRIMARY KEY,\n cursor TEXT,\n updated_at_utc TEXT NOT NULL\n );\n `);\n\n const hasFts = db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name='activity_snapshots_fts'\")\n .get();\n if (!hasFts) {\n // Standalone FTS5 (not external-content): populated explicitly on insert so\n // it never drifts from the base table.\n db.exec(`\n CREATE VIRTUAL TABLE activity_snapshots_fts USING fts5(\n text, app_name, window_title, browser_url\n );\n `);\n }\n\n db.prepare(\"INSERT OR REPLACE INTO activity_meta (key, value) VALUES ('schema_version', ?)\").run(\n String(ACTIVITY_SCHEMA_VERSION),\n );\n}\n\n/**\n * Canonicalize a UTC timestamp to `YYYY-MM-DDTHH:MM:SS.sssZ` so day-window\n * range filtering (a TEXT comparison against activityDayWindow bounds) is valid\n * regardless of the input ISO form (trailing `Z` vs `+00:00`, missing millis).\n * A non-parseable value is stored verbatim (an outlier the day filter skips).\n */\nfunction canonicalizeUtc(iso: string): string {\n const parsed = Date.parse(iso);\n return Number.isFinite(parsed) ? new Date(parsed).toISOString() : iso;\n}\n\nfunction assertRequiredSnapshotFields(s: ActivitySnapshot): void {\n const required: ReadonlyArray<readonly [string, unknown]> = [\n [\"machine\", s.machine],\n [\"app\", s.app],\n [\"windowTitle\", s.windowTitle],\n [\"text\", s.text],\n [\"textSource\", s.textSource],\n [\"contentHash\", s.contentHash],\n ];\n for (const [field, value] of required) {\n if (typeof value !== \"string\") {\n // INSERT OR IGNORE would otherwise swallow a NOT NULL failure as if it\n // were a dedup conflict, silently dropping a malformed capture.\n throw new RangeError(`activity: snapshot field \"${field}\" is required (got ${value === null ? \"null\" : typeof value}).`);\n }\n }\n if (s.machine.length === 0 || s.contentHash.length === 0) {\n throw new RangeError('activity: snapshot \"machine\" and \"contentHash\" must be non-empty.');\n }\n if (s.textSource !== \"ax\" && s.textSource !== \"ocr\") {\n // rowToSnapshot maps any non-\"ocr\" value to \"ax\"; reject unknown sources at\n // the boundary instead of silently coercing them on read.\n throw new RangeError(`activity: snapshot \"textSource\" must be \"ax\" or \"ocr\" (got \"${s.textSource}\").`);\n }\n}\n\n/**\n * Reject an impossible capture timestamp instead of letting Date.parse silently\n * roll it over (e.g. 2026-02-30 → Mar 2, in either `Z` or explicit-offset form),\n * which would file the snapshot under the wrong day. Validates the wall-clock\n * calendar fields in the string directly, independent of the zone designator.\n */\nfunction assertValidUtcInstant(iso: string, what: string): void {\n const m = iso.match(\n /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.\\d+)?(?:Z|[+-](?:0\\d:[0-5]\\d|1[0-3]:[0-5]\\d|14:00))$/,\n );\n if (m === null || !Number.isFinite(Date.parse(iso))) {\n throw new RangeError(`activity: invalid ${what} \"${iso}\".`);\n }\n const [year, month, day, hour, minute, second] = m.slice(1).map(Number);\n const daysInMonth = month >= 1 && month <= 12 ? new Date(Date.UTC(year, month, 0)).getUTCDate() : 0;\n if (day < 1 || day > daysInMonth || hour > 23 || minute > 59 || second > 59) {\n throw new RangeError(`activity: ${what} \"${iso}\" is not a real calendar instant.`);\n }\n}\n\n/** Narrow a sqlite row object so field reads are checked, not asserted. */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction str(value: unknown): string {\n return typeof value === \"string\" ? value : \"\";\n}\n\nfunction optStr(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\n/**\n * Build a safe FTS5 MATCH expression from free text: extract alphanumeric\n * tokens and quote each as a phrase, so punctuation common in captured\n * activity (URLs like github.com/x/pull/412, quotes, bare boolean operators)\n * can never be parsed as FTS5 syntax and make SQLite throw. Tokens are AND-ed\n * (implicit). Returns null when there is nothing to match.\n */\nfunction ftsMatchFor(query: string): string | null {\n if (typeof query !== \"string\") return null;\n const tokens = query.match(/[\\p{L}\\p{N}_]+/gu);\n if (!tokens || tokens.length === 0) return null;\n return tokens.map((token) => `\"${token}\"`).join(\" \");\n}\n\nfunction rowToSnapshot(row: unknown): ActivitySnapshot {\n if (!isRecord(row)) {\n throw new Error(\"activity store: unexpected non-object row\");\n }\n const textSource = row.text_source === \"ocr\" ? \"ocr\" : \"ax\";\n return {\n id: typeof row.id === \"number\" ? row.id : undefined,\n machine: str(row.machine),\n capturedAtUtc: str(row.captured_at_utc),\n app: str(row.app_name),\n windowTitle: str(row.window_title),\n ...(optStr(row.browser_url) !== undefined ? { browserUrl: optStr(row.browser_url) } : {}),\n text: str(row.text),\n textSource,\n contentHash: str(row.content_hash),\n ...(optStr(row.simhash) !== undefined ? { simhash: optStr(row.simhash) } : {}),\n };\n}\n\nexport class ActivityStore {\n private readonly db: BetterSqlite3Database;\n\n constructor(db: BetterSqlite3Database) {\n this.db = db;\n }\n\n static open(memoryDir: string): ActivityStore {\n return new ActivityStore(openActivityDatabase(memoryDir));\n }\n\n /**\n * Insert a snapshot, idempotent on (machine, captured_at_utc, content_hash):\n * the same screen content recurring at a *different* time is kept; only an\n * exact re-ingestion of the same capture dedups. Returns `inserted: false`\n * for a duplicate. The FTS row is written only on a real insert (atomically),\n * so it never drifts from the base table.\n */\n insertSnapshot(snapshot: ActivitySnapshot): { inserted: boolean; id: number } {\n // Base row + FTS row must be atomic. Without a transaction, a crash (or an\n // FTS throw) between the two statements leaves a snapshot in the base table\n // with no matching FTS row — silently unsearchable. The transaction rolls\n // back both on any throw.\n const runInsert = this.db.transaction((s: ActivitySnapshot): { inserted: boolean; id: number } => {\n assertValidUtcInstant(s.capturedAtUtc, \"capture timestamp\");\n assertRequiredSnapshotFields(s);\n const capturedAtUtc = canonicalizeUtc(s.capturedAtUtc);\n const info = this.db\n .prepare(\n `INSERT OR IGNORE INTO activity_snapshots\n (machine, captured_at_utc, app_name, window_title, browser_url, text, text_source, content_hash, simhash)\n VALUES (@machine, @captured_at_utc, @app_name, @window_title, @browser_url, @text, @text_source, @content_hash, @simhash)`,\n )\n .run({\n machine: s.machine,\n captured_at_utc: capturedAtUtc,\n app_name: s.app,\n window_title: s.windowTitle,\n browser_url: s.browserUrl ?? null,\n text: s.text,\n text_source: s.textSource,\n content_hash: s.contentHash,\n simhash: s.simhash ?? null,\n });\n if (info.changes === 0) {\n const existing = this.db\n .prepare(\"SELECT id FROM activity_snapshots WHERE machine = ? AND captured_at_utc = ? AND content_hash = ?\")\n .get(s.machine, capturedAtUtc, s.contentHash);\n const id = isRecord(existing) && typeof existing.id === \"number\" ? existing.id : -1;\n return { inserted: false, id };\n }\n const id = Number(info.lastInsertRowid);\n this.db\n .prepare(\n `INSERT INTO activity_snapshots_fts (rowid, text, app_name, window_title, browser_url)\n VALUES (?, ?, ?, ?, ?)`,\n )\n .run(id, s.text, s.app, s.windowTitle, s.browserUrl ?? \"\");\n return { inserted: true, id };\n });\n return runInsert(snapshot);\n }\n\n /** Snapshots whose capture instant is in the half-open [start, end) window. */\n listSnapshotsForDay(\n machine: string | null,\n startUtcInclusive: string,\n endUtcExclusive: string,\n ): ActivitySnapshot[] {\n assertValidUtcInstant(startUtcInclusive, \"range start\");\n assertValidUtcInstant(endUtcExclusive, \"range end\");\n const start = canonicalizeUtc(startUtcInclusive);\n const end = canonicalizeUtc(endUtcExclusive);\n const rows =\n machine === null\n ? this.db\n .prepare(\n `SELECT * FROM activity_snapshots\n WHERE captured_at_utc >= ? AND captured_at_utc < ?\n ORDER BY captured_at_utc ASC, id ASC`,\n )\n .all(start, end)\n : this.db\n .prepare(\n `SELECT * FROM activity_snapshots\n WHERE machine = ? AND captured_at_utc >= ? AND captured_at_utc < ?\n ORDER BY captured_at_utc ASC, id ASC`,\n )\n .all(machine, start, end);\n return rows.map(rowToSnapshot);\n }\n\n getCursor(machine: string): string | null {\n const row = this.db.prepare(\"SELECT cursor FROM activity_sync_state WHERE machine = ?\").get(machine);\n return isRecord(row) && typeof row.cursor === \"string\" ? row.cursor : null;\n }\n\n setCursor(machine: string, cursor: string | null, updatedAtUtc: string = new Date().toISOString()): void {\n this.db\n .prepare(\n `INSERT INTO activity_sync_state (machine, cursor, updated_at_utc)\n VALUES (?, ?, ?)\n ON CONFLICT(machine) DO UPDATE SET cursor = excluded.cursor, updated_at_utc = excluded.updated_at_utc`,\n )\n .run(machine, cursor, updatedAtUtc);\n }\n\n /** Full-text search over snapshot text/app/window/url; newest first. */\n searchSnapshots(query: string, limit: number): ActivitySnapshot[] {\n const capped = Number.isInteger(limit) && limit > 0 ? Math.min(limit, MAX_SEARCH_RESULTS) : 20;\n const match = ftsMatchFor(query);\n if (match === null) return [];\n const rows = this.db\n .prepare(\n `SELECT s.* FROM activity_snapshots_fts f\n JOIN activity_snapshots s ON s.id = f.rowid\n WHERE activity_snapshots_fts MATCH ?\n ORDER BY s.captured_at_utc DESC, s.id DESC\n LIMIT ?`,\n )\n .all(match, capped);\n // ftsMatchFor sanitizes the query to quoted alphanumeric phrases, so there\n // is no path to an FTS5 syntax error; a real backend failure (closed handle,\n // missing/corrupt table, disk I/O) propagates rather than reading as empty.\n return rows.map(rowToSnapshot);\n }\n\n /** Retention: drop snapshots captured strictly before `cutoffUtc`. */\n pruneOlderThan(cutoffUtc: string): number {\n assertValidUtcInstant(cutoffUtc, \"prune cutoff\");\n const ids = this.db\n .prepare(\"SELECT id FROM activity_snapshots WHERE captured_at_utc < ?\")\n .all(canonicalizeUtc(cutoffUtc))\n .map((row: unknown) => (isRecord(row) && typeof row.id === \"number\" ? row.id : -1))\n .filter((id: number) => id >= 0);\n const deleteFts = this.db.prepare(\"DELETE FROM activity_snapshots_fts WHERE rowid = ?\");\n const deleteRow = this.db.prepare(\"DELETE FROM activity_snapshots WHERE id = ?\");\n const tx = this.db.transaction((rowIds: number[]) => {\n for (const id of rowIds) {\n deleteFts.run(id);\n deleteRow.run(id);\n }\n });\n tx(ids);\n return ids.length;\n }\n\n close(): void {\n this.db.close();\n }\n}\n","import { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { randomUUID } from \"node:crypto\";\nimport path from \"node:path\";\n\nimport { displayErrorDetail } from \"../runtime/better-sqlite.js\";\n\nimport {\n activityDayWindow,\n activityDigestPath,\n composeActivityDigestBody,\n composeActivityDigestMeta,\n isValidActivityDate,\n serializeActivityDigest,\n} from \"./digest.js\";\nimport type { ActivityStore } from \"./store.js\";\nimport type { ActivitySnapshot, ActivitySourceClient } from \"./types.js\";\n\nconst MAX_SYNC_PAGES = 10_000;\n\n/**\n * Sync cursors are scoped per (machine, local day): each date resumes its own\n * pagination independently, so a multi-day window never inherits an earlier\n * day's cursor (which could skip backfill). Encoded into the store's single\n * key column with a NUL separator that cannot occur in a machine label.\n */\nexport function activityCursorKey(machine: string, date: string): string {\n return `${machine}\\u0000${date}`;\n}\n\n// Serializes the list -> compose -> write digest section per (memoryDir, date)\n// so two sources syncing the same day cannot lose-update each other's digest:\n// the last writer in the chain lists every row present and wins.\nconst digestLocks = new Map<string, Promise<unknown>>();\n\nfunction withDigestLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n const prior = digestLocks.get(key) ?? Promise.resolve();\n const next = prior.then(fn, fn);\n const tail = next.then(\n () => undefined,\n () => undefined,\n );\n digestLocks.set(key, tail);\n void tail.then(() => {\n if (digestLocks.get(key) === tail) digestLocks.delete(key);\n });\n return next;\n}\n\nexport interface ActivitySyncOptions {\n date: string;\n timezone: string;\n memoryDir: string;\n store: ActivityStore;\n signal?: AbortSignal;\n /** Runaway-pagination guard; defaults to MAX_SYNC_PAGES. */\n maxPages?: number;\n /**\n * Search-index refresh, run after a digest is (re)written so the durable\n * markdown becomes discoverable (AGENTS.md rule 31; the digest lives in the\n * QMD collection root but bypasses the extraction->persist->index pipeline).\n * Host-agnostic: the caller injects the core index seam (SearchBackend.update).\n * Best-effort — a failure never fails the sync (rows/digest are durable and\n * index on the next update); it surfaces as `reindexError`.\n */\n afterWrites?: (signal?: AbortSignal) => Promise<void>;\n}\n\nexport interface ActivitySyncResult {\n machine: string;\n fetched: number;\n inserted: number;\n duplicates: number;\n cursor: string | null;\n digestWritten: boolean;\n /** Set when the post-write reindex threw; the sync still succeeded. */\n reindexError?: string;\n}\n\nfunction snapshotForMachine(snapshot: ActivitySnapshot, machine: string): ActivitySnapshot {\n return {\n machine,\n capturedAtUtc: snapshot.capturedAtUtc,\n app: snapshot.app,\n windowTitle: snapshot.windowTitle,\n ...(snapshot.browserUrl === undefined ? {} : { browserUrl: snapshot.browserUrl }),\n text: snapshot.text,\n textSource: snapshot.textSource,\n contentHash: snapshot.contentHash,\n ...(snapshot.simhash === undefined ? {} : { simhash: snapshot.simhash }),\n };\n}\n\nasync function writeDigestIfChanged(memoryDir: string, date: string, serialized: string): Promise<boolean> {\n const target = activityDigestPath(memoryDir, date);\n try {\n if ((await readFile(target, \"utf8\")) === serialized) return false;\n } catch (error: unknown) {\n if (!(error instanceof Error) || !(\"code\" in error) || error.code !== \"ENOENT\") throw error;\n }\n\n await mkdir(path.dirname(target), { recursive: true, mode: 0o700 });\n const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;\n await writeFile(temporary, serialized, { encoding: \"utf8\", mode: 0o600 });\n await rename(temporary, target);\n return true;\n}\n\n/**\n * Pull one source's local-day snapshots and persist its cursor only after every\n * page and the derived digest are durable. Replays after a partial failure are\n * safe because ActivityStore deduplicates an exact captured snapshot.\n */\nexport async function syncActivitySource(\n source: ActivitySourceClient,\n options: ActivitySyncOptions,\n): Promise<ActivitySyncResult> {\n if (!isValidActivityDate(options.date)) {\n throw new RangeError(`Invalid activity date \"${options.date}\"; expected a real YYYY-MM-DD day.`);\n }\n if (source.machineLabel.trim().length === 0) {\n throw new RangeError(\"activity source machine label must not be empty\");\n }\n const maxPages = options.maxPages ?? MAX_SYNC_PAGES;\n if (!Number.isInteger(maxPages) || maxPages < 1) {\n throw new RangeError(\"activity sync maxPages must be a positive integer\");\n }\n\n const cursorKey = activityCursorKey(source.machineLabel, options.date);\n let cursor = options.store.getCursor(cursorKey);\n let pages = 0;\n let fetched = 0;\n let inserted = 0;\n let duplicates = 0;\n const seenCursors = new Set<string>();\n let completed = false;\n\n while (pages < maxPages) {\n options.signal?.throwIfAborted();\n const page = await source.fetchSnapshots({\n date: options.date,\n timezone: options.timezone,\n cursor,\n signal: options.signal,\n });\n pages += 1;\n fetched += page.snapshots.length;\n\n for (const snapshot of page.snapshots) {\n // Persist every row keyed by its own timestamp: an out-of-window snapshot\n // (replay file, timezone-boundary bug) is durable under its own day and\n // never lost, while the day digest below is a window query so it never\n // pollutes options.date. A malformed instant is rejected loudly here.\n const result = options.store.insertSnapshot(snapshotForMachine(snapshot, source.machineLabel));\n if (result.inserted) inserted += 1;\n else duplicates += 1;\n }\n if (page.nextCursor === null) {\n completed = true;\n break;\n }\n if (typeof page.nextCursor !== \"string\" || page.nextCursor.length === 0) {\n throw new TypeError(\"activity source returned an invalid cursor\");\n }\n if (seenCursors.has(page.nextCursor)) {\n throw new Error(\"activity source returned a repeated cursor\");\n }\n seenCursors.add(page.nextCursor);\n cursor = page.nextCursor;\n }\n\n // Only a loop that ran out of its page budget without seeing a terminal\n // null cursor is a runaway; completing on the final allowed page is normal.\n if (!completed) {\n throw new Error(`activity source exceeded ${maxPages} pages`);\n }\n\n // Re-check abort before the durable write section: stop() may have fired after\n // the last page returned, or while this date waited behind digestLocks. An\n // aborted tick must not compose/write a digest or advance the cursor.\n options.signal?.throwIfAborted();\n const digestWritten = await withDigestLock(`${options.memoryDir}\\u0000${options.date}`, async () => {\n options.signal?.throwIfAborted();\n const { startUtc, endUtc } = activityDayWindow(options.date, options.timezone);\n const snapshots = options.store.listSnapshotsForDay(null, startUtc, endUtc);\n const body = composeActivityDigestBody(options.date, options.timezone, snapshots);\n const machines = [...new Set(snapshots.map((snapshot) => snapshot.machine))].sort((a, b) =>\n a < b ? -1 : a > b ? 1 : 0,\n );\n const serialized = serializeActivityDigest(composeActivityDigestMeta(options.date, machines, snapshots, body), body);\n return writeDigestIfChanged(options.memoryDir, options.date, serialized);\n });\n\n options.signal?.throwIfAborted();\n options.store.setCursor(cursorKey, cursor);\n\n // Rows and digest are durable and the cursor has advanced; refresh the search\n // index so the new digest is discoverable (rule 31). Best-effort: a failure\n // is reported, not thrown — the data indexes on the next update.\n let reindexError: string | undefined;\n if (digestWritten && options.afterWrites !== undefined) {\n try {\n await options.afterWrites(options.signal);\n } catch (error: unknown) {\n // reindexError is part of the exported ActivitySyncResult. Keep our\n // controlled QMD strict messages, but reduce opaque backend errors (which\n // can embed absolute paths / loader stacks) to a sanitized name+code.\n reindexError =\n error instanceof Error && error.message.startsWith(\"QMD \")\n ? error.message\n : displayErrorDetail(error) || \"reindex failed\";\n }\n }\n\n return {\n machine: source.machineLabel,\n fetched,\n inserted,\n duplicates,\n cursor,\n digestWritten,\n ...(reindexError === undefined ? {} : { reindexError }),\n };\n}\n","import { validateActivityBaseUrl } from \"./config.js\";\nimport { displayErrorDetail } from \"../runtime/better-sqlite.js\";\nimport type { ActivitySnapshot, ActivitySnapshotPage, ActivitySourceCheck, ActivitySourceClient } from \"./types.js\";\n\n/** Abort a stalled capture-daemon request rather than hang the whole sync. */\nconst DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\n\nexport interface ActivityHttpSourceClientOptions {\n machineLabel: string;\n baseUrl: string;\n token?: string;\n /** Per-request timeout in ms; defaults to DEFAULT_REQUEST_TIMEOUT_MS. */\n timeoutMs?: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction stringField(value: Record<string, unknown>, field: string): string {\n const result = value[field];\n if (typeof result !== \"string\" || result.length === 0) {\n throw new TypeError(`activity source response has no valid ${field}`);\n }\n return result;\n}\n\n/**\n * Content fields (app/windowTitle/text) may be legitimately empty: a foreground\n * window can be untitled or have no extractable text. Require the string TYPE\n * (a missing/non-string field still fails loudly) but tolerate \"\", matching the\n * durable store's contract — else a blank window poisons the day cursor by\n * failing every replay of the same page.\n */\nfunction contentStringField(value: Record<string, unknown>, field: string): string {\n const result = value[field];\n if (typeof result !== \"string\") {\n throw new TypeError(`activity source response has invalid ${field}`);\n }\n return result;\n}\n\nfunction optionalStringField(value: Record<string, unknown>, field: string): string | undefined {\n const result = value[field];\n if (result === undefined || result === null) return undefined;\n if (typeof result !== \"string\") throw new TypeError(`activity source response has invalid ${field}`);\n return result;\n}\n\nfunction snapshotFromWire(value: unknown, machine: string): ActivitySnapshot {\n if (!isRecord(value)) throw new TypeError(\"activity source returned a non-object snapshot\");\n const textSource = stringField(value, \"textSource\");\n if (textSource !== \"ax\" && textSource !== \"ocr\") {\n throw new TypeError(\"activity source response has invalid textSource\");\n }\n const browserUrl = optionalStringField(value, \"browserUrl\");\n const simhash = optionalStringField(value, \"simhash\");\n return {\n machine,\n capturedAtUtc: stringField(value, \"capturedAtUtc\"),\n app: contentStringField(value, \"app\"),\n windowTitle: contentStringField(value, \"windowTitle\"),\n ...(browserUrl === undefined ? {} : { browserUrl }),\n text: contentStringField(value, \"text\"),\n textSource,\n contentHash: stringField(value, \"contentHash\"),\n ...(simhash === undefined ? {} : { simhash }),\n };\n}\n\nfunction responsePage(value: unknown, machine: string): ActivitySnapshotPage {\n if (!isRecord(value) || !Array.isArray(value.snapshots)) {\n throw new TypeError(\"activity source response has no snapshots array\");\n }\n // A missing/undefined nextCursor (field omitted, not JSON null) is normal\n // end-of-pagination — only a present non-string value is invalid.\n const nextCursor = value.nextCursor === undefined ? null : value.nextCursor;\n if (nextCursor !== null && typeof nextCursor !== \"string\") {\n throw new TypeError(\"activity source response has invalid nextCursor\");\n }\n return {\n snapshots: value.snapshots.map((snapshot) => snapshotFromWire(snapshot, machine)),\n nextCursor,\n };\n}\n\nfunction requestUrl(baseUrl: string, path: string, query: Record<string, string | undefined>): string {\n const url = new URL(path, baseUrl);\n const parts = Object.entries(query)\n .filter((entry): entry is [string, string] => entry[1] !== undefined)\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`);\n url.search = parts.join(\"&\");\n return url.toString();\n}\n\nexport class ActivityHttpSourceClient implements ActivitySourceClient {\n readonly machineLabel: string;\n private readonly baseUrl: string;\n private readonly token: string | undefined;\n private readonly timeoutMs: number;\n\n constructor(options: ActivityHttpSourceClientOptions) {\n if (options.machineLabel.trim().length === 0) throw new RangeError(\"activity source machine label must not be empty\");\n if (options.timeoutMs !== undefined && (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)) {\n throw new RangeError(\"activity source timeoutMs must be a positive number\");\n }\n this.machineLabel = options.machineLabel;\n this.baseUrl = validateActivityBaseUrl(options.baseUrl).toString();\n this.token = options.token;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n }\n\n private async request(url: string, signal?: AbortSignal): Promise<Response> {\n // Bound every request so a stalled daemon aborts instead of hanging the\n // sync forever; honor the caller's signal too when one is supplied.\n const timeout = AbortSignal.timeout(this.timeoutMs);\n const composed = signal === undefined ? timeout : AbortSignal.any([signal, timeout]);\n const response = await fetch(url, {\n headers: this.token === undefined ? undefined : { authorization: `Bearer ${this.token}` },\n signal: composed,\n });\n if (!response.ok) throw new Error(`activity source HTTP ${response.status}`);\n return response;\n }\n\n async verify(signal?: AbortSignal): Promise<ActivitySourceCheck> {\n try {\n await this.request(requestUrl(this.baseUrl, \"/v1/health\", {}), signal);\n return { ok: true };\n } catch (error: unknown) {\n // The controlled `HTTP <status>` message is safe to surface verbatim;\n // anything else (network/runtime fetch errors that can embed hostnames or\n // absolute paths) is reduced to a sanitized name+code via\n // displayErrorDetail, matching the wearables operator-facing path.\n const detail =\n error instanceof Error && error.message.startsWith(\"activity source HTTP \")\n ? error.message.slice(\"activity source \".length)\n : displayErrorDetail(error) || \"request failed\";\n return { ok: false, detail };\n }\n }\n\n async fetchSnapshots(opts: {\n date: string;\n timezone: string;\n cursor?: string | null;\n signal?: AbortSignal;\n }): Promise<ActivitySnapshotPage> {\n const response = await this.request(\n requestUrl(this.baseUrl, \"/v1/snapshots\", {\n date: opts.date,\n timezone: opts.timezone,\n cursor: opts.cursor ?? undefined,\n }),\n opts.signal,\n );\n return responsePage(await response.json(), this.machineLabel);\n }\n}\n","/**\n * Host-agnostic activity sync runner (issue #1900).\n *\n * Bridges parsed `config.activity` to the durable sync pipeline: the parser\n * (config.ts) validates settings but never contacts a daemon, and\n * `syncActivitySource` (pipeline.ts) syncs one source for one day. This runner\n * is the missing middle — it instantiates one `ActivityHttpSourceClient` per\n * configured source from the parsed config and drives a sync across the\n * `syncDays` local-day window, but ONLY when the feature is enabled.\n *\n * The feature is master default-off: source config alone never starts a sync.\n * When `config.enabled` is false the runner opens no store, builds no client,\n * and makes no HTTP call or write — it returns a disabled summary. This mirrors\n * `runLiveConnectorsOnce`'s disabled-vs-result reporting so a host scheduler\n * (CLI, cron) can trigger it through the runtime with no OpenClaw imports.\n *\n * Cursor semantics are delegated wholesale to `syncActivitySource`: the\n * per-machine cursor advances only after every page and the digest are durable,\n * so a runtime failure (or an aborted stop) leaves the cursor unadvanced and a\n * restart resumes from the persisted position. A per-source failure is captured\n * as an error item and never aborts the other sources or the host flow.\n */\n\nimport { ActivityStore } from \"./store.js\";\nimport { syncActivitySource } from \"./pipeline.js\";\nimport { ActivityHttpSourceClient } from \"./source-client.js\";\nimport { displayErrorDetail } from \"../runtime/better-sqlite.js\";\nimport type { ActivityConfig, ActivitySourceClient, ActivitySourceConfig } from \"./types.js\";\n\ntype ActivityNow = Date | (() => Date);\n\n/** One configured source's aggregate result across the synced day window. */\nexport interface ActivitySourceRunItem {\n machineLabel: string;\n /** True when at least one day synced durably (no error). */\n ran: boolean;\n fetched: number;\n inserted: number;\n duplicates: number;\n digestsWritten: number;\n /** Persisted cursor after the run; unadvanced from prior on failure. */\n cursor: string | null;\n /** Set when the source failed; distinguishes a fault from an empty page. */\n error?: string;\n /**\n * Set when a durable day synced but its post-digest search reindex failed.\n * The rows/digest/cursor are committed; the index is stale until the next\n * refresh, so this is a signal (not a sync failure) for the scheduler.\n */\n reindexError?: string;\n}\n\nexport interface ActivitySyncRunSummary {\n ranAt: string;\n /** False means the feature is off: no client built, no HTTP, no write. */\n enabled: boolean;\n ranCount: number;\n errorCount: number;\n /** Sources whose durable sync succeeded but whose search reindex failed. */\n reindexErrorCount: number;\n totalInserted: number;\n results: ActivitySourceRunItem[];\n}\n\nexport interface ActivitySyncRunOptions {\n config: ActivityConfig;\n memoryDir: string;\n /** Injectable store; opened from memoryDir when omitted (and closed here). */\n store?: ActivityStore;\n /** Clock injection for deterministic day-window resolution in tests. */\n now?: ActivityNow;\n /** Abort a run in flight (stop); an aborted source leaves its cursor intact. */\n signal?: AbortSignal;\n /** Injectable client factory; builds an ActivityHttpSourceClient by default. */\n createSourceClient?: (source: ActivitySourceConfig) => ActivitySourceClient;\n /**\n * Search-index refresh forwarded to each source sync as `afterWrites`, run\n * after a digest is (re)written so it becomes discoverable (rule 31).\n * Host wires the core index seam (SearchBackend.update); best-effort.\n */\n reindexSearch?: (signal?: AbortSignal) => Promise<void>;\n}\n\n/** Format a Date as YYYY-MM-DD in an IANA timezone (local calendar day). */\nfunction localDateInTimezone(instant: Date, timezone: string): string {\n try {\n const parts = new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).formatToParts(instant);\n const get = (type: string): string => parts.find((part) => part.type === type)?.value ?? \"\";\n return `${get(\"year\")}-${get(\"month\")}-${get(\"day\")}`;\n } catch {\n return instant.toISOString().slice(0, 10);\n }\n}\n\n/** Previous calendar date via pure date arithmetic (no DST wall-clock drift). */\nfunction previousIsoDate(date: string): string {\n const parsed = new Date(`${date}T00:00:00Z`);\n parsed.setUTCDate(parsed.getUTCDate() - 1);\n return parsed.toISOString().slice(0, 10);\n}\n\n/**\n * The local-day window to sync, oldest first: today back `syncDays` calendar\n * days in the configured timezone. Walking by calendar days (not fixed 24h\n * intervals) keeps the window correct across DST transitions.\n */\nexport function resolveActivitySyncDates(syncDays: number, timezone: string, now: Date): string[] {\n const dates: string[] = [];\n let cursor = localDateInTimezone(now, timezone);\n for (let count = 0; count < syncDays; count++) {\n dates.unshift(cursor);\n cursor = previousIsoDate(cursor);\n }\n return dates;\n}\n\n/**\n * Operator-facing detail for a per-source/per-day sync fault. Our own\n * controlled messages (activity.../Invalid activity...) are informative and\n * safe to surface; opaque network/runtime errors can embed hostnames or paths,\n * so they are reduced to a sanitized name+code (matches ActivityHttpSourceClient.verify).\n */\nfunction sanitizedSyncError(error: unknown): string {\n return error instanceof Error && /^(activity|Invalid activity)/.test(error.message)\n ? error.message\n : displayErrorDetail(error) || \"activity sync failed\";\n}\n\n/**\n * Run one activity sync pass across every configured source. Disabled config\n * short-circuits before any store/client/HTTP work; an enabled config syncs\n * each source across the `syncDays` window and isolates per-source faults.\n */\nexport async function runActivitySyncOnce(options: ActivitySyncRunOptions): Promise<ActivitySyncRunSummary> {\n const ranAt = typeof options.now === \"function\" ? options.now() : options.now ?? new Date();\n\n if (!options.config.enabled) {\n return {\n ranAt: ranAt.toISOString(),\n enabled: false,\n ranCount: 0,\n errorCount: 0,\n reindexErrorCount: 0,\n totalInserted: 0,\n results: [],\n };\n }\n\n const dates = resolveActivitySyncDates(options.config.syncDays, options.config.timezone, ranAt);\n const createSourceClient =\n options.createSourceClient ??\n ((source: ActivitySourceConfig): ActivitySourceClient =>\n new ActivityHttpSourceClient({\n machineLabel: source.machineLabel,\n baseUrl: source.baseUrl,\n ...(source.token === undefined ? {} : { token: source.token }),\n }));\n const ownStore = options.store === undefined;\n const store = options.store ?? ActivityStore.open(options.memoryDir);\n const results: ActivitySourceRunItem[] = [];\n\n try {\n for (const sourceConfig of options.config.sources) {\n const item: ActivitySourceRunItem = {\n machineLabel: sourceConfig.machineLabel,\n ran: false,\n fetched: 0,\n inserted: 0,\n duplicates: 0,\n digestsWritten: 0,\n // Cursors are now per (machine, date); report the last synced day's\n // cursor. Null until a day advances it.\n cursor: null,\n };\n try {\n const client = createSourceClient(sourceConfig);\n for (const date of dates) {\n try {\n const result = await syncActivitySource(client, {\n date,\n timezone: options.config.timezone,\n memoryDir: options.memoryDir,\n store,\n signal: options.signal,\n ...(options.reindexSearch === undefined ? {} : { afterWrites: options.reindexSearch }),\n });\n item.fetched += result.fetched;\n item.inserted += result.inserted;\n item.duplicates += result.duplicates;\n if (result.digestWritten) item.digestsWritten += 1;\n item.cursor = result.cursor;\n // A day that synced durably counts as ran, even if a later day in\n // the window fails (ran = at least one durable day).\n item.ran = true;\n // The day's rows/digest/cursor are durable; a failed post-digest\n // reindex is a separate, already-sanitized signal (not a sync\n // failure) so the scheduler can observe a stale index.\n if (result.reindexError !== undefined) item.reindexError = result.reindexError;\n } catch (dateError) {\n // One bad day (a malformed historical snapshot, a per-day daemon\n // error) must not starve the remaining dates — record it and move\n // on. Dates are processed oldest-first, so continuing keeps today's\n // sync alive when an old backfill day is broken. An abort, though,\n // halts the whole run.\n item.error = sanitizedSyncError(dateError);\n if (options.signal?.aborted) break;\n }\n }\n } catch (error) {\n // Client construction failed: no date could run for this source.\n item.error = sanitizedSyncError(error);\n }\n results.push(item);\n // An abort halts the WHOLE run: the in-flight source above is recorded\n // (its fast-failing attempt reports an error), but no later source builds\n // a client or contacts a daemon — teardown settles promptly.\n if (options.signal?.aborted) break;\n }\n } finally {\n if (ownStore) store.close();\n }\n\n return {\n ranAt: ranAt.toISOString(),\n enabled: true,\n ranCount: results.filter((item) => item.ran).length,\n errorCount: results.filter((item) => item.error !== undefined).length,\n reindexErrorCount: results.filter((item) => item.reindexError !== undefined).length,\n totalInserted: results.reduce((sum, item) => sum + item.inserted, 0),\n results,\n };\n}\n","/**\n * In-process periodic driver for activity (screen-capture) sync (issue #1900).\n *\n * The parser (config.ts) validates `config.activity` and the runner\n * (runner.ts) performs one durable sync pass, but nothing drove that pass on a\n * cadence — parsed config alone never synced. This scheduler is the missing\n * link: parser -> scheduler -> durable sync. It registers a repeating timer\n * that invokes the runner, and cancels it on stop.\n *\n * Master default-off: when `config.activity.enabled` is false, `start()`\n * registers no timer and never invokes — source config alone starts nothing.\n * `stop()` clears the timer and latches so no later tick invokes, which is what\n * a runtime teardown (or a config disable + restart) needs. Ticks are\n * overlap-guarded (a slow sync never stacks) and never throw out of the timer.\n *\n * Host-agnostic: the timer and clock are injectable, so a host schedules\n * through this without any OpenClaw import, and tests drive it deterministically.\n */\n\nimport { runActivitySyncOnce, type ActivitySyncRunSummary } from \"./runner.js\";\nimport type { ActivityConfig } from \"./types.js\";\n\n/**\n * Default sync cadence: 15 minutes. Issue #1899 contracts\n * `activity.autoSyncIntervalMinutes` with a default of 15, so the effective\n * default interval is 15 * 60_000 = 900_000 ms. Overridable via `intervalMs`.\n */\nexport const ACTIVITY_SYNC_DEFAULT_INTERVAL_MS = 900_000;\n\n/** An opaque timer handle from whichever timer implementation is injected. */\ntype TimerHandle = unknown;\n\nexport interface ActivitySyncSchedulerOptions {\n config: ActivityConfig;\n memoryDir: string;\n /** Cadence in ms; defaults to ACTIVITY_SYNC_DEFAULT_INTERVAL_MS. */\n intervalMs?: number;\n /** The sync pass to run each tick; defaults to the durable runner. */\n invoke?: (signal?: AbortSignal) => Promise<ActivitySyncRunSummary>;\n /** Search-index refresh forwarded to the default runner as `reindexSearch`. */\n reindexSearch?: (signal?: AbortSignal) => Promise<void>;\n /** Timer factory (injectable for tests); defaults to an unref'd setInterval. */\n setTimer?: (fn: () => void, ms: number) => TimerHandle;\n /** Timer canceller matching setTimer; defaults to clearInterval. */\n clearTimer?: (handle: TimerHandle) => void;\n /** Called with each successful run's summary (telemetry/tests). */\n onRun?: (summary: ActivitySyncRunSummary) => void;\n /** Called when a tick's sync rejects; defaults to swallowing (best-effort). */\n onError?: (error: unknown) => void;\n}\n\nexport interface ActivitySyncRegistration {\n /** True when a periodic timer was armed (feature enabled). */\n registered: boolean;\n /** The cadence the timer runs at (reported even when not registered). */\n intervalMs: number;\n}\n\n/**\n * Registers/cancels a periodic activity sync. One instance per memory dir;\n * `start()` and `stop()` are both idempotent.\n */\nexport class ActivitySyncScheduler {\n private readonly config: ActivityConfig;\n private readonly memoryDir: string;\n private readonly intervalMs: number;\n private readonly invoke: (signal?: AbortSignal) => Promise<ActivitySyncRunSummary>;\n private readonly setTimer: (fn: () => void, ms: number) => TimerHandle;\n private readonly clearTimer: (handle: TimerHandle) => void;\n private readonly onRun: ((summary: ActivitySyncRunSummary) => void) | undefined;\n private readonly onError: ((error: unknown) => void) | undefined;\n\n private handle: TimerHandle = null;\n private stopped = false;\n private inFlight = false;\n private inFlightPromise: Promise<void> | null = null;\n private readonly abortController = new AbortController();\n\n constructor(options: ActivitySyncSchedulerOptions) {\n this.config = options.config;\n this.memoryDir = options.memoryDir;\n this.intervalMs = options.intervalMs ?? ACTIVITY_SYNC_DEFAULT_INTERVAL_MS;\n this.invoke =\n options.invoke ??\n ((signal) =>\n runActivitySyncOnce({\n config: this.config,\n memoryDir: this.memoryDir,\n signal,\n ...(options.reindexSearch === undefined ? {} : { reindexSearch: options.reindexSearch }),\n }));\n this.setTimer =\n options.setTimer ??\n ((fn, ms) => {\n const timer = setInterval(fn, ms);\n // Never keep the host process alive solely for the sync cadence.\n timer.unref?.();\n return timer;\n });\n this.clearTimer =\n options.clearTimer ?? ((handle) => clearInterval(handle as NodeJS.Timeout));\n this.onRun = options.onRun;\n this.onError = options.onError;\n }\n\n /**\n * Arm the periodic sync. Disabled config (default-off) registers nothing.\n * Idempotent: a second call while running (or after stop) re-arms nothing.\n */\n start(): ActivitySyncRegistration {\n if (this.handle !== null || this.stopped || !this.config.enabled) {\n return { registered: this.handle !== null, intervalMs: this.intervalMs };\n }\n this.handle = this.setTimer(() => this.tick(), this.intervalMs);\n return { registered: true, intervalMs: this.intervalMs };\n }\n\n /**\n * Cancel the periodic sync and abort any in-flight tick, then resolve once it\n * has unwound. The abort + timer-clear + stop-latch happen synchronously, so\n * even a caller that cannot await (a sync teardown) still signals the running\n * sync to stop; async callers can await the returned drain. Idempotent.\n */\n async stop(): Promise<void> {\n this.stopped = true;\n this.abortController.abort();\n if (this.handle !== null) {\n this.clearTimer(this.handle);\n this.handle = null;\n }\n await this.inFlightPromise?.catch(() => undefined);\n }\n\n private tick(): void {\n // Latched-stop and overlap guards: a callback that fires after stop(), or\n // while a prior slow sync is still running, must not invoke.\n if (this.stopped || this.inFlight) return;\n this.inFlight = true;\n this.inFlightPromise = this.invoke(this.abortController.signal)\n .then((summary) => this.onRun?.(summary))\n .catch((error) => this.onError?.(error))\n .finally(() => {\n this.inFlight = false;\n this.inFlightPromise = null;\n });\n }\n}\n","/**\n * Activity (screen-capture) sync lifecycle owner (issue #1900).\n *\n * The MaintenanceScheduler used to inline the arm / re-arm / teardown wiring\n * for the periodic activity sync, which made the lifecycle invariants easy to\n * break one edit at a time. This module centralizes them in one testable place:\n *\n * - Re-arm and teardown BOTH stop any prior scheduler first, so a repeated\n * registration never orphans a running interval.\n * - An aborted deferred-init signal means teardown began. `destroy()` aborts\n * the init signal *before* it disposes, so an aborted signal here also\n * implies `dispose()` may already have latched teardown. `register()` never\n * arms nor clears the latch while aborted — even when the abort lands\n * mid-drain — so a late re-init cannot resurrect a torn-down instance.\n * - The teardown latch gates the reindex-failure retry, so a draining tick's\n * `onRun` cannot queue QMD maintenance after teardown.\n * - Activity digests + SQLite are written in the clear, so the registrar\n * refuses to arm under a secure store rather than silently bypass opted-in\n * at-rest encryption.\n *\n * Host-agnostic: the scheduler factory is injectable, so tests drive the whole\n * lifecycle against a fake scheduler without real timers.\n */\n\nimport { refreshActivityIndex, type ActivityIndexRefresher } from \"./reindex.js\";\nimport type { ActivitySyncRunSummary } from \"./runner.js\";\nimport { ActivitySyncScheduler, type ActivitySyncSchedulerOptions } from \"./scheduler.js\";\nimport type { ActivityConfig } from \"./types.js\";\nimport { log } from \"../logger.js\";\n\n/** The scheduler surface the registrar drives (injectable for tests). */\nexport interface ActivitySyncSchedulerLike {\n start(): unknown;\n stop(): Promise<void>;\n}\n\nexport interface ActivitySyncRegistrarDeps {\n /** Activity sync settings; the master default-off flag lives here. */\n readonly config: ActivityConfig;\n /** Memory root the digests + SQLite store live under. */\n readonly memoryDir: string;\n /** QMD collection the day digest is (strictly) reindexed into. */\n readonly qmdCollection: string;\n /**\n * True when at-rest encryption is configured. Activity writes are not yet\n * encrypted, so the registrar refuses to arm rather than leak plaintext.\n */\n readonly secureStoreEnabled: boolean;\n /** Live QMD accessor — the host may swap backends after construction. */\n readonly getQmd: () => ActivityIndexRefresher;\n /** Debounced/singleflighted QMD retry used when a tick's reindex fails. */\n readonly requestReindexRetry: () => void;\n /** Scheduler factory (injectable for tests); defaults to the real scheduler. */\n readonly createScheduler?: (options: ActivitySyncSchedulerOptions) => ActivitySyncSchedulerLike;\n}\n\nexport class ActivitySyncRegistrar {\n private scheduler: ActivitySyncSchedulerLike | null = null;\n /** Latched by dispose() so a draining tick's onRun cannot re-arm maintenance. */\n private disposed = false;\n private readonly createScheduler: (options: ActivitySyncSchedulerOptions) => ActivitySyncSchedulerLike;\n\n constructor(private readonly deps: ActivitySyncRegistrarDeps) {\n this.createScheduler = deps.createScheduler ?? ((options) => new ActivitySyncScheduler(options));\n }\n\n /** True while a scheduler is armed (test/telemetry observability). */\n get armed(): boolean {\n return this.scheduler !== null;\n }\n\n /**\n * Arm (or re-arm) the periodic sync for the current lifecycle. Idempotent and\n * safe to call repeatedly; always stops a prior scheduler first.\n */\n async register(signal: AbortSignal): Promise<void> {\n // Re-arm or teardown: stop any prior scheduler before anything else, so a\n // repeated registration never orphans a running interval.\n const prior = this.scheduler;\n this.scheduler = null;\n await prior?.stop();\n // Teardown began (possibly during the drain above). Never arm nor clear the\n // latch: dispose() may already have set it, and it must stay set.\n if (signal.aborted) return;\n // Opted-in at-rest encryption must not be silently bypassed: activity\n // digests + SQLite are written in the clear, so refuse to run under it.\n if (this.deps.secureStoreEnabled && this.deps.config.enabled) {\n log.warn(\n \"activity sync disabled: secure store is enabled but activity digests/SQLite are not encrypted at rest\",\n );\n return;\n }\n // Committed to a fresh lifecycle: clear the latch so this scheduler's\n // reindex retries fire again.\n this.disposed = false;\n try {\n const scheduler = this.createScheduler({\n config: this.deps.config,\n memoryDir: this.deps.memoryDir,\n intervalMs: this.deps.config.autoSyncIntervalMinutes * 60_000,\n // Strict refresh after each digest write (rule 31): bypasses the\n // min-interval gate and throws on a real failure.\n reindexSearch: (sig) => refreshActivityIndex(this.deps.getQmd(), this.deps.qmdCollection, sig),\n // A failed reindex (QMD down) leaves the next unchanged-digest tick\n // skipping afterWrites, so route the retry through the durable,\n // throttled QMD path — but never after teardown.\n onRun: (summary: ActivitySyncRunSummary) => {\n if (summary.reindexErrorCount > 0 && !this.disposed) {\n log.warn(\n `activity sync: ${summary.reindexErrorCount} source(s) had a failed reindex; queuing a maintenance retry`,\n );\n this.deps.requestReindexRetry();\n }\n },\n });\n this.scheduler = scheduler;\n scheduler.start();\n // Close the race where abort fires between the guard above and start().\n if (signal.aborted) {\n await scheduler.stop();\n this.scheduler = null;\n }\n } catch (err) {\n log.debug(`activity sync scheduler start failed (non-fatal): ${err}`);\n }\n }\n\n /**\n * Latch teardown and drain the scheduler. Latching first ensures a draining\n * tick's onRun cannot queue a retry; the drain is awaited so no in-flight\n * tick keeps writing after teardown.\n */\n async dispose(): Promise<void> {\n this.disposed = true;\n const drain = this.scheduler?.stop();\n this.scheduler = null;\n await drain;\n }\n}\n"],"mappings":"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","names":["mkdir","readFile","writeFile","path","id","mkdir","path","mkdir","path","isRecord","path","path","mkdir","writeFile","readFile"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/maintenance/rebuild-memory-lifecycle-ledger.ts"],"sourcesContent":["import path from \"node:path\";\nimport { stat } from \"node:fs/promises\";\nimport { StorageManager } from \"../storage.js\";\nimport { log } from \"../logger.js\";\nimport { displayErrorDetail } from \"../runtime/better-sqlite.js\";\nimport type { MemoryLifecycleEvent } from \"../types.js\";\nimport { toBackupStamp } from \"./backup-stamp.js\";\nimport { copyExistingFileToBackup, writeFileAtomically } from \"./atomic-file.js\";\nimport {\n buildLifecycleEventsForMemory,\n compareMemoryLifecycleEvents,\n sortMemoryLifecycleEvents,\n memoryLifecycleLedgerLockPath,\n MEMORY_LIFECYCLE_LEDGER_LOCK_STALE_MS,\n} from \"../memory-lifecycle-ledger-utils.js\";\nimport { withHeldFileLock, type HeldFileLockOptions, type HeldFileLockController } from \"../utils/serialize-mutations.js\";\nimport { probeEncryptedRegularFileHeader } from \"../secure-store/secure-fs.js\";\n\n/**\n * Event types `buildLifecycleEventsForMemory` reconstructs from frontmatter.\n * Everything else in the ledger is append-only history with no frontmatter\n * equivalent and must be carried over by a preserving rebuild (issue #1910).\n */\nconst FRONTMATTER_DERIVED_EVENT_TYPES: Record<string, true> = {\n created: true,\n updated: true,\n superseded: true,\n archived: true,\n};\n\nexport interface RebuildMemoryLifecycleLedgerOptions {\n memoryDir: string;\n dryRun?: boolean;\n now?: Date;\n /**\n * Active storage context (issue #1910). When provided, memories are read\n * through it (so encrypted-at-rest memories in a secure-store deployment are\n * decrypted with the live key) and the rebuilt ledger is rewritten through\n * its secure writer (so it stays encrypted at rest). Its `dir` MUST equal\n * `memoryDir`. When omitted, a fresh plaintext `StorageManager` is used and\n * the ledger is written as plaintext — the CLI default.\n */\n storage?: StorageManager;\n /**\n * Preserve append-only lifecycle events that have no frontmatter equivalent\n * (issue #1910). `buildLifecycleEventsForMemory` only reconstructs\n * `created`/`updated`/`superseded`/`archived` from frontmatter, so a bare\n * rebuild silently drops history like `explicit_capture_accepted`,\n * `explicit_capture_queued`, `imported`, `promoted`, `merged`, `restored`,\n * and `rejected`. When true, the existing ledger's non-reconstructable rows\n * are read (via the same secure/permissive `readAllMemoryLifecycleEvents`\n * path), merged with the rebuilt rows, deduplicated by `eventId`, and sorted\n * canonically. Background auto-compaction sets this so it never loses events;\n * the manual/CLI repair rebuild leaves it off (reconstruct purely from\n * frontmatter).\n */\n preserveExistingEvents?: boolean;\n /**\n * Override the ledger-lock acquisition timing (issue #2033). Only\n * `maxWaitMs`/`pollMs` are honored; `staleMs` stays fixed at\n * `MEMORY_LIFECYCLE_LEDGER_LOCK_STALE_MS`. Used by tests to force the\n * lock-acquisition-failure branch (`acquired=false`) deterministically\n * without a multi-second wait.\n */\n lockOptions?: Pick<HeldFileLockOptions, \"maxWaitMs\" | \"pollMs\">;\n /**\n * Upper bound (bytes) on the rewritten ledger when preserving events (issue\n * #2033). A preserving compaction carries over ALL append-only history, which\n * — if the history alone is large — could leave the rewritten ledger over the\n * whole-file read/decrypt cap (STATE_FILE_MAX_DECRYPT_BYTES), the exact\n * unreadable-ledger failure compaction exists to prevent. When set and the\n * preserved payload would exceed it, the OLDEST rows are dropped from the\n * active ledger until it fits; because the full pre-compaction ledger is\n * copied verbatim to the timestamped backup first, those rows are archived,\n * not lost. Ignored when `preserveExistingEvents` is off (a frontmatter-only\n * rebuild is already bounded by the corpus). `0`/undefined disables bounding.\n */\n maxLedgerBytes?: number;\n}\n\nexport interface SkippedLifecycleBlankIdMemory {\n path: string;\n}\n\nexport interface SkippedDuplicateLifecycleEvent {\n eventId: string;\n keptPath: string;\n skippedPath: string;\n}\n\nexport interface RebuildMemoryLifecycleLedgerResult {\n dryRun: boolean;\n scannedMemories: number;\n rebuiltRows: number;\n outputPath: string;\n backupPath?: string;\n skippedBlankIdMemories: SkippedLifecycleBlankIdMemory[];\n skippedDuplicateEvents: SkippedDuplicateLifecycleEvent[];\n /**\n * Count of existing-ledger rows carried over beyond the frontmatter\n * reconstruction (only set when `preserveExistingEvents` is on; issue #1910):\n * append-only history with no frontmatter equivalent, plus any\n * frontmatter-derived row that raced in after the corpus scan and so was not\n * reproduced by the reconstruction (#2033).\n */\n preservedAppendOnlyRows?: number;\n /**\n * Count of oldest rows dropped from the ACTIVE ledger to keep it under\n * `maxLedgerBytes` (issue #2033). They remain in the verbatim timestamped\n * backup, so this is a bound-and-archive, not a loss. Set only when bounding\n * actually trimmed rows.\n */\n archivedOverflowRows?: number;\n /**\n * Whether the active ledger was actually backed up and rewritten (issue #2033\n * thread PRRT_kwDORJXyws6SExst). A preserving write-mode rebuild that would\n * reproduce the current ledger byte-for-byte (same decrypted content AND same\n * at-rest format) skips the backup+rewrite, so callers can distinguish a real\n * compaction from a no-op and avoid re-archiving an unchanged ledger every\n * interval. `true` for every dry run's non-write preview is never set — only\n * write-mode results carry it; defaults to `false` when no rewrite happened.\n */\n rewritten?: boolean;\n}\n\nfunction isNodeError(err: unknown): err is NodeJS.ErrnoException {\n return typeof err === \"object\" && err !== null && \"code\" in err;\n}\n\n/** Serialized on-disk size (bytes, incl. trailing newline) of one ledger row. */\nfunction lifecycleRowBytes(event: MemoryLifecycleEvent): number {\n return Buffer.byteLength(`${JSON.stringify(event)}\\n`, \"utf8\");\n}\n\n/**\n * Keep the NEWEST canonically-ordered events whose serialized payload fits\n * strictly under `cap` bytes (issue #2033). Recency is by `timestamp` (newest\n * first), tie-broken by the canonical comparator, so a large append-only\n * history is trimmed to its most recent rows rather than to a single memoryId\n * group (the ledger's on-disk order is by memoryId then time).\n *\n * A row too large to fit the remaining budget is SKIPPED, and scanning\n * continues to older rows that still fit — one oversized (or future-dated,\n * hence recency-first) row cannot truncate the entire older history. The kept\n * subset is returned in the input's canonical order; the caller relies on the\n * verbatim backup to retain the dropped rows.\n */\nexport function boundLifecycleEventsToByteCap(\n events: MemoryLifecycleEvent[],\n cap: number,\n): { kept: MemoryLifecycleEvent[]; dropped: number } {\n if (!(cap > 0)) return { kept: events, dropped: 0 };\n const sizes = new Map<MemoryLifecycleEvent, number>();\n let total = 0;\n for (const event of events) {\n const bytes = lifecycleRowBytes(event);\n sizes.set(event, bytes);\n total += bytes;\n }\n if (total <= cap) return { kept: events, dropped: 0 };\n const byRecency = [...events].sort((a, b) => {\n if (a.timestamp !== b.timestamp) return b.timestamp.localeCompare(a.timestamp);\n return -compareMemoryLifecycleEvents(a, b);\n });\n const keep = new Set<MemoryLifecycleEvent>();\n let running = 0;\n for (const event of byRecency) {\n const bytes = sizes.get(event)!;\n if (running + bytes > cap) continue; // skip this row, keep scanning older rows that still fit\n running += bytes;\n keep.add(event);\n }\n const kept = events.filter((event) => keep.has(event));\n return { kept, dropped: events.length - kept.length };\n}\n\nexport async function backupExistingLedger(\n memoryDir: string,\n outputPath: string,\n now: Date,\n): Promise<string | undefined> {\n try {\n await stat(outputPath);\n } catch (err) {\n if (isNodeError(err) && err.code === \"ENOENT\") {\n return undefined;\n }\n throw err;\n }\n\n const backupPath = path.join(\n memoryDir,\n \"archive\",\n \"memory-lifecycle-ledger\",\n toBackupStamp(now),\n \"state\",\n \"memory-lifecycle-ledger.jsonl\",\n );\n return backupPath;\n}\n\nexport async function rebuildMemoryLifecycleLedger(\n options: RebuildMemoryLifecycleLedgerOptions,\n): Promise<RebuildMemoryLifecycleLedgerResult> {\n const dryRun = options.dryRun !== false;\n const now = options.now ?? new Date();\n const outputPath = path.join(options.memoryDir, \"state\", \"memory-lifecycle-ledger.jsonl\");\n const storage = options.storage ?? new StorageManager(options.memoryDir);\n if (options.storage && path.resolve(storage.dir) !== path.resolve(options.memoryDir)) {\n throw new Error(\n `rebuildMemoryLifecycleLedger: storage.dir (${storage.dir}) must match `\n + `memoryDir (${options.memoryDir})`,\n );\n }\n const secureRewrite = options.storage !== undefined;\n const tiers = [\n await storage.readAllMemories(),\n await storage.readAllColdMemories(),\n await storage.readArchivedMemories(),\n ];\n const allMemories = tiers.flat();\n const skippedBlankIdMemories: SkippedLifecycleBlankIdMemory[] = [];\n const eventCandidates: Array<{ event: MemoryLifecycleEvent; path: string }> = [];\n for (const tier of tiers) {\n for (const memory of [...tier].sort((left, right) => left.path.localeCompare(right.path))) {\n if (!memory.frontmatter.id.trim()) {\n skippedBlankIdMemories.push({ path: memory.path });\n log.warn(`lifecycle ledger rebuild skipped blank memory id: path=${memory.path}`);\n continue;\n }\n for (const event of buildLifecycleEventsForMemory(memory)) {\n eventCandidates.push({ event, path: memory.path });\n }\n }\n }\n\n const orderedEvents = sortMemoryLifecycleEvents(eventCandidates.map((candidate) => candidate.event));\n const candidateByEvent = new Map(\n eventCandidates.map((candidate) => [candidate.event, candidate]),\n );\n const keptByEventId = new Map<string, { event: MemoryLifecycleEvent; path: string }>();\n const events: MemoryLifecycleEvent[] = [];\n const skippedDuplicateEvents: SkippedDuplicateLifecycleEvent[] = [];\n for (const event of orderedEvents) {\n const candidate = candidateByEvent.get(event)!;\n const kept = keptByEventId.get(event.eventId);\n if (kept) {\n skippedDuplicateEvents.push({\n eventId: event.eventId,\n keptPath: kept.path,\n skippedPath: candidate.path,\n });\n log.warn(\n `lifecycle ledger rebuild skipped duplicate event_id=${event.eventId}: `\n + `kept=${kept.path} skipped=${candidate.path}`,\n );\n continue;\n }\n keptByEventId.set(event.eventId, candidate);\n events.push(event);\n }\n\n let finalEvents = events;\n let preservedAppendOnlyRows: number | undefined;\n let archivedOverflowRows: number | undefined;\n let backupPath: string | undefined;\n let rewritten = false;\n // The preserve-read + whole-file rewrite are serialized against concurrent\n // lifecycle appends ONLY in write mode (issue #1910, codex):\n // appendMemoryLifecycleEvents holds this same cross-process lock, so an event\n // appended after the preserve-read but before the atomic rename waits and\n // lands on the compacted ledger instead of being clobbered by the rename. A\n // DRY RUN takes no lock — it reads the ledger read-only to compute the\n // preserve/merge/byte-cap PREVIEW and never rewrites, so it must not block\n // appends (#2033 Cursor). The corpus scan above stays outside the lock in\n // both modes — frontmatter-derived rows come from the memory files.\n const runLedgerCriticalSection = async (\n acquired: boolean,\n lock: HeldFileLockController | null,\n ): Promise<void> => {\n // A WRITE that read or rewrote the ledger without the lock could race a\n // concurrent append/rewrite and lose data, so REFUSE the unlocked fallback\n // withHeldFileLock takes on acquisition timeout (issue #2033 CodeRabbit\n // Critical / codex). A dry run passes lock=null and acquired=true: it never\n // reaches the rewrite, so the unlocked preview read is safe.\n if (!dryRun && !acquired) {\n throw new Error(\n \"lifecycle ledger rebuild aborted: could not acquire the ledger lock within the \"\n + \"acquisition budget; refusing to read or rewrite unlocked so a concurrent append \"\n + \"or compaction cannot be clobbered (issue #1910).\",\n );\n }\n if (options.preserveExistingEvents) {\n // Merge the frontmatter reconstruction (`events`) with the CURRENT ledger\n // so compaction never deletes a lifecycle row that raced the scan (issue\n // #1910, #2033). Read the existing ledger through the uncapped BUFFER\n // path — it decrypts with the live key when `storage` carries the secure\n // context and bypasses the whole-file string decrypt cap, so an oversize\n // encrypted ledger (the exact file compaction must shrink) is still\n // readable here rather than aborting (#2033 Cursor High).\n // preserveExistingEvents means \"never lose history\": if the current\n // ledger genuinely cannot be read (corruption, or a locked required\n // store), ABORT rather than rewrite from frontmatter only and silently\n // drop that history. An absent ledger reads as [] (ENOENT swallowed\n // downstream), so a throw here is always a real read failure.\n let existing: MemoryLifecycleEvent[];\n try {\n existing = await storage.readAllMemoryLifecycleEventsForCompaction();\n } catch (err) {\n throw new Error(\n `lifecycle ledger rebuild aborted: cannot read existing events to preserve `\n + `(${displayErrorDetail(err) || \"unknown error\"})`,\n );\n }\n // The corpus scan (`events`) runs OUTSIDE the lock and is therefore a\n // stale snapshot; `existing` is read here under the lock and is\n // authoritative for what the ledger holds at rewrite time. A\n // frontmatter-derived row (created/updated/superseded/archived) is safe to\n // collapse into the reconstruction ONLY when the scan actually reproduced\n // the same logical event — same (memoryId, eventType, timestamp), which is\n // exactly what the live append stamps (fm.created/updated/...) and what\n // makeRebuiltMemoryLifecycleEvent emits. A frontmatter-derived row whose\n // content key the reconstruction did NOT produce raced in after the scan\n // (a memory created/updated between the scan and this locked read); the\n // reconstruction has no equivalent, so dropping it by eventType alone\n // would delete it. Preserve it instead, deduped by content key so a\n // retried append of the same logical event still collapses to one. Rows\n // that are not frontmatter-derived are append-only history with no\n // reconstruction — always carried over, deduped by eventId in the merge.\n const contentKey = (event: MemoryLifecycleEvent): string =>\n `${event.memoryId}\\u0000${event.eventType}\\u0000${event.timestamp}`;\n const reconstructedKeys = new Set(events.map(contentKey));\n const racedFrontmatterByKey = new Map<string, MemoryLifecycleEvent>();\n const appendOnly: MemoryLifecycleEvent[] = [];\n for (const event of existing) {\n if (!FRONTMATTER_DERIVED_EVENT_TYPES[event.eventType]) {\n appendOnly.push(event);\n continue;\n }\n const key = contentKey(event);\n if (reconstructedKeys.has(key)) continue; // collapsed into reconstruction\n if (!racedFrontmatterByKey.has(key)) racedFrontmatterByKey.set(key, event);\n }\n const mergedById = new Map<string, MemoryLifecycleEvent>();\n for (const event of sortMemoryLifecycleEvents([\n ...events,\n ...racedFrontmatterByKey.values(),\n ...appendOnly,\n ])) {\n if (!mergedById.has(event.eventId)) mergedById.set(event.eventId, event);\n }\n finalEvents = [...mergedById.values()];\n preservedAppendOnlyRows = finalEvents.length - events.length;\n // Bound the rewritten ledger so a preserving compaction can never leave it\n // over the whole-file read/decrypt cap (#2033). This must run whenever a\n // cap is set — NOT only when `existing.length > 0`: an oversized on-disk\n // ledger whose every row fails validation reads as `existing === []`, yet\n // `finalEvents` (the frontmatter reconstruction) can itself exceed the cap.\n // Overflow (oldest) rows are dropped from the active ledger but survive\n // both in the timestamped backup (a verbatim copy when the ledger is\n // plaintext, re-encrypted under the backup path's own AAD when it is\n // encrypted so it still decrypts there, #2033) and, for frontmatter-derived\n // rows, in the memory files they were reconstructed from — so a later full\n // rebuild regenerates them.\n if (options.maxLedgerBytes && options.maxLedgerBytes > 0) {\n const bounded = boundLifecycleEventsToByteCap(finalEvents, options.maxLedgerBytes);\n if (bounded.dropped > 0) {\n finalEvents = bounded.kept;\n archivedOverflowRows = bounded.dropped;\n log.warn(\n `lifecycle ledger rebuild bounded to ${options.maxLedgerBytes} bytes: `\n + `dropped ${bounded.dropped} oldest row(s) from the active ledger `\n + `(preserved in the backup) to stay under the read/decrypt cap.`,\n );\n }\n }\n }\n\n // Re-assert the lock immediately before the destructive active-ledger\n // rewrite. Everything above (the preserve read, merge, sort, byte-cap\n // bound, and JSON serialize) is CPU-bound and blocks the event loop, so the\n // timer heartbeat inside withHeldFileLock cannot refresh the lock's mtime;\n // a peer that judged the lock stale within the 30s window could have broken\n // it and appended to the current ledger. `lock.refresh()` (a) reports the\n // lock LOST so we ABORT rather than rename our compacted file over that\n // peer's append, and (b) when still held, re-stamps the mtime so the bounded\n // write below cannot itself be judged stale mid-rename (issue #1910/#2033).\n const abortIfLockLost = async (): Promise<void> => {\n if (lock && await lock.refresh()) return;\n throw new Error(\n \"lifecycle ledger rebuild aborted: lost the ledger lock during the compaction \"\n + \"rewrite (a peer stale-broke it while the event loop was blocked by CPU-bound \"\n + \"merge/serialize); refusing to clobber a concurrent append (issue #1910/#2033).\",\n );\n };\n if (!dryRun) {\n const payload = finalEvents.map((event) => JSON.stringify(event)).join(\"\\n\");\n const content = payload.length > 0 ? `${payload}\\n` : \"\";\n // No-op skip (#2033 thread PRRT_kwDORJXyws6SExst): a preserving rebuild\n // bounds the ACTIVE ledger to the read/decrypt cap, so a VALID ledger that\n // sits above the configured compaction trigger but below the cap would\n // otherwise be rewritten to identical content and re-archived on every\n // maintenance interval — unbounded growth of archive/memory-lifecycle-ledger\n // plus pointless disk/CPU churn. Skip the backup+rewrite when it would\n // change neither the logical (decrypted) content NOR the at-rest format.\n // The raw read here doubles as the source for the encrypted verbatim backup\n // below, so the current ledger is read at most once. An absent ledger reads\n // as ENOENT (a create is always a real change) and any other read/probe\n // error propagates rather than masking a needed rewrite.\n let currentEncrypted = false;\n let priorRawBuffer: Buffer | null = null;\n try {\n currentEncrypted = await probeEncryptedRegularFileHeader(outputPath);\n priorRawBuffer = await storage.readMemoryLifecycleLedgerRawBufferForCompaction();\n } catch (err) {\n if (!isNodeError(err) || err.code !== \"ENOENT\") throw err;\n }\n // Preserve encryption at rest (#2033). An already-encrypted ledger (or its\n // encrypted backup) MUST be rewritten encrypted even when the\n // `secureStoreEncryptOnWrite` policy is paused — otherwise the compaction\n // silently DOWNGRADES encrypted state to plaintext. Force encryption\n // whenever the current ledger is encrypted and the store is unlocked (a\n // key is available); reaching this point with an encrypted ledger already\n // implies an unlocked store, since the raw read above decrypts and would\n // have thrown under a locked key. A plaintext ledger keeps the ordinary\n // policy (`willEncryptStateWrites`), so existing plaintext behavior holds.\n const forceEncrypt =\n secureRewrite && currentEncrypted && storage.isSecureStoreUnlocked();\n const replacementEncrypted =\n secureRewrite && (storage.willEncryptStateWrites() || forceEncrypt);\n // Compare as buffers, never decoding the raw ledger to one giant string:\n // a multi-hundred-MB decrypted ledger can exceed V8's max string length,\n // so `priorRawBuffer.equals(...)` (byte compare) must be used, NOT\n // `.toString()` (#2033 giant-string invariant). `content` is the freshly\n // serialized replacement, already a string; Buffer.from() reuses it.\n if (\n priorRawBuffer !== null &&\n currentEncrypted === replacementEncrypted &&\n priorRawBuffer.equals(Buffer.from(content, \"utf8\"))\n ) {\n // Nothing to change: leave the active ledger and archive untouched.\n rewritten = false;\n } else {\n const desiredBackup = await backupExistingLedger(options.memoryDir, outputPath, now);\n if (secureRewrite) {\n // Preserve encrypted-at-rest (issue #1910). The BACKUP needs care under\n // secure-store (#2033): when the existing ledger is encrypted, its bytes\n // are path-bound via AAD, so a byte-for-byte copy to the archive path\n // cannot be decrypted there — it would silently orphan the overflow rows\n // a bounded rewrite claims to preserve. Reuse the RAW decrypted ledger\n // content read above (no event parsing, so malformed/truncated/future\n // rows survive exactly as the plaintext verbatim copy keeps them) and\n // re-encrypt it under the backup path's own AAD, yielding a directly\n // decryptable backup that retains every original byte. A PLAINTEXT\n // existing ledger is already readable, so a verbatim copy is correct.\n // Then rewrite the active ledger through the secure writer.\n if (desiredBackup && currentEncrypted) {\n const rawForBackup =\n priorRawBuffer ?? (await storage.readMemoryLifecycleLedgerRawBufferForCompaction());\n await storage.writeMemoryLifecycleLedgerContent(rawForBackup, desiredBackup, forceEncrypt);\n backupPath = desiredBackup;\n } else {\n backupPath = desiredBackup\n ? await copyExistingFileToBackup(outputPath, desiredBackup)\n : undefined;\n }\n await abortIfLockLost();\n await storage.writeMemoryLifecycleLedgerContent(content, undefined, forceEncrypt);\n } else {\n await abortIfLockLost();\n backupPath = await writeFileAtomically(outputPath, content, desiredBackup);\n }\n rewritten = true;\n }\n }\n };\n // Write mode holds the ledger lock across the preserve-read and atomic\n // rewrite so a concurrent append lands on the compacted ledger instead of\n // being clobbered. A dry run computes the SAME preserve/merge/byte-cap\n // preview but WITHOUT the lock (read-only, no rewrite), so `rebuiltRows`,\n // `preservedAppendOnlyRows`, and `archivedOverflowRows` report what a --write\n // would apply while never blocking concurrent appends (#2033 Cursor). A dry\n // run with nothing to preserve touches nothing at all.\n if (!dryRun) {\n await withHeldFileLock(\n memoryLifecycleLedgerLockPath(outputPath),\n { staleMs: MEMORY_LIFECYCLE_LEDGER_LOCK_STALE_MS, ...options.lockOptions },\n runLedgerCriticalSection,\n );\n } else if (options.preserveExistingEvents) {\n await runLedgerCriticalSection(true, null);\n }\n\n return {\n dryRun,\n scannedMemories: allMemories.length,\n rebuiltRows: finalEvents.length,\n outputPath,\n backupPath,\n skippedBlankIdMemories,\n skippedDuplicateEvents,\n preservedAppendOnlyRows,\n archivedOverflowRows,\n rewritten,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/capabilities.ts"],"sourcesContent":["/**\n * CapabilitySet — recall-operation feature gates resolved once, then threaded.\n *\n * Issue #1523 (Phase 1 of epic #1520). Root cause this addresses: 161+\n * scattered `config.<flag>Enabled` reads mean each gate is re-derived at every\n * call site, and reviews keep finding parallel code paths where one branch\n * checks a gate the other forgot (CLAUDE.md rule 39 — the \"gate divergence\"\n * defect class). The fix is to resolve a frozen capability projection ONCE at\n * the top of the recall operation and pass it down explicitly.\n *\n * Scope of THIS module (first migration PR): only the recall-operation-scoped\n * flags below. Flags that are also read in graph construction, writes, CLI, or\n * the summarizer are deliberately deferred to a follow-up so we never leave a\n * single flag half-migrated (some sites on `caps.`, some on `config.`).\n *\n * Field naming: each field is the config flag name with the trailing `Enabled`\n * removed (`recallMmrEnabled` → `recallMmr`).\n *\n * This is plumbing, not a feature — there is deliberately NO `enabled` gate for\n * the CapabilitySet itself (rule 30 governs behavior changes; resolving and\n * threading a capability projection must stay behavior-preserving).\n *\n * Flag-retirement audit (#1780, 2026-07-08): all projected fields across 18\n * capability sets were verified for live consumers. Two dead projections found\n * and removed: recallGraph (RecallEnhancementCapabilitySet — production reads\n * recallGraphEnabled directly via graph-recall.ts) and behaviorLoopAutoTune\n * (ConversationContextCapabilitySet — production reads behaviorLoopAutoTuneEnabled\n * directly via cli.ts). The flags themselves remain alive.\n */\n\nimport type { PluginConfig } from \"./types.js\";\n\n/**\n * Frozen projection of recall-operation feature gates.\n *\n * Every field is `readonly boolean`. The composition that maps a config flag to\n * a capability (including default-when-undefined semantics for optional flags)\n * lives ONLY in {@link resolveCapabilities} — call sites must read the\n * capability, never re-derive it from raw config.\n */\nexport interface CapabilitySet {\n /** `rerankCacheEnabled` — cache reranker scores across recall passes. */\n readonly rerankCache: boolean;\n /** `recallDirectAnswerEnabled` — observation-mode direct-answer tier. */\n readonly recallDirectAnswer: boolean;\n /** `recallMemoryWorthFilterEnabled` — Memory-Worth score reweighting. */\n readonly recallMemoryWorthFilter: boolean;\n /** `trustScoreEnabled` — unified TrustScore recall stage (issue #1577). */\n readonly recallTrustScore: boolean;\n /** `recallMmrEnabled` — maximal-marginal-relevance diversification. */\n readonly recallMmr: boolean;\n /** `recallReasoningTraceBoostEnabled` — boost reasoning-trace memories. */\n readonly recallReasoningTraceBoost: boolean;\n /** `recallPlannerLlmEnabled` — LLM-backed recall-mode planner. */\n readonly recallPlannerLlm: boolean;\n /** `recallPlannerEnabled` — recall-mode planner (heuristic + optional LLM). */\n readonly recallPlanner: boolean;\n /** `recallConfidenceGateEnabled` — Synapse-style confidence gate. */\n readonly recallConfidenceGate: boolean;\n /** `graphRecallEnabled` — graph-mode recall tier (gates planner graph mode). */\n readonly graphRecall: boolean;\n /** `graphAssistInFullModeEnabled` — graph-assist overlay in full mode. */\n readonly graphAssistInFullMode: boolean;\n /** `graphExpandedIntentEnabled` — promote broad-intent asks to graph mode. */\n readonly graphExpandedIntent: boolean;\n // --- Issue #1566 Cluster C: mixed-operation flags (recall + summarizer/CLI/writes) ---\n /** `rerankEnabled` — optional LLM reranking of recall candidates. */\n readonly rerank: boolean;\n /** `harmonicRetrievalEnabled` — abstraction-node harmonic retrieval tier. */\n readonly harmonicRetrieval: boolean;\n /** `parallelRetrievalEnabled` — three-agent parallel retrieval (DirectFact + Contextual + Temporal). */\n readonly parallelRetrieval: boolean;\n}\n\n/**\n * Resolve the recall-operation {@link CapabilitySet} from parsed config.\n *\n * Call this ONCE per recall operation (at the `recall()` / `recallInternal`\n * entry) and thread the result down. Composition lives here and only here.\n *\n * Session toggles are intentionally not a parameter yet: `session-toggles.ts`\n * is agent-scoped (per session/agent enable-disable of the whole plugin), not\n * flag-scoped — none of the flags projected here have a per-session override,\n * so there is nothing for a toggle argument to compose at this layer.\n */\nexport function resolveCapabilities(config: PluginConfig): CapabilitySet {\n return Object.freeze({\n rerankCache: config.rerankCacheEnabled,\n recallDirectAnswer: config.recallDirectAnswerEnabled,\n recallMemoryWorthFilter: config.recallMemoryWorthFilterEnabled,\n // Issue #1577: TrustScore subsumes the Memory Worth multiplier when on;\n // the orchestrator runs exactly one of the two (mutual exclusion, rule 39).\n recallTrustScore: config.trustScoreEnabled === true,\n recallMmr: config.recallMmrEnabled,\n recallReasoningTraceBoost: config.recallReasoningTraceBoostEnabled,\n recallPlannerLlm: config.recallPlannerLlmEnabled,\n recallPlanner: config.recallPlannerEnabled,\n recallConfidenceGate: config.recallConfidenceGateEnabled,\n graphRecall: config.graphRecallEnabled,\n // Optional flags: preserve the exact default-when-undefined semantics the\n // migrated call sites used (`!== false` = default-on, `=== true` = default-off).\n graphAssistInFullMode: config.graphAssistInFullModeEnabled !== false,\n graphExpandedIntent: config.graphExpandedIntentEnabled === true,\n // Issue #1566 Cluster C: mixed-operation flags resolved once per recall op.\n rerank: config.rerankEnabled,\n harmonicRetrieval: config.harmonicRetrievalEnabled,\n parallelRetrieval: config.parallelRetrievalEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Graph-construction capability set (issue #1566 Cluster A).\n//\n// The recall CapabilitySet above covers flags whose EVERY read site lives on\n// the recall call chain. The five flags below are read on graph-construction,\n// write/extraction, AND recall paths — so they cannot join the recall set\n// without leaving some sites on `caps.` and others on `config.` (the exact\n// divergence #1523 forbade). They get their own projection, resolved at graph\n// build/write entry (and alongside `caps` when recall reads them).\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of graph-construction feature gates.\n *\n * Every field is `readonly boolean`. Composition (including default-when-\n * undefined semantics for the optional `graphWriteSessionAdjacencyEnabled`)\n * lives ONLY in {@link resolveGraphConstructionCapabilities}.\n */\nexport interface GraphConstructionCapabilitySet {\n /** `entityGraphEnabled` — maintain entity co-reference graph edges. */\n readonly entityGraph: boolean;\n /** `timeGraphEnabled` — maintain thread-adjacency graph edges. */\n readonly timeGraph: boolean;\n /** `causalGraphEnabled` — maintain causal-language graph edges. */\n readonly causalGraph: boolean;\n /** `multiGraphMemoryEnabled` — master switch for multi-graph writes/traversal. */\n readonly multiGraphMemory: boolean;\n /** `graphWriteSessionAdjacencyEnabled` — session-adjacency fallback for time edges (default-on). */\n readonly graphWriteSessionAdjacency: boolean;\n}\n\n/**\n * Resolve the {@link GraphConstructionCapabilitySet} from parsed config.\n *\n * Call this ONCE at graph build/write entry (extraction, recall graph\n * expansion, graph-health/repair) and thread the result down — exactly the\n * pattern {@link resolveCapabilities} established for recall.\n */\nexport type GraphConstructionConfigProjection = Pick<\n PluginConfig,\n | \"entityGraphEnabled\"\n | \"timeGraphEnabled\"\n | \"causalGraphEnabled\"\n | \"multiGraphMemoryEnabled\"\n | \"graphWriteSessionAdjacencyEnabled\"\n>;\n\nexport function resolveGraphConstructionCapabilities(\n config: GraphConstructionConfigProjection,\n): GraphConstructionCapabilitySet {\n return Object.freeze({\n entityGraph: config.entityGraphEnabled,\n timeGraph: config.timeGraphEnabled,\n causalGraph: config.causalGraphEnabled,\n multiGraphMemory: config.multiGraphMemoryEnabled,\n // Optional flag: preserve the exact default-when-undefined semantics the\n // migrated call site used (`!== false` = default-on).\n graphWriteSessionAdjacency: config.graphWriteSessionAdjacencyEnabled !== false,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Access-setup capability set (issue #1566 Cluster B).\n//\n// These two flags gate cross-namespace recall budgeting and anomaly-detection\n// auditing. Resolved once at access-service handler entry (and the operator\n// doctor check) rather than re-derived from raw config at each site.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of access-setup feature gates (issue #1566 Cluster B).\n *\n * Every field is `readonly boolean`. Composition (including the default-off\n * semantics for the optional `recallAuditAnomalyDetectionEnabled`) lives ONLY\n * in {@link resolveAccessSetupCapabilities}.\n */\nexport interface AccessSetupCapabilitySet {\n /** `recallCrossNamespaceBudgetEnabled` — rolling cross-namespace recall budget. */\n readonly recallCrossNamespaceBudget: boolean;\n /** `recallAuditAnomalyDetectionEnabled` — access-audit anomaly detection (default-off). */\n readonly recallAuditAnomalyDetection: boolean;\n readonly recallSingleFlight: boolean;\n}\n\n/** Resolve the {@link AccessSetupCapabilitySet} from parsed config. Call ONCE at\n * access-service handler entry and thread the result down. */\nexport type AccessSetupConfigProjection = Pick<\n PluginConfig,\n \"recallCrossNamespaceBudgetEnabled\" | \"recallAuditAnomalyDetectionEnabled\" | \"recallSingleFlightEnabled\"\n>;\n\nexport function resolveAccessSetupCapabilities(\n config: AccessSetupConfigProjection,\n): AccessSetupCapabilitySet {\n return Object.freeze({\n recallCrossNamespaceBudget: config.recallCrossNamespaceBudgetEnabled,\n // Optional/default-off flag: preserve `=== true` semantics.\n recallAuditAnomalyDetection: config.recallAuditAnomalyDetectionEnabled === true,\n recallSingleFlight: config.recallSingleFlightEnabled === true,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Memory-lifecycle capability set (issue #1523 batch 3).\n//\n// The flags below gate memory WRITE/EXTRACTION (extraction judge, scope\n// classification, dedupe, telemetry prefilter), temporal supersession of\n// stored facts, and the lifecycle policy pass (promotion / decay / stale\n// filtering). They are read on the persistExtraction write path, the recall\n// candidate-filter path, and the maintenance / lifecycle-pass path — so, like\n// the graph-construction set above, they get their own projection resolved\n// ONCE at each operation entry that reads them (never re-derived from raw\n// config mid-operation — the gate-divergence defect class #1523 targets).\n//\n// Every field projects from an already-resolved PluginConfig boolean (defaults\n// are applied at the config-parse boundary, rule 36), so the resolver is a\n// pure projection — no `!== false` / `=== true` re-coercion here.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of memory-lifecycle feature gates (issue #1523 batch 3).\n *\n * Resolved once per write / recall-filter / maintenance operation and threaded\n * down. Composition lives ONLY in {@link resolveMemoryLifecycleCapabilities}.\n */\nexport interface MemoryLifecycleCapabilitySet {\n /** `temporalSupersessionEnabled` — supersede stale structured facts on write, filter on recall. */\n readonly temporalSupersession: boolean;\n /** `temporalMemoryTreeEnabled` — temporal-memory-tree build + recall tier. */\n readonly temporalMemoryTree: boolean;\n /** `lifecyclePolicyEnabled` — lifecycle promotion / decay metadata pass. */\n readonly lifecyclePolicy: boolean;\n /** `lifecycleFilterStaleEnabled` — filter stale-lifecycle memories from recall. */\n readonly lifecycleFilterStale: boolean;\n /** `lifecycleMetricsEnabled` — emit lifecycle-pass metrics. */\n readonly lifecycleMetrics: boolean;\n /** `extractionScopeClassificationEnabled` — LLM classifies each fact's scope. */\n readonly extractionScopeClassification: boolean;\n /** `extractionJudgeEnabled` — LLM-as-judge fact-worthiness gate. */\n readonly extractionJudge: boolean;\n /** `extractionDedupeEnabled` — dedupe against recent extractions. */\n readonly extractionDedupe: boolean;\n /** `extractionTelemetryPrefilterEnabled` — skip mechanical-telemetry transcripts. */\n readonly extractionTelemetryPrefilter: boolean;\n /** `extractionJudgeTelemetryEnabled` — collect judge verdict telemetry. */\n readonly extractionJudgeTelemetry: boolean;\n /** `embeddingFallbackEnabled` — semantic-dedup / archive-search embedding fallback. */\n readonly embeddingFallback: boolean;\n /** `extractionRetryEnabled` — per-fingerprint backoff + provider circuit breaker on the extraction retry path. */\n readonly extractionRetry: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveMemoryLifecycleCapabilities}.\n */\nexport type MemoryLifecycleConfigProjection = Pick<\n PluginConfig,\n | \"temporalSupersessionEnabled\"\n | \"temporalMemoryTreeEnabled\"\n | \"lifecyclePolicyEnabled\"\n | \"lifecycleFilterStaleEnabled\"\n | \"lifecycleMetricsEnabled\"\n | \"extractionScopeClassificationEnabled\"\n | \"extractionJudgeEnabled\"\n | \"extractionDedupeEnabled\"\n | \"extractionTelemetryPrefilterEnabled\"\n | \"extractionJudgeTelemetryEnabled\"\n | \"embeddingFallbackEnabled\"\n | \"extractionRetryEnabled\"\n>;\n\n/**\n * Resolve the {@link MemoryLifecycleCapabilitySet} from parsed config.\n *\n * Call this ONCE per write / recall-filter / maintenance operation entry and\n * thread the result down — exactly the pattern {@link resolveCapabilities}\n * established for recall.\n */\nexport function resolveMemoryLifecycleCapabilities(\n config: MemoryLifecycleConfigProjection,\n): MemoryLifecycleCapabilitySet {\n return Object.freeze({\n temporalSupersession: config.temporalSupersessionEnabled,\n temporalMemoryTree: config.temporalMemoryTreeEnabled,\n lifecyclePolicy: config.lifecyclePolicyEnabled,\n lifecycleFilterStale: config.lifecycleFilterStaleEnabled,\n lifecycleMetrics: config.lifecycleMetricsEnabled,\n extractionScopeClassification: config.extractionScopeClassificationEnabled,\n extractionJudge: config.extractionJudgeEnabled,\n extractionDedupe: config.extractionDedupeEnabled,\n extractionTelemetryPrefilter: config.extractionTelemetryPrefilterEnabled,\n extractionJudgeTelemetry: config.extractionJudgeTelemetryEnabled,\n embeddingFallback: config.embeddingFallbackEnabled,\n extractionRetry: config.extractionRetryEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Indexing capability set (issue #1523 batch 4).\n//\n// The flags below gate query-aware indexing (temporal/tag index build +\n// prefilter) and the conversation index (QMD/FAISS backend selection). They\n// are read on the recall path, the extraction/persist path, and the\n// maintenance/invalidation path — so they get their own projection resolved\n// ONCE at each operation entry that reads them.\n//\n// Every field projects from an already-resolved PluginConfig boolean (defaults\n// applied at the parse boundary), so the resolver is a pure projection.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of indexing feature gates (issue #1523 batch 4).\n *\n * Resolved once per recall / extraction / maintenance operation and threaded\n * down. Composition lives ONLY in {@link resolveIndexingCapabilities}.\n */\nexport interface IndexingCapabilitySet {\n /** `queryAwareIndexingEnabled` — build + use temporal/tag index for recall prefilter. */\n readonly queryAwareIndexing: boolean;\n /** `conversationIndexEnabled` — conversation-index backend (QMD / FAISS). */\n readonly conversationIndex: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveIndexingCapabilities}.\n */\nexport type IndexingConfigProjection = Pick<\n PluginConfig,\n \"queryAwareIndexingEnabled\" | \"conversationIndexEnabled\"\n>;\n\n/**\n * Resolve the {@link IndexingCapabilitySet} from parsed config.\n *\n * Call this ONCE per recall / extraction / maintenance operation entry and\n * thread the result down — exactly the pattern {@link resolveCapabilities}\n * established for recall.\n */\nexport function resolveIndexingCapabilities(\n config: IndexingConfigProjection,\n): IndexingCapabilitySet {\n return Object.freeze({\n queryAwareIndexing: config.queryAwareIndexingEnabled,\n conversationIndex: config.conversationIndexEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Creation-memory capability set (issue #1523 batch 4).\n//\n// The flags below gate the creation-memory subsystem: resume-bundle handoff,\n// commitment ledger, commitment lifecycle, and the master creation-memory\n// switch. They are read on the extraction/persist path, the recall\n// enrichment path, and the CLI command surface — so they get their own\n// projection resolved ONCE at each operation entry that reads them.\n//\n// Every field projects from an already-resolved PluginConfig boolean (defaults\n// applied at the parse boundary), so the resolver is a pure projection.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of creation-memory feature gates (issue #1523 batch 4).\n *\n * Resolved once per creation-memory operation (CLI command, extraction,\n * recall enrichment) and threaded down. Composition lives ONLY in\n * {@link resolveCreationMemoryCapabilities}.\n */\nexport interface CreationMemoryCapabilitySet {\n /** `creationMemoryEnabled` — master switch for creation-memory subsystem. */\n readonly creationMemory: boolean;\n /** `commitmentLedgerEnabled` — commitment-ledger read/write. */\n readonly commitmentLedger: boolean;\n /** `resumeBundlesEnabled` — resume-bundle handoff bundles. */\n readonly resumeBundles: boolean;\n /** `commitmentLifecycleEnabled` — commitment lifecycle (promotion / decay). */\n readonly commitmentLifecycle: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveCreationMemoryCapabilities}.\n */\nexport type CreationMemoryConfigProjection = Pick<\n PluginConfig,\n | \"creationMemoryEnabled\"\n | \"commitmentLedgerEnabled\"\n | \"resumeBundlesEnabled\"\n | \"commitmentLifecycleEnabled\"\n>;\n\n/**\n * Resolve the {@link CreationMemoryCapabilitySet} from parsed config.\n *\n * Call this ONCE per creation-memory operation entry and thread the result\n * down — exactly the pattern {@link resolveCapabilities} established for\n * recall.\n */\nexport function resolveCreationMemoryCapabilities(\n config: CreationMemoryConfigProjection,\n): CreationMemoryCapabilitySet {\n return Object.freeze({\n creationMemory: config.creationMemoryEnabled,\n commitmentLedger: config.commitmentLedgerEnabled,\n resumeBundles: config.resumeBundlesEnabled,\n commitmentLifecycle: config.commitmentLifecycleEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Namespace capability set (issue #1523 batch 5).\n//\n// `namespacesEnabled` is the single most-scattered config flag in the codebase\n// (101 read sites outside config.ts across 18 files — orchestrator, access-service,\n// namespace modules, maintenance, CLI, admin). It gates whether the multi-namespace\n// system is active at all: every namespace resolution, storage path, recall budget,\n// and maintenance fanout checks it. Like the other projections above it gets its own\n// resolver invoked ONCE per operation entry rather than re-derived at each site.\n//\n// The field is a non-optional boolean in PluginConfig, so no coercion is needed.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of namespace feature gates (issue #1523 batch 5).\n *\n * Every field is `readonly boolean`. Composition lives ONLY in\n * {@link resolveNamespaceCapabilities}.\n */\nexport interface NamespaceCapabilitySet {\n /** `namespacesEnabled` — multi-namespace system master switch. */\n readonly namespaces: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveNamespaceCapabilities}.\n */\nexport type NamespaceConfigProjection = Pick<PluginConfig, \"namespacesEnabled\">;\n\n/**\n * Resolve the {@link NamespaceCapabilitySet} from parsed config.\n *\n * Call this ONCE at operation entry and thread the result down. The flag is a\n * non-optional boolean so the projection is a pure pass-through.\n */\nexport function resolveNamespaceCapabilities(\n config: NamespaceConfigProjection,\n): NamespaceCapabilitySet {\n return Object.freeze({\n namespaces: config.namespacesEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// QMD capability set (issue #1523 batch 6).\n//\n// The twelve QMD (query-managed database) flags are the largest remaining\n// scattered cluster after batch 5. They gate every aspect of the tiered\n// memory system: master switch, tier migration, cold tier, daemon, auto-embed,\n// maintenance, query rerank, intent hints, explain, auto-upgrade, and parity\n// graph. Read sites span orchestrator, operator-toolkit, access-service, and\n// search/factory. All flags are non-optional booleans on PluginConfig (defaults\n// resolved at the parse boundary), so the projection is pure pass-through.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of QMD feature gates (issue #1523 batch 6).\n *\n * Every field is `readonly boolean`. Composition lives ONLY in\n * {@link resolveQmdCapabilities}.\n */\nexport interface QmdCapabilitySet {\n /** `qmdEnabled` — QMD tiered-memory master switch. */\n readonly qmd: boolean;\n /** `qmdTierMigrationEnabled` — automatic tier promotion/demotion. */\n readonly qmdTierMigration: boolean;\n /** `qmdTierAutoBackfillEnabled` — backfill stale tier entries. */\n readonly qmdTierAutoBackfill: boolean;\n /** `qmdAutoEmbedEnabled` — auto-embed documents on write. */\n readonly qmdAutoEmbed: boolean;\n /** `qmdMaintenanceEnabled` — QMD maintenance pass runner. */\n readonly qmdMaintenance: boolean;\n /** `qmdColdTierEnabled` — cold-tier storage. */\n readonly qmdColdTier: boolean;\n /** `qmdDaemonEnabled` — background QMD daemon. */\n readonly qmdDaemon: boolean;\n /** `qmdTierParityGraphEnabled` — tier parity graph. */\n readonly qmdTierParityGraph: boolean;\n /** `qmdQueryRerankEnabled` — QMD query reranking. */\n readonly qmdQueryRerank: boolean;\n /** `qmdIntentHintsEnabled` — QMD intent hint extraction. */\n readonly qmdIntentHints: boolean;\n /** `qmdExplainEnabled` — QMD explain output. */\n readonly qmdExplain: boolean;\n /** `qmdAutoUpgradeEnabled` — QMD auto-upgrade tiers. */\n readonly qmdAutoUpgrade: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveQmdCapabilities}.\n */\nexport type QmdConfigProjection = Pick<\n PluginConfig,\n | \"qmdEnabled\"\n | \"qmdTierMigrationEnabled\"\n | \"qmdTierAutoBackfillEnabled\"\n | \"qmdAutoEmbedEnabled\"\n | \"qmdMaintenanceEnabled\"\n | \"qmdColdTierEnabled\"\n | \"qmdDaemonEnabled\"\n | \"qmdTierParityGraphEnabled\"\n | \"qmdQueryRerankEnabled\"\n | \"qmdIntentHintsEnabled\"\n | \"qmdExplainEnabled\"\n | \"qmdAutoUpgradeEnabled\"\n>;\n\n/**\n * Resolve the {@link QmdCapabilitySet} from parsed config.\n *\n * Call this ONCE at operation entry and thread the result down. Eleven flags\n * are non-optional booleans (pure pass-through); `qmdColdTierEnabled` is\n * optional and coerced with `=== true` (matching the pre-migration call sites).\n */\nexport function resolveQmdCapabilities(config: QmdConfigProjection): QmdCapabilitySet {\n return Object.freeze({\n qmd: config.qmdEnabled,\n qmdTierMigration: config.qmdTierMigrationEnabled,\n qmdTierAutoBackfill: config.qmdTierAutoBackfillEnabled,\n qmdAutoEmbed: config.qmdAutoEmbedEnabled,\n qmdMaintenance: config.qmdMaintenanceEnabled,\n qmdColdTier: config.qmdColdTierEnabled === true,\n qmdDaemon: config.qmdDaemonEnabled,\n qmdTierParityGraph: config.qmdTierParityGraphEnabled,\n qmdQueryRerank: config.qmdQueryRerankEnabled,\n qmdIntentHints: config.qmdIntentHintsEnabled,\n qmdExplain: config.qmdExplainEnabled,\n qmdAutoUpgrade: config.qmdAutoUpgradeEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Identity-continuity capability set (issue #1523 batch 6).\n//\n// `identityContinuityEnabled` gates the identity/continuity system (agent\n// identity tracking, continuity incident logging). Its 13 read sites span\n// orchestrator, access-service, and CLI. The flag is a non-optional boolean\n// on PluginConfig, so the projection is a pure pass-through.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of identity-continuity feature gates (issue #1523 batch 6).\n */\nexport interface IdentityContinuityCapabilitySet {\n /** `identityContinuityEnabled` — identity-continuity master switch. */\n readonly identityContinuity: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveIdentityContinuityCapabilities}.\n */\nexport type IdentityContinuityConfigProjection = Pick<PluginConfig, \"identityContinuityEnabled\">;\n\n/**\n * Resolve the {@link IdentityContinuityCapabilitySet} from parsed config.\n */\nexport function resolveIdentityContinuityCapabilities(\n config: IdentityContinuityConfigProjection,\n): IdentityContinuityCapabilitySet {\n return Object.freeze({\n identityContinuity: config.identityContinuityEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Local-LLM capability set (issue #1523 batch 6).\n//\n// `localLlmEnabled` gates whether a local (on-device) LLM is used for\n// summarization, embedding fallback, extraction, and search. Its 11 read\n// sites span orchestrator, extraction, embedding-fallback, local-llm,\n// search/embed-helper, and summarizer. The flag is a non-optional boolean.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of local-LLM feature gates (issue #1523 batch 6).\n */\nexport interface LocalLlmCapabilitySet {\n /** `localLlmEnabled` — local (on-device) LLM master switch. */\n readonly localLlm: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveLocalLlmCapabilities}.\n */\nexport type LocalLlmConfigProjection = Pick<PluginConfig, \"localLlmEnabled\">;\n\n/**\n * Resolve the {@link LocalLlmCapabilitySet} from parsed config.\n */\nexport function resolveLocalLlmCapabilities(\n config: LocalLlmConfigProjection,\n): LocalLlmCapabilitySet {\n return Object.freeze({\n localLlm: config.localLlmEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Security capability set (issue #1523 batch 7).\n//\n// The four security/trust-zone flags gate quarantine promotion, poisoning\n// defense, and trust-zone recall. Read sites span orchestrator, access-service,\n// CLI, and research-status commands. All flags are non-optional booleans on\n// PluginConfig (defaults resolved at the parse boundary), so the projection is\n// a pure pass-through.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of security/trust-zone feature gates (issue #1523 batch 7).\n */\nexport interface SecurityCapabilitySet {\n /** `trustZonesEnabled` — master switch for trust-zone enforcement. */\n readonly trustZones: boolean;\n /** `quarantinePromotionEnabled` — promote quarantined memories after review. */\n readonly quarantinePromotion: boolean;\n /** `memoryPoisoningDefenseEnabled` — detect and defend against memory poisoning. */\n readonly memoryPoisoningDefense: boolean;\n /** `trustZoneRecallEnabled` — restrict recall to trusted zones. */\n readonly trustZoneRecall: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveSecurityCapabilities}.\n */\nexport type SecurityConfigProjection = Pick<\n PluginConfig,\n \"trustZonesEnabled\" | \"quarantinePromotionEnabled\" | \"memoryPoisoningDefenseEnabled\" | \"trustZoneRecallEnabled\"\n>;\n\n/**\n * Resolve the {@link SecurityCapabilitySet} from parsed config.\n */\nexport function resolveSecurityCapabilities(config: SecurityConfigProjection): SecurityCapabilitySet {\n return Object.freeze({\n trustZones: config.trustZonesEnabled,\n quarantinePromotion: config.quarantinePromotionEnabled,\n memoryPoisoningDefense: config.memoryPoisoningDefenseEnabled,\n trustZoneRecall: config.trustZoneRecallEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Eval/benchmark capability set (issue #1523 batch 7).\n//\n// The five eval/benchmark flags gate the evaluation harness, shadow mode,\n// baseline snapshot management, delta reporting, and red-team benchmarking.\n// Read sites span orchestrator, CLI, and operator-toolkit. All flags are\n// non-optional booleans on PluginConfig.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of eval/benchmark feature gates (issue #1523 batch 7).\n */\nexport interface EvalCapabilitySet {\n /** `evalHarnessEnabled` — master switch for the eval harness. */\n readonly evalHarness: boolean;\n /** `evalShadowModeEnabled` — run evaluations in shadow mode. */\n readonly evalShadowMode: boolean;\n /** `benchmarkBaselineSnapshotsEnabled` — manage baseline snapshots. */\n readonly benchmarkBaselineSnapshots: boolean;\n /** `benchmarkDeltaReporterEnabled` — report benchmark deltas. */\n readonly benchmarkDeltaReporter: boolean;\n /** `memoryRedTeamBenchEnabled` — red-team benchmarking of memory. */\n readonly memoryRedTeamBench: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveEvalCapabilities}.\n */\nexport type EvalConfigProjection = Pick<\n PluginConfig,\n \"evalHarnessEnabled\" | \"evalShadowModeEnabled\" | \"benchmarkBaselineSnapshotsEnabled\" | \"benchmarkDeltaReporterEnabled\" | \"memoryRedTeamBenchEnabled\"\n>;\n\n/**\n * Resolve the {@link EvalCapabilitySet} from parsed config.\n */\nexport function resolveEvalCapabilities(config: EvalConfigProjection): EvalCapabilitySet {\n return Object.freeze({\n evalHarness: config.evalHarnessEnabled,\n evalShadowMode: config.evalShadowModeEnabled,\n benchmarkBaselineSnapshots: config.benchmarkBaselineSnapshotsEnabled,\n benchmarkDeltaReporter: config.benchmarkDeltaReporterEnabled,\n memoryRedTeamBench: config.memoryRedTeamBenchEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Utility-learning capability set (issue #1523 batch 7).\n//\n// The two utility-learning flags gate memory utility learning and\n// promotion-by-outcome. Read sites span orchestrator, CLI research-status\n// commands, and maintenance files. Both flags are non-optional booleans.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of utility-learning feature gates (issue #1523 batch 7).\n */\nexport interface UtilityLearningCapabilitySet {\n /** `memoryUtilityLearningEnabled` — learn memory utility weights from outcomes. */\n readonly memoryUtilityLearning: boolean;\n /** `promotionByOutcomeEnabled` — promote memories by outcome tracking. */\n readonly promotionByOutcome: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveUtilityLearningCapabilities}.\n */\nexport type UtilityLearningConfigProjection = Pick<\n PluginConfig,\n \"memoryUtilityLearningEnabled\" | \"promotionByOutcomeEnabled\"\n>;\n\n/**\n * Resolve the {@link UtilityLearningCapabilitySet} from parsed config.\n */\nexport function resolveUtilityLearningCapabilities(config: UtilityLearningConfigProjection): UtilityLearningCapabilitySet {\n return Object.freeze({\n memoryUtilityLearning: config.memoryUtilityLearningEnabled,\n promotionByOutcome: config.promotionByOutcomeEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Objective-state capability set (issue #1523 batch 7).\n//\n// The three objective-state flags gate objective-state memory, snapshot\n// writes, and recall. Read sites span orchestrator, access-service, CLI\n// creation-ledger commands, and research-status commands. All flags are\n// non-optional booleans.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of objective-state feature gates (issue #1523 batch 7).\n */\nexport interface ObjectiveStateCapabilitySet {\n /** `objectiveStateMemoryEnabled` — store objective-state memories. */\n readonly objectiveStateMemory: boolean;\n /** `objectiveStateSnapshotWritesEnabled` — write objective-state snapshots. */\n readonly objectiveStateSnapshotWrites: boolean;\n /** `objectiveStateRecallEnabled` — recall objective-state memories. */\n readonly objectiveStateRecall: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveObjectiveStateCapabilities}.\n */\nexport type ObjectiveStateConfigProjection = Pick<\n PluginConfig,\n \"objectiveStateMemoryEnabled\" | \"objectiveStateSnapshotWritesEnabled\" | \"objectiveStateRecallEnabled\"\n>;\n\n/**\n * Resolve the {@link ObjectiveStateCapabilitySet} from parsed config.\n */\nexport function resolveObjectiveStateCapabilities(config: ObjectiveStateConfigProjection): ObjectiveStateCapabilitySet {\n return Object.freeze({\n objectiveStateMemory: config.objectiveStateMemoryEnabled,\n objectiveStateSnapshotWrites: config.objectiveStateSnapshotWritesEnabled,\n objectiveStateRecall: config.objectiveStateRecallEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Compression capability set (issue #1523 batch 7).\n//\n// The three compression flags gate guideline learning, semantic refinement,\n// and context-compression actions. Read sites span orchestrator,\n// access-service, and the compression-guideline coordinator. All flags are\n// non-optional booleans.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of compression feature gates (issue #1523 batch 7).\n */\nexport interface CompressionCapabilitySet {\n /** `compressionGuidelineLearningEnabled` — learn compression guidelines. */\n readonly compressionGuidelineLearning: boolean;\n /** `compressionGuidelineSemanticRefinementEnabled` — semantically refine guidelines. */\n readonly compressionGuidelineSemanticRefinement: boolean;\n /** `contextCompressionActionsEnabled` — apply context-compression actions. */\n readonly contextCompressionActions: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveCompressionCapabilities}.\n */\nexport type CompressionConfigProjection = Pick<\n PluginConfig,\n \"compressionGuidelineLearningEnabled\" | \"compressionGuidelineSemanticRefinementEnabled\" | \"contextCompressionActionsEnabled\"\n>;\n\n/**\n * Resolve the {@link CompressionCapabilitySet} from parsed config.\n */\nexport function resolveCompressionCapabilities(config: CompressionConfigProjection): CompressionCapabilitySet {\n return Object.freeze({\n compressionGuidelineLearning: config.compressionGuidelineLearningEnabled,\n compressionGuidelineSemanticRefinement: config.compressionGuidelineSemanticRefinementEnabled,\n contextCompressionActions: config.contextCompressionActionsEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Presentation capability set (issue #1523 batch 8).\n//\n// These flags gate output formatting and presentation across the orchestrator, extraction\n// pipeline, consolidation engine, and related modules. Resolved ONCE per\n// operation entry rather than re-derived from raw config at each read site.\n// All flags are non-optional booleans on PluginConfig (defaults applied at\n// the parse boundary), so the projection is a pure pass-through.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of output formatting and presentation feature gates (issue #1523 batch 8).\n */\nexport interface PresentationCapabilitySet {\n /** `verbatimArtifactsEnabled` — verbatim-artifact rendering in recall output. */\n readonly verbatimArtifacts: boolean;\n /** `memoryBoxesEnabled` — memory-box rendering in recall output. */\n readonly memoryBoxes: boolean;\n /** `bufferSurpriseTriggerEnabled` — buffer surprise-detection trigger. */\n readonly bufferSurpriseTrigger: boolean;\n /** `threadingEnabled` — conversation threading in output. */\n readonly threading: boolean;\n /** `episodeNoteModeEnabled` — episode note-mode formatting. */\n readonly episodeNoteMode: boolean;\n /** `transcriptEnabled` — transcript-mode output. */\n readonly transcript: boolean;\n /** `entitySummaryEnabled` — entity summary generation. */\n readonly entitySummary: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolvePresentationCapabilities}.\n */\nexport type PresentationConfigProjection = Pick<\n PluginConfig,\n \"verbatimArtifactsEnabled\" |\n \"memoryBoxesEnabled\" |\n \"bufferSurpriseTriggerEnabled\" |\n \"threadingEnabled\" |\n \"episodeNoteModeEnabled\" |\n \"transcriptEnabled\" |\n \"entitySummaryEnabled\"\n>;\n\n/**\n * Resolve the {@link PresentationCapabilitySet} from parsed config.\n */\nexport function resolvePresentationCapabilities(config: PresentationConfigProjection): PresentationCapabilitySet {\n return Object.freeze({\n verbatimArtifacts: config.verbatimArtifactsEnabled,\n memoryBoxes: config.memoryBoxesEnabled,\n bufferSurpriseTrigger: config.bufferSurpriseTriggerEnabled,\n threading: config.threadingEnabled,\n episodeNoteMode: config.episodeNoteModeEnabled,\n transcript: config.transcriptEnabled,\n entitySummary: config.entitySummaryEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Consolidation capability set (issue #1523 batch 8).\n//\n// These flags gate memory consolidation, compounding, and abstraction across the orchestrator, extraction\n// pipeline, consolidation engine, and related modules. Resolved ONCE per\n// operation entry rather than re-derived from raw config at each read site.\n// All flags are non-optional booleans on PluginConfig (defaults applied at\n// the parse boundary), so the projection is a pure pass-through.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of memory consolidation, compounding, and abstraction feature gates (issue #1523 batch 8).\n */\nexport interface ConsolidationCapabilitySet {\n /** `compoundingSemanticEnabled` — semantic compounding of memories. */\n readonly compoundingSemantic: boolean;\n /** `abstractionAnchorsEnabled` — abstraction-anchor extraction. */\n readonly abstractionAnchors: boolean;\n /** `compoundingEnabled` — master compounding switch. */\n readonly compounding: boolean;\n /** `calibrationEnabled` — memory calibration pass. */\n readonly calibration: boolean;\n /** `semanticConsolidationEnabled` — semantic consolidation pass. */\n readonly semanticConsolidation: boolean;\n /** `patternReinforcementEnabled` — pattern reinforcement in consolidation. */\n readonly patternReinforcement: boolean;\n /** `continuityAuditEnabled` — continuity audit during consolidation. */\n readonly continuityAudit: boolean;\n /** `graphEdgeDecayEnabled` — graph edge decay in maintenance. */\n readonly graphEdgeDecay: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveConsolidationCapabilities}.\n */\nexport type ConsolidationConfigProjection = Pick<\n PluginConfig,\n \"compoundingSemanticEnabled\" |\n \"abstractionAnchorsEnabled\" |\n \"compoundingEnabled\" |\n \"calibrationEnabled\" |\n \"semanticConsolidationEnabled\" |\n \"patternReinforcementEnabled\" |\n \"continuityAuditEnabled\" |\n \"graphEdgeDecayEnabled\"\n>;\n\n/**\n * Resolve the {@link ConsolidationCapabilitySet} from parsed config.\n */\nexport function resolveConsolidationCapabilities(config: ConsolidationConfigProjection): ConsolidationCapabilitySet {\n return Object.freeze({\n compoundingSemantic: config.compoundingSemanticEnabled,\n abstractionAnchors: config.abstractionAnchorsEnabled,\n compounding: config.compoundingEnabled,\n calibration: config.calibrationEnabled,\n semanticConsolidation: config.semanticConsolidationEnabled,\n patternReinforcement: config.patternReinforcementEnabled,\n continuityAudit: config.continuityAuditEnabled,\n graphEdgeDecay: config.graphEdgeDecayEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// RecallAuxiliary capability set (issue #1523 batch 8).\n//\n// These flags gate extraction pipeline details and auxiliary recall enrichment across the orchestrator, extraction\n// pipeline, consolidation engine, and related modules. Resolved ONCE per\n// operation entry rather than re-derived from raw config at each read site.\n// All flags are non-optional booleans on PluginConfig (defaults applied at\n// the parse boundary), so the projection is a pure pass-through.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of extraction pipeline details and auxiliary recall enrichment feature gates (issue #1523 batch 8).\n */\nexport interface RecallAuxiliaryCapabilitySet {\n /** `causalRuleExtractionEnabled` — causal-rule extraction in pipeline. */\n readonly causalRuleExtraction: boolean;\n /** `correctionEnabled` — memory correction subsystem. */\n readonly correction: boolean;\n /** `continuityIncidentLoggingEnabled` — continuity incident logging. */\n readonly continuityIncidentLogging: boolean;\n /** `daySummaryEnabled` — day-summary generation. */\n readonly daySummary: boolean;\n /** `versioningEnabled` — memory versioning. */\n readonly versioning: boolean;\n /** `verifiedRecallEnabled` — verified-recall tier. */\n readonly verifiedRecall: boolean;\n /** `semanticRuleVerificationEnabled` — semantic-rule verification. */\n readonly semanticRuleVerification: boolean;\n /** `workProductRecallEnabled` — work-product recall tier. */\n readonly workProductRecall: boolean;\n /** `secureStoreEnabled` — secure storage mode. */\n readonly secureStore: boolean;\n /** `knowledgeIndexEnabled` — knowledge index in recall. */\n readonly knowledgeIndex: boolean;\n /** `factDeduplicationEnabled` — fact deduplication in recall. */\n readonly factDeduplication: boolean;\n /** `compactionResetEnabled` — compaction reset on re-ingest. */\n readonly compactionReset: boolean;\n /** `entityRetrievalEnabled` — entity retrieval tier. */\n readonly entityRetrieval: boolean;\n /** `cronRecallPolicyEnabled` — cron-based recall policy. */\n readonly cronRecallPolicy: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveRecallAuxiliaryCapabilities}.\n */\nexport type RecallAuxiliaryConfigProjection = Pick<\n PluginConfig,\n \"causalRuleExtractionEnabled\" |\n \"correctionEnabled\" |\n \"continuityIncidentLoggingEnabled\" |\n \"daySummaryEnabled\" |\n \"versioningEnabled\" |\n \"verifiedRecallEnabled\" |\n \"semanticRuleVerificationEnabled\" |\n \"workProductRecallEnabled\" |\n \"secureStoreEnabled\" |\n \"knowledgeIndexEnabled\" |\n \"factDeduplicationEnabled\" |\n \"compactionResetEnabled\" |\n \"entityRetrievalEnabled\" |\n \"cronRecallPolicyEnabled\"\n>;\n\n/**\n * Resolve the {@link RecallAuxiliaryCapabilitySet} from parsed config.\n */\nexport function resolveRecallAuxiliaryCapabilities(config: RecallAuxiliaryConfigProjection): RecallAuxiliaryCapabilitySet {\n return Object.freeze({\n causalRuleExtraction: config.causalRuleExtractionEnabled,\n correction: config.correctionEnabled,\n continuityIncidentLogging: config.continuityIncidentLoggingEnabled,\n daySummary: config.daySummaryEnabled,\n versioning: config.versioningEnabled,\n verifiedRecall: config.verifiedRecallEnabled,\n semanticRuleVerification: config.semanticRuleVerificationEnabled,\n workProductRecall: config.workProductRecallEnabled,\n secureStore: config.secureStoreEnabled,\n knowledgeIndex: config.knowledgeIndexEnabled,\n factDeduplication: config.factDeduplicationEnabled,\n compactionReset: config.compactionResetEnabled,\n entityRetrieval: config.entityRetrievalEnabled,\n cronRecallPolicy: config.cronRecallPolicyEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// RecallEnhancement capability set (issue #1523 batch 9).\n//\n// These 24 flags gate recall enrichment, memory reconstruction, entity\n// relationships, causal trajectory recall, CMC retrieval, and related\n// enhancement paths. Read sites span orchestrator, access-service, graph-recall,\n// and CLI research-status commands. All flags are non-optional booleans on\n// PluginConfig (defaults resolved at the parse boundary), so the projection\n// is a pure pass-through.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of recall-enhancement feature gates (issue #1523 batch 9).\n */\nexport interface RecallEnhancementCapabilitySet {\n readonly explicitCueRecall: boolean;\n readonly targetedFactRecall: boolean;\n readonly focusedListRecall: boolean;\n readonly responseGuidanceRecall: boolean;\n readonly eventOrderRecall: boolean;\n readonly reinforcementRecallBoost: boolean;\n readonly recallPlannerTelemetry: boolean;\n readonly peerProfileRecall: boolean;\n readonly graphAssistShadowEval: boolean;\n readonly memoryReconstruction: boolean;\n readonly memoryLinking: boolean;\n readonly causalTrajectoryRecall: boolean;\n readonly causalTrajectoryMemory: boolean;\n readonly cmcRetrieval: boolean;\n readonly contradictionDetection: boolean;\n readonly factArchival: boolean;\n readonly entityRelationships: boolean;\n readonly entityActivityLog: boolean;\n readonly compoundingInject: boolean;\n readonly accessTracking: boolean;\n readonly autoPromoteToShared: boolean;\n readonly feedback: boolean;\n readonly identity: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveRecallEnhancementCapabilities}.\n */\nexport type RecallEnhancementConfigProjection = Pick<\n PluginConfig,\n | \"explicitCueRecallEnabled\"\n | \"targetedFactRecallEnabled\"\n | \"focusedListRecallEnabled\"\n | \"responseGuidanceRecallEnabled\"\n | \"eventOrderRecallEnabled\"\n | \"reinforcementRecallBoostEnabled\"\n | \"recallPlannerTelemetryEnabled\"\n | \"peerProfileRecallEnabled\"\n | \"graphAssistShadowEvalEnabled\"\n | \"memoryReconstructionEnabled\"\n | \"memoryLinkingEnabled\"\n | \"causalTrajectoryRecallEnabled\"\n | \"causalTrajectoryMemoryEnabled\"\n | \"cmcRetrievalEnabled\"\n | \"contradictionDetectionEnabled\"\n | \"factArchivalEnabled\"\n | \"entityRelationshipsEnabled\"\n | \"entityActivityLogEnabled\"\n | \"compoundingInjectEnabled\"\n | \"accessTrackingEnabled\"\n | \"autoPromoteToSharedEnabled\"\n | \"feedbackEnabled\"\n | \"identityEnabled\"\n>;\n\n/**\n * Resolve the {@link RecallEnhancementCapabilitySet} from parsed config.\n */\nexport function resolveRecallEnhancementCapabilities(config: RecallEnhancementConfigProjection): RecallEnhancementCapabilitySet {\n return Object.freeze({\n explicitCueRecall: config.explicitCueRecallEnabled,\n targetedFactRecall: config.targetedFactRecallEnabled,\n focusedListRecall: config.focusedListRecallEnabled,\n responseGuidanceRecall: config.responseGuidanceRecallEnabled,\n eventOrderRecall: config.eventOrderRecallEnabled,\n reinforcementRecallBoost: config.reinforcementRecallBoostEnabled,\n recallPlannerTelemetry: config.recallPlannerTelemetryEnabled,\n peerProfileRecall: config.peerProfileRecallEnabled,\n graphAssistShadowEval: config.graphAssistShadowEvalEnabled === true,\n memoryReconstruction: config.memoryReconstructionEnabled,\n memoryLinking: config.memoryLinkingEnabled,\n causalTrajectoryRecall: config.causalTrajectoryRecallEnabled,\n causalTrajectoryMemory: config.causalTrajectoryMemoryEnabled,\n cmcRetrieval: config.cmcRetrievalEnabled,\n contradictionDetection: config.contradictionDetectionEnabled,\n factArchival: config.factArchivalEnabled,\n entityRelationships: config.entityRelationshipsEnabled,\n entityActivityLog: config.entityActivityLogEnabled,\n compoundingInject: config.compoundingInjectEnabled,\n accessTracking: config.accessTrackingEnabled,\n autoPromoteToShared: config.autoPromoteToSharedEnabled,\n feedback: config.feedbackEnabled,\n identity: config.identityEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// PipelineProcessing capability set (issue #1523 batch 9).\n//\n// These 22 flags gate the extraction/chunking/summarization pipeline and LLM\n// infrastructure. Read sites span orchestrator, extraction, summarizer,\n// embedding-fallback, local-llm, search, day-summary, and semantic-consolidation.\n// All flags are non-optional booleans on PluginConfig (defaults resolved at\n// the parse boundary), so the projection is a pure pass-through.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of pipeline-processing feature gates (issue #1523 batch 9).\n */\nexport interface PipelineProcessingCapabilitySet {\n readonly chunking: boolean;\n readonly semanticChunking: boolean;\n readonly semanticDedup: boolean;\n readonly summarization: boolean;\n readonly topicExtraction: boolean;\n readonly sessionObserver: boolean;\n readonly profiling: boolean;\n readonly checkpoint: boolean;\n readonly traceWeaver: boolean;\n readonly routingRules: boolean;\n readonly inlineSourceAttribution: boolean;\n readonly negativeExamples: boolean;\n readonly hourlySummaries: boolean;\n readonly lcm: boolean;\n readonly localLlmFast: boolean;\n readonly proactiveExtraction: boolean;\n readonly delinearize: boolean;\n readonly slowLog: boolean;\n readonly hostEmbeddingProvider: boolean;\n readonly memoryExtensions: boolean;\n readonly hourlySummariesExtended: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolvePipelineProcessingCapabilities}.\n */\nexport type PipelineProcessingConfigProjection = Pick<\n PluginConfig,\n | \"chunkingEnabled\"\n | \"semanticChunkingEnabled\"\n | \"semanticDedupEnabled\"\n | \"summarizationEnabled\"\n | \"topicExtractionEnabled\"\n | \"sessionObserverEnabled\"\n | \"profilingEnabled\"\n | \"checkpointEnabled\"\n | \"traceWeaverEnabled\"\n | \"routingRulesEnabled\"\n | \"inlineSourceAttributionEnabled\"\n | \"negativeExamplesEnabled\"\n | \"hourlySummariesEnabled\"\n | \"lcmEnabled\"\n | \"localLlmFastEnabled\"\n | \"proactiveExtractionEnabled\"\n | \"delinearizeEnabled\"\n | \"slowLogEnabled\"\n | \"hostEmbeddingProviderEnabled\"\n | \"memoryExtensionsEnabled\"\n | \"hourlySummariesExtendedEnabled\"\n>;\n\n/**\n * Resolve the {@link PipelineProcessingCapabilitySet} from parsed config.\n */\nexport function resolvePipelineProcessingCapabilities(config: PipelineProcessingConfigProjection): PipelineProcessingCapabilitySet {\n return Object.freeze({\n chunking: config.chunkingEnabled,\n semanticChunking: config.semanticChunkingEnabled,\n semanticDedup: config.semanticDedupEnabled,\n summarization: config.summarizationEnabled,\n topicExtraction: config.topicExtractionEnabled,\n sessionObserver: config.sessionObserverEnabled === true,\n profiling: config.profilingEnabled,\n checkpoint: config.checkpointEnabled,\n traceWeaver: config.traceWeaverEnabled,\n routingRules: config.routingRulesEnabled,\n inlineSourceAttribution: config.inlineSourceAttributionEnabled,\n negativeExamples: config.negativeExamplesEnabled,\n hourlySummaries: config.hourlySummariesEnabled,\n lcm: config.lcmEnabled,\n localLlmFast: config.localLlmFastEnabled,\n proactiveExtraction: config.proactiveExtractionEnabled,\n delinearize: config.delinearizeEnabled,\n slowLog: config.slowLogEnabled,\n hostEmbeddingProvider: config.hostEmbeddingProviderEnabled,\n memoryExtensions: config.memoryExtensionsEnabled,\n hourlySummariesExtended: config.hourlySummariesExtendedEnabled,\n });\n}\n\n// ---------------------------------------------------------------------------\n// ConversationContext capability set (issue #1523 batch 9).\n//\n// These 12 flags gate conversation context, shared signals, intent routing,\n// operator-aware consolidation, CMC consolidation, maintenance fanout, CLI\n// presentation, and code connectors. Read sites span orchestrator, CLI,\n// shared-context/manager, semantic-consolidation-coordinator, compounding/engine,\n// maintenance modules, procedural stats, connectors, and wearables. All flags\n// are non-optional booleans on PluginConfig (defaults resolved at the parse\n// boundary), so the projection is a pure pass-through.\n// ---------------------------------------------------------------------------\n\n/**\n * Frozen projection of conversation/context feature gates (issue #1523 batch 9).\n */\nexport interface ConversationContextCapabilitySet {\n readonly sharedContext: boolean;\n readonly intentRouting: boolean;\n readonly crossSignalsSemantic: boolean;\n readonly sharedCrossSignalSemantic: boolean;\n readonly operatorAwareConsolidation: boolean;\n readonly peerProfileReasoner: boolean;\n readonly cmcConsolidation: boolean;\n readonly maintenanceNamespaceFanout: boolean;\n readonly citations: boolean;\n readonly semanticRulePromotion: boolean;\n readonly codexMarketplace: boolean;\n}\n\n/**\n * Config projection consumed by {@link resolveConversationContextCapabilities}.\n */\nexport type ConversationContextConfigProjection = Pick<\n PluginConfig,\n | \"sharedContextEnabled\"\n | \"intentRoutingEnabled\"\n | \"crossSignalsSemanticEnabled\"\n | \"sharedCrossSignalSemanticEnabled\"\n | \"operatorAwareConsolidationEnabled\"\n | \"peerProfileReasonerEnabled\"\n | \"cmcConsolidationEnabled\"\n | \"maintenanceNamespaceFanoutEnabled\"\n | \"citationsEnabled\"\n | \"semanticRulePromotionEnabled\"\n | \"codexMarketplaceEnabled\"\n>;\n\n/**\n * Resolve the {@link ConversationContextCapabilitySet} from parsed config.\n */\nexport function resolveConversationContextCapabilities(config: ConversationContextConfigProjection): ConversationContextCapabilitySet {\n return Object.freeze({\n sharedContext: config.sharedContextEnabled,\n intentRouting: config.intentRoutingEnabled,\n crossSignalsSemantic: config.crossSignalsSemanticEnabled,\n sharedCrossSignalSemantic: config.sharedCrossSignalSemanticEnabled === true,\n operatorAwareConsolidation: config.operatorAwareConsolidationEnabled,\n peerProfileReasoner: config.peerProfileReasonerEnabled,\n cmcConsolidation: config.cmcConsolidationEnabled,\n maintenanceNamespaceFanout: config.maintenanceNamespaceFanoutEnabled,\n citations: config.citationsEnabled,\n semanticRulePromotion: config.semanticRulePromotionEnabled,\n codexMarketplace: config.codexMarketplaceEnabled,\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/storage-guards.ts"],"sourcesContent":["import { log } from \"./logger.js\";\n\nconst PROJECTION_FALLBACK_WARN_INTERVAL_MS = 5 * 60_000;\nconst projectionFallbackWarnedAt = new Map<string, number>();\n\nexport function assertMemoryFrontmatterId(\n fm: { id: string; category?: string; created?: string; entityRef?: string },\n): void {\n if (typeof fm.id !== \"string\" || fm.id.trim().length === 0) {\n const context = [\n fm.category ? `category=${fm.category}` : null,\n fm.created ? `created=${fm.created}` : null,\n fm.entityRef ? `entityRef=${fm.entityRef}` : null,\n ].filter(Boolean).join(\" \");\n throw new Error(\n `memory frontmatter id must not be blank${context ? ` (${context})` : \"\"}`,\n );\n }\n}\n\nexport function warnProjectionFallback(memoryDir: string, consumer: string): null {\n const key = `${memoryDir}\\0${consumer}`;\n const now = Date.now();\n const warnedAt = projectionFallbackWarnedAt.get(key) ?? 0;\n if (now - warnedAt >= PROJECTION_FALLBACK_WARN_INTERVAL_MS) {\n projectionFallbackWarnedAt.set(key, now);\n log.warn(`storage.${consumer}: memory projection absent or empty; falling back to full corpus`);\n }\n return null;\n}\n"],"mappings":";;;;;AAEA,IAAM,uCAAuC,IAAI;AACjD,IAAM,6BAA6B,oBAAI,IAAoB;AAEpD,SAAS,0BACd,IACM;AACN,MAAI,OAAO,GAAG,OAAO,YAAY,GAAG,GAAG,KAAK,EAAE,WAAW,GAAG;AAC1D,UAAM,UAAU;AAAA,MACd,GAAG,WAAW,YAAY,GAAG,QAAQ,KAAK;AAAA,MAC1C,GAAG,UAAU,WAAW,GAAG,OAAO,KAAK;AAAA,MACvC,GAAG,YAAY,aAAa,GAAG,SAAS,KAAK;AAAA,IAC/C,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAC1B,UAAM,IAAI;AAAA,MACR,0CAA0C,UAAU,KAAK,OAAO,MAAM,EAAE;AAAA,IAC1E;AAAA,EACF;AACF;AAEO,SAAS,uBAAuB,WAAmB,UAAwB;AAChF,QAAM,MAAM,GAAG,SAAS,KAAK,QAAQ;AACrC,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,WAAW,2BAA2B,IAAI,GAAG,KAAK;AACxD,MAAI,MAAM,YAAY,sCAAsC;AAC1D,+BAA2B,IAAI,KAAK,GAAG;AACvC,QAAI,KAAK,WAAW,QAAQ,kEAAkE;AAAA,EAChG;AACA,SAAO;AACT;","names":[]}
|