@remnic/core 9.3.751 → 9.3.753
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 +194 -0
- package/dist/access-admin-ops-surface.js +128 -0
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +30 -30
- package/dist/access-cli.js +92 -92
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +38 -38
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +35 -35
- package/dist/access-operations-batch.js +32 -32
- package/dist/access-operations.d.ts +11 -11
- package/dist/access-operations.js +34 -34
- package/dist/access-recall-surface.d.ts +363 -0
- package/dist/access-recall-surface.js +128 -0
- package/dist/access-schema.d.ts +116 -116
- package/dist/access-schema.js +8 -8
- package/dist/{access-service-DmAEOJdJ.d.ts → access-service-MitduesD.d.ts} +75 -118
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +33 -29
- 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 +6 -6
- 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 +3 -3
- package/dist/briefing.js +10 -10
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-CZJSLEFG.js} +3 -3
- package/dist/capsule-crypto-CZJSLEFG.js.map +1 -0
- package/dist/{catalog-BpOcwHdE.d.ts → catalog-BEmNVRN0.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +15 -15
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-6SXNQFHB.js → chunk-243P7YYV.js} +7 -7
- package/dist/{chunk-2NLLXCJG.js → chunk-2BFLDH5F.js} +2 -2
- package/dist/{chunk-ZBVQ4ZM4.js → chunk-34THARMH.js} +17 -17
- package/dist/{chunk-SNEXVX3U.js → chunk-3HGZX26P.js} +21 -21
- package/dist/{chunk-5PZCDUJ6.js → chunk-3ZIKDH6C.js} +4 -4
- package/dist/{chunk-X7Y7WX73.js → chunk-437S3G37.js} +2 -2
- package/dist/{chunk-RS25QOKZ.js → chunk-64SE4X5G.js} +2 -2
- package/dist/{chunk-JBPKEARU.js → chunk-AU7Q3LSC.js} +4 -4
- package/dist/{chunk-6FNZ3NMS.js → chunk-AXBRVBT6.js} +2 -2
- package/dist/{chunk-W3BFT5OJ.js → chunk-BWOEYBRB.js} +2 -2
- package/dist/{chunk-UU32EAIY.js → chunk-C3IHRWIC.js} +7 -7
- package/dist/{chunk-DBRVU5PR.js → chunk-DKIZ4BUQ.js} +14 -14
- package/dist/{chunk-EEKDRAWX.js → chunk-DS4QM6I7.js} +4 -4
- package/dist/{chunk-WOX2UW4K.js → chunk-E3MRYDUQ.js} +2 -2
- package/dist/{chunk-K2R3DEEH.js → chunk-E6LENGYV.js} +2 -2
- package/dist/{chunk-CGZWGVQF.js → chunk-EEO4YWQ7.js} +9 -9
- package/dist/{chunk-ORAK3LNZ.js → chunk-ER3CCWK2.js} +2 -2
- package/dist/{chunk-TPLE6SNY.js → chunk-GWGWBK5Y.js} +7 -7
- package/dist/{chunk-BJMBJZ2Y.js → chunk-GWTFLF2P.js} +59 -59
- package/dist/chunk-GWTFLF2P.js.map +1 -0
- package/dist/{chunk-IKHKHADN.js → chunk-ITLJATQI.js} +5 -5
- package/dist/{chunk-E4PFDD3N.js → chunk-JES2O5Q6.js} +4 -4
- package/dist/{chunk-QOGXUNWL.js → chunk-JGPURIEH.js} +7 -7
- package/dist/{chunk-YGGXUNS4.js → chunk-KY4WOZ3P.js} +55 -55
- package/dist/{chunk-WA7FECSG.js → chunk-KYHGLDFU.js} +7 -7
- package/dist/{chunk-V5RVMULT.js → chunk-LZ73Y56P.js} +10 -10
- package/dist/{chunk-ARV3AUOM.js → chunk-M5QKGHCR.js} +2 -2
- package/dist/{chunk-SD34EK4Z.js → chunk-N3VE7OCY.js} +4 -4
- package/dist/{chunk-KQAFEZQX.js → chunk-OUXPVWTZ.js} +6 -6
- package/dist/{chunk-NNL4LJAD.js → chunk-OZMBT7N3.js} +5 -5
- package/dist/{chunk-RWZB6MAY.js → chunk-PN5AMCK2.js} +1065 -1176
- package/dist/chunk-PN5AMCK2.js.map +1 -0
- package/dist/{chunk-WWSXVOGY.js → chunk-QIRZI3C3.js} +2 -2
- package/dist/{chunk-B35L45HZ.js → chunk-TVISHYL5.js} +2 -2
- package/dist/{chunk-SQNED75R.js → chunk-V5UV7BOA.js} +1 -1
- package/dist/{chunk-7TWA7DKP.js → chunk-VVSV2QVY.js} +2 -2
- package/dist/{chunk-4NWIGAIC.js → chunk-XOPLMSVV.js} +2 -2
- package/dist/{chunk-K2JM4DZZ.js → chunk-YNBEHUZH.js} +2 -2
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-UETBEHBA.js → chunk-ZECLLIMX.js} +3442 -3085
- package/dist/chunk-ZECLLIMX.js.map +1 -0
- package/dist/{chunk-QRRBXD24.js → chunk-ZIFPFKB2.js} +7 -7
- package/dist/{chunk-HYI2GP3F.js → chunk-ZMHDSIEX.js} +7 -7
- package/dist/{cli-lmf5RGyQ.d.ts → cli-DqonYBZ0.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +64 -64
- package/dist/compounding/engine.d.ts +3 -3
- package/dist/compounding/engine.js +11 -11
- 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 +11 -11
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +12 -12
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/contradiction/index.js +2 -2
- package/dist/{contradiction-scan-3FJYWS3G.js → contradiction-scan-2SODRNUF.js} +3 -3
- package/dist/contradiction-scan-2SODRNUF.js.map +1 -0
- 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/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 +11 -11
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +6 -6
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +3 -3
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-4I3VDSYS.js → first-start-migration-DY6YCRC2.js} +2 -2
- package/dist/{forget-WYLR4ZFV.js → forget-OEE5OELK.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 +1284 -1284
- package/dist/index.js +148 -148
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +4 -4
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +8 -8
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +10 -10
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
- package/dist/maintenance/rebuild-memory-projection.js +11 -11
- 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 +13 -13
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +1 -1
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +12 -12
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +19 -19
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +15 -15
- package/dist/{orchestrator-D7bb8ZGp.d.ts → orchestrator-D7ZGOQLX.d.ts} +101 -180
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +92 -92
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-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-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +3 -3
- 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 +100 -100
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +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/secure-store/index.js +2 -2
- package/dist/{semantic-consolidation-CTfbVwCA.d.ts → semantic-consolidation-7Sndms2M.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +13 -13
- package/dist/semantic-rule-promotion.js +10 -10
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +10 -10
- 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.d.ts +1 -1
- package/dist/storage.js +9 -9
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +2 -2
- 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/{tier-stats-5XF4UBRQ.js → tier-stats-TG6GUZ6N.js} +4 -4
- 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 +3 -3
- package/dist/transfer/capsule-export.js +4 -4
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/transfer/types.d.ts +110 -110
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-D4NYDtXI.d.ts → types-DTEFxFuL.d.ts} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +10 -10
- package/package.json +2 -2
- package/src/access-admin-ops-surface.ts +828 -0
- package/src/access-recall-surface.ts +1552 -0
- package/src/access-service.ts +70 -1917
- package/src/orchestration/recall-entry.ts +290 -0
- package/src/orchestration/session-context.ts +407 -0
- package/src/orchestrator.ts +125 -462
- package/dist/chunk-BJMBJZ2Y.js.map +0 -1
- package/dist/chunk-RWZB6MAY.js.map +0 -1
- package/dist/chunk-UETBEHBA.js.map +0 -1
- /package/dist/{capsule-crypto-7FJQINUR.js.map → access-admin-ops-surface.js.map} +0 -0
- /package/dist/{contradiction-scan-3FJYWS3G.js.map → access-recall-surface.js.map} +0 -0
- /package/dist/{chunk-6SXNQFHB.js.map → chunk-243P7YYV.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-2BFLDH5F.js.map} +0 -0
- /package/dist/{chunk-ZBVQ4ZM4.js.map → chunk-34THARMH.js.map} +0 -0
- /package/dist/{chunk-SNEXVX3U.js.map → chunk-3HGZX26P.js.map} +0 -0
- /package/dist/{chunk-5PZCDUJ6.js.map → chunk-3ZIKDH6C.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-437S3G37.js.map} +0 -0
- /package/dist/{chunk-RS25QOKZ.js.map → chunk-64SE4X5G.js.map} +0 -0
- /package/dist/{chunk-JBPKEARU.js.map → chunk-AU7Q3LSC.js.map} +0 -0
- /package/dist/{chunk-6FNZ3NMS.js.map → chunk-AXBRVBT6.js.map} +0 -0
- /package/dist/{chunk-W3BFT5OJ.js.map → chunk-BWOEYBRB.js.map} +0 -0
- /package/dist/{chunk-UU32EAIY.js.map → chunk-C3IHRWIC.js.map} +0 -0
- /package/dist/{chunk-DBRVU5PR.js.map → chunk-DKIZ4BUQ.js.map} +0 -0
- /package/dist/{chunk-EEKDRAWX.js.map → chunk-DS4QM6I7.js.map} +0 -0
- /package/dist/{chunk-WOX2UW4K.js.map → chunk-E3MRYDUQ.js.map} +0 -0
- /package/dist/{chunk-K2R3DEEH.js.map → chunk-E6LENGYV.js.map} +0 -0
- /package/dist/{chunk-CGZWGVQF.js.map → chunk-EEO4YWQ7.js.map} +0 -0
- /package/dist/{chunk-ORAK3LNZ.js.map → chunk-ER3CCWK2.js.map} +0 -0
- /package/dist/{chunk-TPLE6SNY.js.map → chunk-GWGWBK5Y.js.map} +0 -0
- /package/dist/{chunk-IKHKHADN.js.map → chunk-ITLJATQI.js.map} +0 -0
- /package/dist/{chunk-E4PFDD3N.js.map → chunk-JES2O5Q6.js.map} +0 -0
- /package/dist/{chunk-QOGXUNWL.js.map → chunk-JGPURIEH.js.map} +0 -0
- /package/dist/{chunk-YGGXUNS4.js.map → chunk-KY4WOZ3P.js.map} +0 -0
- /package/dist/{chunk-WA7FECSG.js.map → chunk-KYHGLDFU.js.map} +0 -0
- /package/dist/{chunk-V5RVMULT.js.map → chunk-LZ73Y56P.js.map} +0 -0
- /package/dist/{chunk-ARV3AUOM.js.map → chunk-M5QKGHCR.js.map} +0 -0
- /package/dist/{chunk-SD34EK4Z.js.map → chunk-N3VE7OCY.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-OUXPVWTZ.js.map} +0 -0
- /package/dist/{chunk-NNL4LJAD.js.map → chunk-OZMBT7N3.js.map} +0 -0
- /package/dist/{chunk-WWSXVOGY.js.map → chunk-QIRZI3C3.js.map} +0 -0
- /package/dist/{chunk-B35L45HZ.js.map → chunk-TVISHYL5.js.map} +0 -0
- /package/dist/{chunk-SQNED75R.js.map → chunk-V5UV7BOA.js.map} +0 -0
- /package/dist/{chunk-7TWA7DKP.js.map → chunk-VVSV2QVY.js.map} +0 -0
- /package/dist/{chunk-4NWIGAIC.js.map → chunk-XOPLMSVV.js.map} +0 -0
- /package/dist/{chunk-K2JM4DZZ.js.map → chunk-YNBEHUZH.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-QRRBXD24.js.map → chunk-ZIFPFKB2.js.map} +0 -0
- /package/dist/{chunk-HYI2GP3F.js.map → chunk-ZMHDSIEX.js.map} +0 -0
- /package/dist/{first-start-migration-4I3VDSYS.js.map → first-start-migration-DY6YCRC2.js.map} +0 -0
- /package/dist/{forget-WYLR4ZFV.js.map → forget-OEE5OELK.js.map} +0 -0
- /package/dist/{tier-stats-5XF4UBRQ.js.map → tier-stats-TG6GUZ6N.js.map} +0 -0
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Access admin/ops read-surface (extracted from access-service.ts;
|
|
3
|
+
* god-file decomposition, #1526 playbook: verbatim move + live selfDeps
|
|
4
|
+
* wiring).
|
|
5
|
+
*
|
|
6
|
+
* Owns the operator-facing read/maintenance surfaces of the access
|
|
7
|
+
* layer: review queue listing, governance runs, conversation-index
|
|
8
|
+
* updates, profiling reports, graph snapshots, capsule listing, dreams
|
|
9
|
+
* pipeline runs, and admin memory promotion.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as nodeFs from "node:fs/promises";
|
|
13
|
+
import * as nodePath from "node:path";
|
|
14
|
+
import { AdminPromotionError, type MemoryPromotionTargetKind, type PromotionStorageProvider, promoteMemory } from "./admin/admin-surfaces.js";
|
|
15
|
+
import { resolveGraphConstructionCapabilities, resolveIndexingCapabilities, resolveNamespaceCapabilities } from "./capabilities.js";
|
|
16
|
+
import { type CapsuleListEntry, defaultCapsulesDir } from "./capsule-cli.js";
|
|
17
|
+
import { resolveCodingNamespaceOverlay } from "./coding/coding-namespace.js";
|
|
18
|
+
import { type GraphSnapshotNodeMetadata, type GraphSnapshotRequest, type GraphSnapshotResponse, buildGraphSnapshot } from "./graph-snapshot.js";
|
|
19
|
+
import { log } from "./logger.js";
|
|
20
|
+
import { buildQualityScore, groupActionsByStatus, listMemoryGovernanceRuns, readMemoryGovernanceRunArtifact, runMemoryGovernance } from "./maintenance/memory-governance.js";
|
|
21
|
+
import { resolveScopeProfilePlan } from "./namespaces/scope-profiles.js";
|
|
22
|
+
import type { Orchestrator } from "./orchestrator.js";
|
|
23
|
+
import { formatProfileTraceAscii } from "./profiling.js";
|
|
24
|
+
import { resolveScopePlan } from "./scopes/scope-plan.js";
|
|
25
|
+
import {
|
|
26
|
+
EngramAccessInputError,
|
|
27
|
+
buildProjectedGovernanceProposedActions,
|
|
28
|
+
hasGroupedGovernanceActions,
|
|
29
|
+
type AccessProfilingReportRequest,
|
|
30
|
+
type AccessProfilingReportResponse,
|
|
31
|
+
type EngramAccessCapsuleListResponse,
|
|
32
|
+
type EngramAccessReviewQueueResponse,
|
|
33
|
+
} from "./access-service.js";
|
|
34
|
+
|
|
35
|
+
export interface AccessAdminOpsSurfaceDeps {
|
|
36
|
+
readonly orchestrator: Orchestrator;
|
|
37
|
+
resolveReadableNamespace(namespace: string | undefined, principal?: string): string;
|
|
38
|
+
writableNamespaceFor(
|
|
39
|
+
namespace: string | undefined,
|
|
40
|
+
sessionKey: string | undefined,
|
|
41
|
+
authenticatedPrincipal?: string,
|
|
42
|
+
): string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class AccessAdminOpsSurface {
|
|
46
|
+
constructor(
|
|
47
|
+
private readonly deps: AccessAdminOpsSurfaceDeps,
|
|
48
|
+
) {}
|
|
49
|
+
|
|
50
|
+
async reviewQueue(runId?: string, namespace?: string, principal?: string): Promise<EngramAccessReviewQueueResponse> {
|
|
51
|
+
const resolvedNamespace = this.deps.resolveReadableNamespace(namespace, principal);
|
|
52
|
+
const storage = await this.deps.orchestrator.getStorage(resolvedNamespace);
|
|
53
|
+
const projected = await storage.getProjectedGovernanceRecord();
|
|
54
|
+
if (projected && (!runId || projected.runId === runId.trim())) {
|
|
55
|
+
const projectedAppliedActions = projected.appliedActionRows.map((row) => ({
|
|
56
|
+
action: row.action,
|
|
57
|
+
memoryId: row.memoryId,
|
|
58
|
+
reasonCode: row.reasonCode,
|
|
59
|
+
beforeStatus: row.beforeStatus,
|
|
60
|
+
afterStatus: row.afterStatus,
|
|
61
|
+
originalPath: row.originalPath,
|
|
62
|
+
currentPath: row.currentPath,
|
|
63
|
+
})) as Awaited<
|
|
64
|
+
ReturnType<typeof readMemoryGovernanceRunArtifact>
|
|
65
|
+
>["appliedActions"];
|
|
66
|
+
const projectedProposedActions = await buildProjectedGovernanceProposedActions(storage, projected);
|
|
67
|
+
const projectedArtifact = await (async () => {
|
|
68
|
+
try {
|
|
69
|
+
return await readMemoryGovernanceRunArtifact(storage.dir, projected.runId);
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
})();
|
|
74
|
+
const metrics = projected.metrics as Awaited<ReturnType<typeof readMemoryGovernanceRunArtifact>>["metrics"];
|
|
75
|
+
const fallbackTransitionReport = {
|
|
76
|
+
proposed: groupActionsByStatus(projectedProposedActions),
|
|
77
|
+
applied: groupActionsByStatus(projectedAppliedActions),
|
|
78
|
+
};
|
|
79
|
+
const transitionReport = projectedArtifact?.transitionReport
|
|
80
|
+
? {
|
|
81
|
+
proposed:
|
|
82
|
+
hasGroupedGovernanceActions(projectedArtifact.transitionReport.proposed) || projectedProposedActions.length === 0
|
|
83
|
+
? projectedArtifact.transitionReport.proposed
|
|
84
|
+
: fallbackTransitionReport.proposed,
|
|
85
|
+
applied:
|
|
86
|
+
hasGroupedGovernanceActions(projectedArtifact.transitionReport.applied) || projectedAppliedActions.length === 0
|
|
87
|
+
? projectedArtifact.transitionReport.applied
|
|
88
|
+
: fallbackTransitionReport.applied,
|
|
89
|
+
}
|
|
90
|
+
: fallbackTransitionReport;
|
|
91
|
+
const qualityScore = projectedArtifact?.qualityScore ?? metrics?.qualityScore ?? buildQualityScore(metrics?.reviewReasons ?? {
|
|
92
|
+
exact_duplicate: 0,
|
|
93
|
+
semantic_duplicate_candidate: 0,
|
|
94
|
+
disputed_memory: 0,
|
|
95
|
+
speculative_low_confidence: 0,
|
|
96
|
+
archive_candidate: 0,
|
|
97
|
+
explicit_capture_review: 0,
|
|
98
|
+
malformed_import: 0,
|
|
99
|
+
});
|
|
100
|
+
const effectiveMetrics = metrics ? { ...metrics, qualityScore: metrics.qualityScore ?? qualityScore } : metrics;
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
found: true,
|
|
104
|
+
namespace: resolvedNamespace,
|
|
105
|
+
runId: projected.runId,
|
|
106
|
+
summary: projected.summary as Awaited<ReturnType<typeof readMemoryGovernanceRunArtifact>>["summary"],
|
|
107
|
+
metrics: effectiveMetrics,
|
|
108
|
+
qualityScore,
|
|
109
|
+
reviewQueue: projected.reviewQueueRows.map((row) => ({
|
|
110
|
+
entryId: row.entryId,
|
|
111
|
+
memoryId: row.memoryId,
|
|
112
|
+
path: row.path,
|
|
113
|
+
reasonCode: row.reasonCode,
|
|
114
|
+
severity: row.severity,
|
|
115
|
+
suggestedAction: row.suggestedAction,
|
|
116
|
+
suggestedStatus: row.suggestedStatus,
|
|
117
|
+
relatedMemoryIds: row.relatedMemoryIds,
|
|
118
|
+
})) as Awaited<
|
|
119
|
+
ReturnType<typeof readMemoryGovernanceRunArtifact>
|
|
120
|
+
>["reviewQueue"],
|
|
121
|
+
appliedActions: projectedAppliedActions,
|
|
122
|
+
transitionReport,
|
|
123
|
+
report: projected.report,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const resolvedRunId = runId?.trim() || (await listMemoryGovernanceRuns(storage.dir))[0];
|
|
128
|
+
if (!resolvedRunId) return { found: false, namespace: resolvedNamespace };
|
|
129
|
+
const artifact = await readMemoryGovernanceRunArtifact(storage.dir, resolvedRunId);
|
|
130
|
+
return {
|
|
131
|
+
found: true,
|
|
132
|
+
namespace: resolvedNamespace,
|
|
133
|
+
runId: resolvedRunId,
|
|
134
|
+
summary: artifact.summary,
|
|
135
|
+
metrics: artifact.metrics,
|
|
136
|
+
qualityScore: artifact.qualityScore,
|
|
137
|
+
reviewQueue: artifact.reviewQueue,
|
|
138
|
+
appliedActions: artifact.appliedActions,
|
|
139
|
+
transitionReport: artifact.transitionReport,
|
|
140
|
+
report: artifact.report,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async governanceRun(
|
|
145
|
+
request: {
|
|
146
|
+
namespace?: string;
|
|
147
|
+
mode?: "shadow" | "apply";
|
|
148
|
+
recentDays?: number;
|
|
149
|
+
maxMemories?: number;
|
|
150
|
+
batchSize?: number;
|
|
151
|
+
authenticatedPrincipal?: string;
|
|
152
|
+
},
|
|
153
|
+
principal?: string,
|
|
154
|
+
): Promise<{
|
|
155
|
+
namespace: string;
|
|
156
|
+
runId: string;
|
|
157
|
+
traceId: string;
|
|
158
|
+
mode: "shadow" | "apply";
|
|
159
|
+
reviewQueueCount: number;
|
|
160
|
+
proposedActionCount: number;
|
|
161
|
+
appliedActionCount: number;
|
|
162
|
+
summaryPath: string;
|
|
163
|
+
reportPath: string;
|
|
164
|
+
}> {
|
|
165
|
+
const deepSleep = this.deps.orchestrator.config.dreamsPhases.deepSleep;
|
|
166
|
+
if (deepSleep.enabled === false && deepSleep.enabledExplicitlySet === true) {
|
|
167
|
+
throw new Error("memory governance is disabled by dreams.phases.deepSleep.enabled=false");
|
|
168
|
+
}
|
|
169
|
+
const resolvedNamespace = this.deps.writableNamespaceFor(
|
|
170
|
+
request.namespace,
|
|
171
|
+
undefined,
|
|
172
|
+
request.authenticatedPrincipal ?? principal,
|
|
173
|
+
);
|
|
174
|
+
const storage = await this.deps.orchestrator.getStorage(resolvedNamespace);
|
|
175
|
+
const mode = request.mode === "apply" ? "apply" : "shadow";
|
|
176
|
+
const boundedBatchSize =
|
|
177
|
+
typeof request.batchSize === "number" && Number.isFinite(request.batchSize)
|
|
178
|
+
? Math.max(1, Math.floor(request.batchSize))
|
|
179
|
+
: undefined;
|
|
180
|
+
const result = await runMemoryGovernance({
|
|
181
|
+
memoryDir: storage.dir,
|
|
182
|
+
mode,
|
|
183
|
+
recentDays:
|
|
184
|
+
typeof request.recentDays === "number" && Number.isFinite(request.recentDays)
|
|
185
|
+
? Math.max(1, Math.floor(request.recentDays))
|
|
186
|
+
: undefined,
|
|
187
|
+
maxMemories:
|
|
188
|
+
typeof request.maxMemories === "number" && Number.isFinite(request.maxMemories)
|
|
189
|
+
? Math.max(1, Math.floor(request.maxMemories))
|
|
190
|
+
: undefined,
|
|
191
|
+
batchSize: boundedBatchSize,
|
|
192
|
+
});
|
|
193
|
+
if (mode === "apply") {
|
|
194
|
+
try {
|
|
195
|
+
await this.deps.orchestrator.processEntitySynthesisQueue(
|
|
196
|
+
resolvedNamespace,
|
|
197
|
+
Math.min(boundedBatchSize ?? 5, 5),
|
|
198
|
+
);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
log.debug(`governanceRun: entity synthesis refresh failed after governance apply: ${error}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
namespace: resolvedNamespace,
|
|
206
|
+
runId: result.runId,
|
|
207
|
+
traceId: result.traceId,
|
|
208
|
+
mode: result.mode,
|
|
209
|
+
reviewQueueCount: result.reviewQueue.length,
|
|
210
|
+
proposedActionCount: result.proposedActions.length,
|
|
211
|
+
appliedActionCount: result.appliedActions.length,
|
|
212
|
+
summaryPath: result.summaryPath,
|
|
213
|
+
reportPath: result.reportPath,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async conversationIndexUpdate(
|
|
218
|
+
request: {
|
|
219
|
+
sessionKey?: string;
|
|
220
|
+
hours?: number;
|
|
221
|
+
embed?: boolean;
|
|
222
|
+
} = {},
|
|
223
|
+
): Promise<{
|
|
224
|
+
enabled: boolean;
|
|
225
|
+
sessionKey?: string;
|
|
226
|
+
sessions: number;
|
|
227
|
+
chunks: number;
|
|
228
|
+
skipped: number;
|
|
229
|
+
skippedSessionKeys: string[];
|
|
230
|
+
embeddedRuns: number;
|
|
231
|
+
reason?: string;
|
|
232
|
+
retryAfterMs?: number;
|
|
233
|
+
}> {
|
|
234
|
+
if (!resolveIndexingCapabilities(this.deps.orchestrator.config).conversationIndex) {
|
|
235
|
+
return {
|
|
236
|
+
enabled: false,
|
|
237
|
+
sessions: 0,
|
|
238
|
+
chunks: 0,
|
|
239
|
+
skipped: 0,
|
|
240
|
+
skippedSessionKeys: [],
|
|
241
|
+
embeddedRuns: 0,
|
|
242
|
+
reason: "disabled",
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const hours =
|
|
247
|
+
typeof request.hours === "number" && Number.isFinite(request.hours)
|
|
248
|
+
? Math.max(1, Math.floor(request.hours))
|
|
249
|
+
: 24;
|
|
250
|
+
|
|
251
|
+
let sessionKey: string | undefined;
|
|
252
|
+
if (request.sessionKey !== undefined) {
|
|
253
|
+
if (typeof request.sessionKey !== "string" || request.sessionKey.trim().length === 0) {
|
|
254
|
+
throw new EngramAccessInputError("sessionKey must be a non-empty string when provided");
|
|
255
|
+
}
|
|
256
|
+
sessionKey = request.sessionKey.trim();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (sessionKey) {
|
|
260
|
+
const result = await this.deps.orchestrator.updateConversationIndex(
|
|
261
|
+
sessionKey,
|
|
262
|
+
hours,
|
|
263
|
+
{ embed: request.embed },
|
|
264
|
+
);
|
|
265
|
+
return {
|
|
266
|
+
enabled: true,
|
|
267
|
+
sessionKey,
|
|
268
|
+
sessions: 1,
|
|
269
|
+
chunks: result.chunks,
|
|
270
|
+
skipped: result.skipped ? 1 : 0,
|
|
271
|
+
skippedSessionKeys: result.skipped ? [sessionKey] : [],
|
|
272
|
+
embeddedRuns: result.embedded ? 1 : 0,
|
|
273
|
+
reason: result.reason,
|
|
274
|
+
retryAfterMs: result.retryAfterMs,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const sessionKeys = await this.deps.orchestrator.transcript.listSessionKeys();
|
|
279
|
+
let chunks = 0;
|
|
280
|
+
let skipped = 0;
|
|
281
|
+
const skippedSessionKeys: string[] = [];
|
|
282
|
+
let embeddedRuns = 0;
|
|
283
|
+
|
|
284
|
+
for (const sessionKey of sessionKeys) {
|
|
285
|
+
const result = await this.deps.orchestrator.updateConversationIndex(
|
|
286
|
+
sessionKey,
|
|
287
|
+
hours,
|
|
288
|
+
{ embed: request.embed },
|
|
289
|
+
);
|
|
290
|
+
chunks += result.chunks;
|
|
291
|
+
if (result.skipped) {
|
|
292
|
+
skipped += 1;
|
|
293
|
+
skippedSessionKeys.push(sessionKey);
|
|
294
|
+
}
|
|
295
|
+
if (result.embedded) {
|
|
296
|
+
embeddedRuns += 1;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
enabled: true,
|
|
302
|
+
sessions: sessionKeys.length,
|
|
303
|
+
chunks,
|
|
304
|
+
skipped,
|
|
305
|
+
skippedSessionKeys,
|
|
306
|
+
embeddedRuns,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async profilingReport(
|
|
311
|
+
request: AccessProfilingReportRequest = {},
|
|
312
|
+
): Promise<AccessProfilingReportResponse> {
|
|
313
|
+
const profiler = this.deps.orchestrator.profiler;
|
|
314
|
+
if (!profiler.isEnabled) {
|
|
315
|
+
return {
|
|
316
|
+
enabled: false,
|
|
317
|
+
reason: "disabled",
|
|
318
|
+
message: "Profiling is disabled. Set profilingEnabled: true in your plugin config to enable.",
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const format = request.format ?? "ascii";
|
|
323
|
+
if (format !== "ascii" && format !== "json") {
|
|
324
|
+
throw new EngramAccessInputError("format must be one of: ascii, json");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const limit = request.limit ?? 5;
|
|
328
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 20) {
|
|
329
|
+
throw new EngramAccessInputError("limit must be an integer between 1 and 20");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const traces = profiler.getRecentTraces(limit);
|
|
333
|
+
const stats = profiler.getStats();
|
|
334
|
+
const bottleneck = profiler.identifyBottleneck();
|
|
335
|
+
|
|
336
|
+
if (format === "json") {
|
|
337
|
+
return {
|
|
338
|
+
enabled: true,
|
|
339
|
+
format,
|
|
340
|
+
traces,
|
|
341
|
+
stats,
|
|
342
|
+
bottleneck,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const lines: string[] = [];
|
|
347
|
+
lines.push("Engram Profiling Report");
|
|
348
|
+
lines.push("=".repeat(60));
|
|
349
|
+
lines.push("");
|
|
350
|
+
|
|
351
|
+
type BucketEntry = { count: number; avgMs: number; p50Ms: number; p95Ms: number; maxMs: number };
|
|
352
|
+
const allBuckets: Array<[string, Record<string, BucketEntry>]> = [
|
|
353
|
+
["byKind", stats.byKind],
|
|
354
|
+
["bySpan", stats.bySpan],
|
|
355
|
+
];
|
|
356
|
+
const hasStats = allBuckets.some(([, entries]) => Object.keys(entries).length > 0);
|
|
357
|
+
if (hasStats) {
|
|
358
|
+
lines.push("Aggregate Stats (all retained traces):");
|
|
359
|
+
for (const [bucket, entries] of allBuckets) {
|
|
360
|
+
for (const [key, summary] of Object.entries(entries)) {
|
|
361
|
+
lines.push(
|
|
362
|
+
` ${bucket}/${key}: avg=${summary.avgMs}ms p50=${summary.p50Ms}ms p95=${summary.p95Ms}ms max=${summary.maxMs}ms (n=${summary.count})`,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
lines.push("");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (bottleneck) {
|
|
370
|
+
lines.push(`Bottleneck: ${bottleneck}`);
|
|
371
|
+
lines.push("");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (traces.length === 0) {
|
|
375
|
+
lines.push("No traces recorded yet. Trigger a recall or extraction to see timing data.");
|
|
376
|
+
} else {
|
|
377
|
+
for (const trace of traces) {
|
|
378
|
+
lines.push(formatProfileTraceAscii(trace));
|
|
379
|
+
lines.push("");
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
enabled: true,
|
|
385
|
+
format,
|
|
386
|
+
report: lines.join("\n"),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Read-only graph snapshot for the admin pane (issue #691 PR 2/5).
|
|
392
|
+
*
|
|
393
|
+
* Reads adjacency from the JSONL edge store written by `GraphIndex` and
|
|
394
|
+
* resolves node metadata via the namespaced storage manager. Namespace
|
|
395
|
+
* resolution mirrors the read-side path used by `recall` /
|
|
396
|
+
* `procedureStats`, so multi-principal deployments can't leak edges from
|
|
397
|
+
* a peer namespace (CLAUDE.md rule 42).
|
|
398
|
+
*/
|
|
399
|
+
async graphSnapshot(
|
|
400
|
+
request: GraphSnapshotRequest & { namespace?: string },
|
|
401
|
+
authenticatedPrincipal?: string,
|
|
402
|
+
): Promise<GraphSnapshotResponse> {
|
|
403
|
+
const namespace = this.deps.resolveReadableNamespace(
|
|
404
|
+
request.namespace,
|
|
405
|
+
authenticatedPrincipal,
|
|
406
|
+
);
|
|
407
|
+
const storage = await this.deps.orchestrator.getStorage(namespace);
|
|
408
|
+
const cfg = this.deps.orchestrator.config;
|
|
409
|
+
const graphCaps = resolveGraphConstructionCapabilities(cfg);
|
|
410
|
+
// Canonicalize the storage root once — through `realpath` so that any
|
|
411
|
+
// symlink in the namespace root path itself is resolved before we
|
|
412
|
+
// compare children against it. This is required because
|
|
413
|
+
// `GraphEdge.from` / `to` are JSONL-parsed strings — a malformed edge
|
|
414
|
+
// with an absolute path, a `..` segment, OR a symlink that resolves
|
|
415
|
+
// to a file outside the namespace would otherwise read a memory file
|
|
416
|
+
// from a peer namespace, leaking metadata across tenants
|
|
417
|
+
// (codex P1 + follow-up on PR #734; CLAUDE.md rule 42).
|
|
418
|
+
let namespaceRootReal: string;
|
|
419
|
+
try {
|
|
420
|
+
namespaceRootReal = await nodeFs.realpath(storage.dir);
|
|
421
|
+
} catch {
|
|
422
|
+
// If the namespace root itself doesn't exist on disk yet (fresh
|
|
423
|
+
// install with no memories), fall back to the lexical resolve so
|
|
424
|
+
// the snapshot can still return an empty result rather than
|
|
425
|
+
// throwing. No symlink can resolve through a missing path, so
|
|
426
|
+
// this fallback is safe — every candidate we see will fail the
|
|
427
|
+
// realpath step below and surface as `null`.
|
|
428
|
+
namespaceRootReal = nodePath.resolve(storage.dir);
|
|
429
|
+
}
|
|
430
|
+
const namespaceRootWithSep = namespaceRootReal.endsWith(nodePath.sep)
|
|
431
|
+
? namespaceRootReal
|
|
432
|
+
: namespaceRootReal + nodePath.sep;
|
|
433
|
+
const loadNode = async (relPath: string): Promise<GraphSnapshotNodeMetadata | null> => {
|
|
434
|
+
// `GraphEdge.from` / `to` are storage-relative paths; resolve against
|
|
435
|
+
// the namespaced storage root so the metadata read honors namespace
|
|
436
|
+
// boundaries even when the same memory id exists in multiple
|
|
437
|
+
// namespaces.
|
|
438
|
+
//
|
|
439
|
+
// Three-stage guard:
|
|
440
|
+
// 1. Reject absolute paths up front — only relative endpoints are
|
|
441
|
+
// ever produced by the writer, so anything else is malformed.
|
|
442
|
+
// 2. Lexical containment check on the resolved path. This catches
|
|
443
|
+
// `..` traversals before we touch the filesystem.
|
|
444
|
+
// 3. `fs.realpath` containment check — resolves symlinks so an
|
|
445
|
+
// in-namespace path that *points* at an out-of-namespace file
|
|
446
|
+
// is still rejected. Without this step a symlinked endpoint
|
|
447
|
+
// could leak a peer namespace's frontmatter.
|
|
448
|
+
// Bad paths surface a length-only warning (never echo the offending
|
|
449
|
+
// segments — those would themselves cross namespace boundaries
|
|
450
|
+
// through the log surface) and fall through to a `null` metadata
|
|
451
|
+
// result.
|
|
452
|
+
if (nodePath.isAbsolute(relPath)) {
|
|
453
|
+
log.warn(
|
|
454
|
+
`graphSnapshot: rejected absolute edge endpoint (len=${relPath.length}) `
|
|
455
|
+
+ `outside namespace root`,
|
|
456
|
+
);
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
const candidate = nodePath.resolve(namespaceRootReal, relPath);
|
|
460
|
+
if (candidate !== namespaceRootReal && !candidate.startsWith(namespaceRootWithSep)) {
|
|
461
|
+
log.warn(
|
|
462
|
+
`graphSnapshot: rejected traversing edge endpoint (len=${relPath.length}) `
|
|
463
|
+
+ `outside namespace root`,
|
|
464
|
+
);
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
let canonical: string;
|
|
468
|
+
try {
|
|
469
|
+
canonical = await nodeFs.realpath(candidate);
|
|
470
|
+
} catch {
|
|
471
|
+
// Missing file — `readMemoryByPath` will return null too. We
|
|
472
|
+
// intentionally still call it so callers see a consistent
|
|
473
|
+
// "unknown" result rather than special-casing missing edges.
|
|
474
|
+
canonical = candidate;
|
|
475
|
+
}
|
|
476
|
+
if (canonical !== namespaceRootReal && !canonical.startsWith(namespaceRootWithSep)) {
|
|
477
|
+
log.warn(
|
|
478
|
+
`graphSnapshot: rejected symlinked edge endpoint (len=${relPath.length}) `
|
|
479
|
+
+ `that resolved outside namespace root`,
|
|
480
|
+
);
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
// Both `canonical` (realpath of candidate) and `namespaceRootReal`
|
|
484
|
+
// (realpath of storage.dir) are fully resolved here, so the
|
|
485
|
+
// containment check above is comparing apples-to-apples even when
|
|
486
|
+
// storage.dir (= storage.baseDir) is itself a symlink to the real
|
|
487
|
+
// directory. Pass `canonical` — not the pre-realpath `candidate` —
|
|
488
|
+
// so the storage read also uses the stable real path.
|
|
489
|
+
const memory = await storage.readMemoryByPath(canonical);
|
|
490
|
+
if (!memory) return null;
|
|
491
|
+
const fm = memory.frontmatter;
|
|
492
|
+
return {
|
|
493
|
+
category: fm.category ?? "unknown",
|
|
494
|
+
label: fm.id ?? nodePath.basename(canonical, nodePath.extname(canonical)),
|
|
495
|
+
updated: fm.updated,
|
|
496
|
+
};
|
|
497
|
+
};
|
|
498
|
+
// Use the realpath-resolved namespace root for the edge-file read so
|
|
499
|
+
// the JSONL location is stable whether storage.dir is a direct path
|
|
500
|
+
// or a symlink. namespaceRootReal was computed via `fs.realpath`
|
|
501
|
+
// above; using it here keeps both the graph-file I/O and the loadNode
|
|
502
|
+
// containment check on the same resolved base path.
|
|
503
|
+
return buildGraphSnapshot({
|
|
504
|
+
memoryDir: namespaceRootReal,
|
|
505
|
+
graphConfig: {
|
|
506
|
+
entityGraph: graphCaps.entityGraph,
|
|
507
|
+
timeGraph: graphCaps.timeGraph,
|
|
508
|
+
causalGraph: graphCaps.causalGraph,
|
|
509
|
+
},
|
|
510
|
+
request: {
|
|
511
|
+
limit: request.limit,
|
|
512
|
+
since: request.since,
|
|
513
|
+
focusNodeId: request.focusNodeId,
|
|
514
|
+
categories: request.categories,
|
|
515
|
+
},
|
|
516
|
+
loadNode,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* List capsule archives in the namespace-scoped capsule store.
|
|
522
|
+
*
|
|
523
|
+
* MCP uses this access-layer method instead of reading arbitrary paths so
|
|
524
|
+
* capsule discovery remains bound to the same namespace ACLs as export and
|
|
525
|
+
* import.
|
|
526
|
+
*/
|
|
527
|
+
async capsuleList(options?: {
|
|
528
|
+
namespace?: string;
|
|
529
|
+
principal?: string;
|
|
530
|
+
}): Promise<EngramAccessCapsuleListResponse> {
|
|
531
|
+
const resolvedNamespace = this.deps.resolveReadableNamespace(options?.namespace, options?.principal);
|
|
532
|
+
const storage = await this.deps.orchestrator.getStorage(resolvedNamespace);
|
|
533
|
+
const capsulesDir = defaultCapsulesDir(storage.dir);
|
|
534
|
+
let dirEntries: import("node:fs").Dirent[];
|
|
535
|
+
try {
|
|
536
|
+
const capsulesDirStat = await nodeFs.lstat(capsulesDir);
|
|
537
|
+
if (capsulesDirStat.isSymbolicLink()) {
|
|
538
|
+
throw new EngramAccessInputError("capsule list failed: capsule store directory must not be a symlink");
|
|
539
|
+
}
|
|
540
|
+
if (!capsulesDirStat.isDirectory()) {
|
|
541
|
+
throw new EngramAccessInputError("capsule list failed: capsule store path must be a directory");
|
|
542
|
+
}
|
|
543
|
+
dirEntries = await nodeFs.readdir(capsulesDir, { withFileTypes: true });
|
|
544
|
+
} catch (err) {
|
|
545
|
+
const code = typeof err === "object" && err !== null && "code" in err
|
|
546
|
+
? (err as { code?: unknown }).code
|
|
547
|
+
: undefined;
|
|
548
|
+
if (code === "ENOENT") {
|
|
549
|
+
return { namespace: resolvedNamespace, capsulesDir, capsules: [] };
|
|
550
|
+
}
|
|
551
|
+
throw err;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const archiveNames = dirEntries
|
|
555
|
+
.filter(
|
|
556
|
+
(entry) =>
|
|
557
|
+
entry.isFile() &&
|
|
558
|
+
(entry.name.endsWith(".capsule.json.gz") ||
|
|
559
|
+
entry.name.endsWith(".capsule.json.gz.enc")),
|
|
560
|
+
)
|
|
561
|
+
.map((entry) => entry.name)
|
|
562
|
+
.sort();
|
|
563
|
+
|
|
564
|
+
const capsules: CapsuleListEntry[] = [];
|
|
565
|
+
for (const archiveName of archiveNames) {
|
|
566
|
+
const archivePath = nodePath.join(capsulesDir, archiveName);
|
|
567
|
+
const id = archiveName
|
|
568
|
+
.replace(/\.capsule\.json\.gz\.enc$/, "")
|
|
569
|
+
.replace(/\.capsule\.json\.gz$/, "");
|
|
570
|
+
const manifestPath = nodePath.join(capsulesDir, `${id}.manifest.json`);
|
|
571
|
+
|
|
572
|
+
let createdAt: string | null = null;
|
|
573
|
+
let pluginVersion: string | null = null;
|
|
574
|
+
let fileCount: number | null = null;
|
|
575
|
+
let description: string | null = null;
|
|
576
|
+
let manifestPathOrNull: string | null = manifestPath;
|
|
577
|
+
|
|
578
|
+
try {
|
|
579
|
+
const manifestStat = await nodeFs.lstat(manifestPath);
|
|
580
|
+
if (manifestStat.isSymbolicLink() || !manifestStat.isFile()) {
|
|
581
|
+
capsules.push({
|
|
582
|
+
id,
|
|
583
|
+
archivePath,
|
|
584
|
+
manifestPath: manifestPathOrNull,
|
|
585
|
+
createdAt,
|
|
586
|
+
pluginVersion,
|
|
587
|
+
fileCount,
|
|
588
|
+
description,
|
|
589
|
+
});
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
const raw = await nodeFs.readFile(manifestPath, "utf-8");
|
|
593
|
+
const sidecar = JSON.parse(raw) as Record<string, unknown>;
|
|
594
|
+
createdAt = typeof sidecar.createdAt === "string" ? sidecar.createdAt : null;
|
|
595
|
+
pluginVersion = typeof sidecar.pluginVersion === "string" ? sidecar.pluginVersion : null;
|
|
596
|
+
fileCount = Array.isArray(sidecar.files) ? sidecar.files.length : null;
|
|
597
|
+
const capsule = sidecar.capsule as Record<string, unknown> | undefined;
|
|
598
|
+
description = capsule && typeof capsule.description === "string"
|
|
599
|
+
? capsule.description
|
|
600
|
+
: null;
|
|
601
|
+
} catch (err) {
|
|
602
|
+
const code = typeof err === "object" && err !== null && "code" in err
|
|
603
|
+
? (err as { code?: unknown }).code
|
|
604
|
+
: undefined;
|
|
605
|
+
if (code === "ENOENT") {
|
|
606
|
+
manifestPathOrNull = null;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
capsules.push({
|
|
611
|
+
id,
|
|
612
|
+
archivePath,
|
|
613
|
+
manifestPath: manifestPathOrNull,
|
|
614
|
+
createdAt,
|
|
615
|
+
pluginVersion,
|
|
616
|
+
fileCount,
|
|
617
|
+
description,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return { namespace: resolvedNamespace, capsulesDir, capsules };
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Manually invoke a single Dreams phase pass (PR 4/4).
|
|
626
|
+
*
|
|
627
|
+
* Deep-sleep delegates to memory governance (shadow → dry-run, apply → live).
|
|
628
|
+
* Light-sleep and REM scan the observation ledger and memory corpus
|
|
629
|
+
* respectively, returning the same telemetry shape as a scheduled run.
|
|
630
|
+
*/
|
|
631
|
+
async dreamsRun(options: {
|
|
632
|
+
phase: import("./types.js").DreamsPhase;
|
|
633
|
+
dryRun?: boolean;
|
|
634
|
+
namespace?: string;
|
|
635
|
+
authenticatedPrincipal?: string;
|
|
636
|
+
}): Promise<import("./types.js").DreamsRunResult> {
|
|
637
|
+
const { runDreamsPhase } = await import("./maintenance/dreams-ledger.js");
|
|
638
|
+
const validPhases = ["lightSleep", "rem", "deepSleep"];
|
|
639
|
+
if (!validPhases.includes(options.phase)) {
|
|
640
|
+
throw new EngramAccessInputError(
|
|
641
|
+
`Invalid phase: ${String(options.phase)}. Must be one of: ${validPhases.join(", ")}`,
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
const deepSleep = this.deps.orchestrator.config.dreamsPhases.deepSleep;
|
|
645
|
+
if (
|
|
646
|
+
options.phase === "deepSleep" &&
|
|
647
|
+
deepSleep.enabled === false &&
|
|
648
|
+
deepSleep.enabledExplicitlySet === true
|
|
649
|
+
) {
|
|
650
|
+
throw new EngramAccessInputError(
|
|
651
|
+
"memory governance is disabled by dreams.phases.deepSleep.enabled=false",
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
const dryRun = options.dryRun === true;
|
|
655
|
+
const resolvedNamespace = this.deps.writableNamespaceFor(
|
|
656
|
+
options.namespace,
|
|
657
|
+
undefined,
|
|
658
|
+
options.authenticatedPrincipal,
|
|
659
|
+
);
|
|
660
|
+
const storage = await this.deps.orchestrator.getStorage(resolvedNamespace);
|
|
661
|
+
const memoryDir = storage.dir;
|
|
662
|
+
const phaseRunner = dryRun || options.phase === "deepSleep"
|
|
663
|
+
? undefined
|
|
664
|
+
: async (_opts: { memoryDir: string; phase: "lightSleep" | "rem" }) => {
|
|
665
|
+
if (_opts.phase === "lightSleep") {
|
|
666
|
+
const result = await this.deps.orchestrator.runLifecyclePolicyNow(storage);
|
|
667
|
+
return {
|
|
668
|
+
itemsProcessed: result.memoriesAssessed,
|
|
669
|
+
notes: `scored ${result.memoriesAssessed} memories`,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
const result = await this.deps.orchestrator.runSemanticConsolidationNow({
|
|
673
|
+
dryRun: false,
|
|
674
|
+
storage,
|
|
675
|
+
});
|
|
676
|
+
const itemsProcessed = result.clusters.reduce(
|
|
677
|
+
(sum, cluster) => sum + cluster.memories.length,
|
|
678
|
+
0,
|
|
679
|
+
);
|
|
680
|
+
return {
|
|
681
|
+
itemsProcessed,
|
|
682
|
+
notes: `REM consolidation found ${result.clustersFound} clusters`,
|
|
683
|
+
};
|
|
684
|
+
};
|
|
685
|
+
const governanceRunner = options.phase === "deepSleep"
|
|
686
|
+
? async (_opts: { memoryDir: string; dryRun: boolean }) => {
|
|
687
|
+
return this.deps.orchestrator.runDeepSleepGovernanceNow({
|
|
688
|
+
storage,
|
|
689
|
+
dryRun: _opts.dryRun,
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
: undefined;
|
|
693
|
+
const result = await runDreamsPhase(
|
|
694
|
+
{ memoryDir, phase: options.phase, dryRun },
|
|
695
|
+
governanceRunner,
|
|
696
|
+
phaseRunner,
|
|
697
|
+
);
|
|
698
|
+
return {
|
|
699
|
+
phase: result.phase,
|
|
700
|
+
dryRun: result.dryRun,
|
|
701
|
+
durationMs: result.durationMs,
|
|
702
|
+
itemsProcessed: result.itemsProcessed,
|
|
703
|
+
notes: result.notes,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Manually promote a memory into one or more authorized targets. Requires
|
|
709
|
+
* a non-empty reason (audit-logged). Reuses the scope-profile promotion
|
|
710
|
+
* resolution and `canWriteNamespace` gate — there is no dashboard-only
|
|
711
|
+
* write path.
|
|
712
|
+
*/
|
|
713
|
+
async adminPromoteMemory(request: {
|
|
714
|
+
sourceMemoryId: string;
|
|
715
|
+
namespace?: string;
|
|
716
|
+
principal?: string;
|
|
717
|
+
sessionKey?: string;
|
|
718
|
+
targets: ReadonlyArray<{ kind: MemoryPromotionTargetKind; namespace?: string }>;
|
|
719
|
+
reason: string;
|
|
720
|
+
actor?: never; // ignored — actor is derived from the authenticated principal
|
|
721
|
+
}) {
|
|
722
|
+
const config = this.deps.orchestrator.config;
|
|
723
|
+
const namespacesEnabled = resolveNamespaceCapabilities(config).namespaces === true;
|
|
724
|
+
// Fix OdCl3: when no namespace is supplied, resolve the source via the
|
|
725
|
+
// scope plan (principal self / scope-profile write layer / coding overlay)
|
|
726
|
+
// instead of falling through to config.defaultNamespace. This matches the
|
|
727
|
+
// runtime observe/write path.
|
|
728
|
+
let sourceNamespace: string;
|
|
729
|
+
if (request.namespace) {
|
|
730
|
+
sourceNamespace = this.deps.resolveReadableNamespace(
|
|
731
|
+
request.namespace,
|
|
732
|
+
request.principal,
|
|
733
|
+
);
|
|
734
|
+
} else {
|
|
735
|
+
const codingContext = request.sessionKey
|
|
736
|
+
? this.deps.orchestrator.getCodingContextForSession(request.sessionKey) ?? null
|
|
737
|
+
: null;
|
|
738
|
+
const plan = resolveScopePlan({
|
|
739
|
+
config,
|
|
740
|
+
sessionKey: request.sessionKey,
|
|
741
|
+
principalOverride: request.principal,
|
|
742
|
+
codingContext,
|
|
743
|
+
namespacesEnabled,
|
|
744
|
+
});
|
|
745
|
+
sourceNamespace = this.deps.resolveReadableNamespace(
|
|
746
|
+
plan.baseNamespace,
|
|
747
|
+
request.principal,
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
// Fix OdB0c: thread coding context into the scope-profile plan so
|
|
751
|
+
// userProject/teamProject promotion targets resolve for project-scoped
|
|
752
|
+
// sessions (previously hard-coded to null).
|
|
753
|
+
const codingContext = request.sessionKey
|
|
754
|
+
? this.deps.orchestrator.getCodingContextForSession(request.sessionKey) ?? null
|
|
755
|
+
: null;
|
|
756
|
+
const codingOverlay = codingContext
|
|
757
|
+
? resolveCodingNamespaceOverlay(
|
|
758
|
+
codingContext,
|
|
759
|
+
config.codingMode,
|
|
760
|
+
config.defaultNamespace,
|
|
761
|
+
)
|
|
762
|
+
: null;
|
|
763
|
+
const scopeProfilePlan = resolveScopeProfilePlan({
|
|
764
|
+
config,
|
|
765
|
+
principal: request.principal,
|
|
766
|
+
codingContext,
|
|
767
|
+
codingOverlay,
|
|
768
|
+
});
|
|
769
|
+
const storage: PromotionStorageProvider = {
|
|
770
|
+
readMemory: async (namespace, memoryId) => {
|
|
771
|
+
const resolved = await this.deps.orchestrator.getStorage(namespace);
|
|
772
|
+
const memory = await resolved.getMemoryById(memoryId);
|
|
773
|
+
if (!memory) return null;
|
|
774
|
+
return {
|
|
775
|
+
category: memory.frontmatter.category,
|
|
776
|
+
content: memory.content,
|
|
777
|
+
frontmatter: memory.frontmatter,
|
|
778
|
+
};
|
|
779
|
+
},
|
|
780
|
+
writePromotedMemory: async (namespace, memory) => {
|
|
781
|
+
const resolved = await this.deps.orchestrator.getStorage(namespace);
|
|
782
|
+
const { id, tombstoneBlocked } = await resolved.writeMemory(
|
|
783
|
+
memory.category,
|
|
784
|
+
memory.content,
|
|
785
|
+
{
|
|
786
|
+
confidence: memory.confidence,
|
|
787
|
+
tags: memory.tags,
|
|
788
|
+
entityRef: memory.entityRef,
|
|
789
|
+
source: `admin-promotion:${memory.sourceNamespace}:${memory.reason.slice(0, 120)}`,
|
|
790
|
+
lineage: memory.lineage,
|
|
791
|
+
sourceMemoryId: memory.sourceMemoryId,
|
|
792
|
+
actor: memory.actor,
|
|
793
|
+
validAt: memory.validAt,
|
|
794
|
+
},
|
|
795
|
+
);
|
|
796
|
+
// #1645: a tombstone-blocked promotion lands pending_review (no active
|
|
797
|
+
// copy in the target). Report it as a failed promotion so the admin
|
|
798
|
+
// sees an honest result — the content is queued for review, not
|
|
799
|
+
// actively promoted. promoteMemory's catch block sanitizes this into
|
|
800
|
+
// a generic "promotion write failed" audit entry.
|
|
801
|
+
if (tombstoneBlocked) {
|
|
802
|
+
throw new Error(
|
|
803
|
+
"target namespace tombstone-blocked the promoted content (pending_review)",
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
return id;
|
|
807
|
+
},
|
|
808
|
+
};
|
|
809
|
+
try {
|
|
810
|
+
return await promoteMemory({
|
|
811
|
+
config,
|
|
812
|
+
sourceMemoryId: request.sourceMemoryId,
|
|
813
|
+
sourceNamespace,
|
|
814
|
+
principal: request.principal,
|
|
815
|
+
targets: request.targets,
|
|
816
|
+
reason: request.reason,
|
|
817
|
+
actor: request.principal ?? "admin-console",
|
|
818
|
+
storage,
|
|
819
|
+
scopeProfilePlan,
|
|
820
|
+
});
|
|
821
|
+
} catch (err) {
|
|
822
|
+
if (err instanceof AdminPromotionError) {
|
|
823
|
+
throw new EngramAccessInputError(err.message);
|
|
824
|
+
}
|
|
825
|
+
throw err;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|