@remnic/core 9.3.725 → 9.3.727
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +28 -27
- package/dist/access-cli.js +65 -63
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +20 -5
- package/dist/access-http.js +34 -33
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +31 -30
- package/dist/access-operations-batch.js +29 -28
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +30 -29
- package/dist/{access-service-B2PlPtxh.d.ts → access-service-Bgpjxo4f.d.ts} +36 -4
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +27 -26
- package/dist/access-surface-catalog.d.ts +5 -5
- 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 +3 -3
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +9 -8
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-CzUb-GyD.d.ts → catalog-bMczlO2G.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 +10 -9
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-KGZF2VO3.js → chunk-27QQC7ZX.js} +80 -19
- package/dist/{chunk-KGZF2VO3.js.map → chunk-27QQC7ZX.js.map} +1 -1
- package/dist/{chunk-HCVWF657.js → chunk-2KAYTPPT.js} +12 -12
- package/dist/{chunk-4SF232E6.js → chunk-36SEDFAU.js} +19 -7
- package/dist/chunk-36SEDFAU.js.map +1 -0
- package/dist/{chunk-ZK2L72VN.js → chunk-37436GML.js} +2 -2
- package/dist/{chunk-SC2YAWRF.js → chunk-3TSTGI6F.js} +2 -2
- package/dist/{chunk-FACTVMXQ.js → chunk-4H4F3BFL.js} +2 -2
- package/dist/{chunk-WAFDHKES.js → chunk-4NOI577P.js} +2 -2
- package/dist/{chunk-YQMPUYHE.js → chunk-4NR2PDZ5.js} +2 -2
- package/dist/{chunk-TAPSNIMA.js → chunk-5CEJH5ZN.js} +4 -4
- package/dist/{chunk-FN6QP7JQ.js → chunk-6PY5YJAV.js} +100 -14
- package/dist/{chunk-FN6QP7JQ.js.map → chunk-6PY5YJAV.js.map} +1 -1
- package/dist/{chunk-PKMQK2WY.js → chunk-773DPI6T.js} +4 -4
- package/dist/{chunk-4R3CGCI5.js → chunk-7RLCCNHU.js} +15 -12
- package/dist/chunk-7RLCCNHU.js.map +1 -0
- package/dist/{chunk-XGTBNVIU.js → chunk-AAD2ZS6P.js} +11 -1
- package/dist/chunk-AAD2ZS6P.js.map +1 -0
- package/dist/{chunk-Z3FGC3QE.js → chunk-AQLOP5F4.js} +139 -30
- package/dist/chunk-AQLOP5F4.js.map +1 -0
- package/dist/{chunk-FXCIOLRB.js → chunk-ASKXIAHD.js} +2 -2
- package/dist/{chunk-5OPYYPJN.js → chunk-CBWI22JN.js} +2 -2
- package/dist/{chunk-ZOHMPFOB.js → chunk-CKFLLAIS.js} +3 -3
- package/dist/{chunk-GZQXOQQ2.js → chunk-CSRXQ6NB.js} +5 -5
- package/dist/{chunk-QR67FU5G.js → chunk-CTOQEZSN.js} +2 -2
- package/dist/{chunk-ZUDM75KG.js → chunk-DCWIQFNA.js} +4 -4
- package/dist/{chunk-6CWGS2LY.js → chunk-DKJCRGWM.js} +3 -3
- package/dist/{chunk-JJJCVCCQ.js → chunk-DMQWV6SQ.js} +2 -2
- package/dist/{chunk-F3XZN24Z.js → chunk-DRUDC2UX.js} +2 -2
- package/dist/{chunk-4WZ3J7M6.js → chunk-EY6MDMHK.js} +2 -2
- package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
- package/dist/{chunk-C4X4QGIO.js → chunk-G6NKALRN.js} +5 -2
- package/dist/chunk-G6NKALRN.js.map +1 -0
- package/dist/{chunk-6SQQOJ4U.js → chunk-GKRLPHPU.js} +2 -2
- package/dist/{chunk-D7EBTQ24.js → chunk-HOHVIVDR.js} +2 -2
- package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
- package/dist/{chunk-UBHVAGO7.js → chunk-JYLFZWUX.js} +2 -2
- package/dist/{chunk-LD53WPMU.js → chunk-LQ6JI4VH.js} +4 -4
- package/dist/chunk-LTHVM4XN.js +109 -0
- package/dist/chunk-LTHVM4XN.js.map +1 -0
- package/dist/{chunk-WM7DNKBD.js → chunk-M4V2JVSZ.js} +2 -2
- package/dist/{chunk-I2HL5M6T.js → chunk-MJIHJ7OG.js} +52 -52
- package/dist/{chunk-J334N6QV.js → chunk-MLKWJOAJ.js} +2 -2
- package/dist/{chunk-C6BWLYW5.js → chunk-N3VNUM4Z.js} +171 -79
- package/dist/chunk-N3VNUM4Z.js.map +1 -0
- package/dist/{chunk-DF3I6JGP.js → chunk-NSA5ZRUL.js} +15 -15
- package/dist/{chunk-VI5SGGXB.js → chunk-OEEW4KJA.js} +4 -4
- package/dist/{chunk-7AG54AF3.js → chunk-PUR2CKX2.js} +1 -1
- package/dist/chunk-PUR2CKX2.js.map +1 -0
- package/dist/{chunk-NT36GU6X.js → chunk-R7IYU5DZ.js} +2 -2
- package/dist/{chunk-TT5U3UDO.js → chunk-SNKPUJHR.js} +4 -4
- package/dist/{chunk-T55EGZSJ.js → chunk-UVPL565C.js} +2 -2
- package/dist/chunk-W5H6ZRPT.js +14 -0
- package/dist/chunk-W5H6ZRPT.js.map +1 -0
- package/dist/{chunk-YQGHW5ML.js → chunk-WRFKZEO6.js} +4 -4
- package/dist/{chunk-KLFYKC3K.js → chunk-XPHVAOBT.js} +195 -74
- package/dist/chunk-XPHVAOBT.js.map +1 -0
- package/dist/{chunk-H7HUQIY7.js → chunk-Y4YD5TYW.js} +3 -3
- package/dist/{chunk-MDBZTH7N.js → chunk-YKBHOBJK.js} +44 -10
- package/dist/chunk-YKBHOBJK.js.map +1 -0
- package/dist/{chunk-YUYPWJD3.js → chunk-YOLZS7NY.js} +2 -2
- package/dist/{chunk-6OIDDKAA.js → chunk-YTU7WKYN.js} +35 -9
- package/dist/chunk-YTU7WKYN.js.map +1 -0
- package/dist/{cli-IOlDHSBT.d.ts → cli-BLME420h.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +60 -59
- package/dist/compat/codex-cli-fallback.d.ts +1 -0
- package/dist/compat/codex-cli-fallback.js +22 -0
- package/dist/compat/codex-cli-fallback.js.map +1 -0
- package/dist/compat/codex-thread-key.d.ts +1 -0
- package/dist/compat/codex-thread-key.js +18 -0
- package/dist/compat/codex-thread-key.js.map +1 -0
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +9 -8
- 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 +3 -3
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +9 -8
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +9 -8
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/content-hash.d.ts +14 -0
- package/dist/content-hash.js +10 -0
- package/dist/content-hash.js.map +1 -0
- package/dist/contradiction/index.d.ts +1 -1
- 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/dashboard-runtime.js +2 -2
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +9 -8
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-faithfulness.js +2 -2
- 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-redaction-rules.d.ts +33 -0
- package/dist/extraction-redaction-rules.js +14 -0
- package/dist/extraction-redaction-rules.js.map +1 -0
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +3 -3
- package/dist/faithfulness-config.d.ts +1 -0
- package/dist/faithfulness-config.js +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{forget-OEE5OELK.js → forget-WYLR4ZFV.js} +2 -2
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +107 -105
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +4 -4
- 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 +9 -4
- package/dist/local-model-endpoint.js +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +9 -8
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +9 -8
- package/dist/maintenance/rebuild-memory-projection.js +10 -9
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +19 -18
- 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 +2 -2
- package/dist/namespaces/storage.js +9 -8
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +28 -27
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +11 -10
- package/dist/{orchestrator-C_OMSIMZ.d.ts → orchestrator-DqPAz8ot.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +51 -49
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/provenance.js +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +4 -4
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/schemas.d.ts +22 -22
- 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 +14 -14
- 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-B8LiYtvx.d.ts → semantic-consolidation-Cl15ksZy.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +10 -9
- package/dist/semantic-rule-promotion.js +9 -8
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +9 -8
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +3 -3
- package/dist/storage.js +9 -8
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/types.d.ts +12 -12
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-DmTaqob2.d.ts → types-ChQb9D6s.d.ts} +8 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +9 -8
- package/package.json +26 -2
- package/src/access-http.ts +68 -1
- package/src/admin/admin-surfaces.test.ts +289 -0
- package/src/admin/admin-surfaces.ts +138 -7
- package/src/bitemporal-dedup-backfill.test.ts +294 -0
- package/src/chat/chat-engine.test.ts +66 -1
- package/src/chat/chat-http.test.ts +119 -1
- package/src/chat/chat-http.ts +252 -97
- package/src/chat/chat-session.ts +143 -7
- package/src/compat/codex-cli-fallback.ts +23 -0
- package/src/compat/codex-subpath-aliases.test.ts +93 -0
- package/src/compat/codex-thread-key.ts +23 -0
- package/src/config.test.ts +50 -0
- package/src/config.ts +21 -4
- package/src/content-hash.ts +24 -0
- package/src/correction/correction-access-wiring.ts +104 -19
- package/src/correction/correction-contract.ts +68 -10
- package/src/correction/correction-executor.test.ts +112 -2
- package/src/correction/correction-executor.ts +65 -5
- package/src/correction/correction-planner.test.ts +47 -0
- package/src/correction/correction-planner.ts +40 -1
- package/src/correction/correction-storage-integration.test.ts +183 -0
- package/src/extraction-faithfulness.test.ts +240 -0
- package/src/extraction-faithfulness.ts +58 -6
- package/src/extraction-redaction-rules.test.ts +213 -0
- package/src/extraction-redaction-rules.ts +208 -0
- package/src/faithfulness-config.ts +16 -1
- package/src/local-model-endpoint.ts +24 -3
- package/src/orchestrator.ts +252 -9
- package/src/provenance-extraction.test.ts +266 -0
- package/src/provenance.ts +66 -16
- package/src/storage.ts +6 -10
- package/src/types.ts +8 -0
- package/dist/chunk-4R3CGCI5.js.map +0 -1
- package/dist/chunk-4SF232E6.js.map +0 -1
- package/dist/chunk-6OIDDKAA.js.map +0 -1
- package/dist/chunk-7AG54AF3.js.map +0 -1
- package/dist/chunk-C4X4QGIO.js.map +0 -1
- package/dist/chunk-C6BWLYW5.js.map +0 -1
- package/dist/chunk-KLFYKC3K.js.map +0 -1
- package/dist/chunk-MDBZTH7N.js.map +0 -1
- package/dist/chunk-XGTBNVIU.js.map +0 -1
- package/dist/chunk-Z3FGC3QE.js.map +0 -1
- /package/dist/{chunk-HCVWF657.js.map → chunk-2KAYTPPT.js.map} +0 -0
- /package/dist/{chunk-ZK2L72VN.js.map → chunk-37436GML.js.map} +0 -0
- /package/dist/{chunk-SC2YAWRF.js.map → chunk-3TSTGI6F.js.map} +0 -0
- /package/dist/{chunk-FACTVMXQ.js.map → chunk-4H4F3BFL.js.map} +0 -0
- /package/dist/{chunk-WAFDHKES.js.map → chunk-4NOI577P.js.map} +0 -0
- /package/dist/{chunk-YQMPUYHE.js.map → chunk-4NR2PDZ5.js.map} +0 -0
- /package/dist/{chunk-TAPSNIMA.js.map → chunk-5CEJH5ZN.js.map} +0 -0
- /package/dist/{chunk-PKMQK2WY.js.map → chunk-773DPI6T.js.map} +0 -0
- /package/dist/{chunk-FXCIOLRB.js.map → chunk-ASKXIAHD.js.map} +0 -0
- /package/dist/{chunk-5OPYYPJN.js.map → chunk-CBWI22JN.js.map} +0 -0
- /package/dist/{chunk-ZOHMPFOB.js.map → chunk-CKFLLAIS.js.map} +0 -0
- /package/dist/{chunk-GZQXOQQ2.js.map → chunk-CSRXQ6NB.js.map} +0 -0
- /package/dist/{chunk-QR67FU5G.js.map → chunk-CTOQEZSN.js.map} +0 -0
- /package/dist/{chunk-ZUDM75KG.js.map → chunk-DCWIQFNA.js.map} +0 -0
- /package/dist/{chunk-6CWGS2LY.js.map → chunk-DKJCRGWM.js.map} +0 -0
- /package/dist/{chunk-JJJCVCCQ.js.map → chunk-DMQWV6SQ.js.map} +0 -0
- /package/dist/{chunk-F3XZN24Z.js.map → chunk-DRUDC2UX.js.map} +0 -0
- /package/dist/{chunk-4WZ3J7M6.js.map → chunk-EY6MDMHK.js.map} +0 -0
- /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
- /package/dist/{chunk-6SQQOJ4U.js.map → chunk-GKRLPHPU.js.map} +0 -0
- /package/dist/{chunk-D7EBTQ24.js.map → chunk-HOHVIVDR.js.map} +0 -0
- /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
- /package/dist/{chunk-UBHVAGO7.js.map → chunk-JYLFZWUX.js.map} +0 -0
- /package/dist/{chunk-LD53WPMU.js.map → chunk-LQ6JI4VH.js.map} +0 -0
- /package/dist/{chunk-WM7DNKBD.js.map → chunk-M4V2JVSZ.js.map} +0 -0
- /package/dist/{chunk-I2HL5M6T.js.map → chunk-MJIHJ7OG.js.map} +0 -0
- /package/dist/{chunk-J334N6QV.js.map → chunk-MLKWJOAJ.js.map} +0 -0
- /package/dist/{chunk-DF3I6JGP.js.map → chunk-NSA5ZRUL.js.map} +0 -0
- /package/dist/{chunk-VI5SGGXB.js.map → chunk-OEEW4KJA.js.map} +0 -0
- /package/dist/{chunk-NT36GU6X.js.map → chunk-R7IYU5DZ.js.map} +0 -0
- /package/dist/{chunk-TT5U3UDO.js.map → chunk-SNKPUJHR.js.map} +0 -0
- /package/dist/{chunk-T55EGZSJ.js.map → chunk-UVPL565C.js.map} +0 -0
- /package/dist/{chunk-YQGHW5ML.js.map → chunk-WRFKZEO6.js.map} +0 -0
- /package/dist/{chunk-H7HUQIY7.js.map → chunk-Y4YD5TYW.js.map} +0 -0
- /package/dist/{chunk-YUYPWJD3.js.map → chunk-YOLZS7NY.js.map} +0 -0
- /package/dist/{forget-OEE5OELK.js.map → forget-WYLR4ZFV.js.map} +0 -0
|
@@ -13,6 +13,7 @@ import path from "node:path";
|
|
|
13
13
|
import test from "node:test";
|
|
14
14
|
|
|
15
15
|
import { NamespaceCatalog } from "../namespaces/catalog.js";
|
|
16
|
+
import { resolveScopeProfilePlan } from "../namespaces/scope-profiles.js";
|
|
16
17
|
import { parseConfig } from "../config.js";
|
|
17
18
|
import {
|
|
18
19
|
AdminPromotionError,
|
|
@@ -456,3 +457,291 @@ test("promoteMemory throws source_not_found when the source memory is absent", a
|
|
|
456
457
|
(err: unknown) => err instanceof AdminPromotionError && err.code === "source_not_found",
|
|
457
458
|
);
|
|
458
459
|
});
|
|
460
|
+
|
|
461
|
+
// ---------------------------------------------------------------------------
|
|
462
|
+
// gatherMaintenanceHealth — honest QMD state classification (issue #1658 thread 1)
|
|
463
|
+
// ---------------------------------------------------------------------------
|
|
464
|
+
|
|
465
|
+
/** Build a QMD health snapshot fixture for the provider. */
|
|
466
|
+
function qmdFixture(namespace: string, overrides: Partial<Record<string, unknown>> = {}) {
|
|
467
|
+
return {
|
|
468
|
+
namespace,
|
|
469
|
+
collection: `col-${namespace}`,
|
|
470
|
+
available: true,
|
|
471
|
+
collectionState: "present",
|
|
472
|
+
debugStatus: "ok",
|
|
473
|
+
installedVersion: "1.0.0",
|
|
474
|
+
supportedVersion: "1.0.0",
|
|
475
|
+
supported: true,
|
|
476
|
+
upgradeAvailable: false,
|
|
477
|
+
daemonMode: false,
|
|
478
|
+
...overrides,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
test("gatherMaintenanceHealth classifies a present QMD collection as healthy (issue #1658 thread 1)", async () => {
|
|
483
|
+
const memoryDir = await makeTempDir();
|
|
484
|
+
try {
|
|
485
|
+
const config = makeConfig({ memoryDir });
|
|
486
|
+
const catalog = new NamespaceCatalog(config);
|
|
487
|
+
await catalog.registerConfiguredNamespaces();
|
|
488
|
+
const report = await gatherMaintenanceHealth({
|
|
489
|
+
catalog,
|
|
490
|
+
qmdHealthProvider: async (ns) => qmdFixture(ns, { collectionState: "present" }),
|
|
491
|
+
});
|
|
492
|
+
assert.equal(report.degradedMode, false);
|
|
493
|
+
const entry = report.perNamespace.find((e) => e.namespace === "default");
|
|
494
|
+
assert.ok(entry, "default namespace entry must exist");
|
|
495
|
+
assert.equal(entry!.qmdState, "healthy");
|
|
496
|
+
assert.equal(entry!.qmdDegraded, false);
|
|
497
|
+
assert.match(entry!.qmdStateReason, /ready/i);
|
|
498
|
+
} finally {
|
|
499
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
test("gatherMaintenanceHealth classifies an unknown collection state distinctly from missing (issue #1658 thread 1)", async () => {
|
|
504
|
+
const memoryDir = await makeTempDir();
|
|
505
|
+
try {
|
|
506
|
+
const config = makeConfig({ memoryDir });
|
|
507
|
+
const catalog = new NamespaceCatalog(config);
|
|
508
|
+
await catalog.registerConfiguredNamespaces();
|
|
509
|
+
const report = await gatherMaintenanceHealth({
|
|
510
|
+
catalog,
|
|
511
|
+
qmdHealthProvider: async (ns) => qmdFixture(ns, { collectionState: "unknown" }),
|
|
512
|
+
});
|
|
513
|
+
// "unknown" is still counted degraded (matches the router health path) so
|
|
514
|
+
// aggregate alerting is unchanged, but it is now RENDERED honestly as a
|
|
515
|
+
// distinct state rather than collapsed into "missing".
|
|
516
|
+
assert.equal(report.degradedMode, true);
|
|
517
|
+
const entry = report.perNamespace.find((e) => e.namespace === "default");
|
|
518
|
+
assert.ok(entry);
|
|
519
|
+
assert.equal(entry!.qmdState, "unknown");
|
|
520
|
+
assert.equal(entry!.qmdDegraded, true);
|
|
521
|
+
assert.match(entry!.qmdStateReason, /transient/i);
|
|
522
|
+
} finally {
|
|
523
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test("gatherMaintenanceHealth classifies a missing collection as an actionable state (issue #1658 thread 1)", async () => {
|
|
528
|
+
const memoryDir = await makeTempDir();
|
|
529
|
+
try {
|
|
530
|
+
const config = makeConfig({ memoryDir });
|
|
531
|
+
const catalog = new NamespaceCatalog(config);
|
|
532
|
+
await catalog.registerConfiguredNamespaces();
|
|
533
|
+
const report = await gatherMaintenanceHealth({
|
|
534
|
+
catalog,
|
|
535
|
+
qmdHealthProvider: async (ns) => qmdFixture(ns, { collectionState: "missing" }),
|
|
536
|
+
});
|
|
537
|
+
const entry = report.perNamespace.find((e) => e.namespace === "default");
|
|
538
|
+
assert.ok(entry);
|
|
539
|
+
assert.equal(entry!.qmdState, "missing");
|
|
540
|
+
assert.equal(entry!.qmdDegraded, true);
|
|
541
|
+
assert.match(entry!.qmdStateReason, /not built|index/i);
|
|
542
|
+
} finally {
|
|
543
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test("gatherMaintenanceHealth classifies an unavailable backend distinctly (issue #1658 thread 1)", async () => {
|
|
548
|
+
const memoryDir = await makeTempDir();
|
|
549
|
+
try {
|
|
550
|
+
const config = makeConfig({ memoryDir });
|
|
551
|
+
const catalog = new NamespaceCatalog(config);
|
|
552
|
+
await catalog.registerConfiguredNamespaces();
|
|
553
|
+
const report = await gatherMaintenanceHealth({
|
|
554
|
+
catalog,
|
|
555
|
+
qmdHealthProvider: async (ns) => qmdFixture(ns, { available: false, collectionState: "unknown" }),
|
|
556
|
+
});
|
|
557
|
+
const entry = report.perNamespace.find((e) => e.namespace === "default");
|
|
558
|
+
assert.ok(entry);
|
|
559
|
+
assert.equal(entry!.qmdState, "unavailable");
|
|
560
|
+
assert.equal(entry!.qmdDegraded, true);
|
|
561
|
+
} finally {
|
|
562
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
test("gatherMaintenanceHealth reports not_probed when no provider is supplied (issue #1658 thread 1)", async () => {
|
|
567
|
+
const memoryDir = await makeTempDir();
|
|
568
|
+
try {
|
|
569
|
+
const config = makeConfig({ memoryDir });
|
|
570
|
+
const catalog = new NamespaceCatalog(config);
|
|
571
|
+
await catalog.registerConfiguredNamespaces();
|
|
572
|
+
const report = await gatherMaintenanceHealth({ catalog });
|
|
573
|
+
const entry = report.perNamespace.find((e) => e.namespace === "default");
|
|
574
|
+
assert.ok(entry);
|
|
575
|
+
assert.equal(entry!.qmdState, "not_probed");
|
|
576
|
+
assert.equal(entry!.qmdDegraded, false);
|
|
577
|
+
} finally {
|
|
578
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// ---------------------------------------------------------------------------
|
|
583
|
+
// promoteMemory — honest project-context promotion reasons (issue #1658 thread 2)
|
|
584
|
+
// ---------------------------------------------------------------------------
|
|
585
|
+
|
|
586
|
+
test("promoteMemory reports an honest 'no active scope profile' reason for a project target without a profile (issue #1658 thread 2)", async () => {
|
|
587
|
+
const config = makeConfig();
|
|
588
|
+
const storage = makeMemoryStorageProvider();
|
|
589
|
+
const result = await promoteMemory({
|
|
590
|
+
config,
|
|
591
|
+
sourceMemoryId: "mem-1",
|
|
592
|
+
sourceNamespace: "default",
|
|
593
|
+
principal: "alice",
|
|
594
|
+
targets: [{ kind: "userProject" }],
|
|
595
|
+
reason: "promote project fact",
|
|
596
|
+
actor: "alice",
|
|
597
|
+
storage,
|
|
598
|
+
// scopeProfilePlan omitted → no active profile.
|
|
599
|
+
});
|
|
600
|
+
assert.equal(result.ok, false);
|
|
601
|
+
const target = result.targets[0]!;
|
|
602
|
+
assert.equal(target.authorized, false);
|
|
603
|
+
assert.equal(target.namespace, "");
|
|
604
|
+
assert.match(target.reason, /no active scope profile/);
|
|
605
|
+
assert.match(target.reason, /coding context/i);
|
|
606
|
+
assert.equal(storage.written.length, 0);
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
test("promoteMemory reports an honest 'no active scope profile' reason for a non-project target without a profile (issue #1658 thread 2)", async () => {
|
|
610
|
+
const config = makeConfig();
|
|
611
|
+
const storage = makeMemoryStorageProvider();
|
|
612
|
+
const result = await promoteMemory({
|
|
613
|
+
config,
|
|
614
|
+
sourceMemoryId: "mem-1",
|
|
615
|
+
sourceNamespace: "default",
|
|
616
|
+
principal: "alice",
|
|
617
|
+
targets: [{ kind: "serverShared" }],
|
|
618
|
+
reason: "promote shared fact",
|
|
619
|
+
actor: "alice",
|
|
620
|
+
storage,
|
|
621
|
+
});
|
|
622
|
+
assert.equal(result.ok, false);
|
|
623
|
+
const target = result.targets[0]!;
|
|
624
|
+
assert.match(target.reason, /no active scope profile/);
|
|
625
|
+
// Non-project targets must NOT mention coding context.
|
|
626
|
+
assert.doesNotMatch(target.reason, /coding context/i);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
test("promoteMemory surfaces the profile layer's 'missing project context' reason when a project target is configured but unresolvable (issue #1658 thread 2)", async () => {
|
|
630
|
+
const config = makeConfig({
|
|
631
|
+
defaultScopeProfile: "team",
|
|
632
|
+
scopeProfiles: {
|
|
633
|
+
team: {
|
|
634
|
+
readOrder: ["userGlobal"],
|
|
635
|
+
writeDefault: "userGlobal",
|
|
636
|
+
promotionTargets: ["userProject"],
|
|
637
|
+
autoPromote: { enabled: false, targets: [], categories: ["fact"], minConfidenceTier: "speculative" },
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
principalFromSessionKeyMode: "prefix",
|
|
641
|
+
principalFromSessionKeyRules: [{ match: "alice:", principal: "alice" }],
|
|
642
|
+
namespacePolicies: [{ name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] }],
|
|
643
|
+
});
|
|
644
|
+
// No coding context → the userProject layer resolves "missing project context".
|
|
645
|
+
const scopeProfilePlan = resolveScopeProfilePlan({
|
|
646
|
+
config,
|
|
647
|
+
principal: "alice",
|
|
648
|
+
codingContext: null,
|
|
649
|
+
codingOverlay: null,
|
|
650
|
+
});
|
|
651
|
+
assert.ok(scopeProfilePlan, "scope profile must be active for this config");
|
|
652
|
+
const storage = makeMemoryStorageProvider();
|
|
653
|
+
const result = await promoteMemory({
|
|
654
|
+
config,
|
|
655
|
+
sourceMemoryId: "mem-1",
|
|
656
|
+
sourceNamespace: "alice",
|
|
657
|
+
principal: "alice",
|
|
658
|
+
targets: [{ kind: "userProject" }],
|
|
659
|
+
reason: "promote project fact",
|
|
660
|
+
actor: "alice",
|
|
661
|
+
storage,
|
|
662
|
+
scopeProfilePlan,
|
|
663
|
+
});
|
|
664
|
+
assert.equal(result.ok, false);
|
|
665
|
+
const target = result.targets[0]!;
|
|
666
|
+
assert.equal(target.authorized, false);
|
|
667
|
+
assert.match(target.reason, /missing project context/i);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
// ---------------------------------------------------------------------------
|
|
671
|
+
// inspectScope — read-only override honest warning (issue #1658 thread 6)
|
|
672
|
+
// ---------------------------------------------------------------------------
|
|
673
|
+
|
|
674
|
+
test("inspectScope warns when an explicit override is readable but not writable (issue #1658 thread 6)", () => {
|
|
675
|
+
const config = makeConfig({
|
|
676
|
+
principalFromSessionKeyMode: "prefix",
|
|
677
|
+
principalFromSessionKeyRules: [{ match: "alice:", principal: "alice" }],
|
|
678
|
+
namespacePolicies: [
|
|
679
|
+
// alice can READ "audit-log" but cannot WRITE it.
|
|
680
|
+
{ name: "audit-log", readPrincipals: ["alice", "bob"], writePrincipals: ["bob"] },
|
|
681
|
+
{ name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] },
|
|
682
|
+
],
|
|
683
|
+
});
|
|
684
|
+
const inspection = inspectScope({
|
|
685
|
+
config,
|
|
686
|
+
sessionKey: "alice:bot:x:1",
|
|
687
|
+
namespace: "audit-log",
|
|
688
|
+
});
|
|
689
|
+
// The override is readable → it becomes the effective write namespace...
|
|
690
|
+
assert.equal(inspection.namespaceOverride, "audit-log");
|
|
691
|
+
assert.equal(inspection.writeNamespace, "audit-log");
|
|
692
|
+
// ...but alice cannot write it → an honest warning must surface the mismatch.
|
|
693
|
+
assert.ok(
|
|
694
|
+
inspection.warnings.some(
|
|
695
|
+
(w) => w.includes("audit-log") && w.includes("not writable") && w.includes("writes will be rejected"),
|
|
696
|
+
),
|
|
697
|
+
"expected a read-only-override warning; got: " + JSON.stringify(inspection.warnings),
|
|
698
|
+
);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
test("inspectScope does not warn when an explicit override is writable (issue #1658 thread 6)", () => {
|
|
702
|
+
const config = makeConfig({
|
|
703
|
+
principalFromSessionKeyMode: "prefix",
|
|
704
|
+
principalFromSessionKeyRules: [{ match: "alice:", principal: "alice" }],
|
|
705
|
+
namespacePolicies: [
|
|
706
|
+
{ name: "shared", readPrincipals: ["alice"], writePrincipals: ["alice"] },
|
|
707
|
+
{ name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] },
|
|
708
|
+
],
|
|
709
|
+
});
|
|
710
|
+
const inspection = inspectScope({
|
|
711
|
+
config,
|
|
712
|
+
sessionKey: "alice:bot:x:1",
|
|
713
|
+
namespace: "shared",
|
|
714
|
+
});
|
|
715
|
+
assert.equal(inspection.namespaceOverride, "shared");
|
|
716
|
+
assert.ok(
|
|
717
|
+
!inspection.warnings.some((w) => w.includes("not writable")),
|
|
718
|
+
"no read-only-override warning expected for a writable override",
|
|
719
|
+
);
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
test("gatherMaintenanceHealth preserves the original qmdDegraded invariant for an unrecognized collectionState (issue #1658 thread 1)", async () => {
|
|
723
|
+
// The original degraded formula was: !available || "missing" || "unknown".
|
|
724
|
+
// An available backend with any OTHER collectionState (present/skipped/and any
|
|
725
|
+
// unrecognized value) was NOT degraded. The classified qmdState surfaces the
|
|
726
|
+
// unrecognized value honestly, but the degraded bit must stay false so the
|
|
727
|
+
// aggregate alerting invariant is byte-for-byte unchanged.
|
|
728
|
+
const memoryDir = await makeTempDir();
|
|
729
|
+
try {
|
|
730
|
+
const config = makeConfig({ memoryDir });
|
|
731
|
+
const catalog = new NamespaceCatalog(config);
|
|
732
|
+
await catalog.registerConfiguredNamespaces();
|
|
733
|
+
const report = await gatherMaintenanceHealth({
|
|
734
|
+
catalog,
|
|
735
|
+
qmdHealthProvider: async (ns) =>
|
|
736
|
+
qmdFixture(ns, { collectionState: "future-state" }),
|
|
737
|
+
});
|
|
738
|
+
const entry = report.perNamespace.find((e) => e.namespace === "default");
|
|
739
|
+
assert.ok(entry);
|
|
740
|
+
assert.equal(entry!.qmdDegraded, false, "unrecognized collectionState must not flip degraded (unchanged semantics)");
|
|
741
|
+
assert.equal(entry!.qmdState, "unknown");
|
|
742
|
+
assert.match(entry!.qmdStateReason, /unrecognized collection state 'future-state'/);
|
|
743
|
+
assert.equal(report.degradedMode, false);
|
|
744
|
+
} finally {
|
|
745
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
746
|
+
}
|
|
747
|
+
});
|
|
@@ -213,6 +213,17 @@ export function inspectScope(options: InspectScopeOptions): ScopeInspection {
|
|
|
213
213
|
`requested namespace override '${options.namespace}' is not readable by this principal and was ignored`
|
|
214
214
|
);
|
|
215
215
|
}
|
|
216
|
+
// Issue #1658 thread 6: when an explicit namespace override is readable but
|
|
217
|
+
// NOT writable, it still becomes the effective write namespace (baseNamespace
|
|
218
|
+
// in the scope plan). Surface the mismatch honestly so the operator knows
|
|
219
|
+
// writes will be rejected rather than reading writeNamespace as a usable
|
|
220
|
+
// target. (The per-layer writable flag already records this, but the
|
|
221
|
+
// top-level writeNamespace field does not.)
|
|
222
|
+
if (plan.namespaceOverride && !canWriteNamespace(plan.principal, plan.namespaceOverride, config)) {
|
|
223
|
+
warnings.push(
|
|
224
|
+
`explicit namespace override '${plan.namespaceOverride}' is readable but not writable by this principal; it is the effective write namespace but writes will be rejected`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
216
227
|
|
|
217
228
|
const layers: ScopeInspectionLayer[] = [];
|
|
218
229
|
const scopeProfilePlan = plan.scopeProfilePlan;
|
|
@@ -417,6 +428,27 @@ function toAdminEntry(record: NamespaceRecord, staleBefore: Date): AdminNamespac
|
|
|
417
428
|
// Maintenance and QMD health
|
|
418
429
|
// ---------------------------------------------------------------------------
|
|
419
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Honest, distinguishable classification of a namespace's QMD/search health
|
|
433
|
+
* (issue #1658 thread 1). A single `qmdDegraded` bit collapses the legitimate
|
|
434
|
+
* transient "unknown" (QMD reachable but collection status not yet populated,
|
|
435
|
+
* normal during startup/reindex) together with the actionable "missing"
|
|
436
|
+
* (collection not built — operator must act) and "unavailable" (backend down).
|
|
437
|
+
* Surfacing a classified state lets the dashboard render each case honestly
|
|
438
|
+
* instead of a flat degraded flag or a silent blank.
|
|
439
|
+
*/
|
|
440
|
+
export type AdminNamespaceQmdState =
|
|
441
|
+
/** Backend reachable and collection present (or intentionally skipped). */
|
|
442
|
+
| "healthy"
|
|
443
|
+
/** Backend reachable but collection status not yet determined (transient). */
|
|
444
|
+
| "unknown"
|
|
445
|
+
/** Backend reachable but the collection is not built for this namespace. */
|
|
446
|
+
| "missing"
|
|
447
|
+
/** Backend unreachable / probe threw. */
|
|
448
|
+
| "unavailable"
|
|
449
|
+
/** No QMD probe was run for this report (provider omitted or returned null). */
|
|
450
|
+
| "not_probed";
|
|
451
|
+
|
|
420
452
|
/** QMD/search health snapshot for one namespace (delegated to NamespaceSearchRouter). */
|
|
421
453
|
export interface AdminNamespaceQmdHealth {
|
|
422
454
|
namespace: string;
|
|
@@ -438,8 +470,20 @@ export interface AdminNamespaceHealth {
|
|
|
438
470
|
lastMaintenanceAt?: Record<string, string>;
|
|
439
471
|
/** True when no maintenance has ever been recorded. */
|
|
440
472
|
maintenanceMissing: boolean;
|
|
441
|
-
/**
|
|
473
|
+
/**
|
|
474
|
+
* True when QMD is unavailable OR its collection is missing/unknown for this
|
|
475
|
+
* namespace. Matches the NamespaceSearchRouter health path so the aggregate
|
|
476
|
+
* `degradedMode` flag stays consistent with runtime alerting.
|
|
477
|
+
*/
|
|
442
478
|
qmdDegraded: boolean;
|
|
479
|
+
/**
|
|
480
|
+
* Honest, distinguishable QMD state (issue #1658 thread 1). Complements
|
|
481
|
+
* `qmdDegraded` so the dashboard can render "unknown" (transient) separately
|
|
482
|
+
* from "missing"/"unavailable" (action needed) instead of a flat bit.
|
|
483
|
+
*/
|
|
484
|
+
qmdState: AdminNamespaceQmdState;
|
|
485
|
+
/** Human-readable explanation of {@link qmdState}, safe to show operators. */
|
|
486
|
+
qmdStateReason: string;
|
|
443
487
|
qmd?: AdminNamespaceQmdHealth;
|
|
444
488
|
/** Reason the QMD probe failed, when it did. */
|
|
445
489
|
qmdError?: string;
|
|
@@ -473,6 +517,66 @@ export interface MaintenanceHealthOptions {
|
|
|
473
517
|
* timestamps from the catalog and (optionally) QMD diagnostics from the
|
|
474
518
|
* injected provider.
|
|
475
519
|
*/
|
|
520
|
+
/**
|
|
521
|
+
* Classify a QMD health snapshot into an honest, distinguishable state bucket
|
|
522
|
+
* (issue #1658 thread 1). The `degraded` bit mirrors the NamespaceSearchRouter
|
|
523
|
+
* health path (unavailable / missing / unknown) so the aggregate `degradedMode`
|
|
524
|
+
* flag and any downstream alerting stay consistent; the classified `state` +
|
|
525
|
+
* `reason` let the dashboard render each case distinctly instead of a flat bit.
|
|
526
|
+
*/
|
|
527
|
+
function classifyQmdState(qmd: AdminNamespaceQmdHealth): {
|
|
528
|
+
state: AdminNamespaceQmdState;
|
|
529
|
+
reason: string;
|
|
530
|
+
degraded: boolean;
|
|
531
|
+
} {
|
|
532
|
+
if (!qmd.available) {
|
|
533
|
+
return {
|
|
534
|
+
state: "unavailable",
|
|
535
|
+
reason: "QMD backend is unavailable for this namespace",
|
|
536
|
+
degraded: true,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
// collectionState is the router's string ("present" | "missing" | "unknown"
|
|
540
|
+
// | "skipped"); classify defensively — an unrecognized value is reported as
|
|
541
|
+
// "unknown" rather than silently treated as healthy.
|
|
542
|
+
switch (qmd.collectionState) {
|
|
543
|
+
case "present":
|
|
544
|
+
return { state: "healthy", reason: "QMD collection is ready", degraded: false };
|
|
545
|
+
case "skipped":
|
|
546
|
+
// "skipped" means QMD is intentionally disabled for this namespace; it is
|
|
547
|
+
// neither degraded nor a blank — surface it honestly as healthy/idle.
|
|
548
|
+
return {
|
|
549
|
+
state: "healthy",
|
|
550
|
+
reason: "QMD is skipped for this namespace (intentionally disabled)",
|
|
551
|
+
degraded: false,
|
|
552
|
+
};
|
|
553
|
+
case "missing":
|
|
554
|
+
return {
|
|
555
|
+
state: "missing",
|
|
556
|
+
reason: "QMD collection is not built for this namespace — add the collection to the QMD index",
|
|
557
|
+
degraded: true,
|
|
558
|
+
};
|
|
559
|
+
case "unknown":
|
|
560
|
+
return {
|
|
561
|
+
state: "unknown",
|
|
562
|
+
reason: "QMD collection status could not be determined (transient during startup/reindex)",
|
|
563
|
+
degraded: true,
|
|
564
|
+
};
|
|
565
|
+
default:
|
|
566
|
+
// Preserve the ORIGINAL qmdDegraded invariant exactly: an available
|
|
567
|
+
// backend with an unrecognized collectionState was NOT degraded before
|
|
568
|
+
// this PR (the pre-image only flagged missing/unknown/unavailable). We
|
|
569
|
+
// still surface the unrecognized value HONESTLY via state/reason so the
|
|
570
|
+
// dashboard can render it, but we do not flip the degraded bit — that
|
|
571
|
+
// would change alerting and contradict the "unchanged semantics" claim.
|
|
572
|
+
return {
|
|
573
|
+
state: "unknown",
|
|
574
|
+
reason: `QMD reported an unrecognized collection state '${qmd.collectionState}'`,
|
|
575
|
+
degraded: false,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
476
580
|
export async function gatherMaintenanceHealth(options: MaintenanceHealthOptions): Promise<MaintenanceHealthReport> {
|
|
477
581
|
const { catalog, qmdHealthProvider } = options;
|
|
478
582
|
if (!catalog.enabled) {
|
|
@@ -488,22 +592,32 @@ export async function gatherMaintenanceHealth(options: MaintenanceHealthOptions)
|
|
|
488
592
|
lastMaintenanceAt: record.lastMaintenanceAt,
|
|
489
593
|
maintenanceMissing: !record.lastMaintenanceAt || Object.keys(record.lastMaintenanceAt).length === 0,
|
|
490
594
|
qmdDegraded: false,
|
|
595
|
+
qmdState: "not_probed",
|
|
596
|
+
qmdStateReason: "QMD health probe not run for this report",
|
|
491
597
|
};
|
|
492
598
|
if (qmdHealthProvider) {
|
|
493
599
|
try {
|
|
494
600
|
const qmd = await qmdHealthProvider(record.namespace);
|
|
495
601
|
if (qmd) {
|
|
496
602
|
entry.qmd = qmd;
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
603
|
+
// Classify the collection state into an honest, distinguishable
|
|
604
|
+
// bucket (issue #1658 thread 1) and derive the degraded bit from
|
|
605
|
+
// the same inputs the NamespaceSearchRouter health path uses.
|
|
606
|
+
const classified = classifyQmdState(qmd);
|
|
607
|
+
entry.qmdState = classified.state;
|
|
608
|
+
entry.qmdStateReason = classified.reason;
|
|
609
|
+
entry.qmdDegraded = classified.degraded;
|
|
610
|
+
} else {
|
|
611
|
+
entry.qmdState = "not_probed";
|
|
612
|
+
entry.qmdStateReason = "QMD health not available for this namespace";
|
|
501
613
|
}
|
|
502
614
|
} catch (err) {
|
|
503
|
-
entry.qmdDegraded = true;
|
|
504
615
|
// Sanitize: never echo raw error messages to the dashboard. The
|
|
505
616
|
// generic diagnostic is sufficient for operators; the full error
|
|
506
617
|
// is logged by the qmdHealthProvider's caller (access-service).
|
|
618
|
+
entry.qmdDegraded = true;
|
|
619
|
+
entry.qmdState = "unavailable";
|
|
620
|
+
entry.qmdStateReason = "QMD health probe failed";
|
|
507
621
|
entry.qmdError = "QMD health probe failed";
|
|
508
622
|
}
|
|
509
623
|
}
|
|
@@ -735,7 +849,24 @@ export async function promoteMemory(options: PromoteMemoryOptions): Promise<Memo
|
|
|
735
849
|
: "explicit namespace is not writable by this principal",
|
|
736
850
|
};
|
|
737
851
|
}
|
|
738
|
-
|
|
852
|
+
if (!scopeProfilePlan) {
|
|
853
|
+
// Issue #1658 thread 2: distinguish "no active scope profile" from
|
|
854
|
+
// "profile does not configure this target". A project-scoped target
|
|
855
|
+
// (userProject/teamProject) cannot resolve without an active profile +
|
|
856
|
+
// coding context; report that honestly instead of a generic "not
|
|
857
|
+
// configured" message that implies a profile is active.
|
|
858
|
+
return {
|
|
859
|
+
target: requested.kind,
|
|
860
|
+
namespace: "",
|
|
861
|
+
authorized: false,
|
|
862
|
+
promoted: false,
|
|
863
|
+
reason:
|
|
864
|
+
requested.kind === "userProject" || requested.kind === "teamProject"
|
|
865
|
+
? `no active scope profile; '${requested.kind}' promotion requires an active scope profile with a coding context (pass a sessionKey)`
|
|
866
|
+
: `no active scope profile; '${requested.kind}' promotion requires an active scope profile`,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
const layer = scopeProfilePlan.promotionTargets.find((t) => t.target === requested.kind);
|
|
739
870
|
if (!layer) {
|
|
740
871
|
return {
|
|
741
872
|
target: requested.kind,
|