@remnic/core 9.26.0 → 9.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +13 -10
- package/dist/access-admin-ops-surface.js +43 -38
- package/dist/access-audit.js +2 -2
- package/dist/access-authorization-probe.d.ts +13 -10
- package/dist/access-authorization-probe.js +44 -39
- package/dist/access-boundary.d.ts +13 -10
- package/dist/access-boundary.js +43 -38
- package/dist/access-cli.js +100 -94
- package/dist/access-cli.js.map +1 -1
- package/dist/access-health-types.d.ts +10 -1
- package/dist/access-http-lcm-compaction.d.ts +13 -10
- package/dist/access-http.d.ts +13 -10
- package/dist/access-http.js +53 -48
- package/dist/access-identity-continuity-surface.d.ts +9 -8
- package/dist/access-identity-continuity-surface.js +43 -38
- package/dist/access-lcm-surface.d.ts +13 -10
- package/dist/access-lcm-surface.js +43 -38
- package/dist/access-mcp.d.ts +13 -10
- package/dist/access-mcp.js +48 -43
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-wRSZEO5V.d.ts → access-namespace-preflight-BiQ_-iUJ.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +43 -38
- package/dist/access-observe-write-surface.d.ts +13 -10
- package/dist/access-observe-write-surface.js +43 -38
- package/dist/access-operations-batch.js +45 -40
- package/dist/access-operations.d.ts +16 -13
- package/dist/access-operations.js +47 -42
- package/dist/access-recall-concurrency.d.ts +13 -10
- package/dist/access-recall-concurrency.js +43 -38
- package/dist/access-recall-response.d.ts +13 -10
- package/dist/access-recall-response.js +43 -38
- package/dist/access-recall-surface.d.ts +13 -10
- package/dist/access-recall-surface.js +43 -38
- package/dist/access-schema.d.ts +80 -80
- package/dist/access-schema.js +4 -4
- package/dist/{access-service-DL8FJnTj.d.ts → access-service-CXkIgcGh.d.ts} +12 -8
- package/dist/access-service.d.ts +13 -10
- package/dist/access-service.js +43 -38
- package/dist/access-surface-catalog.d.ts +13 -10
- package/dist/access-wearables-meetings-surface.d.ts +4 -3
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +4 -2
- package/dist/active-recall.js.map +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 +9 -8
- package/dist/briefing.d.ts +3 -2
- package/dist/briefing.js +12 -10
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +3 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/{catalog-BLLxZ5L5.d.ts → catalog-Bouj8Gfg.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +13 -11
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-7RZJWZ46.js → chunk-227UFSEQ.js} +5 -5
- package/dist/{chunk-VGEH3STZ.js → chunk-2BEJ4XCF.js} +2 -2
- package/dist/{chunk-LIIQPDFN.js → chunk-2FYNLDKJ.js} +24 -3
- package/dist/chunk-2FYNLDKJ.js.map +1 -0
- package/dist/{chunk-BXLOS5AJ.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-6NNNURLK.js → chunk-2PHX3I5C.js} +8 -8
- package/dist/{chunk-YN4AUGNF.js → chunk-2TQ2UFKH.js} +65 -60
- package/dist/chunk-2TQ2UFKH.js.map +1 -0
- package/dist/{chunk-XAAIUXJO.js → chunk-3BSJBIZ3.js} +2 -2
- package/dist/{chunk-JCVQV6QX.js → chunk-3UOSVTFR.js} +11 -11
- package/dist/{chunk-AJ7QCSIS.js → chunk-7CPOZH6Y.js} +2 -2
- package/dist/{chunk-EXM2Q555.js → chunk-AHFZXUZM.js} +7 -5
- package/dist/chunk-AHFZXUZM.js.map +1 -0
- package/dist/{chunk-M2KGXXTL.js → chunk-ALTP3MFS.js} +2 -2
- package/dist/{chunk-77CNV7DY.js → chunk-BLAKS3CP.js} +4 -4
- package/dist/{chunk-FE5FKTCI.js → chunk-BR4UFMLO.js} +2 -2
- package/dist/chunk-CVILPI77.js +432 -0
- package/dist/chunk-CVILPI77.js.map +1 -0
- package/dist/{chunk-H2PBM562.js → chunk-DKQM5Q6Q.js} +2 -2
- package/dist/{chunk-CSXHRAJJ.js → chunk-EG64LXKL.js} +3 -3
- package/dist/{chunk-PTN6UAUA.js → chunk-ES36HTSC.js} +4 -4
- package/dist/{chunk-WMDRLTNA.js → chunk-EZWWCN7I.js} +2 -2
- package/dist/{chunk-XKGPIWGI.js → chunk-FBZTWUK5.js} +5 -5
- package/dist/{chunk-GJJBPI6I.js → chunk-FOCEI3TN.js} +6 -3
- package/dist/chunk-FOCEI3TN.js.map +1 -0
- package/dist/{chunk-Y6E37WRG.js → chunk-GJPCP5CA.js} +2 -2
- package/dist/{chunk-IHMXPG4P.js → chunk-GQSMYXKL.js} +6 -4
- package/dist/{chunk-IHMXPG4P.js.map → chunk-GQSMYXKL.js.map} +1 -1
- package/dist/{chunk-TGQ2NTWH.js → chunk-IJ6WHMFB.js} +16 -2
- package/dist/{chunk-TGQ2NTWH.js.map → chunk-IJ6WHMFB.js.map} +1 -1
- package/dist/{chunk-VDX2J7OX.js → chunk-IPLYGWQF.js} +6 -6
- package/dist/{chunk-XNUBF6RF.js → chunk-IYT632D5.js} +3 -3
- package/dist/{chunk-2QSZNTDO.js → chunk-JBPKEARU.js} +7 -7
- package/dist/{chunk-67DSDRUB.js → chunk-JIF3VRKQ.js} +8 -8
- package/dist/{chunk-V25OWXAC.js → chunk-JQ7T5VDC.js} +1 -1
- package/dist/chunk-JQ7T5VDC.js.map +1 -0
- package/dist/{chunk-MIFF37NN.js → chunk-KKBTBUVT.js} +2 -2
- package/dist/{chunk-ZHP4SUPB.js → chunk-LIHC3T43.js} +2 -2
- package/dist/{chunk-PX7VDW7R.js → chunk-MJPV4LBH.js} +79 -79
- package/dist/{chunk-PX7VDW7R.js.map → chunk-MJPV4LBH.js.map} +1 -1
- package/dist/{chunk-UGH6VPPO.js → chunk-MWTMTW2T.js} +2 -2
- package/dist/{chunk-F6LSNPPB.js → chunk-MXJ3VP6S.js} +6 -6
- package/dist/{chunk-KPWLZBXZ.js → chunk-NBNDVNJ2.js} +186 -25
- package/dist/chunk-NBNDVNJ2.js.map +1 -0
- package/dist/{chunk-M5EPMNS4.js → chunk-P2CLYN6H.js} +2 -2
- package/dist/{chunk-7XXCIO6X.js → chunk-PXQM2HOG.js} +13 -6
- package/dist/{chunk-7XXCIO6X.js.map → chunk-PXQM2HOG.js.map} +1 -1
- package/dist/{chunk-UZK7VVPD.js → chunk-Q5VJUNXA.js} +1858 -272
- package/dist/chunk-Q5VJUNXA.js.map +1 -0
- package/dist/{chunk-KGTD2KZJ.js → chunk-Q6YJW6KE.js} +2 -2
- package/dist/{chunk-X3SQYAPP.js → chunk-R372P2EH.js} +2 -2
- package/dist/{chunk-HBVOJNV4.js → chunk-RKXZ7P4V.js} +3 -3
- package/dist/chunk-RMMVQQA6.js +70 -0
- package/dist/chunk-RMMVQQA6.js.map +1 -0
- package/dist/{chunk-E6EAB4J6.js → chunk-S24E4MBQ.js} +14 -14
- package/dist/chunk-S24E4MBQ.js.map +1 -0
- package/dist/{chunk-RYHWZ52A.js → chunk-S2BBISHU.js} +4 -4
- package/dist/{chunk-LLISELSY.js → chunk-TBMRQZDY.js} +2 -2
- package/dist/{chunk-FMLKOL2Z.js → chunk-TC3VNKGX.js} +4 -4
- package/dist/{chunk-7K5Q6COX.js → chunk-TVVEYCNW.js} +4 -4
- package/dist/{chunk-2XP3PWL3.js → chunk-UR5SIN7H.js} +53 -35
- package/dist/chunk-UR5SIN7H.js.map +1 -0
- package/dist/{chunk-NKDVXEFU.js → chunk-VFNI3RPT.js} +2 -2
- package/dist/chunk-VNA7W7QF.js +134 -0
- package/dist/chunk-VNA7W7QF.js.map +1 -0
- package/dist/{chunk-PQPSZICJ.js → chunk-VYYNBJXH.js} +5 -5
- package/dist/chunk-WNIUPHWV.js +152 -0
- package/dist/chunk-WNIUPHWV.js.map +1 -0
- package/dist/chunk-WRJVB3C2.js +118 -0
- package/dist/chunk-WRJVB3C2.js.map +1 -0
- package/dist/{chunk-SN3ZESWQ.js → chunk-WVVK2RFM.js} +6 -6
- package/dist/{chunk-DQEMWVMT.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-4KKV7W62.js → chunk-YOBNCD3Z.js} +6 -6
- package/dist/chunk-YXZG5TVN.js +40 -0
- package/dist/chunk-YXZG5TVN.js.map +1 -0
- package/dist/{chunk-4DJQYKMN.js → chunk-ZIKENA2C.js} +2 -2
- package/dist/chunk-ZIKENA2C.js.map +1 -0
- package/dist/{chunk-SK3EYPKC.js → chunk-ZN4UZWMT.js} +2 -2
- package/dist/{cli-BoouX6LX.d.ts → cli-DJbkaZBU.d.ts} +5 -5
- package/dist/cli.d.ts +14 -12
- package/dist/cli.js +79 -74
- package/dist/compounding/engine.d.ts +3 -2
- package/dist/compounding/engine.js +12 -10
- 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 +4 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +12 -10
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +12 -10
- package/dist/consolidation-provenance-check.d.ts +3 -2
- package/dist/consolidation-undo.d.ts +3 -2
- package/dist/contradiction/index.d.ts +3 -2
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/backend.js +2 -2
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/corpus-watermark.d.ts +15 -2
- package/dist/corpus-watermark.js +25 -21
- 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-id-normalization.d.ts +4 -0
- package/dist/entity-id-normalization.js +10 -0
- package/dist/entity-id-normalization.js.map +1 -0
- package/dist/entity-retrieval-boundaries.d.ts +3 -0
- package/dist/entity-retrieval-boundaries.js +8 -0
- package/dist/entity-retrieval-boundaries.js.map +1 -0
- package/dist/entity-retrieval.d.ts +3 -2
- package/dist/entity-retrieval.js +13 -10
- package/dist/entity-schema.d.ts +1 -1
- package/dist/entity-schema.js +1 -1
- package/dist/explicit-capture.d.ts +9 -8
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-LEXL3XYL.js → first-start-migration-BKNGOFED.js} +4 -4
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/index.d.ts +409 -407
- package/dist/index.js +147 -141
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +9 -9
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +12 -10
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +3 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -10
- package/dist/maintenance/rebuild-memory-projection.d.ts +3 -2
- package/dist/maintenance/rebuild-memory-projection.js +13 -11
- package/dist/{maintenance-DD22oIx9.d.ts → maintenance-DveW6azo.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +13 -10
- 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-mutations.d.ts +4 -0
- package/dist/memory-projection-mutations.js +15 -0
- package/dist/memory-projection-mutations.js.map +1 -0
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +3 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +4 -3
- package/dist/namespaces/migrate.js +22 -20
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.d.ts +4 -3
- package/dist/namespaces/storage.js +12 -10
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/offline-sync-impression-drain.js +3 -3
- package/dist/operator-doctor-corpus.d.ts +22 -3
- package/dist/operator-doctor-corpus.js +32 -22
- package/dist/operator-doctor-replica.d.ts +52 -0
- package/dist/operator-doctor-replica.js +15 -0
- package/dist/operator-doctor-replica.js.map +1 -0
- package/dist/operator-toolkit.d.ts +7 -3
- package/dist/operator-toolkit.js +33 -27
- package/dist/orchestration/compression-guideline-coordinator.d.ts +3 -2
- package/dist/orchestration/maintenance.d.ts +5 -4
- package/dist/orchestration/maintenance.js +17 -15
- package/dist/{orchestrator-CFJ_TG6D.d.ts → orchestrator-Cvlgpe6I.d.ts} +7 -7
- package/dist/orchestrator.d.ts +9 -8
- package/dist/orchestrator.js +99 -93
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-Bq5zU6Bp.d.ts → pipeline-BveYVaz7.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{qmd-3ILJkC70.d.ts → qmd-CLWDsbYR.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-state.js +2 -2
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/replica-divergence.d.ts +279 -0
- package/dist/replica-divergence.js +28 -0
- package/dist/replica-divergence.js.map +1 -0
- package/dist/replica-peers-config.d.ts +5 -0
- package/dist/replica-peers-config.js +13 -0
- package/dist/replica-peers-config.js.map +1 -0
- package/dist/resolve-auth-token.d.ts +21 -2
- package/dist/resolve-auth-token.js +5 -1
- package/dist/resume-bundles.js +5 -3
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/schemas.d.ts +76 -76
- package/dist/{scope-profiles-D0-ff4xT.d.ts → scope-profiles-BVf3DZ7z.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 +8 -8
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +13 -13
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +2 -2
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-4Tfh7c1g.d.ts → semantic-consolidation-DXLV47y2.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +13 -11
- package/dist/semantic-rule-promotion.js +12 -10
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +12 -10
- package/dist/{service-Z8uzoAST.d.ts → service-DbgPTeMJ.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +9 -9
- package/dist/signal.d.ts +1 -1
- package/dist/{storage-BZVMn8yJ.d.ts → storage-DOzTm8wG.d.ts} +13 -22
- package/dist/storage.d.ts +3 -2
- package/dist/storage.js +13 -10
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +3 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +3 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +3 -3
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/types.d.ts +66 -66
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-DHBxIF0s.d.ts → types-BA7W-_xy.d.ts} +53 -4
- 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 +12 -10
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/access-health-types.ts +8 -0
- package/src/access-http.test.ts +4 -0
- package/src/access-service-health.test.ts +23 -0
- package/src/access-service.ts +27 -4
- package/src/cli.ts +9 -7
- package/src/config.ts +2 -1
- package/src/corpus-watermark.test.ts +21 -0
- package/src/corpus-watermark.ts +25 -3
- package/src/entity-id-normalization.ts +55 -0
- package/src/entity-retrieval-boundaries.ts +134 -0
- package/src/entity-retrieval.ts +253 -27
- package/src/entity-schema.ts +2 -1
- package/src/memory-projection-mutations.ts +132 -0
- package/src/namespaces/storage.ts +5 -3
- package/src/operator-doctor-corpus.ts +41 -0
- package/src/operator-doctor-replica.test.ts +261 -0
- package/src/operator-doctor-replica.ts +129 -0
- package/src/operator-toolkit.ts +5 -4
- package/src/orchestration/orchestrator-init.ts +2 -2
- package/src/replica-divergence.test.ts +1475 -0
- package/src/replica-divergence.ts +910 -0
- package/src/replica-peers-config.ts +250 -0
- package/src/resolve-auth-token.ts +38 -0
- package/src/storage/entity-canonical-id-migration-adapter.ts +72 -0
- package/src/storage/entity-canonical-id-migration-runner.ts +68 -0
- package/src/storage/entity-canonical-id-migration.ts +904 -0
- package/src/storage/entity-store.ts +44 -0
- package/src/storage/memory-frontmatter-metadata.ts +17 -0
- package/src/storage/memory-migration-serialization.ts +37 -0
- package/src/storage/memory-read-store.ts +3 -2
- package/src/storage/profile-header.ts +898 -0
- package/src/storage-profile-timestamp.test.ts +3358 -0
- package/src/storage.ts +73 -77
- package/src/testing/subjects/profile-header-write.test.ts +71 -0
- package/src/types.ts +3 -3
- package/dist/chunk-2XP3PWL3.js.map +0 -1
- package/dist/chunk-4DJQYKMN.js.map +0 -1
- package/dist/chunk-E6EAB4J6.js.map +0 -1
- package/dist/chunk-EXM2Q555.js.map +0 -1
- package/dist/chunk-GJJBPI6I.js.map +0 -1
- package/dist/chunk-KPWLZBXZ.js.map +0 -1
- package/dist/chunk-LIIQPDFN.js.map +0 -1
- package/dist/chunk-UZK7VVPD.js.map +0 -1
- package/dist/chunk-V25OWXAC.js.map +0 -1
- package/dist/chunk-YN4AUGNF.js.map +0 -1
- /package/dist/{capsule-crypto-YO5QJ6L3.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
- /package/dist/{chunk-7RZJWZ46.js.map → chunk-227UFSEQ.js.map} +0 -0
- /package/dist/{chunk-VGEH3STZ.js.map → chunk-2BEJ4XCF.js.map} +0 -0
- /package/dist/{chunk-BXLOS5AJ.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-6NNNURLK.js.map → chunk-2PHX3I5C.js.map} +0 -0
- /package/dist/{chunk-XAAIUXJO.js.map → chunk-3BSJBIZ3.js.map} +0 -0
- /package/dist/{chunk-JCVQV6QX.js.map → chunk-3UOSVTFR.js.map} +0 -0
- /package/dist/{chunk-AJ7QCSIS.js.map → chunk-7CPOZH6Y.js.map} +0 -0
- /package/dist/{chunk-M2KGXXTL.js.map → chunk-ALTP3MFS.js.map} +0 -0
- /package/dist/{chunk-77CNV7DY.js.map → chunk-BLAKS3CP.js.map} +0 -0
- /package/dist/{chunk-FE5FKTCI.js.map → chunk-BR4UFMLO.js.map} +0 -0
- /package/dist/{chunk-H2PBM562.js.map → chunk-DKQM5Q6Q.js.map} +0 -0
- /package/dist/{chunk-CSXHRAJJ.js.map → chunk-EG64LXKL.js.map} +0 -0
- /package/dist/{chunk-PTN6UAUA.js.map → chunk-ES36HTSC.js.map} +0 -0
- /package/dist/{chunk-WMDRLTNA.js.map → chunk-EZWWCN7I.js.map} +0 -0
- /package/dist/{chunk-XKGPIWGI.js.map → chunk-FBZTWUK5.js.map} +0 -0
- /package/dist/{chunk-Y6E37WRG.js.map → chunk-GJPCP5CA.js.map} +0 -0
- /package/dist/{chunk-VDX2J7OX.js.map → chunk-IPLYGWQF.js.map} +0 -0
- /package/dist/{chunk-XNUBF6RF.js.map → chunk-IYT632D5.js.map} +0 -0
- /package/dist/{chunk-2QSZNTDO.js.map → chunk-JBPKEARU.js.map} +0 -0
- /package/dist/{chunk-67DSDRUB.js.map → chunk-JIF3VRKQ.js.map} +0 -0
- /package/dist/{chunk-MIFF37NN.js.map → chunk-KKBTBUVT.js.map} +0 -0
- /package/dist/{chunk-ZHP4SUPB.js.map → chunk-LIHC3T43.js.map} +0 -0
- /package/dist/{chunk-UGH6VPPO.js.map → chunk-MWTMTW2T.js.map} +0 -0
- /package/dist/{chunk-F6LSNPPB.js.map → chunk-MXJ3VP6S.js.map} +0 -0
- /package/dist/{chunk-M5EPMNS4.js.map → chunk-P2CLYN6H.js.map} +0 -0
- /package/dist/{chunk-KGTD2KZJ.js.map → chunk-Q6YJW6KE.js.map} +0 -0
- /package/dist/{chunk-X3SQYAPP.js.map → chunk-R372P2EH.js.map} +0 -0
- /package/dist/{chunk-HBVOJNV4.js.map → chunk-RKXZ7P4V.js.map} +0 -0
- /package/dist/{chunk-RYHWZ52A.js.map → chunk-S2BBISHU.js.map} +0 -0
- /package/dist/{chunk-LLISELSY.js.map → chunk-TBMRQZDY.js.map} +0 -0
- /package/dist/{chunk-FMLKOL2Z.js.map → chunk-TC3VNKGX.js.map} +0 -0
- /package/dist/{chunk-7K5Q6COX.js.map → chunk-TVVEYCNW.js.map} +0 -0
- /package/dist/{chunk-NKDVXEFU.js.map → chunk-VFNI3RPT.js.map} +0 -0
- /package/dist/{chunk-PQPSZICJ.js.map → chunk-VYYNBJXH.js.map} +0 -0
- /package/dist/{chunk-SN3ZESWQ.js.map → chunk-WVVK2RFM.js.map} +0 -0
- /package/dist/{chunk-DQEMWVMT.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-4KKV7W62.js.map → chunk-YOBNCD3Z.js.map} +0 -0
- /package/dist/{chunk-SK3EYPKC.js.map → chunk-ZN4UZWMT.js.map} +0 -0
- /package/dist/{first-start-migration-LEXL3XYL.js.map → first-start-migration-BKNGOFED.js.map} +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { createServer, type RequestListener } from "node:http";
|
|
4
|
+
import type { AddressInfo, Socket } from "node:net";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import test from "node:test";
|
|
8
|
+
/**
|
|
9
|
+
* Replica-divergence doctor check (issue #2149).
|
|
10
|
+
*
|
|
11
|
+
* Verifies `summarizeReplicaDivergence` is ok when disabled / peerless /
|
|
12
|
+
* converged, warns (never ok) when a peer diverges or cannot be polled, surfaces
|
|
13
|
+
* concrete deltas, never leaks a token, and is wired into `runOperatorDoctor`.
|
|
14
|
+
* The check takes a pre-computed local watermark set (kept DTS-light); a real
|
|
15
|
+
* ephemeral 127.0.0.1 server stands in for the peer daemon.
|
|
16
|
+
*/
|
|
17
|
+
import { parseConfig } from "./config.js";
|
|
18
|
+
import type { CorpusWatermark } from "./corpus-watermark.js";
|
|
19
|
+
import { summarizeReplicaDivergence } from "./operator-doctor-replica.js";
|
|
20
|
+
import { type OperatorToolkitOrchestrator, runOperatorDoctor } from "./operator-toolkit.js";
|
|
21
|
+
import type { ReplicaDivergenceStatus } from "./replica-divergence.js";
|
|
22
|
+
import { type ReplicaPeersConfig, parseReplicaPeersConfig } from "./replica-peers-config.js";
|
|
23
|
+
import { StorageManager } from "./storage.js";
|
|
24
|
+
import type { PluginConfig } from "./types.js";
|
|
25
|
+
|
|
26
|
+
function watermark(namespace: string, overrides: Partial<CorpusWatermark> = {}): CorpusWatermark {
|
|
27
|
+
return {
|
|
28
|
+
namespace,
|
|
29
|
+
memoryFileCount: 10,
|
|
30
|
+
newestPartition: "2026-03-08",
|
|
31
|
+
newestWriteAt: "2026-03-08T00:00:00.000Z",
|
|
32
|
+
digest: `digest-${namespace}`,
|
|
33
|
+
computedAt: new Date().toISOString(),
|
|
34
|
+
...overrides,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function replicaConfig(block: Record<string, unknown>): ReplicaPeersConfig {
|
|
39
|
+
return parseReplicaPeersConfig({ replicaPeers: block });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface Peer {
|
|
43
|
+
url: string;
|
|
44
|
+
requests: { authorization: string | undefined }[];
|
|
45
|
+
close: () => Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function startPeer(handler: RequestListener): Promise<Peer> {
|
|
49
|
+
const requests: Peer["requests"] = [];
|
|
50
|
+
const sockets = new Set<Socket>();
|
|
51
|
+
const server = createServer((req, res) => {
|
|
52
|
+
requests.push({ authorization: req.headers.authorization });
|
|
53
|
+
handler(req, res);
|
|
54
|
+
});
|
|
55
|
+
server.on("connection", (socket) => {
|
|
56
|
+
sockets.add(socket);
|
|
57
|
+
socket.on("close", () => sockets.delete(socket));
|
|
58
|
+
});
|
|
59
|
+
const listening = Promise.withResolvers<void>();
|
|
60
|
+
server.once("error", listening.reject);
|
|
61
|
+
server.listen(0, "127.0.0.1", listening.resolve);
|
|
62
|
+
await listening.promise;
|
|
63
|
+
const address = server.address() as AddressInfo;
|
|
64
|
+
return {
|
|
65
|
+
url: `http://127.0.0.1:${address.port}`,
|
|
66
|
+
requests,
|
|
67
|
+
close: async () => {
|
|
68
|
+
for (const socket of sockets) socket.destroy();
|
|
69
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function healthHandler(corpus: CorpusWatermark[]): RequestListener {
|
|
75
|
+
return (req, res) => {
|
|
76
|
+
if (new URL(req.url ?? "/", "http://127.0.0.1").pathname === "/engram/v1/health") {
|
|
77
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
78
|
+
res.end(JSON.stringify({ ok: true, corpus }));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
res.writeHead(404, { "content-type": "application/json" });
|
|
82
|
+
res.end(JSON.stringify({ error: "not_found" }));
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
test("summarizeReplicaDivergence: disabled resolves ok and performs no network I/O", async () => {
|
|
87
|
+
const peer = await startPeer(healthHandler([]));
|
|
88
|
+
try {
|
|
89
|
+
const check = await summarizeReplicaDivergence(
|
|
90
|
+
replicaConfig({ enabled: false, peers: [{ url: peer.url }] }),
|
|
91
|
+
[watermark("global")],
|
|
92
|
+
);
|
|
93
|
+
assert.equal(check.key, "replica_divergence");
|
|
94
|
+
assert.equal(check.status, "ok");
|
|
95
|
+
assert.match(check.summary, /disabled/i);
|
|
96
|
+
assert.equal(peer.requests.length, 0, "a disabled check must not poll");
|
|
97
|
+
} finally {
|
|
98
|
+
await peer.close();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("summarizeReplicaDivergence: enabled with no peers resolves ok", async () => {
|
|
103
|
+
const check = await summarizeReplicaDivergence(replicaConfig({ enabled: true, peers: [] }), [watermark("global")]);
|
|
104
|
+
assert.equal(check.status, "ok");
|
|
105
|
+
assert.match(check.summary, /no peers/i);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("summarizeReplicaDivergence: a converged peer resolves ok", async () => {
|
|
109
|
+
const local = [watermark("global")];
|
|
110
|
+
const peer = await startPeer(healthHandler(local));
|
|
111
|
+
try {
|
|
112
|
+
const check = await summarizeReplicaDivergence(replicaConfig({ enabled: true, peers: [{ url: peer.url }] }), local);
|
|
113
|
+
assert.equal(check.status, "ok");
|
|
114
|
+
assert.match(check.summary, /converged/i);
|
|
115
|
+
} finally {
|
|
116
|
+
await peer.close();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("summarizeReplicaDivergence: an incomplete local census downgrades a converged peer AND warns (round 6)", async () => {
|
|
121
|
+
// /health downgrades a would-be-converged peer to unknown/local_census_incomplete
|
|
122
|
+
// when the local census is partial; the doctor must derive the SAME peer state
|
|
123
|
+
// through the shared gate, not render `peer: converged` with converged details.
|
|
124
|
+
const local = [watermark("global")];
|
|
125
|
+
const peer = await startPeer(healthHandler(local));
|
|
126
|
+
try {
|
|
127
|
+
const check = await summarizeReplicaDivergence(
|
|
128
|
+
replicaConfig({ enabled: true, peers: [{ url: peer.url }] }),
|
|
129
|
+
local,
|
|
130
|
+
{ localCensusComplete: false },
|
|
131
|
+
);
|
|
132
|
+
assert.equal(check.status, "warn", "an incomplete local census cannot certify convergence");
|
|
133
|
+
assert.match(check.summary, /local census incomplete/i);
|
|
134
|
+
const report = check.details as ReplicaDivergenceStatus;
|
|
135
|
+
assert.equal(report.censusComplete, false);
|
|
136
|
+
assert.equal(report.peers[0]?.state, "unknown", "the peer detail is downgraded, not left converged");
|
|
137
|
+
assert.equal(report.peers[0]?.reason, "local_census_incomplete");
|
|
138
|
+
assert.notEqual(report.peers[0]?.state, "converged");
|
|
139
|
+
} finally {
|
|
140
|
+
await peer.close();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("summarizeReplicaDivergence: a diverged peer warns with the concrete delta", async () => {
|
|
145
|
+
const peer = await startPeer(healthHandler([watermark("global", { memoryFileCount: 340_000, digest: "peer" })]));
|
|
146
|
+
try {
|
|
147
|
+
const check = await summarizeReplicaDivergence(
|
|
148
|
+
replicaConfig({ enabled: true, peers: [{ url: peer.url }] }),
|
|
149
|
+
[watermark("global", { memoryFileCount: 190_000, digest: "local" })],
|
|
150
|
+
);
|
|
151
|
+
assert.equal(check.status, "warn", "a divergence must warn, never ok");
|
|
152
|
+
assert.match(check.summary, /diverged/);
|
|
153
|
+
assert.match(check.summary, /file_count_delta=150000/, "the operator sees the concrete delta");
|
|
154
|
+
} finally {
|
|
155
|
+
await peer.close();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("summarizeReplicaDivergence: an unreachable peer warns and is not conflated with ok (§22)", async () => {
|
|
160
|
+
const peer = await startPeer((_req, res) => {
|
|
161
|
+
res.writeHead(500);
|
|
162
|
+
res.end("boom");
|
|
163
|
+
});
|
|
164
|
+
try {
|
|
165
|
+
const check = await summarizeReplicaDivergence(
|
|
166
|
+
replicaConfig({ enabled: true, peers: [{ url: peer.url }] }),
|
|
167
|
+
[watermark("global")],
|
|
168
|
+
);
|
|
169
|
+
assert.equal(check.status, "warn");
|
|
170
|
+
assert.match(check.summary, /unreachable/);
|
|
171
|
+
assert.match(check.summary, /http_500/);
|
|
172
|
+
} finally {
|
|
173
|
+
await peer.close();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("summarizeReplicaDivergence: the peer token never appears in the doctor check output", async () => {
|
|
178
|
+
const secret = "DOCTOR-SECRET-TOKEN";
|
|
179
|
+
const peer = await startPeer(healthHandler([watermark("global", { memoryFileCount: 999_999, digest: "x" })]));
|
|
180
|
+
try {
|
|
181
|
+
const check = await summarizeReplicaDivergence(
|
|
182
|
+
replicaConfig({ enabled: true, peers: [{ url: peer.url, token: secret }] }),
|
|
183
|
+
[watermark("global")],
|
|
184
|
+
);
|
|
185
|
+
assert.ok(peer.requests.some((request) => request.authorization === `Bearer ${secret}`), "token IS used (non-vacuous)");
|
|
186
|
+
assert.ok(!JSON.stringify(check).includes(secret), "token must never appear in the doctor output");
|
|
187
|
+
} finally {
|
|
188
|
+
await peer.close();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
async function writeMemory(memoryDir: string, rel: string): Promise<void> {
|
|
193
|
+
const full = path.join(memoryDir, rel);
|
|
194
|
+
await mkdir(path.dirname(full), { recursive: true });
|
|
195
|
+
await writeFile(full, `---\nid: ${path.basename(rel, ".md")}\n---\n\nbody\n`, "utf-8");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function makeFixture(): Promise<{
|
|
199
|
+
root: string;
|
|
200
|
+
memoryDir: string;
|
|
201
|
+
configPath: string;
|
|
202
|
+
orchestrator: OperatorToolkitOrchestrator;
|
|
203
|
+
}> {
|
|
204
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-replica-doctor-"));
|
|
205
|
+
const memoryDir = path.join(root, "memory");
|
|
206
|
+
await mkdir(memoryDir, { recursive: true });
|
|
207
|
+
const rawConfig = {
|
|
208
|
+
openaiApiKey: "sk-test",
|
|
209
|
+
memoryDir,
|
|
210
|
+
qmdEnabled: false,
|
|
211
|
+
namespacesEnabled: false,
|
|
212
|
+
defaultNamespace: "global",
|
|
213
|
+
};
|
|
214
|
+
const config: PluginConfig = parseConfig(rawConfig);
|
|
215
|
+
const configPath = path.join(root, "openclaw.json");
|
|
216
|
+
await writeFile(
|
|
217
|
+
configPath,
|
|
218
|
+
JSON.stringify({ plugins: { entries: { "openclaw-remnic": { config: rawConfig } } } }, null, 2),
|
|
219
|
+
"utf-8",
|
|
220
|
+
);
|
|
221
|
+
const orchestrator: OperatorToolkitOrchestrator = {
|
|
222
|
+
config,
|
|
223
|
+
storage: new StorageManager(memoryDir),
|
|
224
|
+
qmd: {
|
|
225
|
+
async probe() {
|
|
226
|
+
return false;
|
|
227
|
+
},
|
|
228
|
+
isAvailable() {
|
|
229
|
+
return false;
|
|
230
|
+
},
|
|
231
|
+
async ensureCollection() {
|
|
232
|
+
return "skipped";
|
|
233
|
+
},
|
|
234
|
+
debugStatus() {
|
|
235
|
+
return "disabled";
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
conversationIndexCoordinator: {
|
|
239
|
+
async getHealth() {
|
|
240
|
+
return { enabled: false, backend: "qmd" as const, status: "disabled" as const, chunkDocCount: 0, lastUpdateAt: null };
|
|
241
|
+
},
|
|
242
|
+
async rebuild() {
|
|
243
|
+
return { chunks: 0, skipped: true, reason: "disabled", embedded: false, rebuilt: false };
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
return { root, memoryDir, configPath, orchestrator };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
test("runOperatorDoctor: includes the replica_divergence check (disabled -> ok)", async () => {
|
|
251
|
+
const { root, memoryDir, configPath, orchestrator } = await makeFixture();
|
|
252
|
+
try {
|
|
253
|
+
await writeMemory(memoryDir, "facts/2026-03-08/a.md");
|
|
254
|
+
const report = await runOperatorDoctor({ configPath, orchestrator });
|
|
255
|
+
const check = report.checks.find((entry) => entry.key === "replica_divergence");
|
|
256
|
+
assert.ok(check, "expected a replica_divergence check");
|
|
257
|
+
assert.equal(check?.status, "ok");
|
|
258
|
+
} finally {
|
|
259
|
+
await rm(root, { recursive: true, force: true });
|
|
260
|
+
}
|
|
261
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replica-divergence doctor check (issue #2149).
|
|
3
|
+
*
|
|
4
|
+
* Kept deliberately LIGHT: it takes the ALREADY-computed local corpus watermark
|
|
5
|
+
* set (the same array the `corpus_watermark` check produces) instead of importing
|
|
6
|
+
* the corpus census helpers or `PluginConfig`. That keeps this new tsup DTS entry
|
|
7
|
+
* from pulling the heavy `PluginConfig` type graph into its `.d.ts` — two sibling
|
|
8
|
+
* PRs OOMed the default-heap DTS worker exactly that way (the issue #1562 heap
|
|
9
|
+
* cliff). `runOperatorDoctor` computes the local watermarks once for the corpus
|
|
10
|
+
* check and hands them here via `corpusWatermarksFromCheck`, so there is also no
|
|
11
|
+
* second corpus scan.
|
|
12
|
+
*
|
|
13
|
+
* Status: `ok` when disabled, peerless, or every peer converged; `warn` when any
|
|
14
|
+
* peer diverged beyond threshold, could not be polled (unreachable/unknown), or
|
|
15
|
+
* the local corpus census was incomplete — a scan that cannot ask a peer, or
|
|
16
|
+
* cannot fully read its OWN corpus, must never read as agreement (AGENTS.md §22).
|
|
17
|
+
* The summary carries concrete deltas so an operator sees numbers, not a verdict.
|
|
18
|
+
* Peer tokens are never resolved into, logged from, or printed by this check; a
|
|
19
|
+
* peer is identified only by its redacted host:port. The CLI forwards the host
|
|
20
|
+
* SecretRef resolver (when one is configured) so a SecretRef peer token
|
|
21
|
+
* authenticates here too; without a resolver such a token degrades to a per-peer
|
|
22
|
+
* `token_error` (never a throw). The always-on `/health` surface remains the
|
|
23
|
+
* primary reporting path. Reconciliation is issue #2150.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { CorpusWatermark } from "./corpus-watermark.js";
|
|
27
|
+
import type { OperatorDoctorCheck } from "./operator-doctor-types.js";
|
|
28
|
+
import { type FetchLike, type ReplicaPeerReport, gateReportByCensus, pollReplicaPeers } from "./replica-divergence.js";
|
|
29
|
+
import { resolveReplicaPeersConfig, type ReplicaPeersConfig } from "./replica-peers-config.js";
|
|
30
|
+
import type { ResolveSecretRefFn } from "./resolve-auth-token.js";
|
|
31
|
+
|
|
32
|
+
export interface SummarizeReplicaDivergenceOptions {
|
|
33
|
+
now?: Date;
|
|
34
|
+
resolveSecretRef?: ResolveSecretRefFn | null;
|
|
35
|
+
/** Injectable for tests; production polls with global fetch. */
|
|
36
|
+
fetchImpl?: FetchLike;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the LOCAL corpus census that produced `localWatermarks` was complete
|
|
39
|
+
* (the `corpus_watermark` check was `ok`). When false, a would-be-converged
|
|
40
|
+
* result is downgraded to `warn`: a namespace missing from an incomplete local
|
|
41
|
+
* scan must never let the replica check certify convergence (cursor finding).
|
|
42
|
+
*/
|
|
43
|
+
localCensusComplete?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function formatPeerLine(peer: ReplicaPeerReport): string {
|
|
47
|
+
if (peer.state === "unreachable" || peer.state === "unknown") {
|
|
48
|
+
return `${peer.peer}: ${peer.state} (${peer.reason ?? "unknown"})`;
|
|
49
|
+
}
|
|
50
|
+
if (peer.state === "converged") return `${peer.peer}: converged`;
|
|
51
|
+
const divergedNamespaces = peer.namespaces
|
|
52
|
+
.filter((delta) => delta.diverged)
|
|
53
|
+
.map((delta) => `${delta.namespace}: ${delta.reasons.join("+")}`);
|
|
54
|
+
return `${peer.peer}: diverged [${divergedNamespaces.join(", ")}]`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function summarizeReplicaDivergence(
|
|
58
|
+
rawConfig: ReplicaPeersConfig | undefined,
|
|
59
|
+
localWatermarks: readonly CorpusWatermark[],
|
|
60
|
+
options: SummarizeReplicaDivergenceOptions = {},
|
|
61
|
+
): Promise<OperatorDoctorCheck> {
|
|
62
|
+
// A host adapter or hand-built PluginConfig can omit the block entirely;
|
|
63
|
+
// normalize through the same read-boundary resolver /health uses so doctor
|
|
64
|
+
// degrades to disabled instead of aborting the whole run (round 5, codex P2).
|
|
65
|
+
const replicaConfig = resolveReplicaPeersConfig(rawConfig);
|
|
66
|
+
if (!replicaConfig.enabled) {
|
|
67
|
+
return {
|
|
68
|
+
key: "replica_divergence",
|
|
69
|
+
status: "ok",
|
|
70
|
+
summary: "Replica divergence detection is disabled.",
|
|
71
|
+
details: { enabled: false, pending: false, polledAt: null, peers: [] },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (replicaConfig.peers.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
key: "replica_divergence",
|
|
77
|
+
status: "ok",
|
|
78
|
+
summary: "Replica divergence detection is enabled, but no peers are configured.",
|
|
79
|
+
details: { enabled: true, pending: false, polledAt: null, peers: [] },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const report = await pollReplicaPeers({
|
|
84
|
+
config: replicaConfig,
|
|
85
|
+
localWatermarks,
|
|
86
|
+
now: options.now,
|
|
87
|
+
resolveSecretRef: options.resolveSecretRef,
|
|
88
|
+
fetchImpl: options.fetchImpl,
|
|
89
|
+
});
|
|
90
|
+
// Apply the SAME census gate /health uses (round 6): otherwise the doctor
|
|
91
|
+
// renders `peer: converged` lines and converged `details` while /health
|
|
92
|
+
// downgrades those same peers to `unknown`/`local_census_incomplete`, so a
|
|
93
|
+
// machine reading doctor JSON sees agreement /health denies. Gating here
|
|
94
|
+
// turns every would-be-converged peer into `unknown` when the local census
|
|
95
|
+
// was incomplete, so both surfaces derive peer state from one function.
|
|
96
|
+
const gated = gateReportByCensus(report, options.localCensusComplete !== false);
|
|
97
|
+
const diverged = gated.peers.filter((peer) => peer.state === "diverged");
|
|
98
|
+
const unreachable = gated.peers.filter((peer) => peer.state === "unreachable" || peer.state === "unknown");
|
|
99
|
+
const lines = gated.peers.map(formatPeerLine);
|
|
100
|
+
const censusIncomplete = gated.censusComplete === false;
|
|
101
|
+
|
|
102
|
+
if (diverged.length > 0 || unreachable.length > 0 || censusIncomplete) {
|
|
103
|
+
const counts = [`${diverged.length} diverged`, `${unreachable.length} unreachable/unknown`];
|
|
104
|
+
if (censusIncomplete) counts.push("local census incomplete");
|
|
105
|
+
return {
|
|
106
|
+
key: "replica_divergence",
|
|
107
|
+
status: "warn",
|
|
108
|
+
summary:
|
|
109
|
+
`Replica divergence across ${gated.peers.length} peer(s): ` +
|
|
110
|
+
`${counts.join(", ")}. ${lines.join("; ")}`,
|
|
111
|
+
remediation:
|
|
112
|
+
(censusIncomplete
|
|
113
|
+
? "The local corpus census was incomplete (see the corpus_watermark check), so convergence cannot be " +
|
|
114
|
+
"certified — resolve that first. "
|
|
115
|
+
: "") +
|
|
116
|
+
"Investigate the flagged peer(s): a diverged peer's corpus differs beyond the configured threshold " +
|
|
117
|
+
"(a possible split-brain — the pair's recall will differ across failover); an unreachable/unknown peer " +
|
|
118
|
+
"could not be polled or compared. Detection only; reconciliation is tracked in issue #2150.",
|
|
119
|
+
details: gated,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
key: "replica_divergence",
|
|
125
|
+
status: "ok",
|
|
126
|
+
summary: `All ${gated.peers.length} replica peer(s) converged. ${lines.join("; ")}`,
|
|
127
|
+
details: gated,
|
|
128
|
+
};
|
|
129
|
+
}
|
package/src/operator-toolkit.ts
CHANGED
|
@@ -57,7 +57,8 @@ import type {
|
|
|
57
57
|
} from "./types.js";
|
|
58
58
|
import { reportBufferSurpriseDistribution } from "./buffer-surprise-report.js";
|
|
59
59
|
import { readJudgeVerdictStats } from "./extraction-judge-telemetry.js";
|
|
60
|
-
import {
|
|
60
|
+
import { summarizeCorpusAndReplica } from "./operator-doctor-corpus.js";
|
|
61
|
+
import type { ResolveSecretRefFn } from "./resolve-auth-token.js";
|
|
61
62
|
|
|
62
63
|
const OPENCLAW_REMNIC_PLUGIN_IDS = ["openclaw-remnic", "openclaw-engram"] as const;
|
|
63
64
|
|
|
@@ -384,6 +385,8 @@ export interface OperatorDoctorOptions {
|
|
|
384
385
|
orchestrator: OperatorToolkitOrchestrator;
|
|
385
386
|
configPath?: string;
|
|
386
387
|
now?: Date;
|
|
388
|
+
/** Host SecretRef resolver so a SecretRef peer token authenticates during doctor replica polling (#2149). */
|
|
389
|
+
resolveSecretRef?: ResolveSecretRefFn | null;
|
|
387
390
|
}
|
|
388
391
|
|
|
389
392
|
export interface OperatorConfigReviewOptions {
|
|
@@ -1496,11 +1499,9 @@ export async function runOperatorDoctor(options: OperatorDoctorOptions): Promise
|
|
|
1496
1499
|
// Shows hot/cold counts, per-status breakdown, and forgotten-memory count.
|
|
1497
1500
|
// Informational only — never errors, never blocks doctor from returning ok.
|
|
1498
1501
|
checks.push(await summarizeTierDistribution(options.orchestrator.storage));
|
|
1499
|
-
|
|
1500
1502
|
checks.push(await summarizeDreamsPhases(config, storage));
|
|
1501
1503
|
checks.push(await summarizeExtractionLiveness(config, storage, options.orchestrator.buffer));
|
|
1502
|
-
checks.push(await
|
|
1503
|
-
|
|
1504
|
+
checks.push(...(await summarizeCorpusAndReplica(config, (d) => new StorageManager(d), options.resolveSecretRef)));
|
|
1504
1505
|
// Security mitigation status (issue #565).
|
|
1505
1506
|
// Reports whether the cross-namespace budget and anomaly detection
|
|
1506
1507
|
// mitigations are enabled and surfaces config values for operator review.
|
|
@@ -116,8 +116,8 @@ export class OrchestratorInitCoordinator {
|
|
|
116
116
|
quiet: true,
|
|
117
117
|
logger: (message) => log.info(message),
|
|
118
118
|
});
|
|
119
|
-
await this.deps.storage.ensureDirectories();
|
|
120
119
|
await this.deps.storage.loadAliases();
|
|
120
|
+
await this.deps.storage.ensureDirectories();
|
|
121
121
|
if (resolveNamespaceCapabilities(this.deps.config).namespaces) {
|
|
122
122
|
const namespaces = new Set<string>([
|
|
123
123
|
this.deps.config.defaultNamespace,
|
|
@@ -126,8 +126,8 @@ export class OrchestratorInitCoordinator {
|
|
|
126
126
|
]);
|
|
127
127
|
for (const ns of namespaces) {
|
|
128
128
|
const sm = await this.deps.storageRouter.storageFor(ns);
|
|
129
|
-
await sm.ensureDirectories();
|
|
130
129
|
await sm.loadAliases().catch(() => undefined);
|
|
130
|
+
await sm.ensureDirectories();
|
|
131
131
|
}
|
|
132
132
|
// Explicitly seed the catalog with all configured namespaces at startup
|
|
133
133
|
// (round 6, cursor Medium — NBLlR). The storageFor loop above fires the
|