@remnic/core 9.3.718 → 9.3.720
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-boundary.d.ts +10 -7
- package/dist/access-boundary.js +18 -17
- package/dist/access-cli.js +39 -37
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +11 -8
- package/dist/access-http.js +25 -24
- package/dist/access-mcp.d.ts +11 -8
- package/dist/access-mcp.js +21 -20
- package/dist/access-operations-batch.js +19 -18
- package/dist/access-operations.d.ts +11 -8
- package/dist/access-operations.js +20 -19
- package/dist/access-schema.d.ts +19 -19
- package/dist/{access-service-CZbnt0t2.d.ts → access-service-Dw-tK1pa.d.ts} +3 -3
- package/dist/access-service.d.ts +11 -8
- package/dist/access-service.js +17 -16
- package/dist/access-surface-catalog.d.ts +10 -7
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +1 -1
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +11 -8
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +4 -4
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +30 -2
- package/dist/capabilities.js +3 -1
- package/dist/{catalog-CEYlMs9X.d.ts → catalog-B9SPfcsE.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +7 -7
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.d.ts +4 -0
- package/dist/causal-trajectory-graph.js +1 -1
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-7EEVXW7X.js → chunk-2RBMGTK4.js} +2 -2
- package/dist/{chunk-L5ASZA2F.js → chunk-3XEGHEQ2.js} +17 -2
- package/dist/chunk-3XEGHEQ2.js.map +1 -0
- package/dist/{chunk-YYOK4GVF.js → chunk-422TIKIH.js} +3 -3
- package/dist/{chunk-KRGC4EDX.js → chunk-5YQN2XYC.js} +2 -2
- package/dist/{chunk-ANI5DUAE.js → chunk-6S5ZI7HB.js} +7 -7
- package/dist/{chunk-JIZGYQYH.js → chunk-ATGRQ2NE.js} +4 -4
- package/dist/chunk-BCDKBDSY.js +173 -0
- package/dist/chunk-BCDKBDSY.js.map +1 -0
- package/dist/{chunk-P5IPFZVH.js → chunk-BGQ7WKMT.js} +5 -5
- package/dist/{chunk-JAKYZGV4.js → chunk-CYJV5AYC.js} +3 -3
- package/dist/{chunk-APUJIMDM.js → chunk-DG3ORVCU.js} +2 -2
- package/dist/{chunk-PBH4JUAB.js → chunk-DVRHLN2O.js} +18 -3
- package/dist/chunk-DVRHLN2O.js.map +1 -0
- package/dist/{chunk-S35FX6MU.js → chunk-H2AGJ5SV.js} +2 -2
- package/dist/{chunk-A66YRSOS.js → chunk-HAPVZ4CO.js} +2 -2
- package/dist/{chunk-4KZGLDJJ.js → chunk-HHSO6VCQ.js} +2 -2
- package/dist/{chunk-2VHMVWPU.js → chunk-HTIL3EJK.js} +2 -2
- package/dist/{chunk-UCJ5CDTK.js → chunk-HU4EBIXO.js} +22 -14
- package/dist/chunk-HU4EBIXO.js.map +1 -0
- package/dist/{chunk-CTOQEZSN.js → chunk-IPJD7WJH.js} +2 -2
- package/dist/{chunk-DIFU33GU.js → chunk-JV7Q226R.js} +2 -2
- package/dist/{chunk-U5H7WHTS.js → chunk-KNN6DAZQ.js} +4 -4
- package/dist/{chunk-3JJWNZTT.js → chunk-L2B3EE7B.js} +102 -42
- package/dist/chunk-L2B3EE7B.js.map +1 -0
- package/dist/{chunk-5RHM43BJ.js → chunk-LLLG5PTE.js} +3 -3
- package/dist/{chunk-F3PVXKHP.js → chunk-M3GBOKUX.js} +22 -16
- package/dist/chunk-M3GBOKUX.js.map +1 -0
- package/dist/{chunk-2PTBLS2S.js → chunk-NBYXOEO7.js} +2 -2
- package/dist/{chunk-URM3HW2R.js → chunk-NE5WFIY6.js} +2 -2
- package/dist/{chunk-REWQAFSA.js → chunk-NERBFQNY.js} +2 -2
- package/dist/{chunk-773DPI6T.js → chunk-NO4DWSQE.js} +10 -10
- package/dist/{chunk-OZ2KQ7HF.js → chunk-O6SEGJNV.js} +2 -2
- package/dist/{chunk-IF362TF6.js → chunk-PMBWVQ7D.js} +2 -2
- package/dist/chunk-PMBWVQ7D.js.map +1 -0
- package/dist/{chunk-OJU73BUX.js → chunk-PPVCZPYA.js} +2 -2
- package/dist/{chunk-S3OPGGZB.js → chunk-RIW3UDDY.js} +2 -2
- package/dist/{chunk-AU7Q3LSC.js → chunk-RKNJBZ55.js} +7 -7
- package/dist/{chunk-EH27LHML.js → chunk-RX7EBFFJ.js} +2 -2
- package/dist/{chunk-2COL35PK.js → chunk-T3XSXAWK.js} +2 -2
- package/dist/{chunk-2KAYTPPT.js → chunk-UUMYB3FR.js} +4 -4
- package/dist/{chunk-72VDBALG.js → chunk-V5EGLYXE.js} +52 -43
- package/dist/chunk-V5EGLYXE.js.map +1 -0
- package/dist/{chunk-LXSG4L55.js → chunk-V7UUIIR7.js} +2 -2
- package/dist/{chunk-HLIR3RJ7.js → chunk-WBHQ5LP5.js} +2 -2
- package/dist/{chunk-YGKUAX2B.js → chunk-XROP4ZHB.js} +8 -8
- package/dist/chunk-XROP4ZHB.js.map +1 -0
- package/dist/{chunk-NFRBADNR.js → chunk-Y4I7SOJN.js} +559 -318
- package/dist/chunk-Y4I7SOJN.js.map +1 -0
- package/dist/{chunk-YTZLBBEB.js → chunk-YMPEGBLL.js} +3 -3
- package/dist/chunk-YN2UN7N3.js +217 -0
- package/dist/chunk-YN2UN7N3.js.map +1 -0
- package/dist/{chunk-YI7RZO7Y.js → chunk-ZE7GJ76H.js} +1 -1
- package/dist/{chunk-74MYJSKB.js → chunk-ZG2WP425.js} +2 -2
- package/dist/{chunk-A5TUPZ3N.js → chunk-ZKUW2MJG.js} +43 -1
- package/dist/chunk-ZKUW2MJG.js.map +1 -0
- package/dist/{chunk-N3DUH6OO.js → chunk-ZW5I3UNP.js} +1 -1
- package/dist/chunk-ZW5I3UNP.js.map +1 -0
- package/dist/{cli-1orcdXOY.d.ts → cli-qcLCJlOD.d.ts} +3 -3
- package/dist/cli.d.ts +9 -6
- package/dist/cli.js +43 -42
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +4 -4
- 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 +4 -4
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +4 -4
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +4 -4
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +11 -8
- 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/fallback-llm.d.ts +1 -1
- package/dist/{forget-U66KPDPY.js → forget-OEE5OELK.js} +2 -2
- package/dist/graph-dashboard-diff.d.ts +4 -0
- package/dist/graph-dashboard-key.d.ts +4 -0
- package/dist/graph-dashboard-parser.d.ts +4 -0
- package/dist/{graph-edge-decay-JLOF3YW4.js → graph-edge-decay-QDDC6AVV.js} +2 -2
- package/dist/graph-edge-reinforcement.d.ts +4 -0
- package/dist/graph-snapshot.d.ts +7 -3
- package/dist/graph-snapshot.js +2 -2
- package/dist/graph.d.ts +7 -1
- package/dist/graph.js +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +571 -570
- package/dist/index.js +57 -55
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +4 -4
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +8 -8
- 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/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +4 -4
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
- package/dist/maintenance/rebuild-memory-projection.js +5 -5
- package/dist/mcp-memory-inspector-app.d.ts +11 -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 +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +9 -9
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +4 -4
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -2
- package/dist/operator-toolkit.js +16 -15
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +6 -6
- package/dist/{orchestrator-kOxredSr.d.ts → orchestrator-Bg5Fnza6.d.ts} +178 -81
- package/dist/orchestrator.d.ts +8 -5
- package/dist/orchestrator.js +31 -29
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.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-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-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 +18 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +3 -3
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +7 -7
- 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-B58v3S-a.d.ts → semantic-consolidation-ClOiS6KP.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +5 -5
- package/dist/semantic-rule-promotion.js +4 -4
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +4 -4
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +45 -1
- package/dist/storage.js +3 -3
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.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 +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/trust-score-stage.d.ts +178 -0
- package/dist/trust-score-stage.js +18 -0
- package/dist/trust-score-stage.js.map +1 -0
- package/dist/trust-score.d.ts +164 -0
- package/dist/trust-score.js +22 -0
- package/dist/trust-score.js.map +1 -0
- package/dist/{types-ACmDOScJ.d.ts → types-BSdUWePh.d.ts} +37 -1
- 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 +4 -4
- package/package.json +2 -2
- package/src/access-service.ts +8 -5
- package/src/capabilities-cluster-b.test.ts +78 -0
- package/src/capabilities.test.ts +11 -1
- package/src/capabilities.ts +58 -0
- package/src/cli/research-status-commands.ts +7 -3
- package/src/cli.ts +11 -6
- package/src/config.ts +51 -0
- package/src/graph-snapshot.ts +3 -3
- package/src/graph.ts +8 -7
- package/src/lifecycle/tombstones.test.ts +242 -1
- package/src/lifecycle/tombstones.ts +157 -70
- package/src/operator-toolkit.ts +24 -7
- package/src/orchestration/tier-migration-coordinator.ts +286 -0
- package/src/orchestrator.ts +331 -222
- package/src/recall-xray.test.ts +62 -0
- package/src/recall-xray.ts +34 -0
- package/src/trust-score-recall-paths.test.ts +157 -0
- package/src/trust-score-stage.test.ts +350 -0
- package/src/trust-score-stage.ts +398 -0
- package/src/trust-score.test.ts +282 -0
- package/src/trust-score.ts +449 -0
- package/src/types.ts +42 -0
- package/dist/chunk-3JJWNZTT.js.map +0 -1
- package/dist/chunk-72VDBALG.js.map +0 -1
- package/dist/chunk-A5TUPZ3N.js.map +0 -1
- package/dist/chunk-F3PVXKHP.js.map +0 -1
- package/dist/chunk-IF362TF6.js.map +0 -1
- package/dist/chunk-L5ASZA2F.js.map +0 -1
- package/dist/chunk-N3DUH6OO.js.map +0 -1
- package/dist/chunk-NFRBADNR.js.map +0 -1
- package/dist/chunk-PBH4JUAB.js.map +0 -1
- package/dist/chunk-UCJ5CDTK.js.map +0 -1
- package/dist/chunk-YGKUAX2B.js.map +0 -1
- /package/dist/{chunk-7EEVXW7X.js.map → chunk-2RBMGTK4.js.map} +0 -0
- /package/dist/{chunk-YYOK4GVF.js.map → chunk-422TIKIH.js.map} +0 -0
- /package/dist/{chunk-KRGC4EDX.js.map → chunk-5YQN2XYC.js.map} +0 -0
- /package/dist/{chunk-ANI5DUAE.js.map → chunk-6S5ZI7HB.js.map} +0 -0
- /package/dist/{chunk-JIZGYQYH.js.map → chunk-ATGRQ2NE.js.map} +0 -0
- /package/dist/{chunk-P5IPFZVH.js.map → chunk-BGQ7WKMT.js.map} +0 -0
- /package/dist/{chunk-JAKYZGV4.js.map → chunk-CYJV5AYC.js.map} +0 -0
- /package/dist/{chunk-APUJIMDM.js.map → chunk-DG3ORVCU.js.map} +0 -0
- /package/dist/{chunk-S35FX6MU.js.map → chunk-H2AGJ5SV.js.map} +0 -0
- /package/dist/{chunk-A66YRSOS.js.map → chunk-HAPVZ4CO.js.map} +0 -0
- /package/dist/{chunk-4KZGLDJJ.js.map → chunk-HHSO6VCQ.js.map} +0 -0
- /package/dist/{chunk-2VHMVWPU.js.map → chunk-HTIL3EJK.js.map} +0 -0
- /package/dist/{chunk-CTOQEZSN.js.map → chunk-IPJD7WJH.js.map} +0 -0
- /package/dist/{chunk-DIFU33GU.js.map → chunk-JV7Q226R.js.map} +0 -0
- /package/dist/{chunk-U5H7WHTS.js.map → chunk-KNN6DAZQ.js.map} +0 -0
- /package/dist/{chunk-5RHM43BJ.js.map → chunk-LLLG5PTE.js.map} +0 -0
- /package/dist/{chunk-2PTBLS2S.js.map → chunk-NBYXOEO7.js.map} +0 -0
- /package/dist/{chunk-URM3HW2R.js.map → chunk-NE5WFIY6.js.map} +0 -0
- /package/dist/{chunk-REWQAFSA.js.map → chunk-NERBFQNY.js.map} +0 -0
- /package/dist/{chunk-773DPI6T.js.map → chunk-NO4DWSQE.js.map} +0 -0
- /package/dist/{chunk-OZ2KQ7HF.js.map → chunk-O6SEGJNV.js.map} +0 -0
- /package/dist/{chunk-OJU73BUX.js.map → chunk-PPVCZPYA.js.map} +0 -0
- /package/dist/{chunk-S3OPGGZB.js.map → chunk-RIW3UDDY.js.map} +0 -0
- /package/dist/{chunk-AU7Q3LSC.js.map → chunk-RKNJBZ55.js.map} +0 -0
- /package/dist/{chunk-EH27LHML.js.map → chunk-RX7EBFFJ.js.map} +0 -0
- /package/dist/{chunk-2COL35PK.js.map → chunk-T3XSXAWK.js.map} +0 -0
- /package/dist/{chunk-2KAYTPPT.js.map → chunk-UUMYB3FR.js.map} +0 -0
- /package/dist/{chunk-LXSG4L55.js.map → chunk-V7UUIIR7.js.map} +0 -0
- /package/dist/{chunk-HLIR3RJ7.js.map → chunk-WBHQ5LP5.js.map} +0 -0
- /package/dist/{chunk-YTZLBBEB.js.map → chunk-YMPEGBLL.js.map} +0 -0
- /package/dist/{chunk-YI7RZO7Y.js.map → chunk-ZE7GJ76H.js.map} +0 -0
- /package/dist/{chunk-74MYJSKB.js.map → chunk-ZG2WP425.js.map} +0 -0
- /package/dist/{forget-U66KPDPY.js.map → forget-OEE5OELK.js.map} +0 -0
- /package/dist/{graph-edge-decay-JLOF3YW4.js.map → graph-edge-decay-QDDC6AVV.js.map} +0 -0
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
// - Semantic tier (4) ships dark, off by default (rule 48).
|
|
41
41
|
// ---------------------------------------------------------------------------
|
|
42
42
|
|
|
43
|
-
import { serializeMutations } from "../utils/serialize-mutations.js";
|
|
43
|
+
import { serializeMutations, withHeldFileLock } from "../utils/serialize-mutations.js";
|
|
44
44
|
|
|
45
45
|
/** Why a tombstone was emitted. */
|
|
46
46
|
export type TombstoneReason =
|
|
@@ -129,6 +129,20 @@ export interface TombstoneStoreOptions {
|
|
|
129
129
|
* skipped entirely.
|
|
130
130
|
*/
|
|
131
131
|
semanticSimilarity?: (a: string, b: string) => number;
|
|
132
|
+
/**
|
|
133
|
+
* Cross-process write-lock timings for the tombstone JSONL mutation lock
|
|
134
|
+
* (issue #1639). The secure-store append is a read-merge-write (read
|
|
135
|
+
* encrypted → decrypt → concat → re-encrypt → atomic rename), so two
|
|
136
|
+
* Remnic processes appending concurrently can each read the same contents
|
|
137
|
+
* and the last writer drops the other's entry — breaking the
|
|
138
|
+
* non-resurrection invariant. The lock serializes the mutation across
|
|
139
|
+
* processes. When omitted, defaults that fit the short tombstone critical
|
|
140
|
+
* section (mirroring the summary-snapshot lock): staleMs 30s, maxWaitMs 5s,
|
|
141
|
+
* heartbeatMs floor(staleMs/3).
|
|
142
|
+
*/
|
|
143
|
+
readonly lockStaleMs?: number;
|
|
144
|
+
readonly lockMaxWaitMs?: number;
|
|
145
|
+
readonly lockHeartbeatMs?: number;
|
|
132
146
|
}
|
|
133
147
|
|
|
134
148
|
/** Injected file I/O — the StorageManager wires its secure-store-aware
|
|
@@ -261,7 +275,33 @@ export class TombstoneStore {
|
|
|
261
275
|
private readonly namespace: string,
|
|
262
276
|
private readonly options: TombstoneStoreOptions,
|
|
263
277
|
private readonly io: TombstoneFileIo,
|
|
264
|
-
) {
|
|
278
|
+
) {
|
|
279
|
+
this.lockStaleMs = this.options.lockStaleMs ?? 30_000;
|
|
280
|
+
this.lockMaxWaitMs = this.options.lockMaxWaitMs ?? 5_000;
|
|
281
|
+
this.lockHeartbeatMs =
|
|
282
|
+
this.options.lockHeartbeatMs ?? Math.max(100, Math.floor(this.lockStaleMs / 3));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Resolved cross-process write-lock timings (issue #1639). Defaults mirror
|
|
287
|
+
* the summary-snapshot lock: a tombstone append critical section is
|
|
288
|
+
* sub-second, so a stale lock older than 30s is a crashed holder; bounded
|
|
289
|
+
* acquisition gives up after 5s and strict-fails rather than clobbering a
|
|
290
|
+
* concurrent writer (which would reintroduce the lost-write race this lock
|
|
291
|
+
* closes).
|
|
292
|
+
*/
|
|
293
|
+
private readonly lockStaleMs: number;
|
|
294
|
+
private readonly lockMaxWaitMs: number;
|
|
295
|
+
private readonly lockHeartbeatMs: number;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Sibling advisory lockfile: `<tombstones>.jsonl` → `<tombstones>.lock`.
|
|
299
|
+
* Shared by append, revoke, and rebuild so every JSONL mutation serializes
|
|
300
|
+
* against the others across processes (issue #1639: "shared with rebuilds").
|
|
301
|
+
*/
|
|
302
|
+
private get lockPath(): string {
|
|
303
|
+
return this.filePath.replace(/\.jsonl$/i, "") + ".lock";
|
|
304
|
+
}
|
|
265
305
|
|
|
266
306
|
/** Lazy load + build the in-memory index. Idempotent. */
|
|
267
307
|
async load(): Promise<void> {
|
|
@@ -473,10 +513,48 @@ export class TombstoneStore {
|
|
|
473
513
|
|
|
474
514
|
private serializeAppend(entry: TombstoneEntry): Promise<void> {
|
|
475
515
|
const line = JSON.stringify(entry) + "\n";
|
|
476
|
-
// serializeMutations
|
|
477
|
-
//
|
|
516
|
+
// serializeMutations (in-process, rule 40 rejection recovery) wraps the
|
|
517
|
+
// cross-process lock: within one process appends are queued, and across
|
|
518
|
+
// processes the advisory lockfile serializes the read-merge-write so two
|
|
519
|
+
// Remnic processes appending concurrently cannot drop each other's entry
|
|
520
|
+
// (issue #1639). Under the lock we re-sync the in-memory index against
|
|
521
|
+
// disk first, so indexEntry builds on the peer's just-appended entries.
|
|
478
522
|
return serializeMutations(`tombstone:${this.filePath}`, () =>
|
|
479
|
-
this.
|
|
523
|
+
this.withWriteLock(async () => {
|
|
524
|
+
await this.ensureFreshAgainstDisk();
|
|
525
|
+
await this.io.append(this.filePath, line);
|
|
526
|
+
}),
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Acquire the cross-process tombstone write lock and run `task` under it
|
|
532
|
+
* (issue #1639). Strict-fail on acquisition timeout: the secure-store append
|
|
533
|
+
* is a read-merge-write, so a best-effort unlocked run would clobber a
|
|
534
|
+
* concurrent writer — reintroducing the lost-write race this lock closes.
|
|
535
|
+
* The thrown error surfaces to the caller (rule 34: never silently drop);
|
|
536
|
+
* rebuild recovers the tombstone on the next maintenance cycle.
|
|
537
|
+
*
|
|
538
|
+
* In-process callers are already serialized by serializeMutations (one key
|
|
539
|
+
* for append/revoke/rebuild), so this lock only contends across processes —
|
|
540
|
+
* the single-process daemon (default deployment) acquires it immediately.
|
|
541
|
+
*/
|
|
542
|
+
private withWriteLock<T>(task: () => Promise<T>): Promise<T> {
|
|
543
|
+
return withHeldFileLock(
|
|
544
|
+
this.lockPath,
|
|
545
|
+
{
|
|
546
|
+
staleMs: this.lockStaleMs,
|
|
547
|
+
maxWaitMs: this.lockMaxWaitMs,
|
|
548
|
+
heartbeatMs: this.lockHeartbeatMs,
|
|
549
|
+
},
|
|
550
|
+
async (acquired) => {
|
|
551
|
+
if (!acquired) {
|
|
552
|
+
throw new Error(
|
|
553
|
+
"could not acquire tombstone write lock (contention timeout or filesystem error)",
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
return task();
|
|
557
|
+
},
|
|
480
558
|
);
|
|
481
559
|
}
|
|
482
560
|
|
|
@@ -595,72 +673,81 @@ export class TombstoneStore {
|
|
|
595
673
|
* Returns the count of tombstone entries written.
|
|
596
674
|
*/
|
|
597
675
|
async rebuild(retiredMemories: ReadonlyArray<RetiredMemoryRecord>): Promise<number> {
|
|
598
|
-
//
|
|
599
|
-
//
|
|
600
|
-
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
//
|
|
604
|
-
//
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
)
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
676
|
+
// The full read-merge-write runs UNDER the cross-process lock (issue #1639,
|
|
677
|
+
// cursor/codex review): we ensureFreshAgainstDisk() first so the in-memory
|
|
678
|
+
// index reflects any peer append/revoke that landed while we waited for the
|
|
679
|
+
// lock, THEN compute the payload. Rebuild rewrites the entire JSONL, so a
|
|
680
|
+
// payload built from a stale this.entries would overwrite a peer's just-
|
|
681
|
+
// written entry and resurrect a retired fact — the exact invariant this
|
|
682
|
+
// store exists to enforce. serializeMutations (in-process) wraps the
|
|
683
|
+
// cross-process lock; the key is unified with append/revoke so they cannot
|
|
684
|
+
// interleave in-process either.
|
|
685
|
+
const rebuiltCount = await serializeMutations(`tombstone:${this.filePath}`, () =>
|
|
686
|
+
this.withWriteLock(async () => {
|
|
687
|
+
await this.ensureFreshAgainstDisk();
|
|
688
|
+
// Preserve existing revocations (all namespaces — ids are globally
|
|
689
|
+
// unique) so a rebuild does not silently un-revoke.
|
|
690
|
+
const existingRevocations = this.entries.filter((e) => e.kind === "revocation");
|
|
691
|
+
// Preserve tombstone entries from OTHER namespaces when the backing
|
|
692
|
+
// file is shared (issue #1579 thread Oc2MJ). rebuild rewrites the
|
|
693
|
+
// entire file; without preserving foreign entries, rebuilding
|
|
694
|
+
// namespace A would silently delete namespace B's tombstones, allowing
|
|
695
|
+
// resurrection in B.
|
|
696
|
+
const foreignTombstones = this.entries.filter(
|
|
697
|
+
(e) => e.kind === "tombstone" && e.namespace !== this.namespace,
|
|
698
|
+
);
|
|
699
|
+
// Reuse existing tombstone ids for source-equivalent entries so a prior
|
|
700
|
+
// revocation (which references the tombstone id) survives rebuild —
|
|
701
|
+
// minting fresh ids would orphan the revocation and silently un-revoke.
|
|
702
|
+
// Issue #1579 thread Oci-T: key the reuse map by (sourceMemoryId,
|
|
703
|
+
// supersessionKey). See the append path above for the full rationale.
|
|
704
|
+
const existingBySource = new Map<string, string>();
|
|
705
|
+
for (const e of this.entries) {
|
|
706
|
+
if (e.kind === "tombstone") {
|
|
707
|
+
existingBySource.set(`${e.sourceMemoryId}\u{0000}${e.supersessionKey ?? ""}`, e.id);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const rebuilt: TombstoneEntry[] = retiredMemories.map((m) => ({
|
|
711
|
+
id:
|
|
712
|
+
existingBySource.get(`${m.memoryId}\u{0000}${m.supersessionKey ?? ""}`) ??
|
|
713
|
+
newTombstoneId(),
|
|
714
|
+
kind: "tombstone" as const,
|
|
715
|
+
reason: m.reason,
|
|
716
|
+
sourceMemoryId: m.memoryId,
|
|
717
|
+
contentHash: m.contentHash ?? this.options.hashContent(m.rawContent),
|
|
718
|
+
normalizedText: this.options.normalizeText(m.rawContent),
|
|
719
|
+
...(m.entityRef ? { entityRef: m.entityRef } : {}),
|
|
720
|
+
...(m.supersessionKey ? { supersessionKey: m.supersessionKey } : {}),
|
|
721
|
+
namespace: this.namespace,
|
|
722
|
+
createdAt: m.createdAt,
|
|
723
|
+
createdBy: m.createdBy,
|
|
724
|
+
}));
|
|
725
|
+
// Sort deterministically (rule 38): createdAt, then id for stability.
|
|
726
|
+
rebuilt.sort((a, b) =>
|
|
727
|
+
a.createdAt === b.createdAt
|
|
728
|
+
? a.id < b.id ? -1 : a.id > b.id ? 1 : 0
|
|
729
|
+
: a.createdAt < b.createdAt ? -1 : 1,
|
|
730
|
+
);
|
|
731
|
+
const all = [...rebuilt, ...existingRevocations, ...foreignTombstones].sort((a, b) =>
|
|
732
|
+
a.createdAt === b.createdAt
|
|
733
|
+
? a.id < b.id ? -1 : a.id > b.id ? 1 : 0
|
|
734
|
+
: a.createdAt < b.createdAt ? -1 : 1,
|
|
735
|
+
);
|
|
736
|
+
const serialized =
|
|
737
|
+
all.map((e) => JSON.stringify(e)).join("\n") + (all.length > 0 ? "\n" : "");
|
|
738
|
+
await this.io.write(this.filePath, serialized);
|
|
739
|
+
// Update the in-memory index under the same lock so it stays consistent
|
|
740
|
+
// with the file we just wrote (no window for a lookup to see a stale
|
|
741
|
+
// index that misses a just-rebuilt entry).
|
|
742
|
+
this.resetIndex();
|
|
743
|
+
for (const entry of all) this.indexEntry(entry);
|
|
744
|
+
this.corruptedLines = 0;
|
|
745
|
+
this.loaded = true;
|
|
746
|
+
this.markWritten();
|
|
747
|
+
return rebuilt.length;
|
|
748
|
+
}),
|
|
657
749
|
);
|
|
658
|
-
|
|
659
|
-
for (const entry of all) this.indexEntry(entry);
|
|
660
|
-
this.corruptedLines = 0;
|
|
661
|
-
this.loaded = true;
|
|
662
|
-
this.markWritten();
|
|
663
|
-
return rebuilt.length;
|
|
750
|
+
return rebuiltCount;
|
|
664
751
|
}
|
|
665
752
|
}
|
|
666
753
|
|
package/src/operator-toolkit.ts
CHANGED
|
@@ -24,6 +24,11 @@ import {
|
|
|
24
24
|
type EvalHarnessStatus,
|
|
25
25
|
} from "./evals.js";
|
|
26
26
|
import { analyzeGraphHealth, type GraphHealthReport } from "./graph.js";
|
|
27
|
+
import {
|
|
28
|
+
resolveAccessSetupCapabilities,
|
|
29
|
+
resolveCapabilities,
|
|
30
|
+
resolveGraphConstructionCapabilities,
|
|
31
|
+
} from "./capabilities.js";
|
|
27
32
|
import {
|
|
28
33
|
analyzeSessionIntegrity,
|
|
29
34
|
applySessionRepair,
|
|
@@ -412,7 +417,12 @@ export interface BenchmarkRecallOptions {
|
|
|
412
417
|
export interface OperatorRepairOptions {
|
|
413
418
|
config: Pick<
|
|
414
419
|
PluginConfig,
|
|
415
|
-
|
|
420
|
+
| "memoryDir"
|
|
421
|
+
| "entityGraphEnabled"
|
|
422
|
+
| "timeGraphEnabled"
|
|
423
|
+
| "causalGraphEnabled"
|
|
424
|
+
| "multiGraphMemoryEnabled"
|
|
425
|
+
| "graphWriteSessionAdjacencyEnabled"
|
|
416
426
|
>;
|
|
417
427
|
apply?: boolean;
|
|
418
428
|
dryRun?: boolean;
|
|
@@ -904,6 +914,9 @@ async function buildOperatorConfigReviewReport(input: {
|
|
|
904
914
|
config: PluginConfig;
|
|
905
915
|
}): Promise<OperatorConfigReviewReport> {
|
|
906
916
|
const { now, configStatus, config } = input;
|
|
917
|
+
// Resolve recall-operation gates once so this diagnostic reads capabilities,
|
|
918
|
+
// not scattered config flags (#1566 Cluster C — rerank is a mixed-op flag).
|
|
919
|
+
const caps = resolveCapabilities(config);
|
|
907
920
|
const findings: OperatorConfigReviewFinding[] = [];
|
|
908
921
|
const searchBackend = config.searchBackend ?? "qmd";
|
|
909
922
|
const workspaceBootstrapFiles = [
|
|
@@ -920,7 +933,7 @@ async function buildOperatorConfigReviewReport(input: {
|
|
|
920
933
|
config.memoryOsPreset !== "local-llm-heavy" &&
|
|
921
934
|
config.queryAwareIndexingEnabled === false &&
|
|
922
935
|
config.verbatimArtifactsEnabled === false &&
|
|
923
|
-
|
|
936
|
+
!caps.rerank
|
|
924
937
|
) {
|
|
925
938
|
findings.push(buildConfigReviewFinding({
|
|
926
939
|
key: "balanced_preset",
|
|
@@ -1855,8 +1868,10 @@ function summarizeSecurityMitigations(
|
|
|
1855
1868
|
| "recallAuditAnomalyRapidFireLimit"
|
|
1856
1869
|
>,
|
|
1857
1870
|
): OperatorDoctorCheck {
|
|
1858
|
-
|
|
1859
|
-
const
|
|
1871
|
+
// Resolve access-setup gates once (#1566 Cluster B).
|
|
1872
|
+
const accessCaps = resolveAccessSetupCapabilities(config);
|
|
1873
|
+
const budgetEnabled = accessCaps.recallCrossNamespaceBudget;
|
|
1874
|
+
const anomalyEnabled = accessCaps.recallAuditAnomalyDetection;
|
|
1860
1875
|
|
|
1861
1876
|
if (!budgetEnabled && !anomalyEnabled) {
|
|
1862
1877
|
return {
|
|
@@ -2338,10 +2353,12 @@ export async function runOperatorRepair(options: OperatorRepairOptions): Promise
|
|
|
2338
2353
|
const sessionRepairApply = await applySessionRepair({
|
|
2339
2354
|
plan: sessionRepairPlan,
|
|
2340
2355
|
});
|
|
2356
|
+
// Resolve graph-construction gates once for this diagnostic (#1566 Cluster A).
|
|
2357
|
+
const graphCaps = resolveGraphConstructionCapabilities(options.config);
|
|
2341
2358
|
const graphHealth = await analyzeGraphHealth(options.config.memoryDir, {
|
|
2342
|
-
entityGraphEnabled:
|
|
2343
|
-
timeGraphEnabled:
|
|
2344
|
-
causalGraphEnabled:
|
|
2359
|
+
entityGraphEnabled: graphCaps.entityGraph,
|
|
2360
|
+
timeGraphEnabled: graphCaps.timeGraph,
|
|
2361
|
+
causalGraphEnabled: graphCaps.causalGraph,
|
|
2345
2362
|
includeRepairGuidance: true,
|
|
2346
2363
|
});
|
|
2347
2364
|
return {
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier migration coordinator — extracted from the orchestrator (issue #1526).
|
|
3
|
+
*
|
|
4
|
+
* Owns the hot/cold tier-migration lifecycle for a single cycle:
|
|
5
|
+
* - gating (config flags, in-flight guard, min-interval throttle)
|
|
6
|
+
* - budget selection (compounding override or config default)
|
|
7
|
+
* - candidate scanning across hot + cold storage
|
|
8
|
+
* - promotion/demotion execution via TierMigrationExecutor
|
|
9
|
+
* - status recording through TierMigrationStatusStore
|
|
10
|
+
*
|
|
11
|
+
* The orchestrator constructs one instance and delegates the three public
|
|
12
|
+
* entrypoints (`runTierMigrationCycle`, `runTierMigrationNow`,
|
|
13
|
+
* `getTierMigrationStatus`) to it. Storage is passed per-call so extraction
|
|
14
|
+
* (which writes to a namespace-scoped storage) and maintenance/manual (which
|
|
15
|
+
* use the default storage) share one coordinator.
|
|
16
|
+
*
|
|
17
|
+
* Behavior-preserving move from orchestrator.ts. No logic changes — the
|
|
18
|
+
* orchestrator keeps thin delegating methods so existing call sites and
|
|
19
|
+
* tests that exercise the public API continue to work.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
|
|
24
|
+
// StorageManager type comes from the package barrel (type-only) so this
|
|
25
|
+
// module does not add a direct storage.ts import (#1533 ratchet).
|
|
26
|
+
import type { StorageManager } from "../index.js";
|
|
27
|
+
import type { SearchBackend } from "../search/port.js";
|
|
28
|
+
import { TierMigrationExecutor } from "../tier-migration.js";
|
|
29
|
+
import { decideTierTransition, type MemoryTier } from "../tier-routing.js";
|
|
30
|
+
import {
|
|
31
|
+
TierMigrationStatusStore,
|
|
32
|
+
type TierMigrationCycleSummary,
|
|
33
|
+
type TierMigrationStatusSnapshot,
|
|
34
|
+
} from "../recall-state.js";
|
|
35
|
+
import {
|
|
36
|
+
type CompoundingEngine,
|
|
37
|
+
defaultTierMigrationCycleBudget,
|
|
38
|
+
} from "../compounding/engine.js";
|
|
39
|
+
import {
|
|
40
|
+
applyUtilityPromotionRuntimePolicy,
|
|
41
|
+
type UtilityRuntimeValues,
|
|
42
|
+
} from "../utility-runtime.js";
|
|
43
|
+
import type { PluginConfig, MemoryFile } from "../types.js";
|
|
44
|
+
import { log } from "../logger.js";
|
|
45
|
+
|
|
46
|
+
/** Dependencies injected by the orchestrator. All stable references or live
|
|
47
|
+
* accessors (the orchestrator reassigns `qmd` to NoopSearchBackend and
|
|
48
|
+
* `utilityRuntimeValues` after async init, so those arrive as getters). */
|
|
49
|
+
export interface TierMigrationCoordinatorDeps {
|
|
50
|
+
config: PluginConfig;
|
|
51
|
+
/** Live accessor — the orchestrator reassigns this.qmd to NoopSearchBackend
|
|
52
|
+
* after construction when the collection is missing. */
|
|
53
|
+
getQmd: () => SearchBackend;
|
|
54
|
+
tierMigrationStatus: TierMigrationStatusStore;
|
|
55
|
+
/** Live accessor — loaded asynchronously during init, null before that. */
|
|
56
|
+
getUtilityRuntimeValues: () => UtilityRuntimeValues | null;
|
|
57
|
+
/** Live accessor — undefined when compounding is disabled. */
|
|
58
|
+
getCompounding: () => CompoundingEngine | undefined;
|
|
59
|
+
/** Constructs the cold-tier StorageManager for a given parent memory dir.
|
|
60
|
+
* Injected so this module owns no direct storage.ts import (#1533). */
|
|
61
|
+
createColdStorage: (parentDir: string) => StorageManager;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Coordinates a single tier-migration cycle. Owns the in-flight guard and
|
|
66
|
+
* last-run timestamp that previously lived as private orchestrator fields.
|
|
67
|
+
*/
|
|
68
|
+
export class TierMigrationCoordinator {
|
|
69
|
+
private inFlight = false;
|
|
70
|
+
private lastRunAtMs = 0;
|
|
71
|
+
|
|
72
|
+
constructor(private readonly deps: TierMigrationCoordinatorDeps) {}
|
|
73
|
+
|
|
74
|
+
/** Whether a migration cycle is currently executing (diagnostics). */
|
|
75
|
+
isInFlight(): boolean {
|
|
76
|
+
return this.inFlight;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Latest status snapshot (delegates to the status store). */
|
|
80
|
+
getStatus(): TierMigrationStatusSnapshot {
|
|
81
|
+
return this.deps.tierMigrationStatus.get();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Run one tier-migration cycle. Behavior-preserving move of the
|
|
86
|
+
* orchestrator's former `runTierMigrationCycle`.
|
|
87
|
+
*/
|
|
88
|
+
async runCycle(
|
|
89
|
+
storage: StorageManager,
|
|
90
|
+
trigger: "extraction" | "maintenance" | "manual",
|
|
91
|
+
options?: {
|
|
92
|
+
dryRun?: boolean;
|
|
93
|
+
limitOverride?: number;
|
|
94
|
+
force?: boolean;
|
|
95
|
+
},
|
|
96
|
+
): Promise<TierMigrationCycleSummary> {
|
|
97
|
+
const { config, tierMigrationStatus } = this.deps;
|
|
98
|
+
const dryRun = options?.dryRun === true;
|
|
99
|
+
const persistSkipped = options?.force === true || trigger === "manual";
|
|
100
|
+
if (!config.qmdTierMigrationEnabled && options?.force !== true) {
|
|
101
|
+
const skipped: TierMigrationCycleSummary = {
|
|
102
|
+
trigger,
|
|
103
|
+
scanned: 0,
|
|
104
|
+
migrated: 0,
|
|
105
|
+
promoted: 0,
|
|
106
|
+
demoted: 0,
|
|
107
|
+
limit: 0,
|
|
108
|
+
dryRun,
|
|
109
|
+
skipped: "tier_migration_disabled",
|
|
110
|
+
};
|
|
111
|
+
if (persistSkipped) await tierMigrationStatus.recordCycle(skipped);
|
|
112
|
+
return skipped;
|
|
113
|
+
}
|
|
114
|
+
if (
|
|
115
|
+
trigger === "maintenance" &&
|
|
116
|
+
!config.qmdTierAutoBackfillEnabled &&
|
|
117
|
+
options?.force !== true
|
|
118
|
+
) {
|
|
119
|
+
const skipped: TierMigrationCycleSummary = {
|
|
120
|
+
trigger,
|
|
121
|
+
scanned: 0,
|
|
122
|
+
migrated: 0,
|
|
123
|
+
promoted: 0,
|
|
124
|
+
demoted: 0,
|
|
125
|
+
limit: 0,
|
|
126
|
+
dryRun,
|
|
127
|
+
skipped: "maintenance_backfill_disabled",
|
|
128
|
+
};
|
|
129
|
+
if (persistSkipped) await tierMigrationStatus.recordCycle(skipped);
|
|
130
|
+
return skipped;
|
|
131
|
+
}
|
|
132
|
+
if (this.inFlight) {
|
|
133
|
+
const skipped: TierMigrationCycleSummary = {
|
|
134
|
+
trigger,
|
|
135
|
+
scanned: 0,
|
|
136
|
+
migrated: 0,
|
|
137
|
+
promoted: 0,
|
|
138
|
+
demoted: 0,
|
|
139
|
+
limit: 0,
|
|
140
|
+
dryRun,
|
|
141
|
+
skipped: "migration_in_flight",
|
|
142
|
+
};
|
|
143
|
+
if (persistSkipped) await tierMigrationStatus.recordCycle(skipped);
|
|
144
|
+
return skipped;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const budgetTrigger = trigger === "manual" ? "maintenance" : trigger;
|
|
148
|
+
const budget =
|
|
149
|
+
this.deps.getCompounding()?.tierMigrationCycleBudget(budgetTrigger) ??
|
|
150
|
+
defaultTierMigrationCycleBudget(config, budgetTrigger);
|
|
151
|
+
const limit =
|
|
152
|
+
options?.limitOverride !== undefined
|
|
153
|
+
? Math.max(0, Math.floor(options.limitOverride))
|
|
154
|
+
: budget.limit;
|
|
155
|
+
const nowMs = Date.now();
|
|
156
|
+
if (
|
|
157
|
+
options?.force !== true &&
|
|
158
|
+
nowMs - this.lastRunAtMs < budget.minIntervalMs
|
|
159
|
+
) {
|
|
160
|
+
const skipped: TierMigrationCycleSummary = {
|
|
161
|
+
trigger,
|
|
162
|
+
scanned: 0,
|
|
163
|
+
migrated: 0,
|
|
164
|
+
promoted: 0,
|
|
165
|
+
demoted: 0,
|
|
166
|
+
limit,
|
|
167
|
+
dryRun,
|
|
168
|
+
skipped: "min_interval",
|
|
169
|
+
};
|
|
170
|
+
if (persistSkipped) await tierMigrationStatus.recordCycle(skipped);
|
|
171
|
+
return skipped;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const policy = applyUtilityPromotionRuntimePolicy(
|
|
175
|
+
{
|
|
176
|
+
enabled: config.qmdTierMigrationEnabled,
|
|
177
|
+
demotionMinAgeDays: config.qmdTierDemotionMinAgeDays,
|
|
178
|
+
demotionValueThreshold: config.qmdTierDemotionValueThreshold,
|
|
179
|
+
promotionValueThreshold: config.qmdTierPromotionValueThreshold,
|
|
180
|
+
},
|
|
181
|
+
this.deps.getUtilityRuntimeValues(),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
this.inFlight = true;
|
|
185
|
+
try {
|
|
186
|
+
const coldStorage = this.deps.createColdStorage(
|
|
187
|
+
path.join(storage.dir, "cold"),
|
|
188
|
+
);
|
|
189
|
+
const [hotMemories, coldMemories] = await Promise.all([
|
|
190
|
+
storage.readAllMemories(),
|
|
191
|
+
coldStorage.readAllMemories(),
|
|
192
|
+
]);
|
|
193
|
+
const now = new Date();
|
|
194
|
+
const scanLimit = Math.max(0, Math.floor(budget.scanLimit));
|
|
195
|
+
const hotScanLimit = Math.min(
|
|
196
|
+
hotMemories.length,
|
|
197
|
+
Math.ceil(scanLimit * 0.75),
|
|
198
|
+
);
|
|
199
|
+
const coldScanLimit = Math.min(
|
|
200
|
+
coldMemories.length,
|
|
201
|
+
Math.max(0, scanLimit - hotScanLimit),
|
|
202
|
+
);
|
|
203
|
+
const toTimestamp = (memory: MemoryFile): number =>
|
|
204
|
+
Date.parse(memory.frontmatter.updated ?? memory.frontmatter.created);
|
|
205
|
+
const hotCandidates = hotMemories
|
|
206
|
+
.map((memory) => ({ memory, tier: "hot" as MemoryTier }))
|
|
207
|
+
.sort((a, b) => toTimestamp(a.memory) - toTimestamp(b.memory))
|
|
208
|
+
.slice(0, hotScanLimit);
|
|
209
|
+
const coldCandidates = coldMemories
|
|
210
|
+
.map((memory) => ({ memory, tier: "cold" as MemoryTier }))
|
|
211
|
+
.sort((a, b) => toTimestamp(b.memory) - toTimestamp(a.memory))
|
|
212
|
+
.slice(0, coldScanLimit);
|
|
213
|
+
const candidates = [...hotCandidates, ...coldCandidates];
|
|
214
|
+
|
|
215
|
+
const migration = new TierMigrationExecutor({
|
|
216
|
+
storage,
|
|
217
|
+
qmd: this.deps.getQmd(),
|
|
218
|
+
hotCollection: config.qmdCollection,
|
|
219
|
+
coldCollection:
|
|
220
|
+
config.qmdColdCollection ?? `${config.qmdCollection}-cold`,
|
|
221
|
+
autoEmbed: config.qmdAutoEmbedEnabled,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
let migrated = 0;
|
|
225
|
+
let promoted = 0;
|
|
226
|
+
let demoted = 0;
|
|
227
|
+
for (const candidate of candidates) {
|
|
228
|
+
if (migrated >= limit) break;
|
|
229
|
+
const decision = decideTierTransition(
|
|
230
|
+
candidate.memory,
|
|
231
|
+
candidate.tier,
|
|
232
|
+
policy,
|
|
233
|
+
now,
|
|
234
|
+
);
|
|
235
|
+
if (!decision.changed) continue;
|
|
236
|
+
|
|
237
|
+
if (!dryRun) {
|
|
238
|
+
const res = await migration.migrateMemory({
|
|
239
|
+
memory: candidate.memory,
|
|
240
|
+
fromTier: candidate.tier,
|
|
241
|
+
toTier: decision.nextTier,
|
|
242
|
+
reason: `${trigger}:${decision.reason}`,
|
|
243
|
+
});
|
|
244
|
+
if (!res.changed) continue;
|
|
245
|
+
}
|
|
246
|
+
migrated += 1;
|
|
247
|
+
if (decision.nextTier === "cold") demoted += 1;
|
|
248
|
+
if (decision.nextTier === "hot") promoted += 1;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!dryRun) this.lastRunAtMs = Date.now();
|
|
252
|
+
log.debug(
|
|
253
|
+
`tier migration cycle completed: trigger=${trigger} scanned=${candidates.length} migrated=${migrated} limit=${limit}${dryRun ? " dryRun=true" : ""}`,
|
|
254
|
+
);
|
|
255
|
+
const summary: TierMigrationCycleSummary = {
|
|
256
|
+
trigger,
|
|
257
|
+
scanned: candidates.length,
|
|
258
|
+
migrated,
|
|
259
|
+
promoted,
|
|
260
|
+
demoted,
|
|
261
|
+
limit,
|
|
262
|
+
dryRun,
|
|
263
|
+
};
|
|
264
|
+
const shouldPersistCycle = trigger === "manual" || migrated > 0;
|
|
265
|
+
if (shouldPersistCycle) await tierMigrationStatus.recordCycle(summary);
|
|
266
|
+
return summary;
|
|
267
|
+
} catch (err) {
|
|
268
|
+
this.lastRunAtMs = Date.now();
|
|
269
|
+
log.warn(`tier migration cycle failed (${trigger}, fail-open): ${err}`);
|
|
270
|
+
const failed: TierMigrationCycleSummary = {
|
|
271
|
+
trigger,
|
|
272
|
+
scanned: 0,
|
|
273
|
+
migrated: 0,
|
|
274
|
+
promoted: 0,
|
|
275
|
+
demoted: 0,
|
|
276
|
+
limit,
|
|
277
|
+
dryRun,
|
|
278
|
+
errorCount: 1,
|
|
279
|
+
};
|
|
280
|
+
await tierMigrationStatus.recordCycle(failed);
|
|
281
|
+
return failed;
|
|
282
|
+
} finally {
|
|
283
|
+
this.inFlight = false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|