@remnic/core 9.65.2 → 9.65.4
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 +44 -44
- package/dist/access-admin-ops-surface.js +21 -19
- package/dist/access-authorization-probe.d.ts +38 -38
- package/dist/access-authorization-probe.js +22 -20
- package/dist/access-boundary.d.ts +40 -40
- package/dist/access-boundary.js +21 -19
- package/dist/access-cli.js +62 -60
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.d.ts +3 -1
- package/dist/access-extraction-force-flush.d.ts +25 -25
- package/dist/access-extraction-force-flush.js +21 -19
- package/dist/access-health-types.d.ts +3 -1
- package/dist/access-http-lcm-compaction.d.ts +39 -39
- package/dist/access-http-lifecycle-flush.d.ts +39 -39
- package/dist/access-http-offline-stream.d.ts +25 -25
- package/dist/access-http-query.d.ts +4 -2
- package/dist/access-http-query.js +23 -21
- package/dist/access-http.d.ts +51 -44
- package/dist/access-http.js +37 -35
- package/dist/access-identity-continuity-surface.d.ts +12 -12
- package/dist/access-identity-continuity-surface.js +21 -19
- package/dist/access-lcm-surface.d.ts +27 -27
- package/dist/access-lcm-surface.js +21 -19
- package/dist/access-mcp-admin-tools.d.ts +9 -1
- package/dist/access-mcp-admin-tools.js +3 -1
- package/dist/access-mcp-output-schemas.js +1 -1
- package/dist/access-mcp.d.ts +39 -39
- package/dist/access-mcp.js +31 -29
- package/dist/access-memory-search-fanout.d.ts +4 -2
- package/dist/{access-namespace-preflight-Dhvcl-T0.d.ts → access-namespace-preflight-D_6mo8ms.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +5 -3
- package/dist/access-namespace-preflight.js +21 -19
- package/dist/access-observe-write-surface.d.ts +39 -39
- package/dist/access-observe-write-surface.js +21 -19
- package/dist/access-offline-manifest.d.ts +12 -12
- package/dist/access-offline-manifest.js +3 -3
- package/dist/access-operations-batch.js +25 -23
- package/dist/access-operations.d.ts +40 -40
- package/dist/access-operations.js +28 -26
- package/dist/access-recall-concurrency.d.ts +40 -40
- package/dist/access-recall-concurrency.js +21 -19
- package/dist/access-recall-response.d.ts +24 -24
- package/dist/access-recall-response.js +21 -19
- package/dist/access-recall-surface.d.ts +24 -24
- package/dist/access-recall-surface.js +21 -19
- package/dist/access-schema.d.ts +45 -45
- package/dist/{access-service-Bke1alZO.d.ts → access-service-DqPa3hy4.d.ts} +219 -75
- package/dist/access-service-helpers.d.ts +40 -40
- package/dist/access-service.d.ts +47 -47
- package/dist/access-service.js +21 -19
- package/dist/access-surface-catalog.d.ts +40 -40
- package/dist/access-surface-catalog.js +27 -21
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/access-wearables-meetings-surface.d.ts +5 -3
- package/dist/action-confidence.d.ts +3 -1
- package/dist/active-memory-bridge.d.ts +3 -1
- package/dist/active-recall.d.ts +3 -1
- package/dist/active-recall.js +10 -1
- package/dist/active-recall.js.map +1 -1
- package/dist/ambient-provenance.d.ts +3 -1
- package/dist/artifact-search.d.ts +3 -1
- package/dist/{auto-sync-H64ZCNIR.js → auto-sync-XVMLL2RH.js} +5 -5
- package/dist/behavior-learner.d.ts +3 -1
- package/dist/behavior-signals.d.ts +3 -1
- package/dist/bootstrap.d.ts +12 -12
- package/dist/briefing-window.d.ts +4 -2
- package/dist/briefing.d.ts +4 -2
- package/dist/briefing.js +4 -4
- package/dist/buffer-surprise-report.d.ts +3 -1
- package/dist/buffer-turn-helpers.d.ts +3 -1
- package/dist/buffer.d.ts +4 -2
- package/dist/bulk-import/index.d.ts +5 -3
- package/dist/calibration.d.ts +3 -1
- package/dist/capabilities.d.ts +3 -1
- package/dist/{catalog-DV6qnbr7.d.ts → catalog-DO_7zfkv.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +3 -1
- package/dist/causal-consolidation.js +12 -9
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/chunk-236PXYG7.js +232 -0
- package/dist/chunk-236PXYG7.js.map +1 -0
- package/dist/{chunk-YB42WZCY.js → chunk-2IXQSGCU.js} +2 -2
- package/dist/{chunk-PE255F4T.js → chunk-2MVTAIL3.js} +2 -2
- package/dist/{chunk-LGO6A7GJ.js → chunk-3JZBPTLD.js} +338 -80
- package/dist/chunk-3JZBPTLD.js.map +1 -0
- package/dist/{chunk-GDFVPUJ5.js → chunk-3NH3VP3U.js} +2 -2
- package/dist/{chunk-TBOJ76ER.js → chunk-AB3A7EFH.js} +18 -2
- package/dist/{chunk-TBOJ76ER.js.map → chunk-AB3A7EFH.js.map} +1 -1
- package/dist/{chunk-GNCCUR6K.js → chunk-AXGFOFNY.js} +2 -2
- package/dist/{chunk-SB7D4BT7.js → chunk-AZLLH6YA.js} +1554 -114
- package/dist/chunk-AZLLH6YA.js.map +1 -0
- package/dist/{chunk-5YQ63T4B.js → chunk-BK5G2MHO.js} +2 -2
- package/dist/{chunk-LZH5FR2W.js → chunk-CQBIN77D.js} +2 -2
- package/dist/{chunk-35NN3ZFA.js → chunk-CTVWELNK.js} +21 -3
- package/dist/chunk-CTVWELNK.js.map +1 -0
- package/dist/{chunk-NKKD4ZAU.js → chunk-DORFF5N5.js} +2 -2
- package/dist/{chunk-BWLRUMOG.js → chunk-DPNXADEI.js} +441 -2
- package/dist/chunk-DPNXADEI.js.map +1 -0
- package/dist/{chunk-SQI57UCJ.js → chunk-DUEDOYZD.js} +2 -2
- package/dist/{chunk-5NM4OH4P.js → chunk-DUZARALI.js} +3 -3
- package/dist/{chunk-2E36UXVW.js → chunk-ECLJ4LA2.js} +2 -2
- package/dist/{chunk-TZNQUNKL.js → chunk-FC7TIRFS.js} +15 -1
- package/dist/chunk-FC7TIRFS.js.map +1 -0
- package/dist/{chunk-IID2CMUU.js → chunk-GHD2LLG4.js} +2 -2
- package/dist/{chunk-ZQHI2BZB.js → chunk-HF5BUTI6.js} +2 -2
- package/dist/{chunk-3DVBKTSO.js → chunk-HSZMX3ZJ.js} +4 -4
- package/dist/{chunk-S2DPMJXH.js → chunk-I23ZHYSL.js} +2 -2
- package/dist/{chunk-ODZG724P.js → chunk-I2HTKOOP.js} +2 -2
- package/dist/chunk-I32NKXE2.js +879 -0
- package/dist/chunk-I32NKXE2.js.map +1 -0
- package/dist/{chunk-NTWN6HB2.js → chunk-IURMJBGP.js} +2 -2
- package/dist/{chunk-OCZIYAE5.js → chunk-IWKGOCL6.js} +3 -3
- package/dist/{chunk-UXO5IMBK.js → chunk-J3ATBB7M.js} +2 -2
- package/dist/{chunk-Y7DK7HHJ.js → chunk-JPAKBNTM.js} +2 -2
- package/dist/{chunk-SPUJNJJC.js → chunk-KFIOPZ25.js} +2 -2
- package/dist/{chunk-JYYBA7TF.js → chunk-KOO37E4Y.js} +2 -2
- package/dist/{chunk-OTHZWLUD.js → chunk-KPB2QHJZ.js} +2 -2
- package/dist/{chunk-SBDCZ2AX.js → chunk-LBFFNDFM.js} +1 -1
- package/dist/chunk-LBFFNDFM.js.map +1 -0
- package/dist/{chunk-CT56C22C.js → chunk-LYBIHM4N.js} +4 -4
- package/dist/{chunk-3I7ASZN3.js → chunk-MFOSZUIT.js} +2 -2
- package/dist/{chunk-DZUJ46DO.js → chunk-NLEH4RTB.js} +2 -2
- package/dist/{chunk-ZO6AXLEH.js → chunk-OCCHUEZB.js} +2 -2
- package/dist/{chunk-EILZG5VC.js → chunk-OU34IQXV.js} +2 -2
- package/dist/{chunk-2XDWWPTN.js → chunk-OZDFB22S.js} +2 -2
- package/dist/{chunk-UIN2BWDD.js → chunk-PTZQYCJ4.js} +2 -2
- package/dist/{chunk-7BY7V4YX.js → chunk-Q4WONDMJ.js} +4 -3
- package/dist/{chunk-7BY7V4YX.js.map → chunk-Q4WONDMJ.js.map} +1 -1
- package/dist/{chunk-Z7PBQPJ4.js → chunk-QJW4MZSW.js} +2 -2
- package/dist/{chunk-R7JAM2XF.js → chunk-R2MZCYA6.js} +3 -3
- package/dist/{chunk-TF77C7SV.js → chunk-SR5LNI6Q.js} +7 -5
- package/dist/chunk-SR5LNI6Q.js.map +1 -0
- package/dist/{chunk-LBWQ65SX.js → chunk-T42L3S6A.js} +4 -4
- package/dist/{chunk-J275IYMG.js → chunk-THVI4Y7X.js} +34 -7
- package/dist/chunk-THVI4Y7X.js.map +1 -0
- package/dist/{chunk-QOS5LBZN.js → chunk-TJFI3K2O.js} +15 -3
- package/dist/chunk-TJFI3K2O.js.map +1 -0
- package/dist/{chunk-2YI72KM3.js → chunk-UCKG36HO.js} +2 -2
- package/dist/{chunk-SIHSCCUQ.js → chunk-UFR6HX7A.js} +1 -1
- package/dist/{chunk-CDUUDBWJ.js → chunk-VFYRYFLL.js} +2 -2
- package/dist/{chunk-XRYZRW54.js → chunk-WDXD6ITO.js} +2 -2
- package/dist/{chunk-4KYOLI6M.js → chunk-XGM3VQWG.js} +120 -14
- package/dist/chunk-XGM3VQWG.js.map +1 -0
- package/dist/{chunk-MN4KB2A4.js → chunk-YDKF4MSJ.js} +3 -3
- package/dist/{chunk-YLDXWCZP.js → chunk-YHGCT462.js} +14 -9
- package/dist/chunk-YHGCT462.js.map +1 -0
- package/dist/{chunk-7YQ4IYS2.js → chunk-YOXVZJFM.js} +2 -2
- package/dist/{chunk-ZWXJBFZU.js → chunk-YZJM6CLF.js} +8 -8
- package/dist/chunk-ZCUTSMR2.js +61 -0
- package/dist/chunk-ZCUTSMR2.js.map +1 -0
- package/dist/{chunk-LUNJ3WFO.js → chunk-ZK4BFABK.js} +4 -4
- package/dist/{chunk-V3MXUL3D.js → chunk-ZPOHEB5Z.js} +329 -653
- package/dist/chunk-ZPOHEB5Z.js.map +1 -0
- package/dist/{chunk-RDFF2NAU.js → chunk-ZXBTTMGZ.js} +184 -88
- package/dist/chunk-ZXBTTMGZ.js.map +1 -0
- package/dist/{cli-cShQ13XG.d.ts → cli-DO3i14pM.d.ts} +5 -5
- package/dist/cli.d.ts +25 -25
- package/dist/cli.js +51 -48
- package/dist/coding/pre-action-gate.d.ts +2 -60
- package/dist/compounding/engine.d.ts +4 -2
- package/dist/compounding/engine.js +4 -4
- package/dist/compounding/preference-consolidator.d.ts +3 -1
- package/dist/compression-optimizer.d.ts +3 -1
- package/dist/config.d.ts +3 -1
- package/dist/config.js +10 -1
- package/dist/connectors/codex-materialize-runner.d.ts +3 -1
- package/dist/connectors/codex-materialize-runner.js +8 -5
- package/dist/connectors/codex-materialize.d.ts +16 -1
- package/dist/connectors/codex-materialize.js +10 -1
- package/dist/connectors/index.d.ts +3 -1
- package/dist/connectors/index.js +8 -5
- package/dist/consolidation-provenance-check.d.ts +4 -2
- package/dist/consolidation-undo.d.ts +4 -2
- package/dist/contradiction/index.d.ts +10 -270
- package/dist/contradiction/index.js +12 -7
- package/dist/{contradiction-review-QKJH5DKD.js → contradiction-review-WRVLRWHS.js} +6 -2
- package/dist/{contradiction-scan-VOTQRRSX.js → contradiction-scan-AKUWPX5N.js} +5 -3
- package/dist/converge-config.d.ts +3 -1
- package/dist/convergence-refresh.d.ts +4 -2
- package/dist/convergence-refresh.js +6 -6
- package/dist/conversation-index/backend.d.ts +3 -1
- package/dist/conversation-index/chunker.d.ts +3 -1
- package/dist/conversation-index/faiss-adapter.d.ts +3 -1
- package/dist/conversation-index/indexer.d.ts +3 -1
- package/dist/conversation-index/search.d.ts +3 -1
- package/dist/corpus-watermark.d.ts +3 -1
- package/dist/corpus-watermark.js +6 -6
- package/dist/cross-lingual-recall.d.ts +3 -1
- package/dist/day-summary.d.ts +3 -1
- package/dist/delinearize.d.ts +3 -1
- package/dist/dependency-propagation-config.d.ts +3 -1
- package/dist/{dependency-propagation-delivery-CsGg-VwS.d.ts → dependency-propagation-delivery-D23_x-s5.d.ts} +1 -1
- package/dist/direct-answer-wiring.d.ts +3 -1
- package/dist/direct-answer.d.ts +3 -1
- package/dist/embedding-fallback.d.ts +3 -1
- package/dist/enrichment/index.d.ts +3 -1
- package/dist/entity-origin-fields.d.ts +3 -1
- package/dist/entity-retrieval.d.ts +4 -2
- package/dist/entity-retrieval.js +4 -4
- package/dist/entity-schema.d.ts +3 -1
- package/dist/episodic-context.d.ts +3 -1
- package/dist/explicit-capture.d.ts +12 -12
- package/dist/explicit-capture.js +1 -1
- package/dist/external-wiki-access.d.ts +40 -40
- package/dist/external-wiki-access.js +22 -20
- package/dist/external-wiki-collection-registration.d.ts +3 -1
- package/dist/external-wiki-collection.d.ts +3 -1
- package/dist/external-wiki-mcp-tools.d.ts +40 -40
- package/dist/extraction-error-classification.d.ts +3 -1
- package/dist/extraction-faithfulness.d.ts +3 -1
- package/dist/extraction-judge-telemetry.d.ts +3 -1
- package/dist/extraction-judge-training.d.ts +3 -1
- package/dist/extraction-judge.d.ts +3 -1
- package/dist/extraction-judge.js +2 -2
- package/dist/extraction-liveness.d.ts +3 -1
- package/dist/extraction-normalization.d.ts +3 -1
- package/dist/extraction-prompt.d.ts +3 -1
- package/dist/extraction-source-grounding-rules.d.ts +3 -1
- package/dist/extraction-source-grounding.d.ts +3 -1
- package/dist/extraction.d.ts +3 -1
- package/dist/extraction.js +9 -7
- package/dist/fallback-llm.d.ts +3 -1
- package/dist/graph-dashboard-diff.d.ts +3 -1
- package/dist/graph-dashboard-key.d.ts +3 -1
- package/dist/graph-dashboard-parser.d.ts +3 -1
- package/dist/graph-edge-reinforcement.d.ts +3 -1
- package/dist/graph-path-reconstruction.d.ts +3 -1
- package/dist/graph-path-scoring.d.ts +3 -1
- package/dist/graph-snapshot.d.ts +3 -1
- package/dist/graph.d.ts +3 -1
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/importance.d.ts +3 -1
- package/dist/importers/index.d.ts +3 -1
- package/dist/in-flight-reads.d.ts +3 -1
- package/dist/index--QhGIXF2.d.ts +284 -0
- package/dist/index.d.ts +314 -45
- package/dist/index.js +163 -88
- package/dist/intent.d.ts +3 -1
- package/dist/lcm/engine.d.ts +3 -1
- package/dist/lcm/index.d.ts +3 -1
- package/dist/lcm/tools.d.ts +3 -1
- package/dist/lifecycle.d.ts +3 -1
- package/dist/live-connectors-runner.d.ts +3 -1
- package/dist/local-llm.d.ts +3 -1
- package/dist/local-model-endpoint.d.ts +3 -1
- package/dist/maintenance/memory-governance.d.ts +3 -1
- package/dist/maintenance/memory-governance.js +4 -4
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +4 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
- package/dist/maintenance/rebuild-memory-projection.d.ts +4 -2
- package/dist/maintenance/rebuild-memory-projection.js +5 -5
- package/dist/{maintenance-jhMpeGne.d.ts → maintenance-PFvY1Pw_.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +37 -37
- package/dist/memory-action-policy.d.ts +3 -1
- package/dist/memory-cache.d.ts +3 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +3 -1
- package/dist/memory-projection-store.d.ts +3 -1
- package/dist/memory-provenance.d.ts +3 -1
- package/dist/memory-snapshot.d.ts +3 -1
- package/dist/memory-subject-cli.d.ts +40 -40
- package/dist/memory-subject.d.ts +42 -42
- package/dist/memory-worth-outcomes.d.ts +4 -2
- package/dist/models-json.d.ts +3 -1
- package/dist/namespaces/migrate.d.ts +5 -3
- package/dist/namespaces/migrate.js +5 -5
- package/dist/namespaces/principal.d.ts +3 -1
- package/dist/namespaces/search.d.ts +3 -1
- package/dist/namespaces/storage.d.ts +5 -3
- package/dist/namespaces/storage.js +4 -4
- package/dist/native-knowledge.d.ts +3 -1
- package/dist/offline-sync-impression-drain.d.ts +3 -1
- package/dist/operator-doctor-corpus.d.ts +3 -1
- package/dist/operator-doctor-corpus.js +7 -7
- package/dist/operator-doctor-replica.d.ts +3 -1
- package/dist/operator-toolkit.d.ts +5 -3
- package/dist/operator-toolkit.js +19 -13
- package/dist/orchestration/compression-guideline-coordinator.d.ts +4 -2
- package/dist/orchestration/maintenance.d.ts +6 -4
- package/dist/orchestration/maintenance.js +10 -10
- package/dist/{orchestrator-DyGUC7re.d.ts → orchestrator-4wzJrPVm.d.ts} +23 -9
- package/dist/orchestrator.d.ts +12 -12
- package/dist/orchestrator.js +62 -60
- package/dist/patterns-cli.d.ts +3 -1
- package/dist/{pipeline-C25Oxtp1.d.ts → pipeline-CgXgdIrF.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +3 -1
- package/dist/proactive-contention.d.ts +3 -1
- package/dist/provenance.d.ts +3 -1
- package/dist/{public-http-CMe7NQzj.d.ts → public-http-DhIXYBYI.d.ts} +1 -1
- package/dist/{qmd-DkDVCbgF.d.ts → qmd-BppGGxsi.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +4 -2
- package/dist/qmd-recall-cache.d.ts +3 -1
- package/dist/qmd.d.ts +4 -2
- package/dist/recall-concurrency-config.d.ts +3 -1
- package/dist/recall-disclosure-escalation.d.ts +3 -1
- package/dist/recall-explain-renderer.d.ts +3 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-memory-map.d.ts +3 -1
- package/dist/recall-planner-llm.d.ts +3 -1
- package/dist/recall-state.d.ts +3 -1
- package/dist/recall-tag-filter.d.ts +3 -1
- package/dist/recall-timings.d.ts +3 -1
- package/dist/recall-xray-cli.d.ts +3 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +3 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +3 -1
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/cursor.d.ts +3 -1
- package/dist/reconcile/manifest.d.ts +3 -1
- package/dist/reconcile/manifest.js +2 -2
- package/dist/reconcile/plan.d.ts +3 -1
- package/dist/replay/normalizers/chatgpt.d.ts +3 -1
- package/dist/replay/normalizers/claude.d.ts +3 -1
- package/dist/replay/normalizers/openclaw.d.ts +3 -1
- package/dist/replay/normalizers/shared.d.ts +3 -1
- package/dist/replay/runner.d.ts +3 -1
- package/dist/replay/types.d.ts +3 -1
- package/dist/replica-divergence.d.ts +3 -1
- package/dist/replica-peers-config.d.ts +3 -1
- package/dist/{resolution-MFY72STR.js → resolution-4552BNRV.js} +14 -4
- package/dist/resolve-auth-token.d.ts +3 -1
- package/dist/resume-bundles.js +8 -2
- package/dist/retrieval-agents.d.ts +4 -2
- package/dist/retrieval-tiers.d.ts +3 -1
- package/dist/routing/engine.d.ts +3 -1
- package/dist/routing/store.d.ts +3 -1
- package/dist/salvage-envelope.d.ts +3 -1
- package/dist/salvage-envelope.js +1 -1
- package/dist/schemas.d.ts +44 -44
- package/dist/{scope-profiles-B9xMoFO-.d.ts → scope-profiles-ChZUtxDU.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +3 -1
- package/dist/search/factory.d.ts +3 -1
- package/dist/search/index.d.ts +3 -1
- package/dist/search/lancedb-backend.d.ts +3 -1
- package/dist/search/meilisearch-backend.d.ts +3 -1
- package/dist/search/noop-backend.d.ts +3 -1
- package/dist/search/orama-backend.d.ts +3 -1
- package/dist/search/port.d.ts +3 -1
- package/dist/search/remote-backend.d.ts +3 -1
- package/dist/{semantic-TddhF0y7.d.ts → semantic-BalR6EXU.d.ts} +1 -1
- package/dist/{semantic-consolidation-C4uykW84.d.ts → semantic-consolidation-mSPwQSmF.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +4 -2
- package/dist/semantic-consolidation.js +9 -6
- package/dist/semantic-rule-promotion.js +4 -4
- package/dist/semantic-rule-verifier.d.ts +3 -1
- package/dist/semantic-rule-verifier.js +4 -4
- package/dist/{service-DCZDZNcV.d.ts → service-BICWLwSP.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +3 -1
- package/dist/session-observer-state.d.ts +3 -1
- package/dist/shared-context/manager.d.ts +3 -1
- package/dist/signal.d.ts +3 -1
- package/dist/skills-registry.js +4 -56
- package/dist/skills-registry.js.map +1 -1
- package/dist/source-agent-qualifier.d.ts +3 -1
- package/dist/source-agent-qualifier.js +8 -5
- package/dist/{storage-CcKmEjL8.d.ts → storage-B7urx1K3.d.ts} +1 -1
- package/dist/storage.d.ts +4 -2
- package/dist/storage.js +6 -6
- package/dist/subject-config.d.ts +3 -1
- package/dist/summarizer.d.ts +3 -1
- package/dist/summary-snapshot.d.ts +3 -1
- package/dist/support-passport/index.d.ts +43 -43
- package/dist/support-passport/index.js +24 -22
- package/dist/temporal-supersession.d.ts +4 -2
- package/dist/temporal-timeline-recall.d.ts +3 -1
- package/dist/temporal-validity.d.ts +3 -1
- package/dist/threading.d.ts +3 -1
- package/dist/tier-migration.d.ts +4 -2
- package/dist/tier-routing.d.ts +3 -1
- package/dist/topics.d.ts +3 -1
- package/dist/transcript.d.ts +3 -1
- package/dist/transfer/types.d.ts +20 -20
- package/dist/trust-score-stage.d.ts +3 -1
- package/dist/trust-score.d.ts +3 -1
- package/dist/{types-Pi0lfaEt.d.ts → types-ETU5N6nB.d.ts} +433 -7
- package/dist/types.d.ts +3 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +3 -1
- package/dist/verified-recall.js +4 -4
- package/dist/who-knows-cli.d.ts +40 -40
- package/dist/who-knows-cli.js +5 -5
- package/dist/who-knows.d.ts +42 -42
- package/dist/who-knows.js +4 -4
- package/dist/write-envelope.d.ts +3 -1
- package/dist/write-envelope.js +1 -1
- package/package.json +2 -2
- package/src/access-boundary.ts +4 -0
- package/src/access-http.ts +6 -5
- package/src/access-mcp-admin-tools.ts +24 -0
- package/src/access-mcp-output-schemas.ts +14 -0
- package/src/access-mcp.ts +2 -2
- package/src/access-operations-batch.ts +5 -0
- package/src/access-service.ts +27 -0
- package/src/access-surface-catalog.test.ts +24 -0
- package/src/access-surface-catalog.ts +4 -0
- package/src/cli/skills-commands.ts +150 -0
- package/src/cli.ts +4 -2
- package/src/coding/action-gate.test.ts +341 -0
- package/src/coding/action-gate.ts +556 -0
- package/src/config.ts +9 -2
- package/src/connectors/codex-materialize-runner.ts +16 -0
- package/src/connectors/codex-materialize-skills.test.ts +141 -0
- package/src/connectors/codex-materialize.ts +58 -40
- package/src/connectors/materialize-paths.ts +50 -0
- package/src/connectors/materialize-skills.ts +127 -0
- package/src/contradiction/contradiction-review.ts +50 -4
- package/src/contradiction/contradiction-scan.ts +14 -3
- package/src/contradiction/resolution.ts +50 -2
- package/src/index.ts +9 -13
- package/src/orchestration/recall-rerank-coordinator.ts +123 -4
- package/src/orchestration/recall-result-formatter.ts +6 -2
- package/src/orchestration/recall-search-pipeline.ts +14 -1
- package/src/preferences/drift-config.ts +118 -0
- package/src/preferences/drift-recall.ts +133 -0
- package/src/preferences/drift-types.ts +87 -0
- package/src/preferences/index.ts +12 -0
- package/src/preferences/preference-drift.test.ts +690 -0
- package/src/preferences/preference-drift.ts +724 -0
- package/src/procedural/skill-io.test.ts +190 -0
- package/src/procedural/skill-io.ts +220 -0
- package/src/procedural/skill-projection.test.ts +214 -0
- package/src/procedural/skill-projection.ts +356 -0
- package/src/review/review-deck-access.test.ts +325 -0
- package/src/review/review-deck-http-base.ts +133 -0
- package/src/review/review-deck-mutation.test.ts +417 -0
- package/src/review/review-deck-mutation.ts +677 -0
- package/src/review/review-deck-snapshot.ts +196 -0
- package/src/review/review-deck-surface.ts +117 -0
- package/src/review/review-deck.test.ts +347 -0
- package/src/review/review-deck.ts +311 -0
- package/src/storage.ts +4 -6
- package/src/types.ts +9 -6
- package/dist/chunk-35NN3ZFA.js.map +0 -1
- package/dist/chunk-4KYOLI6M.js.map +0 -1
- package/dist/chunk-BWLRUMOG.js.map +0 -1
- package/dist/chunk-J275IYMG.js.map +0 -1
- package/dist/chunk-LGO6A7GJ.js.map +0 -1
- package/dist/chunk-QOS5LBZN.js.map +0 -1
- package/dist/chunk-RDFF2NAU.js.map +0 -1
- package/dist/chunk-SB7D4BT7.js.map +0 -1
- package/dist/chunk-SBDCZ2AX.js.map +0 -1
- package/dist/chunk-TF77C7SV.js.map +0 -1
- package/dist/chunk-TZNQUNKL.js.map +0 -1
- package/dist/chunk-V3MXUL3D.js.map +0 -1
- package/dist/chunk-XCTXS7VY.js +0 -402
- package/dist/chunk-XCTXS7VY.js.map +0 -1
- package/dist/chunk-YLDXWCZP.js.map +0 -1
- /package/dist/{auto-sync-H64ZCNIR.js.map → auto-sync-XVMLL2RH.js.map} +0 -0
- /package/dist/{chunk-YB42WZCY.js.map → chunk-2IXQSGCU.js.map} +0 -0
- /package/dist/{chunk-PE255F4T.js.map → chunk-2MVTAIL3.js.map} +0 -0
- /package/dist/{chunk-GDFVPUJ5.js.map → chunk-3NH3VP3U.js.map} +0 -0
- /package/dist/{chunk-GNCCUR6K.js.map → chunk-AXGFOFNY.js.map} +0 -0
- /package/dist/{chunk-5YQ63T4B.js.map → chunk-BK5G2MHO.js.map} +0 -0
- /package/dist/{chunk-LZH5FR2W.js.map → chunk-CQBIN77D.js.map} +0 -0
- /package/dist/{chunk-NKKD4ZAU.js.map → chunk-DORFF5N5.js.map} +0 -0
- /package/dist/{chunk-SQI57UCJ.js.map → chunk-DUEDOYZD.js.map} +0 -0
- /package/dist/{chunk-5NM4OH4P.js.map → chunk-DUZARALI.js.map} +0 -0
- /package/dist/{chunk-2E36UXVW.js.map → chunk-ECLJ4LA2.js.map} +0 -0
- /package/dist/{chunk-IID2CMUU.js.map → chunk-GHD2LLG4.js.map} +0 -0
- /package/dist/{chunk-ZQHI2BZB.js.map → chunk-HF5BUTI6.js.map} +0 -0
- /package/dist/{chunk-3DVBKTSO.js.map → chunk-HSZMX3ZJ.js.map} +0 -0
- /package/dist/{chunk-S2DPMJXH.js.map → chunk-I23ZHYSL.js.map} +0 -0
- /package/dist/{chunk-ODZG724P.js.map → chunk-I2HTKOOP.js.map} +0 -0
- /package/dist/{chunk-NTWN6HB2.js.map → chunk-IURMJBGP.js.map} +0 -0
- /package/dist/{chunk-OCZIYAE5.js.map → chunk-IWKGOCL6.js.map} +0 -0
- /package/dist/{chunk-UXO5IMBK.js.map → chunk-J3ATBB7M.js.map} +0 -0
- /package/dist/{chunk-Y7DK7HHJ.js.map → chunk-JPAKBNTM.js.map} +0 -0
- /package/dist/{chunk-SPUJNJJC.js.map → chunk-KFIOPZ25.js.map} +0 -0
- /package/dist/{chunk-JYYBA7TF.js.map → chunk-KOO37E4Y.js.map} +0 -0
- /package/dist/{chunk-OTHZWLUD.js.map → chunk-KPB2QHJZ.js.map} +0 -0
- /package/dist/{chunk-CT56C22C.js.map → chunk-LYBIHM4N.js.map} +0 -0
- /package/dist/{chunk-3I7ASZN3.js.map → chunk-MFOSZUIT.js.map} +0 -0
- /package/dist/{chunk-DZUJ46DO.js.map → chunk-NLEH4RTB.js.map} +0 -0
- /package/dist/{chunk-ZO6AXLEH.js.map → chunk-OCCHUEZB.js.map} +0 -0
- /package/dist/{chunk-EILZG5VC.js.map → chunk-OU34IQXV.js.map} +0 -0
- /package/dist/{chunk-2XDWWPTN.js.map → chunk-OZDFB22S.js.map} +0 -0
- /package/dist/{chunk-UIN2BWDD.js.map → chunk-PTZQYCJ4.js.map} +0 -0
- /package/dist/{chunk-Z7PBQPJ4.js.map → chunk-QJW4MZSW.js.map} +0 -0
- /package/dist/{chunk-R7JAM2XF.js.map → chunk-R2MZCYA6.js.map} +0 -0
- /package/dist/{chunk-LBWQ65SX.js.map → chunk-T42L3S6A.js.map} +0 -0
- /package/dist/{chunk-2YI72KM3.js.map → chunk-UCKG36HO.js.map} +0 -0
- /package/dist/{chunk-SIHSCCUQ.js.map → chunk-UFR6HX7A.js.map} +0 -0
- /package/dist/{chunk-CDUUDBWJ.js.map → chunk-VFYRYFLL.js.map} +0 -0
- /package/dist/{chunk-XRYZRW54.js.map → chunk-WDXD6ITO.js.map} +0 -0
- /package/dist/{chunk-MN4KB2A4.js.map → chunk-YDKF4MSJ.js.map} +0 -0
- /package/dist/{chunk-7YQ4IYS2.js.map → chunk-YOXVZJFM.js.map} +0 -0
- /package/dist/{chunk-ZWXJBFZU.js.map → chunk-YZJM6CLF.js.map} +0 -0
- /package/dist/{chunk-LUNJ3WFO.js.map → chunk-ZK4BFABK.js.map} +0 -0
- /package/dist/{contradiction-review-QKJH5DKD.js.map → contradiction-review-WRVLRWHS.js.map} +0 -0
- /package/dist/{contradiction-scan-VOTQRRSX.js.map → contradiction-scan-AKUWPX5N.js.map} +0 -0
- /package/dist/{resolution-MFY72STR.js.map → resolution-4552BNRV.js.map} +0 -0
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preference drift detection (issue #2371).
|
|
3
|
+
*
|
|
4
|
+
* Remnic already handles *explicit* preference change: the contradiction scan
|
|
5
|
+
* catches a new statement that conflicts with a stored one, and temporal
|
|
6
|
+
* supersession retires a fact that gets restated. Both require new evidence to
|
|
7
|
+
* arrive. This module covers the opposite failure mode the agent-memory survey
|
|
8
|
+
* calls stale preference reuse: a `category: preference` memory keeps being
|
|
9
|
+
* injected at full prominence even though the last N days of conversation
|
|
10
|
+
* contain no corroboration for it — or contain behavioral evidence pointing the
|
|
11
|
+
* other way that never rose to an explicit contradiction.
|
|
12
|
+
*
|
|
13
|
+
* Per aging preference the scan gathers recent same-namespace evidence, then
|
|
14
|
+
* classifies:
|
|
15
|
+
*
|
|
16
|
+
* - `corroborated` — recent evidence restates or is consistent with it.
|
|
17
|
+
* - `stale` — the lookback window held nothing either way.
|
|
18
|
+
* - `drifted` — recent evidence points away from it (judge verdict
|
|
19
|
+
* `contradicts`), so a review item is opened.
|
|
20
|
+
* - `skipped` — the classification could not be made honestly:
|
|
21
|
+
* `backend_unavailable` (evidence lookup failed — §22:
|
|
22
|
+
* never counted as "no evidence") or
|
|
23
|
+
* `verification_unavailable` (evidence exists but no LLM
|
|
24
|
+
* could judge it, so neither a positive nor a negative
|
|
25
|
+
* claim is warranted).
|
|
26
|
+
*
|
|
27
|
+
* Apply mode stamps `lastCorroborated` / `driftState` and opens review items.
|
|
28
|
+
* It never auto-deletes and never auto-supersedes: drift is inference, and the
|
|
29
|
+
* least-privileged default for an inferred change to user state is "ask"
|
|
30
|
+
* (Review Prevention Checklist §36). Resolution runs through the existing
|
|
31
|
+
* review queue and `review_resolve`, with the drift verbs `keep`, `supersede`,
|
|
32
|
+
* and `archive`.
|
|
33
|
+
*
|
|
34
|
+
* Deliberately NOT part of `runMemoryGovernance` — same isolation decision as
|
|
35
|
+
* procedure mining and procedure-library maintenance.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import path from "node:path";
|
|
39
|
+
import type { MemoryFile, MemoryStatus, PluginConfig } from "../types.js";
|
|
40
|
+
import type { StorageManager } from "../storage.js";
|
|
41
|
+
import type { LocalLlmClient } from "../local-llm.js";
|
|
42
|
+
import type { FallbackLlmClient } from "../fallback-llm.js";
|
|
43
|
+
import type { SemanticDedupHit, SemanticDedupLookup } from "../dedup/semantic.js";
|
|
44
|
+
import {
|
|
45
|
+
judgeContradictionPairs,
|
|
46
|
+
type ContradictionJudgeInput,
|
|
47
|
+
type ContradictionJudgeResult,
|
|
48
|
+
} from "../contradiction/contradiction-judge.js";
|
|
49
|
+
import {
|
|
50
|
+
computeMemoryContentHash,
|
|
51
|
+
writePairs,
|
|
52
|
+
type ContradictionPair,
|
|
53
|
+
} from "../contradiction/contradiction-review.js";
|
|
54
|
+
import { resolveScanStorage, type ScanStorageResolution } from "../contradiction/contradiction-scan.js";
|
|
55
|
+
import { bumpMemoryCorpusVersionForDir } from "../memory-corpus-version.js";
|
|
56
|
+
import { composeMemoryEnvelope } from "../write-envelope.js";
|
|
57
|
+
import { readJsonFile, writeJsonFileAtomic } from "../json-store.js";
|
|
58
|
+
import { log } from "../logger.js";
|
|
59
|
+
|
|
60
|
+
const DRIFT_ACTOR = "preference-drift";
|
|
61
|
+
const DRIFT_RULE_VERSION = "preference-drift:1";
|
|
62
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
63
|
+
|
|
64
|
+
/** Evidence hits fetched per preference. Bounded so one scan cannot fan out. */
|
|
65
|
+
const EVIDENCE_LOOKUP_LIMIT = 5;
|
|
66
|
+
|
|
67
|
+
export type PreferenceDriftClassification =
|
|
68
|
+
| "corroborated"
|
|
69
|
+
| "stale"
|
|
70
|
+
| "drifted"
|
|
71
|
+
| "skipped";
|
|
72
|
+
|
|
73
|
+
export type PreferenceDriftSkipReason =
|
|
74
|
+
| "backend_unavailable"
|
|
75
|
+
| "verification_unavailable";
|
|
76
|
+
|
|
77
|
+
export interface PreferenceDriftEvidence {
|
|
78
|
+
memoryId: string;
|
|
79
|
+
/** Similarity score reported by the evidence lookup. */
|
|
80
|
+
score: number;
|
|
81
|
+
/** `updated` (or `created`) of the evidence memory, ISO 8601. */
|
|
82
|
+
observedAt: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface PreferenceDriftFinding {
|
|
86
|
+
memoryId: string;
|
|
87
|
+
classification: PreferenceDriftClassification;
|
|
88
|
+
/** Set only for `classification: "skipped"`. */
|
|
89
|
+
skipped?: PreferenceDriftSkipReason;
|
|
90
|
+
/** Age of the preference in whole days at the run instant. */
|
|
91
|
+
ageDays: number;
|
|
92
|
+
/** Existing `lastCorroborated` stamp, or null when never corroborated. */
|
|
93
|
+
lastCorroborated: string | null;
|
|
94
|
+
reason: string;
|
|
95
|
+
evidence: PreferenceDriftEvidence[];
|
|
96
|
+
/** Review pair id opened for a `drifted` finding in apply mode. */
|
|
97
|
+
reviewPairId?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface PreferenceDriftReport {
|
|
101
|
+
schemaVersion: 1;
|
|
102
|
+
generatedAt: string;
|
|
103
|
+
mode: "shadow" | "apply";
|
|
104
|
+
namespace?: string;
|
|
105
|
+
/** Active preferences examined after the min-age filter and per-run cap. */
|
|
106
|
+
scanned: number;
|
|
107
|
+
/** Active preferences that passed the category/status filter before the cap. */
|
|
108
|
+
eligible: number;
|
|
109
|
+
findings: PreferenceDriftFinding[];
|
|
110
|
+
counts: Record<PreferenceDriftClassification, number>;
|
|
111
|
+
/** Frontmatter stamps + review items actually written. Always 0 in shadow mode. */
|
|
112
|
+
appliedCount: number;
|
|
113
|
+
reviewItemsOpened: number;
|
|
114
|
+
elapsedMs: number;
|
|
115
|
+
skippedReason?: "drift_disabled" | "scan_disabled";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface PreferenceDriftMarker {
|
|
119
|
+
schemaVersion: 1;
|
|
120
|
+
lastRunAt: string;
|
|
121
|
+
lastApplyAt: string | null;
|
|
122
|
+
appliedCount: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface PreferenceDriftDependencies {
|
|
126
|
+
storage: StorageManager;
|
|
127
|
+
config: PluginConfig;
|
|
128
|
+
memoryDir: string;
|
|
129
|
+
/** Pre-built evidence lookup. Superseded by `embeddingLookupFactory`. */
|
|
130
|
+
embeddingLookup?: SemanticDedupLookup;
|
|
131
|
+
/** Builds a namespace-scoped evidence lookup from the scan's own storage. */
|
|
132
|
+
embeddingLookupFactory?: (storage: StorageManager) => SemanticDedupLookup | undefined;
|
|
133
|
+
storageForNamespace?: (
|
|
134
|
+
namespace: string | undefined,
|
|
135
|
+
) => StorageManager | ScanStorageResolution | Promise<StorageManager | ScanStorageResolution>;
|
|
136
|
+
localLlm: LocalLlmClient | null;
|
|
137
|
+
fallbackLlm: FallbackLlmClient | null;
|
|
138
|
+
namespace?: string;
|
|
139
|
+
/** When true (and `driftDetection.enabled`), stamps and review items are written. */
|
|
140
|
+
apply?: boolean;
|
|
141
|
+
now?: Date;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function driftMarkerPath(memoryDir: string): string {
|
|
145
|
+
return path.join(memoryDir, "state", "preference-drift.json");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Read the last-run marker. Absent marker (or shadow-only history) → null apply timestamp. */
|
|
149
|
+
export async function readPreferenceDriftMarker(
|
|
150
|
+
memoryDir: string,
|
|
151
|
+
): Promise<PreferenceDriftMarker | null> {
|
|
152
|
+
try {
|
|
153
|
+
const raw = await readJsonFile(driftMarkerPath(memoryDir));
|
|
154
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
155
|
+
const m = raw as Record<string, unknown>;
|
|
156
|
+
if (typeof m.lastRunAt !== "string") return null;
|
|
157
|
+
return {
|
|
158
|
+
schemaVersion: 1,
|
|
159
|
+
lastRunAt: m.lastRunAt,
|
|
160
|
+
lastApplyAt: typeof m.lastApplyAt === "string" ? m.lastApplyAt : null,
|
|
161
|
+
appliedCount: typeof m.appliedCount === "number" ? m.appliedCount : 0,
|
|
162
|
+
};
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function tsMs(value: unknown): number | null {
|
|
169
|
+
if (typeof value !== "string") return null;
|
|
170
|
+
const ms = Date.parse(value);
|
|
171
|
+
return Number.isFinite(ms) ? ms : null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Statuses that make a memory a drift candidate. Stated as an inclusion set,
|
|
176
|
+
* never an exclusion list, so a newly added status can never leak through
|
|
177
|
+
* (§41): `superseded`, `archived`, `quarantined`, `rejected`, and
|
|
178
|
+
* `pending_review` preferences are all excluded by construction.
|
|
179
|
+
*/
|
|
180
|
+
const CANDIDATE_STATUSES: Partial<Record<MemoryStatus, true>> = { active: true };
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Deterministic candidate order (§12): oldest preference first, stable id
|
|
184
|
+
* tiebreak, so the per-run cap always selects the same slice on identical
|
|
185
|
+
* fixtures.
|
|
186
|
+
*/
|
|
187
|
+
function compareCandidates(a: MemoryFile, b: MemoryFile): number {
|
|
188
|
+
const aMs = tsMs(a.frontmatter.created) ?? tsMs(a.frontmatter.updated) ?? 0;
|
|
189
|
+
const bMs = tsMs(b.frontmatter.created) ?? tsMs(b.frontmatter.updated) ?? 0;
|
|
190
|
+
if (aMs !== bMs) return aMs < bMs ? -1 : 1;
|
|
191
|
+
const aId = a.frontmatter.id ?? "";
|
|
192
|
+
const bId = b.frontmatter.id ?? "";
|
|
193
|
+
if (aId !== bId) return aId < bId ? -1 : 1;
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
type EvidenceResult =
|
|
198
|
+
| { ok: true; hits: PreferenceDriftEvidence[]; hitMemories: MemoryFile[] }
|
|
199
|
+
| { ok: false; reason: "backend_unavailable" };
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Gather recent same-namespace evidence for one preference.
|
|
203
|
+
*
|
|
204
|
+
* §22: an empty result and a failed backend are DIFFERENT outcomes. The
|
|
205
|
+
* `SemanticDedupLookup` contract is that implementations throw when the
|
|
206
|
+
* embedding backend is unavailable and return `[]` only when a reachable
|
|
207
|
+
* backend reports no hits, so the throw is the failure signal — it must never
|
|
208
|
+
* be swallowed into "no evidence", which would mark a live preference stale.
|
|
209
|
+
*/
|
|
210
|
+
async function gatherEvidence(
|
|
211
|
+
preference: MemoryFile,
|
|
212
|
+
lookup: SemanticDedupLookup,
|
|
213
|
+
byId: ReadonlyMap<string, MemoryFile>,
|
|
214
|
+
windowStartMs: number,
|
|
215
|
+
nowMs: number,
|
|
216
|
+
): Promise<EvidenceResult> {
|
|
217
|
+
let raw: SemanticDedupHit[];
|
|
218
|
+
try {
|
|
219
|
+
raw = await lookup(preference.content, EVIDENCE_LOOKUP_LIMIT);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
log.debug("[preference-drift] evidence lookup failed", {
|
|
222
|
+
memoryId: preference.frontmatter.id,
|
|
223
|
+
error: err instanceof Error ? err.message : String(err),
|
|
224
|
+
});
|
|
225
|
+
return { ok: false, reason: "backend_unavailable" };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const hits: PreferenceDriftEvidence[] = [];
|
|
229
|
+
const hitMemories: MemoryFile[] = [];
|
|
230
|
+
for (const hit of raw) {
|
|
231
|
+
if (hit.id === preference.frontmatter.id) continue;
|
|
232
|
+
const memory = byId.get(hit.id);
|
|
233
|
+
if (!memory) continue;
|
|
234
|
+
const observedMs = tsMs(memory.frontmatter.updated) ?? tsMs(memory.frontmatter.created);
|
|
235
|
+
if (observedMs === null) continue;
|
|
236
|
+
// Half-open window [windowStart, now): a memory observed exactly at the
|
|
237
|
+
// window start counts, one observed exactly at the run instant does not
|
|
238
|
+
// (§23).
|
|
239
|
+
if (observedMs < windowStartMs || observedMs >= nowMs) continue;
|
|
240
|
+
hits.push({
|
|
241
|
+
memoryId: hit.id,
|
|
242
|
+
score: hit.score,
|
|
243
|
+
observedAt: memory.frontmatter.updated ?? memory.frontmatter.created ?? "",
|
|
244
|
+
});
|
|
245
|
+
hitMemories.push(memory);
|
|
246
|
+
}
|
|
247
|
+
return { ok: true, hits, hitMemories };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Map judge verdicts over a preference's evidence to a drift classification.
|
|
252
|
+
*
|
|
253
|
+
* - `contradicts` on any hit wins: the preference has drifted.
|
|
254
|
+
* - `duplicates` means the evidence restates the preference → corroborated.
|
|
255
|
+
* - `independent` is evidence about something else, so it corroborates
|
|
256
|
+
* nothing; with no better verdict the preference is stale.
|
|
257
|
+
* - `needs-user` (which is also what the judge returns when no LLM is
|
|
258
|
+
* reachable) leaves the question genuinely unanswered. Stamping
|
|
259
|
+
* `lastCorroborated` would assert something unverified and stamping `stale`
|
|
260
|
+
* would contradict the evidence we did find, so the honest outcome is
|
|
261
|
+
* `skipped: verification_unavailable`.
|
|
262
|
+
*/
|
|
263
|
+
function classifyFromVerdicts(
|
|
264
|
+
verdicts: readonly ContradictionJudgeResult[],
|
|
265
|
+
): { classification: PreferenceDriftClassification; skipped?: PreferenceDriftSkipReason; reason: string; driftedAgainst?: string } {
|
|
266
|
+
const contradiction = verdicts.find((v) => v.verdict === "contradicts");
|
|
267
|
+
if (contradiction) {
|
|
268
|
+
return {
|
|
269
|
+
classification: "drifted",
|
|
270
|
+
reason: `recent evidence points away from this preference: ${contradiction.rationale}`,
|
|
271
|
+
driftedAgainst: contradiction.memoryIdB,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const restatement = verdicts.find((v) => v.verdict === "duplicates");
|
|
275
|
+
if (restatement) {
|
|
276
|
+
return {
|
|
277
|
+
classification: "corroborated",
|
|
278
|
+
reason: `recent evidence restates this preference: ${restatement.rationale}`,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
if (verdicts.some((v) => v.verdict === "needs-user")) {
|
|
282
|
+
return {
|
|
283
|
+
classification: "skipped",
|
|
284
|
+
skipped: "verification_unavailable",
|
|
285
|
+
reason: "recent evidence found but no judge verdict was available to classify it",
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
classification: "stale",
|
|
290
|
+
reason: "recent evidence is independent of this preference — no corroboration either way",
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Run one preference-drift pass over a namespace.
|
|
296
|
+
*
|
|
297
|
+
* Shadow mode (the default) performs NO writes — not even the run marker.
|
|
298
|
+
* Applying requires `apply: true` AND `driftDetection.enabled: true`.
|
|
299
|
+
*/
|
|
300
|
+
export async function runPreferenceDriftScan(
|
|
301
|
+
deps: PreferenceDriftDependencies,
|
|
302
|
+
): Promise<PreferenceDriftReport> {
|
|
303
|
+
const startTime = Date.now();
|
|
304
|
+
const now = deps.now ?? new Date();
|
|
305
|
+
const nowIso = now.toISOString();
|
|
306
|
+
const nowMs = now.getTime();
|
|
307
|
+
const mode: "shadow" | "apply" = deps.apply === true ? "apply" : "shadow";
|
|
308
|
+
const cfg = deps.config.driftDetection;
|
|
309
|
+
|
|
310
|
+
const emptyCounts: Record<PreferenceDriftClassification, number> = {
|
|
311
|
+
corroborated: 0,
|
|
312
|
+
stale: 0,
|
|
313
|
+
drifted: 0,
|
|
314
|
+
skipped: 0,
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
if (cfg?.enabled !== true) {
|
|
318
|
+
return {
|
|
319
|
+
schemaVersion: 1,
|
|
320
|
+
generatedAt: nowIso,
|
|
321
|
+
mode,
|
|
322
|
+
scanned: 0,
|
|
323
|
+
eligible: 0,
|
|
324
|
+
findings: [],
|
|
325
|
+
counts: emptyCounts,
|
|
326
|
+
appliedCount: 0,
|
|
327
|
+
reviewItemsOpened: 0,
|
|
328
|
+
elapsedMs: Date.now() - startTime,
|
|
329
|
+
skippedReason: "drift_disabled",
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
// `0` is the documented disable value and must behave as a true zero (§33).
|
|
333
|
+
if (cfg.maxCandidatesPerRun === 0) {
|
|
334
|
+
return {
|
|
335
|
+
schemaVersion: 1,
|
|
336
|
+
generatedAt: nowIso,
|
|
337
|
+
mode,
|
|
338
|
+
scanned: 0,
|
|
339
|
+
eligible: 0,
|
|
340
|
+
findings: [],
|
|
341
|
+
counts: emptyCounts,
|
|
342
|
+
appliedCount: 0,
|
|
343
|
+
reviewItemsOpened: 0,
|
|
344
|
+
elapsedMs: Date.now() - startTime,
|
|
345
|
+
skippedReason: "scan_disabled",
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const { storage, namespace } = await resolveScanStorage(deps, deps.namespace?.trim() || undefined);
|
|
350
|
+
const lookup = deps.embeddingLookupFactory
|
|
351
|
+
? deps.embeddingLookupFactory(storage)
|
|
352
|
+
: deps.embeddingLookup;
|
|
353
|
+
|
|
354
|
+
let all: MemoryFile[];
|
|
355
|
+
try {
|
|
356
|
+
all = await storage.readAllMemories();
|
|
357
|
+
} catch (err) {
|
|
358
|
+
log.warn(
|
|
359
|
+
`[preference-drift] failed to read memories: ${err instanceof Error ? err.message : String(err)}`,
|
|
360
|
+
);
|
|
361
|
+
all = [];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const byId = new Map<string, MemoryFile>();
|
|
365
|
+
for (const m of all) {
|
|
366
|
+
const id = m.frontmatter.id;
|
|
367
|
+
if (typeof id === "string" && id.length > 0) byId.set(id, m);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const minAgeMs = cfg.minAgeDays * DAY_MS;
|
|
371
|
+
const eligible = all.filter((m) => {
|
|
372
|
+
const fm = m.frontmatter;
|
|
373
|
+
if (fm.category !== "preference") return false;
|
|
374
|
+
if (CANDIDATE_STATUSES[fm.status ?? "active"] !== true) return false;
|
|
375
|
+
if (typeof fm.id !== "string" || fm.id.length === 0) return false;
|
|
376
|
+
if (!m.content || m.content.trim().length === 0) return false;
|
|
377
|
+
const bornMs = tsMs(fm.created) ?? tsMs(fm.updated);
|
|
378
|
+
if (bornMs === null) return false;
|
|
379
|
+
return nowMs - bornMs >= minAgeMs;
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const candidates = [...eligible].sort(compareCandidates).slice(0, cfg.maxCandidatesPerRun);
|
|
383
|
+
|
|
384
|
+
const report: PreferenceDriftReport = {
|
|
385
|
+
schemaVersion: 1,
|
|
386
|
+
generatedAt: nowIso,
|
|
387
|
+
mode,
|
|
388
|
+
namespace,
|
|
389
|
+
scanned: candidates.length,
|
|
390
|
+
eligible: eligible.length,
|
|
391
|
+
findings: [],
|
|
392
|
+
counts: { ...emptyCounts },
|
|
393
|
+
appliedCount: 0,
|
|
394
|
+
reviewItemsOpened: 0,
|
|
395
|
+
elapsedMs: 0,
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const windowStartMs = nowMs - cfg.lookbackDays * DAY_MS;
|
|
399
|
+
// One judge cache per run so the module-level singleton never leaks across
|
|
400
|
+
// namespaces or scans.
|
|
401
|
+
const judgeCache = new Map<string, ContradictionJudgeResult>();
|
|
402
|
+
const driftedPairs: Array<{ preference: MemoryFile; evidenceId: string; finding: PreferenceDriftFinding; rationale: string; confidence: number }> = [];
|
|
403
|
+
const stamps: Array<{ memory: MemoryFile; patch: { driftState?: "stale"; lastCorroborated?: string }; reasonCode: string }> = [];
|
|
404
|
+
|
|
405
|
+
for (const preference of candidates) {
|
|
406
|
+
const bornMs = tsMs(preference.frontmatter.created) ?? tsMs(preference.frontmatter.updated) ?? nowMs;
|
|
407
|
+
const finding: PreferenceDriftFinding = {
|
|
408
|
+
memoryId: preference.frontmatter.id,
|
|
409
|
+
classification: "stale",
|
|
410
|
+
ageDays: Math.floor((nowMs - bornMs) / DAY_MS),
|
|
411
|
+
lastCorroborated: preference.frontmatter.lastCorroborated ?? null,
|
|
412
|
+
reason: "",
|
|
413
|
+
evidence: [],
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
if (!lookup) {
|
|
417
|
+
finding.classification = "skipped";
|
|
418
|
+
finding.skipped = "backend_unavailable";
|
|
419
|
+
finding.reason = "no evidence lookup is configured, so corroboration cannot be checked";
|
|
420
|
+
report.findings.push(finding);
|
|
421
|
+
report.counts.skipped += 1;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const evidence = await gatherEvidence(preference, lookup, byId, windowStartMs, nowMs);
|
|
426
|
+
if (!evidence.ok) {
|
|
427
|
+
finding.classification = "skipped";
|
|
428
|
+
finding.skipped = evidence.reason;
|
|
429
|
+
finding.reason = "evidence lookup backend was unavailable — not classified as stale";
|
|
430
|
+
report.findings.push(finding);
|
|
431
|
+
report.counts.skipped += 1;
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
finding.evidence = evidence.hits;
|
|
435
|
+
|
|
436
|
+
if (evidence.hits.length === 0) {
|
|
437
|
+
finding.classification = "stale";
|
|
438
|
+
finding.reason = `no corroborating evidence in the last ${cfg.lookbackDays} days`;
|
|
439
|
+
report.findings.push(finding);
|
|
440
|
+
report.counts.stale += 1;
|
|
441
|
+
stamps.push({ memory: preference, patch: { driftState: "stale" }, reasonCode: "drift_stale" });
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// The judge call happens only when candidate evidence exists.
|
|
446
|
+
const judgeInputs: ContradictionJudgeInput[] = evidence.hitMemories.map((hit) => ({
|
|
447
|
+
memoryIdA: preference.frontmatter.id,
|
|
448
|
+
memoryIdB: hit.frontmatter.id,
|
|
449
|
+
textA: preference.content,
|
|
450
|
+
textB: hit.content,
|
|
451
|
+
categoryA: preference.frontmatter.category,
|
|
452
|
+
categoryB: hit.frontmatter.category,
|
|
453
|
+
}));
|
|
454
|
+
const judged = await judgeContradictionPairs(
|
|
455
|
+
judgeInputs,
|
|
456
|
+
deps.config,
|
|
457
|
+
deps.localLlm,
|
|
458
|
+
deps.fallbackLlm,
|
|
459
|
+
judgeCache,
|
|
460
|
+
);
|
|
461
|
+
// Preserve evidence order (score desc) so the verdict scan is deterministic
|
|
462
|
+
// rather than dependent on Map iteration of the judge's keyed results.
|
|
463
|
+
const orderedVerdicts: ContradictionJudgeResult[] = [];
|
|
464
|
+
for (const input of judgeInputs) {
|
|
465
|
+
for (const result of judged.results.values()) {
|
|
466
|
+
if (result.memoryIdA === input.memoryIdA && result.memoryIdB === input.memoryIdB) {
|
|
467
|
+
orderedVerdicts.push(result);
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const outcome = classifyFromVerdicts(orderedVerdicts);
|
|
474
|
+
finding.classification = outcome.classification;
|
|
475
|
+
finding.reason = outcome.reason;
|
|
476
|
+
if (outcome.skipped) finding.skipped = outcome.skipped;
|
|
477
|
+
report.findings.push(finding);
|
|
478
|
+
report.counts[outcome.classification] += 1;
|
|
479
|
+
|
|
480
|
+
if (outcome.classification === "corroborated") {
|
|
481
|
+
stamps.push({
|
|
482
|
+
memory: preference,
|
|
483
|
+
patch: { lastCorroborated: nowIso, driftState: undefined },
|
|
484
|
+
reasonCode: "drift_corroborated",
|
|
485
|
+
});
|
|
486
|
+
} else if (outcome.classification === "stale") {
|
|
487
|
+
stamps.push({ memory: preference, patch: { driftState: "stale" }, reasonCode: "drift_stale" });
|
|
488
|
+
} else if (outcome.classification === "drifted" && outcome.driftedAgainst) {
|
|
489
|
+
const verdict = orderedVerdicts.find((v) => v.verdict === "contradicts");
|
|
490
|
+
driftedPairs.push({
|
|
491
|
+
preference,
|
|
492
|
+
evidenceId: outcome.driftedAgainst,
|
|
493
|
+
finding,
|
|
494
|
+
rationale: verdict?.rationale ?? outcome.reason,
|
|
495
|
+
confidence: verdict?.confidence ?? 0,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (mode === "shadow") {
|
|
501
|
+
report.elapsedMs = Date.now() - startTime;
|
|
502
|
+
return report;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let writes = 0;
|
|
506
|
+
for (const stamp of stamps) {
|
|
507
|
+
try {
|
|
508
|
+
// `driftState: undefined` is an explicit clear: writeMemoryFrontmatter
|
|
509
|
+
// merges the patch over existing frontmatter, and the serializer omits
|
|
510
|
+
// undefined fields, so a corroborated preference loses its stale mark.
|
|
511
|
+
await storage.writeMemoryFrontmatter(stamp.memory, { ...stamp.patch, updated: nowIso }, {
|
|
512
|
+
at: now,
|
|
513
|
+
actor: DRIFT_ACTOR,
|
|
514
|
+
reasonCode: stamp.reasonCode,
|
|
515
|
+
ruleVersion: DRIFT_RULE_VERSION,
|
|
516
|
+
});
|
|
517
|
+
writes += 1;
|
|
518
|
+
report.appliedCount += 1;
|
|
519
|
+
} catch (err) {
|
|
520
|
+
log.warn(
|
|
521
|
+
`[preference-drift] stamp ${stamp.reasonCode} on ${stamp.memory.frontmatter.id} failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (driftedPairs.length > 0) {
|
|
527
|
+
const queueEntries: Array<Omit<ContradictionPair, "pairId"> & { memoryIds: [string, string] }> =
|
|
528
|
+
driftedPairs.map((d) => ({
|
|
529
|
+
memoryIds: [d.preference.frontmatter.id, d.evidenceId],
|
|
530
|
+
kind: "preference-drift",
|
|
531
|
+
// A drift finding IS a detected conflict, so the honest verdict is
|
|
532
|
+
// `contradicts`; `kind` is what distinguishes it from a pair the
|
|
533
|
+
// contradiction scan produced. Existing review surfaces render it
|
|
534
|
+
// unchanged.
|
|
535
|
+
verdict: "contradicts",
|
|
536
|
+
rationale: d.rationale,
|
|
537
|
+
confidence: d.confidence,
|
|
538
|
+
detectedAt: nowIso,
|
|
539
|
+
memoryContentHashes: {
|
|
540
|
+
[d.preference.frontmatter.id]: computeMemoryContentHash(
|
|
541
|
+
d.preference.content,
|
|
542
|
+
d.preference.frontmatter.category,
|
|
543
|
+
),
|
|
544
|
+
},
|
|
545
|
+
namespace,
|
|
546
|
+
}));
|
|
547
|
+
const written = writePairs(deps.memoryDir, queueEntries);
|
|
548
|
+
report.reviewItemsOpened = written.length;
|
|
549
|
+
report.appliedCount += written.length;
|
|
550
|
+
for (const entry of written) {
|
|
551
|
+
const match = driftedPairs.find((d) => d.preference.frontmatter.id === entry.memoryIds[0]);
|
|
552
|
+
if (match) match.finding.reviewPairId = entry.pairId;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (writes > 0) {
|
|
557
|
+
// Frontmatter stamps bypass the extraction write pipeline, so bump the
|
|
558
|
+
// shared corpus version out of band — otherwise hot recall caches keep
|
|
559
|
+
// serving pre-stamp frontmatter and the damping stage never sees the new
|
|
560
|
+
// `driftState` (§25/§31).
|
|
561
|
+
bumpMemoryCorpusVersionForDir(storage.dir);
|
|
562
|
+
if (storage.dir !== deps.memoryDir) bumpMemoryCorpusVersionForDir(deps.memoryDir);
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
await writeJsonFileAtomic(driftMarkerPath(deps.memoryDir), {
|
|
566
|
+
schemaVersion: 1,
|
|
567
|
+
lastRunAt: nowIso,
|
|
568
|
+
lastApplyAt: nowIso,
|
|
569
|
+
appliedCount: report.appliedCount,
|
|
570
|
+
} satisfies PreferenceDriftMarker);
|
|
571
|
+
} catch (err) {
|
|
572
|
+
log.warn(
|
|
573
|
+
`[preference-drift] failed to persist run marker: ${err instanceof Error ? err.message : String(err)}`,
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
report.elapsedMs = Date.now() - startTime;
|
|
578
|
+
return report;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// ── Review resolution ────────────────────────────────────────────────────────
|
|
582
|
+
|
|
583
|
+
export interface PreferenceDriftResolutionOptions {
|
|
584
|
+
/** Replacement preference text. Required for `supersede`. */
|
|
585
|
+
mergedContent?: string;
|
|
586
|
+
/** Fires after a resolution leaves a durable memory mutation in the namespace. */
|
|
587
|
+
onMemoryWritten?: (namespace: string | undefined, storageDir: string) => void;
|
|
588
|
+
now?: Date;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export interface PreferenceDriftResolutionResult {
|
|
592
|
+
affectedIds: string[];
|
|
593
|
+
message: string;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Execute a `kind: "preference-drift"` resolution verb.
|
|
598
|
+
*
|
|
599
|
+
* The first memory id on the item is the aging preference; the second is the
|
|
600
|
+
* evidence that pointed away from it. Only the preference is ever mutated —
|
|
601
|
+
* the evidence memory is a witness, not a target.
|
|
602
|
+
*
|
|
603
|
+
* - `keep` — the preference still holds: stamp `lastCorroborated`, clear
|
|
604
|
+
* `driftState`.
|
|
605
|
+
* - `supersede` — write the corrected preference and retire the old one
|
|
606
|
+
* through the normal supersession path.
|
|
607
|
+
* - `archive` — demote the preference to `archived`. Demotion, never
|
|
608
|
+
* deletion; page versioning snapshots the overwrite.
|
|
609
|
+
*
|
|
610
|
+
* Callers resolve the pair record itself (`resolvePair`); this function owns
|
|
611
|
+
* only the memory-side effects.
|
|
612
|
+
*/
|
|
613
|
+
export async function resolvePreferenceDrift(
|
|
614
|
+
storage: StorageManager,
|
|
615
|
+
pair: Pick<ContradictionPair, "pairId" | "memoryIds" | "namespace">,
|
|
616
|
+
verb: "keep" | "supersede" | "archive",
|
|
617
|
+
options: PreferenceDriftResolutionOptions = {},
|
|
618
|
+
): Promise<PreferenceDriftResolutionResult> {
|
|
619
|
+
const now = options.now ?? new Date();
|
|
620
|
+
const nowIso = now.toISOString();
|
|
621
|
+
const preferenceId = pair.memoryIds[0];
|
|
622
|
+
const preference = await storage.getMemoryById(preferenceId);
|
|
623
|
+
if (!preference) {
|
|
624
|
+
return { affectedIds: [], message: `Preference ${preferenceId} not found; not resolving ${pair.pairId}` };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const touched = (): void => {
|
|
628
|
+
try {
|
|
629
|
+
options.onMemoryWritten?.(pair.namespace, storage.dir);
|
|
630
|
+
} catch (err) {
|
|
631
|
+
log.warn(
|
|
632
|
+
`[preference-drift] catalog write touch failed for ${pair.pairId}: ${err instanceof Error ? err.message : String(err)}`,
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
if (verb === "keep") {
|
|
638
|
+
await storage.writeMemoryFrontmatter(
|
|
639
|
+
preference,
|
|
640
|
+
{ lastCorroborated: nowIso, driftState: undefined, updated: nowIso },
|
|
641
|
+
{ at: now, actor: DRIFT_ACTOR, reasonCode: "drift_kept", ruleVersion: DRIFT_RULE_VERSION },
|
|
642
|
+
);
|
|
643
|
+
bumpMemoryCorpusVersionForDir(storage.dir);
|
|
644
|
+
touched();
|
|
645
|
+
return { affectedIds: [preferenceId], message: `Preference ${preferenceId} confirmed still current` };
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (verb === "archive") {
|
|
649
|
+
const archivedPath = await storage.archiveMemory(preference, {
|
|
650
|
+
at: now,
|
|
651
|
+
actor: DRIFT_ACTOR,
|
|
652
|
+
reasonCode: "drift_archived",
|
|
653
|
+
ruleVersion: DRIFT_RULE_VERSION,
|
|
654
|
+
relatedMemoryIds: [pair.memoryIds[1]],
|
|
655
|
+
});
|
|
656
|
+
if (archivedPath === null) {
|
|
657
|
+
return { affectedIds: [], message: `Preference ${preferenceId} could not be archived; not resolving` };
|
|
658
|
+
}
|
|
659
|
+
bumpMemoryCorpusVersionForDir(storage.dir);
|
|
660
|
+
touched();
|
|
661
|
+
return { affectedIds: [preferenceId], message: `Preference ${preferenceId} archived` };
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const replacementText = options.mergedContent?.trim();
|
|
665
|
+
if (!replacementText) {
|
|
666
|
+
return {
|
|
667
|
+
affectedIds: [],
|
|
668
|
+
message: "supersede requires mergedContent carrying the updated preference; no memories changed",
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Sealed-envelope write: the replacement text is user- or LLM-supplied, so
|
|
673
|
+
// compose with salvage and warn-log any dropped optional field rather than
|
|
674
|
+
// failing the whole resolution on a cosmetic value.
|
|
675
|
+
const envelope = composeMemoryEnvelope(
|
|
676
|
+
{
|
|
677
|
+
content: replacementText,
|
|
678
|
+
category: "preference",
|
|
679
|
+
confidence: preference.frontmatter.confidence ?? 0.8,
|
|
680
|
+
tags: ["preference-drift", "supersede"],
|
|
681
|
+
entityRef: preference.frontmatter.entityRef,
|
|
682
|
+
},
|
|
683
|
+
{ source: DRIFT_ACTOR },
|
|
684
|
+
{ salvage: true },
|
|
685
|
+
);
|
|
686
|
+
if (envelope.salvageNotes.length > 0) {
|
|
687
|
+
log.warn(`[preference-drift] supersede write salvaged invalid fields: ${envelope.salvageNotes.join("; ")}`);
|
|
688
|
+
}
|
|
689
|
+
const written = await storage.writeSealedMemory(envelope, {
|
|
690
|
+
actor: DRIFT_ACTOR,
|
|
691
|
+
lineage: [preferenceId],
|
|
692
|
+
// `update` is the operator for "newer version of the same claim" — the
|
|
693
|
+
// drift replacement restates one preference, it does not merge two.
|
|
694
|
+
derivedVia: "update",
|
|
695
|
+
});
|
|
696
|
+
// A tombstone-blocked replacement is NOT active, so superseding the old
|
|
697
|
+
// preference to it would retire the only live copy (§14: never destroy old
|
|
698
|
+
// state before the new state is confirmed viable).
|
|
699
|
+
if (written.tombstoneBlocked) {
|
|
700
|
+
return {
|
|
701
|
+
affectedIds: [],
|
|
702
|
+
message: `Replacement preference for ${pair.pairId} was tombstone-blocked (pending_review); not resolving — original kept active`,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
const superseded = await storage.supersedeMemory(
|
|
706
|
+
preferenceId,
|
|
707
|
+
written.id,
|
|
708
|
+
`preference drift resolved by supersede (${pair.pairId})`,
|
|
709
|
+
undefined,
|
|
710
|
+
{ actor: DRIFT_ACTOR },
|
|
711
|
+
);
|
|
712
|
+
bumpMemoryCorpusVersionForDir(storage.dir);
|
|
713
|
+
touched();
|
|
714
|
+
if (!superseded) {
|
|
715
|
+
return {
|
|
716
|
+
affectedIds: [written.id],
|
|
717
|
+
message: `Replacement ${written.id} written but superseding ${preferenceId} failed; both remain — re-run resolution`,
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
return {
|
|
721
|
+
affectedIds: [preferenceId, written.id],
|
|
722
|
+
message: `Preference ${preferenceId} superseded by ${written.id}`,
|
|
723
|
+
};
|
|
724
|
+
}
|