@remnic/core 9.3.701 → 9.3.703
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 +7 -7
- package/dist/access-boundary.js +15 -12
- package/dist/access-cli.js +36 -33
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +7 -7
- package/dist/access-http.js +18 -15
- package/dist/access-mcp.d.ts +7 -7
- package/dist/access-mcp.js +17 -14
- package/dist/access-operations.d.ts +7 -7
- package/dist/access-operations.js +16 -13
- package/dist/{access-service-CGVWK6lZ.d.ts → access-service-COCzhgEL.d.ts} +397 -19
- package/dist/access-service.d.ts +6 -6
- package/dist/access-service.js +14 -11
- package/dist/access-surface-catalog.d.ts +7 -7
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-memory-bridge.js +2 -2
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +7 -6
- package/dist/active-recall.js.map +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +5 -5
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +6 -5
- 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-DBIghceA.d.ts → catalog-DxCzhjE6.d.ts} +2 -2
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +7 -6
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-YXLT4EMM.js → chunk-4463LUIE.js} +12 -2
- package/dist/chunk-4463LUIE.js.map +1 -0
- package/dist/{chunk-R5DB26G6.js → chunk-4SBW47WK.js} +9 -23
- package/dist/chunk-4SBW47WK.js.map +1 -0
- package/dist/{chunk-KF4TXW7Z.js → chunk-5VXVXJH6.js} +149 -6
- package/dist/{chunk-KF4TXW7Z.js.map → chunk-5VXVXJH6.js.map} +1 -1
- package/dist/{chunk-ODTWHSY2.js → chunk-64XXOQRW.js} +2 -2
- package/dist/{chunk-ZT7B64BE.js → chunk-6TAETM63.js} +2 -2
- package/dist/{chunk-HF4N43Q7.js → chunk-6VVP6NK7.js} +2 -2
- package/dist/{chunk-FN2SM5SN.js → chunk-ABCGDW3A.js} +75 -12
- package/dist/chunk-ABCGDW3A.js.map +1 -0
- package/dist/{chunk-7TAQEPLE.js → chunk-AMNLZ6SF.js} +2 -2
- package/dist/{chunk-KS7WQ4BZ.js → chunk-CZJ6QSKG.js} +3 -3
- package/dist/{chunk-27LQPUMZ.js → chunk-EOOVJK2U.js} +3 -3
- package/dist/{chunk-QP37KL5H.js → chunk-EYJD6KIO.js} +2 -2
- package/dist/{chunk-WFEZUGU5.js → chunk-EZR35XHX.js} +2 -2
- package/dist/{chunk-UU6MVCJ6.js → chunk-FIOYURII.js} +32 -25
- package/dist/chunk-FIOYURII.js.map +1 -0
- package/dist/{chunk-JO3E5VGS.js → chunk-FVI5B7DE.js} +2 -2
- package/dist/{chunk-SFMRLXIV.js → chunk-GFIARMA7.js} +15 -7
- package/dist/chunk-GFIARMA7.js.map +1 -0
- package/dist/{chunk-PONNZ54D.js → chunk-GY3SKOS4.js} +4 -4
- package/dist/{chunk-U33LWTQQ.js → chunk-HV57RHMD.js} +4 -4
- package/dist/{chunk-IKNQAGBV.js → chunk-IX3UQT4H.js} +1 -1
- package/dist/{chunk-IKNQAGBV.js.map → chunk-IX3UQT4H.js.map} +1 -1
- package/dist/{chunk-IYOPIG3E.js → chunk-IX72AAMZ.js} +2 -2
- package/dist/{chunk-JKOKX3PS.js → chunk-JMA4RYRN.js} +2 -2
- package/dist/{chunk-DEDQXIDL.js → chunk-JTKFZMZ7.js} +2 -2
- package/dist/{chunk-OLOYQZFB.js → chunk-KC6TCAWV.js} +5 -5
- package/dist/{chunk-ROZJACKP.js → chunk-KKK7YTYN.js} +4 -1
- package/dist/chunk-KKK7YTYN.js.map +1 -0
- package/dist/{chunk-EC2AYKRX.js → chunk-L6W77GWW.js} +10 -24
- package/dist/chunk-L6W77GWW.js.map +1 -0
- package/dist/{chunk-ED35D32I.js → chunk-LQ4J7ELC.js} +2 -2
- package/dist/chunk-LUPVCGYK.js +63 -0
- package/dist/chunk-LUPVCGYK.js.map +1 -0
- package/dist/{chunk-TFVVONWD.js → chunk-MBUM2Y3L.js} +2 -2
- package/dist/{chunk-T5QAZIBO.js → chunk-MOXFPLD6.js} +3 -3
- package/dist/{chunk-XTIRCSIH.js → chunk-MXEWQKM7.js} +2 -2
- package/dist/{chunk-ZYNMX6IU.js → chunk-NUIJEGVD.js} +6 -6
- package/dist/{chunk-FUCJAZ25.js → chunk-OXEAMU42.js} +529 -9
- package/dist/chunk-OXEAMU42.js.map +1 -0
- package/dist/{chunk-YO4MBK3I.js → chunk-QGJAGC2J.js} +2 -2
- package/dist/{chunk-ISLJ5WIM.js → chunk-QIMFOCSH.js} +2 -2
- package/dist/{chunk-IJEZMWKA.js → chunk-SFOAQQDJ.js} +3 -3
- package/dist/{chunk-HRUULBBV.js → chunk-SHRRWOVY.js} +81 -4
- package/dist/chunk-SHRRWOVY.js.map +1 -0
- package/dist/{chunk-NINRTFSV.js → chunk-SINGJCUR.js} +5 -5
- package/dist/{chunk-ZPQVJEVQ.js → chunk-SK2CR6MW.js} +146 -2
- package/dist/chunk-SK2CR6MW.js.map +1 -0
- package/dist/{chunk-6W2D6FGG.js → chunk-TGAHHCB6.js} +2 -2
- package/dist/{chunk-D75JXBV4.js → chunk-WN4GHSDH.js} +2 -2
- package/dist/{chunk-K4DWSPMW.js → chunk-WRGPE6AW.js} +2 -2
- package/dist/{chunk-4HIAWLA2.js → chunk-XGMCUY5P.js} +60 -114
- package/dist/chunk-XGMCUY5P.js.map +1 -0
- package/dist/{chunk-SDPDU2PM.js → chunk-YTMDF6S7.js} +2 -2
- package/dist/{chunk-MNU5G4TK.js → chunk-Z7XEIAV4.js} +2 -2
- package/dist/{chunk-HXHKLVAS.js → chunk-ZCEI242W.js} +23 -23
- package/dist/{cli-D3XeenwN.d.ts → cli-BM4xQPp4.d.ts} +3 -3
- package/dist/cli.d.ts +7 -7
- package/dist/cli.js +33 -32
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +6 -5
- 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 +6 -5
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +7 -6
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +6 -5
- package/dist/entity-schema.d.ts +1 -1
- package/dist/event-order-recall.js +2 -1
- package/dist/explicit-capture.d.ts +5 -5
- package/dist/explicit-capture.js +2 -2
- package/dist/explicit-cue-recall.js +2 -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.d.ts +14 -1
- package/dist/extraction.js +5 -3
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +123 -123
- package/dist/index.js +53 -52
- 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/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +6 -5
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +6 -5
- package/dist/maintenance/rebuild-memory-projection.js +7 -6
- package/dist/mcp-memory-inspector-app.d.ts +7 -7
- 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 +7 -6
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +6 -5
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +12 -11
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +8 -7
- package/dist/{orchestrator-BzMCZlKn.d.ts → orchestrator-C9CDWAm6.d.ts} +4 -4
- package/dist/orchestrator.d.ts +5 -5
- package/dist/orchestrator.js +29 -27
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +36 -2
- package/dist/provenance.js +5 -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-pipeline-stages.d.ts +15 -0
- package/dist/recall-pipeline-stages.js +3 -56
- package/dist/recall-pipeline-stages.js.map +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-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/response-guidance-recall.js +2 -1
- package/dist/resume-bundles.js +7 -5
- 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 +41 -22
- package/dist/schemas.js +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-DJR8_DMQ.d.ts → semantic-SLAa_prH.d.ts} +1 -1
- package/dist/{semantic-consolidation-BtUfv-AL.d.ts → semantic-consolidation-DgFXyALl.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +7 -6
- package/dist/semantic-rule-promotion.js +6 -5
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +6 -5
- 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 +13 -1
- package/dist/storage.js +5 -4
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/targeted-fact-recall.js +2 -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/transcript.js +2 -2
- package/dist/transfer/types.d.ts +12 -12
- package/dist/{types-PuiPZ9iE.d.ts → types-MWKPZnM0.d.ts} +25 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +6 -5
- package/package.json +2 -2
- package/src/access-http.ts +162 -0
- package/src/access-service.ts +215 -0
- package/src/admin/admin-surfaces.test.ts +458 -0
- package/src/admin/admin-surfaces.ts +819 -0
- package/src/event-order-recall.test.ts +53 -0
- package/src/event-order-recall.ts +61 -37
- package/src/explicit-cue-recall.ts +21 -1
- package/src/extraction.ts +91 -6
- package/src/orchestrator.ts +46 -5
- package/src/provenance-extraction.test.ts +838 -0
- package/src/provenance.ts +413 -0
- package/src/recall-pipeline-parity.test.ts +288 -0
- package/src/recall-pipeline-stages.test.ts +43 -0
- package/src/recall-pipeline-stages.ts +22 -0
- package/src/response-guidance-recall.ts +32 -36
- package/src/schemas.ts +7 -0
- package/src/storage.ts +23 -0
- package/src/targeted-fact-recall.ts +25 -32
- package/src/types.ts +24 -0
- package/dist/chunk-4HIAWLA2.js.map +0 -1
- package/dist/chunk-EC2AYKRX.js.map +0 -1
- package/dist/chunk-FN2SM5SN.js.map +0 -1
- package/dist/chunk-FUCJAZ25.js.map +0 -1
- package/dist/chunk-HRUULBBV.js.map +0 -1
- package/dist/chunk-R5DB26G6.js.map +0 -1
- package/dist/chunk-ROZJACKP.js.map +0 -1
- package/dist/chunk-SFMRLXIV.js.map +0 -1
- package/dist/chunk-UU6MVCJ6.js.map +0 -1
- package/dist/chunk-YXLT4EMM.js.map +0 -1
- package/dist/chunk-ZPQVJEVQ.js.map +0 -1
- /package/dist/{chunk-ODTWHSY2.js.map → chunk-64XXOQRW.js.map} +0 -0
- /package/dist/{chunk-ZT7B64BE.js.map → chunk-6TAETM63.js.map} +0 -0
- /package/dist/{chunk-HF4N43Q7.js.map → chunk-6VVP6NK7.js.map} +0 -0
- /package/dist/{chunk-7TAQEPLE.js.map → chunk-AMNLZ6SF.js.map} +0 -0
- /package/dist/{chunk-KS7WQ4BZ.js.map → chunk-CZJ6QSKG.js.map} +0 -0
- /package/dist/{chunk-27LQPUMZ.js.map → chunk-EOOVJK2U.js.map} +0 -0
- /package/dist/{chunk-QP37KL5H.js.map → chunk-EYJD6KIO.js.map} +0 -0
- /package/dist/{chunk-WFEZUGU5.js.map → chunk-EZR35XHX.js.map} +0 -0
- /package/dist/{chunk-JO3E5VGS.js.map → chunk-FVI5B7DE.js.map} +0 -0
- /package/dist/{chunk-PONNZ54D.js.map → chunk-GY3SKOS4.js.map} +0 -0
- /package/dist/{chunk-U33LWTQQ.js.map → chunk-HV57RHMD.js.map} +0 -0
- /package/dist/{chunk-IYOPIG3E.js.map → chunk-IX72AAMZ.js.map} +0 -0
- /package/dist/{chunk-JKOKX3PS.js.map → chunk-JMA4RYRN.js.map} +0 -0
- /package/dist/{chunk-DEDQXIDL.js.map → chunk-JTKFZMZ7.js.map} +0 -0
- /package/dist/{chunk-OLOYQZFB.js.map → chunk-KC6TCAWV.js.map} +0 -0
- /package/dist/{chunk-ED35D32I.js.map → chunk-LQ4J7ELC.js.map} +0 -0
- /package/dist/{chunk-TFVVONWD.js.map → chunk-MBUM2Y3L.js.map} +0 -0
- /package/dist/{chunk-T5QAZIBO.js.map → chunk-MOXFPLD6.js.map} +0 -0
- /package/dist/{chunk-XTIRCSIH.js.map → chunk-MXEWQKM7.js.map} +0 -0
- /package/dist/{chunk-ZYNMX6IU.js.map → chunk-NUIJEGVD.js.map} +0 -0
- /package/dist/{chunk-YO4MBK3I.js.map → chunk-QGJAGC2J.js.map} +0 -0
- /package/dist/{chunk-ISLJ5WIM.js.map → chunk-QIMFOCSH.js.map} +0 -0
- /package/dist/{chunk-IJEZMWKA.js.map → chunk-SFOAQQDJ.js.map} +0 -0
- /package/dist/{chunk-NINRTFSV.js.map → chunk-SINGJCUR.js.map} +0 -0
- /package/dist/{chunk-6W2D6FGG.js.map → chunk-TGAHHCB6.js.map} +0 -0
- /package/dist/{chunk-D75JXBV4.js.map → chunk-WN4GHSDH.js.map} +0 -0
- /package/dist/{chunk-K4DWSPMW.js.map → chunk-WRGPE6AW.js.map} +0 -0
- /package/dist/{chunk-SDPDU2PM.js.map → chunk-YTMDF6S7.js.map} +0 -0
- /package/dist/{chunk-MNU5G4TK.js.map → chunk-Z7XEIAV4.js.map} +0 -0
- /package/dist/{chunk-HXHKLVAS.js.map → chunk-ZCEI242W.js.map} +0 -0
package/src/access-service.ts
CHANGED
|
@@ -100,10 +100,26 @@ import {
|
|
|
100
100
|
type ScopeProfileLayerResolution,
|
|
101
101
|
type ScopeProfilePromotionResolution,
|
|
102
102
|
} from "./namespaces/scope-profiles.js";
|
|
103
|
+
import { type ScopePlan, resolveScopePlan } from "./scopes/scope-plan.js";
|
|
103
104
|
import { namespaceIdentityFromToken } from "./namespaces/identity.js";
|
|
104
105
|
import { namespaceCollectionName } from "./namespaces/search.js";
|
|
105
106
|
import { SecureStoreLockedError } from "./secure-store/index.js";
|
|
106
107
|
import { isPathInsideStorageRoot } from "./storage-paths.js";
|
|
108
|
+
import {
|
|
109
|
+
AdminPromotionError,
|
|
110
|
+
auditTranscripts,
|
|
111
|
+
gatherMaintenanceHealth,
|
|
112
|
+
inspectScope,
|
|
113
|
+
listAdminNamespaces,
|
|
114
|
+
promoteMemory,
|
|
115
|
+
redactSensitive,
|
|
116
|
+
type AdminNamespaceFilter,
|
|
117
|
+
type AdminNamespaceQmdHealth,
|
|
118
|
+
type InspectScopeOptions,
|
|
119
|
+
type MemoryPromotionTargetKind,
|
|
120
|
+
type PromotionStorageProvider,
|
|
121
|
+
type ScopeInspection,
|
|
122
|
+
} from "./admin/admin-surfaces.js";
|
|
107
123
|
import type { LastRecallSnapshot } from "./recall-state.js";
|
|
108
124
|
import type {
|
|
109
125
|
GraphRecallSnapshot,
|
|
@@ -8561,5 +8577,204 @@ export class EngramAccessService {
|
|
|
8561
8577
|
limit: request.limit,
|
|
8562
8578
|
});
|
|
8563
8579
|
}
|
|
8580
|
+
|
|
8581
|
+
// ── Admin console surfaces (issue #1502) ────────────────────────────────
|
|
8582
|
+
//
|
|
8583
|
+
// Thin delegators. Every method threads `this.orchestrator` references
|
|
8584
|
+
// into the pure `admin/admin-surfaces.ts` functions so the dashboard
|
|
8585
|
+
// never re-resolves scope, re-derives promotion targets, or re-lists
|
|
8586
|
+
// namespaces.
|
|
8587
|
+
|
|
8588
|
+
/**
|
|
8589
|
+
* Resolve an effective scope inspection for the console. Inputs are
|
|
8590
|
+
* redacted before return so an operator-supplied principal preview /
|
|
8591
|
+
* namespace override never echoes a pasted credential back.
|
|
8592
|
+
*/
|
|
8593
|
+
async adminInspectScope(options: {
|
|
8594
|
+
sessionKey?: string;
|
|
8595
|
+
namespace?: string;
|
|
8596
|
+
principalOverride?: string;
|
|
8597
|
+
operation?: InspectScopeOptions["operation"];
|
|
8598
|
+
}): Promise<ScopeInspection> {
|
|
8599
|
+
const config = this.orchestrator.config;
|
|
8600
|
+
// Fetch the coding context when a session is supplied; the pure
|
|
8601
|
+
// inspectScope helper (admin-surfaces.ts) owns the single namespace-flag
|
|
8602
|
+
// read for this surface and threads it through resolveScopePlan.
|
|
8603
|
+
const codingContext = options.sessionKey
|
|
8604
|
+
? this.orchestrator.getCodingContextForSession(options.sessionKey) ?? null
|
|
8605
|
+
: null;
|
|
8606
|
+
const inspection = inspectScope({
|
|
8607
|
+
config,
|
|
8608
|
+
sessionKey: options.sessionKey,
|
|
8609
|
+
namespace: options.namespace,
|
|
8610
|
+
principalOverride: options.principalOverride,
|
|
8611
|
+
codingContext,
|
|
8612
|
+
operation: options.operation,
|
|
8613
|
+
});
|
|
8614
|
+
// Redact operator free-text inputs that may carry credentials. The plan
|
|
8615
|
+
// itself is derived from config + storage layout, not user input, so its
|
|
8616
|
+
// fields stay intact for diagnostics.
|
|
8617
|
+
return redactSensitive(inspection);
|
|
8618
|
+
}
|
|
8619
|
+
|
|
8620
|
+
/**
|
|
8621
|
+
* List configured + discovered namespaces from the catalog with the
|
|
8622
|
+
* admin filters the console exposes.
|
|
8623
|
+
*/
|
|
8624
|
+
async adminListNamespaces(filter?: AdminNamespaceFilter) {
|
|
8625
|
+
const result = await listAdminNamespaces({
|
|
8626
|
+
catalog: this.orchestrator.namespaceCatalog,
|
|
8627
|
+
filter,
|
|
8628
|
+
});
|
|
8629
|
+
return redactSensitive(result);
|
|
8630
|
+
}
|
|
8631
|
+
|
|
8632
|
+
/**
|
|
8633
|
+
* Per-namespace maintenance + QMD health. QMD diagnostics are probed via
|
|
8634
|
+
* the orchestrator's search health accessor (one probe per namespace).
|
|
8635
|
+
*/
|
|
8636
|
+
async adminMaintenanceHealth() {
|
|
8637
|
+
const report = await gatherMaintenanceHealth({
|
|
8638
|
+
catalog: this.orchestrator.namespaceCatalog,
|
|
8639
|
+
qmdHealthProvider: async (namespace): Promise<AdminNamespaceQmdHealth | null> => {
|
|
8640
|
+
try {
|
|
8641
|
+
const health = await this.orchestrator.searchHealthForNamespace(namespace);
|
|
8642
|
+
return {
|
|
8643
|
+
namespace,
|
|
8644
|
+
collection: health.collection,
|
|
8645
|
+
available: health.available,
|
|
8646
|
+
collectionState: health.collectionState,
|
|
8647
|
+
debugStatus: health.debugStatus,
|
|
8648
|
+
installedVersion: health.installedVersion,
|
|
8649
|
+
supportedVersion: health.supportedVersion,
|
|
8650
|
+
supported: health.supported,
|
|
8651
|
+
upgradeAvailable: health.upgradeAvailable,
|
|
8652
|
+
daemonMode: health.daemonMode,
|
|
8653
|
+
};
|
|
8654
|
+
} catch (err) {
|
|
8655
|
+
// The report marks the namespace degraded and records the reason;
|
|
8656
|
+
// a probe failure must not abort the whole report.
|
|
8657
|
+
throw err;
|
|
8658
|
+
}
|
|
8659
|
+
},
|
|
8660
|
+
});
|
|
8661
|
+
return redactSensitive(report);
|
|
8662
|
+
}
|
|
8663
|
+
|
|
8664
|
+
/**
|
|
8665
|
+
* Dry-run transcript/session audit. Detects mixed `other/default` data
|
|
8666
|
+
* and legacy stranded directories. Never applies a migration.
|
|
8667
|
+
*/
|
|
8668
|
+
async adminTranscriptAudit() {
|
|
8669
|
+
const result = await auditTranscripts(this.orchestrator.config.memoryDir);
|
|
8670
|
+
return redactSensitive(result);
|
|
8671
|
+
}
|
|
8672
|
+
|
|
8673
|
+
/**
|
|
8674
|
+
* Manually promote a memory into one or more authorized targets. Requires
|
|
8675
|
+
* a non-empty reason (audit-logged). Reuses the scope-profile promotion
|
|
8676
|
+
* resolution and `canWriteNamespace` gate — there is no dashboard-only
|
|
8677
|
+
* write path.
|
|
8678
|
+
*/
|
|
8679
|
+
async adminPromoteMemory(request: {
|
|
8680
|
+
sourceMemoryId: string;
|
|
8681
|
+
namespace?: string;
|
|
8682
|
+
principal?: string;
|
|
8683
|
+
sessionKey?: string;
|
|
8684
|
+
targets: ReadonlyArray<{ kind: MemoryPromotionTargetKind; namespace?: string }>;
|
|
8685
|
+
reason: string;
|
|
8686
|
+
actor?: never; // ignored — actor is derived from the authenticated principal
|
|
8687
|
+
}) {
|
|
8688
|
+
const config = this.orchestrator.config;
|
|
8689
|
+
const namespacesEnabled = config.namespacesEnabled === true;
|
|
8690
|
+
// Fix OdCl3: when no namespace is supplied, resolve the source via the
|
|
8691
|
+
// scope plan (principal self / scope-profile write layer / coding overlay)
|
|
8692
|
+
// instead of falling through to config.defaultNamespace. This matches the
|
|
8693
|
+
// runtime observe/write path.
|
|
8694
|
+
let sourceNamespace: string;
|
|
8695
|
+
if (request.namespace) {
|
|
8696
|
+
sourceNamespace = this.resolveReadableNamespace(
|
|
8697
|
+
request.namespace,
|
|
8698
|
+
request.principal,
|
|
8699
|
+
);
|
|
8700
|
+
} else {
|
|
8701
|
+
const codingContext = request.sessionKey
|
|
8702
|
+
? this.orchestrator.getCodingContextForSession(request.sessionKey) ?? null
|
|
8703
|
+
: null;
|
|
8704
|
+
const plan = resolveScopePlan({
|
|
8705
|
+
config,
|
|
8706
|
+
sessionKey: request.sessionKey,
|
|
8707
|
+
principalOverride: request.principal,
|
|
8708
|
+
codingContext,
|
|
8709
|
+
namespacesEnabled,
|
|
8710
|
+
});
|
|
8711
|
+
sourceNamespace = this.resolveReadableNamespace(
|
|
8712
|
+
plan.baseNamespace,
|
|
8713
|
+
request.principal,
|
|
8714
|
+
);
|
|
8715
|
+
}
|
|
8716
|
+
// Fix OdB0c: thread coding context into the scope-profile plan so
|
|
8717
|
+
// userProject/teamProject promotion targets resolve for project-scoped
|
|
8718
|
+
// sessions (previously hard-coded to null).
|
|
8719
|
+
const codingContext = request.sessionKey
|
|
8720
|
+
? this.orchestrator.getCodingContextForSession(request.sessionKey) ?? null
|
|
8721
|
+
: null;
|
|
8722
|
+
const codingOverlay = codingContext
|
|
8723
|
+
? resolveCodingNamespaceOverlay(
|
|
8724
|
+
codingContext,
|
|
8725
|
+
config.codingMode,
|
|
8726
|
+
config.defaultNamespace,
|
|
8727
|
+
)
|
|
8728
|
+
: null;
|
|
8729
|
+
const scopeProfilePlan = resolveScopeProfilePlan({
|
|
8730
|
+
config,
|
|
8731
|
+
principal: request.principal,
|
|
8732
|
+
codingContext,
|
|
8733
|
+
codingOverlay,
|
|
8734
|
+
});
|
|
8735
|
+
const storage: PromotionStorageProvider = {
|
|
8736
|
+
readMemory: async (namespace, memoryId) => {
|
|
8737
|
+
const resolved = await this.orchestrator.getStorage(namespace);
|
|
8738
|
+
const memory = await resolved.getMemoryById(memoryId);
|
|
8739
|
+
if (!memory) return null;
|
|
8740
|
+
return {
|
|
8741
|
+
category: memory.frontmatter.category,
|
|
8742
|
+
content: memory.content,
|
|
8743
|
+
frontmatter: memory.frontmatter,
|
|
8744
|
+
};
|
|
8745
|
+
},
|
|
8746
|
+
writePromotedMemory: async (namespace, memory) => {
|
|
8747
|
+
const resolved = await this.orchestrator.getStorage(namespace);
|
|
8748
|
+
return resolved.writeMemory(memory.category, memory.content, {
|
|
8749
|
+
confidence: memory.confidence,
|
|
8750
|
+
tags: memory.tags,
|
|
8751
|
+
entityRef: memory.entityRef,
|
|
8752
|
+
source: `admin-promotion:${memory.sourceNamespace}:${memory.reason.slice(0, 120)}`,
|
|
8753
|
+
lineage: memory.lineage,
|
|
8754
|
+
sourceMemoryId: memory.sourceMemoryId,
|
|
8755
|
+
actor: memory.actor,
|
|
8756
|
+
validAt: memory.validAt,
|
|
8757
|
+
});
|
|
8758
|
+
},
|
|
8759
|
+
};
|
|
8760
|
+
try {
|
|
8761
|
+
return await promoteMemory({
|
|
8762
|
+
config,
|
|
8763
|
+
sourceMemoryId: request.sourceMemoryId,
|
|
8764
|
+
sourceNamespace,
|
|
8765
|
+
principal: request.principal,
|
|
8766
|
+
targets: request.targets,
|
|
8767
|
+
reason: request.reason,
|
|
8768
|
+
actor: request.principal ?? "admin-console",
|
|
8769
|
+
storage,
|
|
8770
|
+
scopeProfilePlan,
|
|
8771
|
+
});
|
|
8772
|
+
} catch (err) {
|
|
8773
|
+
if (err instanceof AdminPromotionError) {
|
|
8774
|
+
throw new EngramAccessInputError(err.message);
|
|
8775
|
+
}
|
|
8776
|
+
throw err;
|
|
8777
|
+
}
|
|
8778
|
+
}
|
|
8564
8779
|
}
|
|
8565
8780
|
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the admin console surfaces (issue #1502).
|
|
3
|
+
*
|
|
4
|
+
* Each surface is exercised against the SAME core APIs the runtime uses so
|
|
5
|
+
* the acceptance criteria ("scope inspector returns the same plan as runtime
|
|
6
|
+
* resolver"; "promotion requires reason and enforces authorization"; etc.)
|
|
7
|
+
* hold by construction rather than by mock.
|
|
8
|
+
*/
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import test from "node:test";
|
|
14
|
+
|
|
15
|
+
import { NamespaceCatalog } from "../namespaces/catalog.js";
|
|
16
|
+
import { parseConfig } from "../config.js";
|
|
17
|
+
import {
|
|
18
|
+
AdminPromotionError,
|
|
19
|
+
auditTranscripts,
|
|
20
|
+
gatherMaintenanceHealth,
|
|
21
|
+
inspectScope,
|
|
22
|
+
listAdminNamespaces,
|
|
23
|
+
promoteMemory,
|
|
24
|
+
REDACTED,
|
|
25
|
+
redactSensitive,
|
|
26
|
+
type PromotionStorageProvider,
|
|
27
|
+
} from "./admin-surfaces.js";
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Helpers
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
/** Minimal namespace-enabled config for admin-surface tests. */
|
|
34
|
+
function makeConfig(overrides: Record<string, unknown> = {}) {
|
|
35
|
+
return parseConfig({
|
|
36
|
+
namespacesEnabled: true,
|
|
37
|
+
defaultNamespace: "default",
|
|
38
|
+
sharedNamespace: "shared",
|
|
39
|
+
...overrides,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function makeTempDir(): Promise<string> {
|
|
44
|
+
return mkdtemp(path.join(os.tmpdir(), "remnic-admin-test-"));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// redactSensitive
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
test("redactSensitive redacts bearer-token-shaped values and sensitive keys", () => {
|
|
52
|
+
const redacted = redactSensitive({
|
|
53
|
+
namespace: "default",
|
|
54
|
+
// The KEY name triggers redaction (contains "token"); value is an inert fixture.
|
|
55
|
+
authToken: "test-auth-token-fixture",
|
|
56
|
+
nested: {
|
|
57
|
+
// The KEY name triggers redaction (matches "bearer"); value is inert.
|
|
58
|
+
bearer: "test-bearer-fixture",
|
|
59
|
+
safe: "keep me",
|
|
60
|
+
// The KEY name triggers redaction (matches "password"); value is inert.
|
|
61
|
+
password: "test-password-fixture",
|
|
62
|
+
},
|
|
63
|
+
// A value matching BEARER_VALUE_RE under a non-sensitive key exercises the
|
|
64
|
+
// value-based branch independently. "bearer-test" is obviously not a real
|
|
65
|
+
// credential — it just matches the prefix regex.
|
|
66
|
+
description: "bearer-test-fixture",
|
|
67
|
+
// A ≥40-char opaque string under a non-sensitive key exercises the long-
|
|
68
|
+
// opaque branch. All-a's has zero entropy so it won't trip secret scanners.
|
|
69
|
+
opaqueField: "a".repeat(46),
|
|
70
|
+
array: ["bearer-test-array-value", "normal value"],
|
|
71
|
+
identityToken: "ns-test-routing-token", // routing token — NOT redacted (exempt key)
|
|
72
|
+
});
|
|
73
|
+
assert.equal(redacted.namespace, "default");
|
|
74
|
+
assert.equal((redacted as Record<string, unknown>).authToken, REDACTED);
|
|
75
|
+
assert.equal(
|
|
76
|
+
((redacted as Record<string, unknown>).nested as Record<string, unknown>).bearer,
|
|
77
|
+
REDACTED,
|
|
78
|
+
);
|
|
79
|
+
assert.equal(
|
|
80
|
+
((redacted as Record<string, unknown>).nested as Record<string, unknown>).safe,
|
|
81
|
+
"keep me",
|
|
82
|
+
);
|
|
83
|
+
assert.equal(
|
|
84
|
+
((redacted as Record<string, unknown>).nested as Record<string, unknown>).password,
|
|
85
|
+
REDACTED,
|
|
86
|
+
);
|
|
87
|
+
// Value-based branch: "bearer-test-fixture" matches BEARER_VALUE_RE even under
|
|
88
|
+
// a non-sensitive key.
|
|
89
|
+
assert.equal((redacted as Record<string, unknown>).description, REDACTED);
|
|
90
|
+
// Long-opaque branch: 46-char alphanumeric string under a non-sensitive key.
|
|
91
|
+
assert.equal((redacted as Record<string, unknown>).opaqueField, REDACTED);
|
|
92
|
+
// The 16-hex identity token must survive (it's a routing token, not a credential).
|
|
93
|
+
assert.equal((redacted as Record<string, unknown>).identityToken, "ns-test-routing-token");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("redactSensitive leaves primitives untouched", () => {
|
|
97
|
+
assert.equal(redactSensitive("plain"), "plain");
|
|
98
|
+
assert.equal(redactSensitive(42), 42);
|
|
99
|
+
assert.equal(redactSensitive(true), true);
|
|
100
|
+
assert.equal(redactSensitive(null), null);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// inspectScope — single-user (namespaces disabled)
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
test("inspectScope returns a self-layer plan in single-user mode", () => {
|
|
108
|
+
const config = parseConfig({ namespacesEnabled: false });
|
|
109
|
+
const inspection = inspectScope({ config, sessionKey: "agent:bot:discord:channel:1" });
|
|
110
|
+
assert.equal(inspection.principal, "default");
|
|
111
|
+
assert.equal(inspection.scopeProfileId, null);
|
|
112
|
+
assert.equal(inspection.codingOverlay, null);
|
|
113
|
+
assert.ok(inspection.readNamespaces.length >= 1);
|
|
114
|
+
assert.equal(inspection.writeNamespace, config.defaultNamespace);
|
|
115
|
+
const selfLayer = inspection.layers.find((layer) => layer.id === "self");
|
|
116
|
+
assert.ok(selfLayer, "a self layer must be present in single-user mode");
|
|
117
|
+
assert.equal(selfLayer?.namespace, config.defaultNamespace);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// inspectScope — namespace-aware: principal derived from session key
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
test("inspectScope resolves a principal-scoped write namespace when namespaces are enabled", () => {
|
|
125
|
+
const config = makeConfig({
|
|
126
|
+
principalFromSessionKeyMode: "prefix",
|
|
127
|
+
principalFromSessionKeyRules: [{ match: "alice:", principal: "alice" }],
|
|
128
|
+
namespacePolicies: [{ name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] }],
|
|
129
|
+
});
|
|
130
|
+
const inspection = inspectScope({ config, sessionKey: "alice:bot:discord:1" });
|
|
131
|
+
assert.equal(inspection.principal, "alice");
|
|
132
|
+
assert.equal(inspection.writeNamespace, "alice");
|
|
133
|
+
assert.ok(
|
|
134
|
+
inspection.readNamespaces.includes("alice"),
|
|
135
|
+
"read namespaces must include the principal self namespace",
|
|
136
|
+
);
|
|
137
|
+
// The plan field MUST be present so the "same plan as runtime resolver"
|
|
138
|
+
// acceptance test holds — the dashboard displays this verbatim.
|
|
139
|
+
assert.ok(inspection.plan, "plan must be present");
|
|
140
|
+
assert.equal(inspection.plan.baseNamespace, inspection.writeNamespace);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("inspectScope surfaces a warning when namespaces are enabled but no principal resolves", () => {
|
|
144
|
+
const config = makeConfig();
|
|
145
|
+
const inspection = inspectScope({ config, sessionKey: undefined });
|
|
146
|
+
assert.ok(
|
|
147
|
+
inspection.warnings.some((w) => w.includes("no principal resolved")),
|
|
148
|
+
"expected a no-principal warning",
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("inspectScope flags an ignored namespace override that is not readable", () => {
|
|
153
|
+
const config = makeConfig({
|
|
154
|
+
namespacePolicies: [
|
|
155
|
+
{ name: "protected", readPrincipals: ["bob"], writePrincipals: ["bob"] },
|
|
156
|
+
],
|
|
157
|
+
});
|
|
158
|
+
const inspection = inspectScope({
|
|
159
|
+
config,
|
|
160
|
+
sessionKey: "alice:bot:x:1",
|
|
161
|
+
namespace: "protected",
|
|
162
|
+
});
|
|
163
|
+
// The override is not readable by alice → warning + plan falls through.
|
|
164
|
+
assert.equal(inspection.namespaceOverride, undefined);
|
|
165
|
+
assert.ok(
|
|
166
|
+
inspection.warnings.some((w) => w.includes("not readable")),
|
|
167
|
+
"expected an ignored-override warning",
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// listAdminNamespaces
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
test("listAdminNamespaces returns disabled payload when the catalog is off", async () => {
|
|
176
|
+
const config = parseConfig({ namespacesEnabled: false });
|
|
177
|
+
const catalog = new NamespaceCatalog(config);
|
|
178
|
+
const result = await listAdminNamespaces({ catalog });
|
|
179
|
+
assert.equal(result.enabled, false);
|
|
180
|
+
assert.equal(result.entries.length, 0);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("listAdminNamespaces lists configured + discovered namespaces and marks stale entries", async () => {
|
|
184
|
+
const memoryDir = await makeTempDir();
|
|
185
|
+
try {
|
|
186
|
+
const config = makeConfig({ memoryDir });
|
|
187
|
+
const catalog = new NamespaceCatalog(config);
|
|
188
|
+
await catalog.registerConfiguredNamespaces();
|
|
189
|
+
const result = await listAdminNamespaces({ catalog, staleThresholdDays: 0 });
|
|
190
|
+
assert.equal(result.enabled, true);
|
|
191
|
+
// default + shared should both be present.
|
|
192
|
+
const names = result.entries.map((entry) => entry.namespace);
|
|
193
|
+
assert.ok(names.includes("default"), "default namespace must be listed");
|
|
194
|
+
assert.ok(names.includes("shared"), "shared namespace must be listed");
|
|
195
|
+
// staleThresholdDays: 0 → every entry written > 0 days ago is stale.
|
|
196
|
+
// freshly-registered namespaces have no lastWriteAt, so they are stale.
|
|
197
|
+
assert.ok(result.entries.every((entry) => entry.stale), "fresh entries should be stale");
|
|
198
|
+
} finally {
|
|
199
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("listAdminNamespaces filters by principal", async () => {
|
|
204
|
+
const memoryDir = await makeTempDir();
|
|
205
|
+
try {
|
|
206
|
+
const config = makeConfig({
|
|
207
|
+
memoryDir,
|
|
208
|
+
namespacePolicies: [
|
|
209
|
+
{ name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] },
|
|
210
|
+
{ name: "bob", readPrincipals: ["bob"], writePrincipals: ["bob"] },
|
|
211
|
+
],
|
|
212
|
+
});
|
|
213
|
+
const catalog = new NamespaceCatalog(config);
|
|
214
|
+
await catalog.registerConfiguredNamespaces();
|
|
215
|
+
const result = await listAdminNamespaces({ catalog, filter: { principal: "alice" } });
|
|
216
|
+
assert.ok(result.entries.every((entry) => entry.principal === "alice"));
|
|
217
|
+
} finally {
|
|
218
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// gatherMaintenanceHealth
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
test("gatherMaintenanceHealth reports degraded mode when QMD probe fails", async () => {
|
|
227
|
+
const memoryDir = await makeTempDir();
|
|
228
|
+
try {
|
|
229
|
+
const config = makeConfig({ memoryDir });
|
|
230
|
+
const catalog = new NamespaceCatalog(config);
|
|
231
|
+
await catalog.registerConfiguredNamespaces();
|
|
232
|
+
const report = await gatherMaintenanceHealth({
|
|
233
|
+
catalog,
|
|
234
|
+
qmdHealthProvider: async () => {
|
|
235
|
+
throw new Error("qmd unreachable");
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
assert.equal(report.enabled, true);
|
|
239
|
+
assert.equal(report.degradedMode, true);
|
|
240
|
+
assert.ok(
|
|
241
|
+
report.perNamespace.some((entry) => entry.qmdDegraded && entry.qmdError === "QMD health probe failed"),
|
|
242
|
+
"the failing namespace must be flagged degraded with a sanitized error message",
|
|
243
|
+
);
|
|
244
|
+
} finally {
|
|
245
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("gatherMaintenanceHealth reports healthy when QMD probe returns available", async () => {
|
|
250
|
+
const memoryDir = await makeTempDir();
|
|
251
|
+
try {
|
|
252
|
+
const config = makeConfig({ memoryDir });
|
|
253
|
+
const catalog = new NamespaceCatalog(config);
|
|
254
|
+
await catalog.registerConfiguredNamespaces();
|
|
255
|
+
const report = await gatherMaintenanceHealth({
|
|
256
|
+
catalog,
|
|
257
|
+
qmdHealthProvider: async (namespace) => ({
|
|
258
|
+
namespace,
|
|
259
|
+
collection: `col-${namespace}`,
|
|
260
|
+
available: true,
|
|
261
|
+
collectionState: "present",
|
|
262
|
+
debugStatus: "ok",
|
|
263
|
+
installedVersion: "1.0.0",
|
|
264
|
+
supportedVersion: "1.0.0",
|
|
265
|
+
supported: true,
|
|
266
|
+
upgradeAvailable: false,
|
|
267
|
+
daemonMode: false,
|
|
268
|
+
}),
|
|
269
|
+
});
|
|
270
|
+
assert.equal(report.degradedMode, false);
|
|
271
|
+
assert.ok(report.perNamespace.every((entry) => !entry.qmdDegraded));
|
|
272
|
+
} finally {
|
|
273
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
// auditTranscripts
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
|
|
281
|
+
test("auditTranscripts detects mixed other/default data and stays dry-run", async () => {
|
|
282
|
+
const memoryDir = await makeTempDir();
|
|
283
|
+
try {
|
|
284
|
+
// Seed a shared other/default file holding two distinct sessions.
|
|
285
|
+
const transcriptsDir = path.join(memoryDir, "transcripts", "other", "default");
|
|
286
|
+
await mkdir(transcriptsDir, { recursive: true });
|
|
287
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
288
|
+
await writeFile(
|
|
289
|
+
path.join(transcriptsDir, `${today}.jsonl`),
|
|
290
|
+
[
|
|
291
|
+
JSON.stringify({ sessionKey: "pi-geek:abc", entry: "hello from abc", role: "user" }),
|
|
292
|
+
JSON.stringify({ sessionKey: "pi-geek:def", entry: "hello from def", role: "user" }),
|
|
293
|
+
].join("\n") + "\n",
|
|
294
|
+
);
|
|
295
|
+
const report = await auditTranscripts(memoryDir);
|
|
296
|
+
assert.equal(report.dryRun, true);
|
|
297
|
+
assert.equal(report.mixedOtherDefault, true);
|
|
298
|
+
assert.equal(report.distinctSessions, 2);
|
|
299
|
+
assert.equal(report.movedEntries, 2);
|
|
300
|
+
assert.ok(report.summary.includes("2 distinct sessions"));
|
|
301
|
+
} finally {
|
|
302
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("auditTranscripts reports clean when no legacy data exists", async () => {
|
|
307
|
+
const memoryDir = await makeTempDir();
|
|
308
|
+
try {
|
|
309
|
+
const report = await auditTranscripts(memoryDir);
|
|
310
|
+
assert.equal(report.mixedOtherDefault, false);
|
|
311
|
+
assert.equal(report.distinctSessions, 0);
|
|
312
|
+
assert.equal(report.movedEntries, 0);
|
|
313
|
+
assert.ok(report.summary.includes("nothing to migrate"));
|
|
314
|
+
} finally {
|
|
315
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
// promoteMemory — reason + authorization enforcement
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
|
|
323
|
+
/** In-memory storage provider for promotion tests. */
|
|
324
|
+
function makeMemoryStorageProvider(): PromotionStorageProvider & {
|
|
325
|
+
written: Array<{ namespace: string; memoryId: string; tags: string[]; lineage: string[] }>;
|
|
326
|
+
} {
|
|
327
|
+
const written: Array<{ namespace: string; memoryId: string; tags: string[]; lineage: string[] }> = [];
|
|
328
|
+
let counter = 0;
|
|
329
|
+
return {
|
|
330
|
+
written,
|
|
331
|
+
readMemory: async () => ({
|
|
332
|
+
category: "fact" as const,
|
|
333
|
+
content: "the sky is blue",
|
|
334
|
+
frontmatter: { confidence: 0.9, tags: ["test"] } as never,
|
|
335
|
+
}),
|
|
336
|
+
writePromotedMemory: async (namespace, memory) => {
|
|
337
|
+
counter += 1;
|
|
338
|
+
const memoryId = `promoted-${counter}`;
|
|
339
|
+
written.push({
|
|
340
|
+
namespace,
|
|
341
|
+
memoryId,
|
|
342
|
+
tags: memory.tags,
|
|
343
|
+
lineage: memory.lineage ?? [],
|
|
344
|
+
});
|
|
345
|
+
return memoryId;
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
test("promoteMemory rejects an empty reason", async () => {
|
|
351
|
+
const config = makeConfig();
|
|
352
|
+
const storage = makeMemoryStorageProvider();
|
|
353
|
+
await assert.rejects(
|
|
354
|
+
() =>
|
|
355
|
+
promoteMemory({
|
|
356
|
+
config,
|
|
357
|
+
sourceMemoryId: "mem-1",
|
|
358
|
+
sourceNamespace: "default",
|
|
359
|
+
targets: [{ kind: "explicit", namespace: "shared" }],
|
|
360
|
+
reason: " ",
|
|
361
|
+
actor: "operator",
|
|
362
|
+
storage,
|
|
363
|
+
}),
|
|
364
|
+
(err: unknown) => err instanceof AdminPromotionError && err.code === "reason_required",
|
|
365
|
+
);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test("promoteMemory rejects when no targets are supplied", async () => {
|
|
369
|
+
const config = makeConfig();
|
|
370
|
+
const storage = makeMemoryStorageProvider();
|
|
371
|
+
await assert.rejects(
|
|
372
|
+
() =>
|
|
373
|
+
promoteMemory({
|
|
374
|
+
config,
|
|
375
|
+
sourceMemoryId: "mem-1",
|
|
376
|
+
sourceNamespace: "default",
|
|
377
|
+
targets: [],
|
|
378
|
+
reason: "ok reason",
|
|
379
|
+
actor: "operator",
|
|
380
|
+
storage,
|
|
381
|
+
}),
|
|
382
|
+
(err: unknown) => err instanceof AdminPromotionError && err.code === "targets_required",
|
|
383
|
+
);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("promoteMemory writes into an explicit writable target namespace and records audit lineage", async () => {
|
|
387
|
+
const config = makeConfig({
|
|
388
|
+
namespacePolicies: [
|
|
389
|
+
{ name: "shared", readPrincipals: ["alice"], writePrincipals: ["alice"] },
|
|
390
|
+
],
|
|
391
|
+
});
|
|
392
|
+
const storage = makeMemoryStorageProvider();
|
|
393
|
+
const result = await promoteMemory({
|
|
394
|
+
config,
|
|
395
|
+
sourceMemoryId: "mem-1",
|
|
396
|
+
sourceNamespace: "default",
|
|
397
|
+
principal: "alice",
|
|
398
|
+
targets: [{ kind: "explicit", namespace: "shared" }],
|
|
399
|
+
reason: "promote for team visibility",
|
|
400
|
+
actor: "alice",
|
|
401
|
+
storage,
|
|
402
|
+
});
|
|
403
|
+
assert.equal(result.ok, true);
|
|
404
|
+
assert.equal(result.targets.length, 1);
|
|
405
|
+
assert.equal(result.targets[0]?.promoted, true);
|
|
406
|
+
assert.equal(result.targets[0]?.namespace, "shared");
|
|
407
|
+
assert.equal(result.audit.reason, "promote for team visibility");
|
|
408
|
+
assert.equal(result.audit.targets.length, 1);
|
|
409
|
+
assert.equal(result.audit.targets[0]?.promoted, true);
|
|
410
|
+
assert.equal(storage.written.length, 1);
|
|
411
|
+
assert.equal(storage.written[0]?.namespace, "shared");
|
|
412
|
+
assert.ok(storage.written[0]?.tags.includes("admin-promotion-explicit"));
|
|
413
|
+
assert.deepEqual(storage.written[0]?.lineage, ["mem-1"]);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("promoteMemory refuses an explicit target the principal cannot write", async () => {
|
|
417
|
+
const config = makeConfig({
|
|
418
|
+
namespacePolicies: [
|
|
419
|
+
{ name: "protected", readPrincipals: ["bob"], writePrincipals: ["bob"] },
|
|
420
|
+
],
|
|
421
|
+
});
|
|
422
|
+
const storage = makeMemoryStorageProvider();
|
|
423
|
+
const result = await promoteMemory({
|
|
424
|
+
config,
|
|
425
|
+
sourceMemoryId: "mem-1",
|
|
426
|
+
sourceNamespace: "default",
|
|
427
|
+
principal: "alice",
|
|
428
|
+
targets: [{ kind: "explicit", namespace: "protected" }],
|
|
429
|
+
reason: "attempt unauthorized promotion",
|
|
430
|
+
actor: "alice",
|
|
431
|
+
storage,
|
|
432
|
+
});
|
|
433
|
+
assert.equal(result.ok, false);
|
|
434
|
+
assert.equal(result.targets[0]?.authorized, false);
|
|
435
|
+
assert.equal(result.targets[0]?.promoted, false);
|
|
436
|
+
assert.equal(storage.written.length, 0);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test("promoteMemory throws source_not_found when the source memory is absent", async () => {
|
|
440
|
+
const config = makeConfig();
|
|
441
|
+
const storage: PromotionStorageProvider = {
|
|
442
|
+
readMemory: async () => null,
|
|
443
|
+
writePromotedMemory: async () => "unused",
|
|
444
|
+
};
|
|
445
|
+
await assert.rejects(
|
|
446
|
+
() =>
|
|
447
|
+
promoteMemory({
|
|
448
|
+
config,
|
|
449
|
+
sourceMemoryId: "missing",
|
|
450
|
+
sourceNamespace: "default",
|
|
451
|
+
targets: [{ kind: "explicit", namespace: "shared" }],
|
|
452
|
+
reason: "ok",
|
|
453
|
+
actor: "alice",
|
|
454
|
+
storage,
|
|
455
|
+
}),
|
|
456
|
+
(err: unknown) => err instanceof AdminPromotionError && err.code === "source_not_found",
|
|
457
|
+
);
|
|
458
|
+
});
|