@remnic/core 9.3.716 → 9.3.718
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +10 -9
- package/dist/access-cli.js +26 -25
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +14 -13
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +13 -12
- package/dist/access-operations-batch.js +11 -10
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +12 -11
- package/dist/{access-service-BW8cm_uU.d.ts → access-service-CZbnt0t2.d.ts} +4 -4
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +9 -8
- 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 +1 -1
- 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 -1
- package/dist/briefing.js +3 -3
- 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-CbR0CsUw.d.ts → catalog-CEYlMs9X.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +4 -4
- package/dist/{chunk-JEIROMLX.js → chunk-2COL35PK.js} +95 -9
- package/dist/chunk-2COL35PK.js.map +1 -0
- package/dist/{chunk-XZTUMWKQ.js → chunk-2PTBLS2S.js} +2 -2
- package/dist/{chunk-2Y6LMOQQ.js → chunk-2VHMVWPU.js} +2 -2
- package/dist/{chunk-6UU43ZGD.js → chunk-4KZGLDJJ.js} +2 -2
- package/dist/{chunk-K44MIN6D.js → chunk-5RHM43BJ.js} +3 -3
- package/dist/{chunk-MECU6WSH.js → chunk-72VDBALG.js} +18 -18
- package/dist/{chunk-UY4JMEAK.js → chunk-7EEVXW7X.js} +2 -2
- package/dist/{chunk-XCTDHF7U.js → chunk-A5TUPZ3N.js} +33 -1
- package/dist/chunk-A5TUPZ3N.js.map +1 -0
- package/dist/{chunk-WI4QX7GH.js → chunk-A66YRSOS.js} +2 -2
- package/dist/{chunk-CTAHKIG6.js → chunk-ANI5DUAE.js} +10 -8
- package/dist/{chunk-CTAHKIG6.js.map → chunk-ANI5DUAE.js.map} +1 -1
- package/dist/{chunk-O62P2LFL.js → chunk-APUJIMDM.js} +2 -2
- package/dist/{chunk-MPHDVHIB.js → chunk-DIFU33GU.js} +2 -2
- package/dist/{chunk-DXGS5YOQ.js → chunk-EH27LHML.js} +2 -2
- package/dist/{chunk-BNOAB5HH.js → chunk-F3PVXKHP.js} +78 -1442
- package/dist/chunk-F3PVXKHP.js.map +1 -0
- package/dist/{chunk-6QUPUTT7.js → chunk-HLIR3RJ7.js} +6 -10
- package/dist/{chunk-6QUPUTT7.js.map → chunk-HLIR3RJ7.js.map} +1 -1
- package/dist/{chunk-SHRRWOVY.js → chunk-J6BH2OBA.js} +64 -21
- package/dist/chunk-J6BH2OBA.js.map +1 -0
- package/dist/{chunk-6FUJ2VYY.js → chunk-JAKYZGV4.js} +3 -3
- package/dist/{chunk-3VUPWDSE.js → chunk-JIZGYQYH.js} +4 -4
- package/dist/{chunk-QOIONSOI.js → chunk-KRGC4EDX.js} +2 -2
- package/dist/{chunk-AJOIM6UR.js → chunk-L5ASZA2F.js} +2 -2
- package/dist/{chunk-I56LKMF7.js → chunk-LXSG4L55.js} +2 -2
- package/dist/{chunk-MSVSP5VO.js → chunk-N3DUH6OO.js} +1 -1
- package/dist/chunk-N3DUH6OO.js.map +1 -0
- package/dist/{chunk-SQ4LU27I.js → chunk-NFRBADNR.js} +195 -80
- package/dist/chunk-NFRBADNR.js.map +1 -0
- package/dist/chunk-OJU73BUX.js +1688 -0
- package/dist/chunk-OJU73BUX.js.map +1 -0
- package/dist/{chunk-JVEMUX6L.js → chunk-OZ2KQ7HF.js} +2 -2
- package/dist/{chunk-SYDHFFAW.js → chunk-P5IPFZVH.js} +5 -5
- package/dist/{chunk-GM3BMWKR.js → chunk-REWQAFSA.js} +2 -2
- package/dist/{chunk-V7GDTQ6I.js → chunk-S35FX6MU.js} +2 -2
- package/dist/{chunk-MTHNJCOJ.js → chunk-S3OPGGZB.js} +2 -2
- package/dist/{chunk-IHMT6XTY.js → chunk-U5H7WHTS.js} +6 -4
- package/dist/{chunk-IHMT6XTY.js.map → chunk-U5H7WHTS.js.map} +1 -1
- package/dist/{chunk-RBHANGVX.js → chunk-UCJ5CDTK.js} +6 -6
- package/dist/{chunk-HHLWSWWF.js → chunk-URM3HW2R.js} +2 -2
- package/dist/{chunk-VO4UICII.js → chunk-YTZLBBEB.js} +2 -2
- package/dist/{chunk-OC56UWV5.js → chunk-YYOK4GVF.js} +2 -2
- package/dist/{cli-Dpivy32W.d.ts → cli-1orcdXOY.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +28 -27
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +3 -3
- 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 +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +3 -3
- 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/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 +3 -3
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-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/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 +9 -9
- package/dist/index.js +42 -41
- 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 +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- 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 +4 -4
- 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 +3 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +11 -9
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +8 -5
- package/dist/{orchestrator-vygo2YIM.d.ts → orchestrator-kOxredSr.d.ts} +22 -5
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +17 -16
- 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-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/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-consolidation-DSjMDZ1u.d.ts → semantic-consolidation-B58v3S-a.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +3 -3
- 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 +1 -1
- package/dist/storage.js +2 -2
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/types.d.ts +12 -12
- package/dist/{types-D9dwzU_x.d.ts → types-ACmDOScJ.d.ts} +36 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +3 -3
- package/package.json +2 -2
- package/src/access-service.ts +56 -41
- package/src/briefing.ts +32 -0
- package/src/config.ts +32 -0
- package/src/correction/index.ts +24 -0
- package/src/correction/passive-capture.test.ts +500 -0
- package/src/correction/passive-capture.ts +335 -0
- package/src/correction/passive-correction-detector.test.ts +263 -0
- package/src/correction/passive-correction-detector.ts +325 -0
- package/src/correction/passive-correction-notifications.ts +123 -0
- package/src/maintenance/namespace-planner.ts +3 -10
- package/src/orchestrator.ts +231 -73
- package/src/scopes/scope-plan.test.ts +168 -1
- package/src/scopes/scope-plan.ts +163 -0
- package/src/types.ts +36 -0
- package/dist/chunk-BNOAB5HH.js.map +0 -1
- package/dist/chunk-JEIROMLX.js.map +0 -1
- package/dist/chunk-MSVSP5VO.js.map +0 -1
- package/dist/chunk-SHRRWOVY.js.map +0 -1
- package/dist/chunk-SQ4LU27I.js.map +0 -1
- package/dist/chunk-XCTDHF7U.js.map +0 -1
- /package/dist/{chunk-XZTUMWKQ.js.map → chunk-2PTBLS2S.js.map} +0 -0
- /package/dist/{chunk-2Y6LMOQQ.js.map → chunk-2VHMVWPU.js.map} +0 -0
- /package/dist/{chunk-6UU43ZGD.js.map → chunk-4KZGLDJJ.js.map} +0 -0
- /package/dist/{chunk-K44MIN6D.js.map → chunk-5RHM43BJ.js.map} +0 -0
- /package/dist/{chunk-MECU6WSH.js.map → chunk-72VDBALG.js.map} +0 -0
- /package/dist/{chunk-UY4JMEAK.js.map → chunk-7EEVXW7X.js.map} +0 -0
- /package/dist/{chunk-WI4QX7GH.js.map → chunk-A66YRSOS.js.map} +0 -0
- /package/dist/{chunk-O62P2LFL.js.map → chunk-APUJIMDM.js.map} +0 -0
- /package/dist/{chunk-MPHDVHIB.js.map → chunk-DIFU33GU.js.map} +0 -0
- /package/dist/{chunk-DXGS5YOQ.js.map → chunk-EH27LHML.js.map} +0 -0
- /package/dist/{chunk-6FUJ2VYY.js.map → chunk-JAKYZGV4.js.map} +0 -0
- /package/dist/{chunk-3VUPWDSE.js.map → chunk-JIZGYQYH.js.map} +0 -0
- /package/dist/{chunk-QOIONSOI.js.map → chunk-KRGC4EDX.js.map} +0 -0
- /package/dist/{chunk-AJOIM6UR.js.map → chunk-L5ASZA2F.js.map} +0 -0
- /package/dist/{chunk-I56LKMF7.js.map → chunk-LXSG4L55.js.map} +0 -0
- /package/dist/{chunk-JVEMUX6L.js.map → chunk-OZ2KQ7HF.js.map} +0 -0
- /package/dist/{chunk-SYDHFFAW.js.map → chunk-P5IPFZVH.js.map} +0 -0
- /package/dist/{chunk-GM3BMWKR.js.map → chunk-REWQAFSA.js.map} +0 -0
- /package/dist/{chunk-V7GDTQ6I.js.map → chunk-S35FX6MU.js.map} +0 -0
- /package/dist/{chunk-MTHNJCOJ.js.map → chunk-S3OPGGZB.js.map} +0 -0
- /package/dist/{chunk-RBHANGVX.js.map → chunk-UCJ5CDTK.js.map} +0 -0
- /package/dist/{chunk-HHLWSWWF.js.map → chunk-URM3HW2R.js.map} +0 -0
- /package/dist/{chunk-VO4UICII.js.map → chunk-YTZLBBEB.js.map} +0 -0
- /package/dist/{chunk-OC56UWV5.js.map → chunk-YYOK4GVF.js.map} +0 -0
package/src/orchestrator.ts
CHANGED
|
@@ -24,6 +24,10 @@ import { SmartBuffer } from "./buffer.js";
|
|
|
24
24
|
import { chunkContent, type ChunkingConfig } from "./chunking.js";
|
|
25
25
|
import { semanticChunkContent, type SemanticChunkResult } from "./semantic-chunking.js";
|
|
26
26
|
import { ExtractionEngine } from "./extraction.js";
|
|
27
|
+
import { detectPassiveCorrections } from "./correction/passive-correction-detector.js";
|
|
28
|
+
import { capturePassiveCorrections, type PassiveCaptureConfig } from "./correction/passive-capture.js";
|
|
29
|
+
import { createCorrectionService } from "./correction/correction-access-wiring.js";
|
|
30
|
+
import type { CorrectionService } from "./correction/correction-service.js";
|
|
27
31
|
import { isAboveImportanceThreshold, scoreImportance } from "./importance.js";
|
|
28
32
|
import {
|
|
29
33
|
judgeFactDurability,
|
|
@@ -338,11 +342,16 @@ import {
|
|
|
338
342
|
} from "./namespaces/identity.js";
|
|
339
343
|
import {
|
|
340
344
|
canReadNamespace,
|
|
345
|
+
canWriteNamespace,
|
|
341
346
|
defaultNamespaceForPrincipal,
|
|
342
347
|
recallNamespacesForPrincipal,
|
|
343
348
|
resolvePrincipal,
|
|
344
349
|
} from "./namespaces/principal.js";
|
|
345
|
-
import {
|
|
350
|
+
import {
|
|
351
|
+
getConfiguredNamespaces,
|
|
352
|
+
resolveNamespaceFromStorageDir,
|
|
353
|
+
resolveScopePlan,
|
|
354
|
+
} from "./scopes/scope-plan.js";
|
|
346
355
|
import {
|
|
347
356
|
expandScopeProfileReadNamespaces,
|
|
348
357
|
resolveScopeProfilePlan,
|
|
@@ -1976,6 +1985,16 @@ export class Orchestrator {
|
|
|
1976
1985
|
{ count: number; lastAccessed: string }
|
|
1977
1986
|
> = new Map();
|
|
1978
1987
|
|
|
1988
|
+
// Passive correction capture (issue #1581) — dedup state + lazy service.
|
|
1989
|
+
private passiveCorrectionDedup: Set<string> = new Set();
|
|
1990
|
+
private _passiveCorrectionService: CorrectionService | null = null;
|
|
1991
|
+
private passiveCorrectionTelemetry: {
|
|
1992
|
+
detected: number;
|
|
1993
|
+
queued: number;
|
|
1994
|
+
autoApplied: number;
|
|
1995
|
+
suppressedReasonCounts: Record<string, number>;
|
|
1996
|
+
} = { detected: 0, queued: 0, autoApplied: 0, suppressedReasonCounts: {} };
|
|
1997
|
+
|
|
1979
1998
|
// Background serial queue for extractions (agent_end optimization)
|
|
1980
1999
|
// Queue stores promises that resolve when extraction should run
|
|
1981
2000
|
private extractionQueue: Array<() => Promise<void>> = [];
|
|
@@ -2317,18 +2336,11 @@ export class Orchestrator {
|
|
|
2317
2336
|
return this.storageRouter.storageFor(ns);
|
|
2318
2337
|
}
|
|
2319
2338
|
|
|
2320
|
-
private
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
this.config.sharedNamespace,
|
|
2326
|
-
...this.config.namespacePolicies.map((policy) => policy.name),
|
|
2327
|
-
]
|
|
2328
|
-
.map((value) => value.trim())
|
|
2329
|
-
.filter(Boolean),
|
|
2330
|
-
),
|
|
2331
|
-
);
|
|
2339
|
+
private configuredNamespaceList(): string[] {
|
|
2340
|
+
// #1521: delegates to the scope-module resolver. The inline derivation is
|
|
2341
|
+
// retired so the adHocNamespaceResolutions ratchet no longer counts this
|
|
2342
|
+
// site.
|
|
2343
|
+
return getConfiguredNamespaces(this.config);
|
|
2332
2344
|
}
|
|
2333
2345
|
|
|
2334
2346
|
private rememberNamespaceStorageDirHint(namespace: string, storageDir?: string): void {
|
|
@@ -2456,7 +2468,7 @@ export class Orchestrator {
|
|
|
2456
2468
|
}
|
|
2457
2469
|
|
|
2458
2470
|
const configured = new Set(
|
|
2459
|
-
this.
|
|
2471
|
+
this.configuredNamespaceList().map((namespace) => normalizeNamespaceIdentity(namespace)),
|
|
2460
2472
|
);
|
|
2461
2473
|
const preferredByStorageDir = new Map<
|
|
2462
2474
|
string,
|
|
@@ -2570,7 +2582,7 @@ export class Orchestrator {
|
|
|
2570
2582
|
new Set(
|
|
2571
2583
|
(options.namespaces?.length
|
|
2572
2584
|
? options.namespaces
|
|
2573
|
-
: this.
|
|
2585
|
+
: this.configuredNamespaceList()
|
|
2574
2586
|
)
|
|
2575
2587
|
.map((value) => value.trim())
|
|
2576
2588
|
.filter(Boolean),
|
|
@@ -4138,7 +4150,7 @@ export class Orchestrator {
|
|
|
4138
4150
|
if (options?.dryRun !== true) {
|
|
4139
4151
|
try {
|
|
4140
4152
|
await this.processEntitySynthesisQueue(
|
|
4141
|
-
this.
|
|
4153
|
+
this.storageDirNamespace(targetStorage.dir),
|
|
4142
4154
|
5,
|
|
4143
4155
|
);
|
|
4144
4156
|
} catch (error) {
|
|
@@ -13015,6 +13027,139 @@ export class Orchestrator {
|
|
|
13015
13027
|
}
|
|
13016
13028
|
}
|
|
13017
13029
|
|
|
13030
|
+
/**
|
|
13031
|
+
* Passive correction capture (issue #1581) — detects corrections expressed
|
|
13032
|
+
* passively in conversation turns and routes them to the Correction Contract
|
|
13033
|
+
* (#1580). Called from `runExtraction` after persistence completes.
|
|
13034
|
+
*
|
|
13035
|
+
* Thin wiring: delegates ALL correction logic to the detector + capture
|
|
13036
|
+
* modules + the CorrectionService. This method only checks gates, calls the
|
|
13037
|
+
* detector, and routes results. Fail-open: capture errors never block the
|
|
13038
|
+
* extraction return path.
|
|
13039
|
+
*/
|
|
13040
|
+
private async maybeCapturePassiveCorrections(
|
|
13041
|
+
turns: readonly BufferTurn[],
|
|
13042
|
+
opts: {
|
|
13043
|
+
sessionKey: string;
|
|
13044
|
+
principal?: string;
|
|
13045
|
+
namespace: string;
|
|
13046
|
+
bufferKey: string;
|
|
13047
|
+
isLiveSession: boolean;
|
|
13048
|
+
},
|
|
13049
|
+
): Promise<void> {
|
|
13050
|
+
const mode = this.config.correctionCaptureMode;
|
|
13051
|
+
if (mode === "off") return;
|
|
13052
|
+
if (!this.config.correctionEnabled) return;
|
|
13053
|
+
|
|
13054
|
+
try {
|
|
13055
|
+
const corrections = detectPassiveCorrections(
|
|
13056
|
+
turns.map((t) => ({ role: t.role, content: t.content })),
|
|
13057
|
+
);
|
|
13058
|
+
if (corrections.length === 0) return;
|
|
13059
|
+
|
|
13060
|
+
// Replay/import: force queue-only mode even if config says auto.
|
|
13061
|
+
const effectiveMode = opts.isLiveSession ? mode : "queue";
|
|
13062
|
+
const captureConfig: PassiveCaptureConfig = {
|
|
13063
|
+
mode: effectiveMode,
|
|
13064
|
+
confidenceFloor: this.config.correctionCaptureConfidenceFloor,
|
|
13065
|
+
autoApplyMaxAffected: this.config.correctionCaptureAutoApplyMaxAffected,
|
|
13066
|
+
};
|
|
13067
|
+
|
|
13068
|
+
const service = this.passiveCorrectionService();
|
|
13069
|
+
const result = await capturePassiveCorrections(
|
|
13070
|
+
corrections,
|
|
13071
|
+
{
|
|
13072
|
+
correctionEnabled: this.config.correctionEnabled,
|
|
13073
|
+
isLiveSession: opts.isLiveSession,
|
|
13074
|
+
bufferKey: opts.bufferKey,
|
|
13075
|
+
sessionKey: opts.sessionKey,
|
|
13076
|
+
principal: opts.principal,
|
|
13077
|
+
namespace: opts.namespace,
|
|
13078
|
+
},
|
|
13079
|
+
captureConfig,
|
|
13080
|
+
{
|
|
13081
|
+
planCorrection: (req) => service.plan(req),
|
|
13082
|
+
applyCorrection: (planId, applyOpts) => service.apply(planId, applyOpts),
|
|
13083
|
+
storageDir: async (ns) => (await this.getStorage(ns)).dir,
|
|
13084
|
+
// Resolve `[m:xxxx]` handles to concrete memory ids via the single
|
|
13085
|
+
// shared helper (#1582). Returns null on miss/ambiguity so the
|
|
13086
|
+
// capture loop drops the handle and the planner falls back to text
|
|
13087
|
+
// search (review: "memory handles not resolved").
|
|
13088
|
+
resolveHandle: (ref, sessionKey) => {
|
|
13089
|
+
try {
|
|
13090
|
+
return this.resolveMemoryIdOrHandle(ref, sessionKey);
|
|
13091
|
+
} catch {
|
|
13092
|
+
return null;
|
|
13093
|
+
}
|
|
13094
|
+
},
|
|
13095
|
+
},
|
|
13096
|
+
this.passiveCorrectionDedup,
|
|
13097
|
+
);
|
|
13098
|
+
|
|
13099
|
+
// Accumulate telemetry
|
|
13100
|
+
this.passiveCorrectionTelemetry.detected += result.telemetry.detected;
|
|
13101
|
+
this.passiveCorrectionTelemetry.queued += result.telemetry.queued;
|
|
13102
|
+
this.passiveCorrectionTelemetry.autoApplied += result.telemetry.autoApplied;
|
|
13103
|
+
for (const [reason, count] of Object.entries(result.telemetry.suppressedReasons)) {
|
|
13104
|
+
this.passiveCorrectionTelemetry.suppressedReasonCounts[reason] =
|
|
13105
|
+
(this.passiveCorrectionTelemetry.suppressedReasonCounts[reason] ?? 0) + count;
|
|
13106
|
+
}
|
|
13107
|
+
} catch (err) {
|
|
13108
|
+
// Fail-open: passive capture never blocks extraction.
|
|
13109
|
+
log.debug(
|
|
13110
|
+
`passive-correction: capture failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`,
|
|
13111
|
+
);
|
|
13112
|
+
}
|
|
13113
|
+
}
|
|
13114
|
+
|
|
13115
|
+
/** Lazily construct the CorrectionService for passive capture. Stateless
|
|
13116
|
+
* across requests (per #1580 design); cached for the orchestrator's life. */
|
|
13117
|
+
private passiveCorrectionService(): CorrectionService {
|
|
13118
|
+
if (this._passiveCorrectionService) return this._passiveCorrectionService;
|
|
13119
|
+
this._passiveCorrectionService = createCorrectionService({
|
|
13120
|
+
orchestrator: this,
|
|
13121
|
+
// Session-scoped write ACL (review: "passive capture bypasses write
|
|
13122
|
+
// ACL"). A correction detected in session S plans only against S readable
|
|
13123
|
+
// namespaces and applies only to writable ones (rule 42) — passive
|
|
13124
|
+
// capture never becomes a cross-tenant mutation vector. Mirrors the
|
|
13125
|
+
// access-service createCorrectionService wiring rather than bypassing it.
|
|
13126
|
+
resolveAuthorizedNamespace: async (req) => {
|
|
13127
|
+
const principal = req.principal || resolvePrincipal(req.sessionKey, this.config);
|
|
13128
|
+
const ns = req.namespace ?? defaultNamespaceForPrincipal(principal, this.config);
|
|
13129
|
+
if (!canWriteNamespace(principal, ns, this.config)) {
|
|
13130
|
+
throw new Error(
|
|
13131
|
+
`passive correction: namespace "${ns}" is not writable for principal ${principal ?? "(none)"}`,
|
|
13132
|
+
);
|
|
13133
|
+
}
|
|
13134
|
+
return ns;
|
|
13135
|
+
},
|
|
13136
|
+
resolveReadableNamespaces: (req) => {
|
|
13137
|
+
const principal = req.principal || resolvePrincipal(req.sessionKey, this.config);
|
|
13138
|
+
return recallNamespacesForPrincipal(principal, this.config);
|
|
13139
|
+
},
|
|
13140
|
+
canWriteNamespace: async (req) => {
|
|
13141
|
+
const principal = req.principal || resolvePrincipal(req.sessionKey, this.config);
|
|
13142
|
+
return canWriteNamespace(principal, req.namespace, this.config);
|
|
13143
|
+
},
|
|
13144
|
+
llmComplete: async ({ system, user }) => {
|
|
13145
|
+
const llmResult = await this.localLlm.chatCompletion(
|
|
13146
|
+
[
|
|
13147
|
+
{ role: "system", content: system },
|
|
13148
|
+
{ role: "user", content: user },
|
|
13149
|
+
],
|
|
13150
|
+
{ operation: "correction-classify", priority: "background" },
|
|
13151
|
+
);
|
|
13152
|
+
if (!llmResult) {
|
|
13153
|
+
throw new Error(
|
|
13154
|
+
"passive correction classify+draft: local LLM unavailable (disabled or in cooldown)",
|
|
13155
|
+
);
|
|
13156
|
+
}
|
|
13157
|
+
return llmResult.content;
|
|
13158
|
+
},
|
|
13159
|
+
});
|
|
13160
|
+
return this._passiveCorrectionService;
|
|
13161
|
+
}
|
|
13162
|
+
|
|
13018
13163
|
private async runExtraction(
|
|
13019
13164
|
turns: BufferTurn[],
|
|
13020
13165
|
options: {
|
|
@@ -13100,13 +13245,13 @@ export class Orchestrator {
|
|
|
13100
13245
|
);
|
|
13101
13246
|
if (targetTurns.length === 0) {
|
|
13102
13247
|
log.debug("skipping extraction: no non-context turns after normalization");
|
|
13248
|
+
// Context-only turns may still contain corrections (review: "context-only
|
|
13249
|
+
// turns skip capture"). Scan normalizedTurns before clearing the buffer.
|
|
13250
|
+
if (normalizedTurns.length > 0) {
|
|
13251
|
+
await this.maybeCapturePassiveCorrections(normalizedTurns as BufferTurn[], { sessionKey, principal: resolvePrincipal(sessionKey, this.config), namespace: this.resolveSelfNamespace(sessionKey), bufferKey, isLiveSession: clearBufferAfterExtraction });
|
|
13252
|
+
}
|
|
13103
13253
|
await clearBuffer();
|
|
13104
|
-
return {
|
|
13105
|
-
status: "skipped",
|
|
13106
|
-
reason: "empty_normalized_turns",
|
|
13107
|
-
persistedCount: 0,
|
|
13108
|
-
durableOutputCount: 0,
|
|
13109
|
-
};
|
|
13254
|
+
return { status: "skipped", reason: "empty_normalized_turns", persistedCount: 0, durableOutputCount: 0 };
|
|
13110
13255
|
}
|
|
13111
13256
|
const sourceValidAt = latestSourceValidAtFromTurns(targetTurns);
|
|
13112
13257
|
throwIfDeadlineExceeded("before_extract");
|
|
@@ -13126,6 +13271,30 @@ export class Orchestrator {
|
|
|
13126
13271
|
`skipping extraction: below threshold (totalChars=${totalChars}, userTurns=${userTurns.length})`,
|
|
13127
13272
|
);
|
|
13128
13273
|
await clearBuffer();
|
|
13274
|
+
// Passive correction capture runs even when extraction is skipped for
|
|
13275
|
+
// being below the char/user-turn threshold (review: "skipped extraction
|
|
13276
|
+
// skips capture"). A short correction like "stop using Vim" is under
|
|
13277
|
+
// extractionMinChars but is exactly the high-value case this feature
|
|
13278
|
+
// exists for. The write namespace resolves through the SAME scope-profile
|
|
13279
|
+
// plan as the full extraction path so corrections target the active
|
|
13280
|
+
// profile's write layer, not just resolveSelfNamespace (review:
|
|
13281
|
+
// "below-threshold wrong namespace"). The ACL in passiveCorrectionService
|
|
13282
|
+
// authorizes the actual plan/apply.
|
|
13283
|
+
{
|
|
13284
|
+
const capturePrincipal =
|
|
13285
|
+
typeof options.principalOverride === "string" && options.principalOverride.length > 0
|
|
13286
|
+
? options.principalOverride
|
|
13287
|
+
: resolvePrincipal(sessionKey, this.config);
|
|
13288
|
+
const captureWO = typeof options.writeNamespaceOverride === "string" && options.writeNamespaceOverride.length > 0
|
|
13289
|
+
? options.writeNamespaceOverride : undefined;
|
|
13290
|
+
const captureCodingCtx = sessionKey ? this.getCodingContextForSession(sessionKey) : null;
|
|
13291
|
+
const captureCodingOv = resolveCodingNamespaceOverlay(captureCodingCtx, this.config.codingMode, this.config.defaultNamespace);
|
|
13292
|
+
const captureScopePlan = resolveScopeProfilePlan({ config: this.config, principal: capturePrincipal, codingContext: captureCodingCtx, codingOverlay: captureCodingOv });
|
|
13293
|
+
const captureNamespace = captureWO ?? captureScopePlan?.writeNamespace ?? this.resolveSelfNamespace(sessionKey);
|
|
13294
|
+
await this.maybeCapturePassiveCorrections(normalizedTurns as BufferTurn[], {
|
|
13295
|
+
sessionKey, principal: capturePrincipal, namespace: captureNamespace, bufferKey, isLiveSession: clearBufferAfterExtraction,
|
|
13296
|
+
});
|
|
13297
|
+
}
|
|
13129
13298
|
return {
|
|
13130
13299
|
status: "skipped",
|
|
13131
13300
|
reason: "below_threshold",
|
|
@@ -13325,13 +13494,12 @@ export class Orchestrator {
|
|
|
13325
13494
|
meta.lastExtractionAt = new Date().toISOString();
|
|
13326
13495
|
await storage.saveMeta(meta);
|
|
13327
13496
|
}
|
|
13497
|
+
// Correction-only turns that meet char/user-turn thresholds but yield
|
|
13498
|
+
// zero facts still need passive capture (review: "empty extraction skips
|
|
13499
|
+
// capture"). selfNamespace/principal already resolved above.
|
|
13500
|
+
await this.maybeCapturePassiveCorrections(normalizedTurns as BufferTurn[], { sessionKey, principal, namespace: selfNamespace, bufferKey, isLiveSession: clearBufferAfterExtraction });
|
|
13328
13501
|
await clearBuffer();
|
|
13329
|
-
return {
|
|
13330
|
-
status: "skipped",
|
|
13331
|
-
reason: "empty_extraction_result",
|
|
13332
|
-
persistedCount: 0,
|
|
13333
|
-
durableOutputCount: 0,
|
|
13334
|
-
};
|
|
13502
|
+
return { status: "skipped", reason: "empty_extraction_result", persistedCount: 0, durableOutputCount: 0 };
|
|
13335
13503
|
}
|
|
13336
13504
|
|
|
13337
13505
|
let threadIdForExtraction: string | null = null;
|
|
@@ -13448,6 +13616,19 @@ export class Orchestrator {
|
|
|
13448
13616
|
|
|
13449
13617
|
await clearBuffer({ ignoreAbort: true });
|
|
13450
13618
|
|
|
13619
|
+
// Passive correction capture (issue #1581) — detect corrections expressed
|
|
13620
|
+
// passively in the extracted turns and route to the Correction Contract.
|
|
13621
|
+
// Runs AFTER persistence + buffer clear so a capture failure never blocks
|
|
13622
|
+
// the extraction return. `clearBufferAfterExtraction` gates live-session
|
|
13623
|
+
// auto-apply (replay/import → queue-only).
|
|
13624
|
+
await this.maybeCapturePassiveCorrections(normalizedTurns as BufferTurn[], {
|
|
13625
|
+
sessionKey,
|
|
13626
|
+
principal,
|
|
13627
|
+
namespace: selfNamespace,
|
|
13628
|
+
bufferKey,
|
|
13629
|
+
isLiveSession: clearBufferAfterExtraction,
|
|
13630
|
+
});
|
|
13631
|
+
|
|
13451
13632
|
// Build memory box from this extraction (v8.0 Phase 2A)
|
|
13452
13633
|
// Topics are derived from the current extraction's facts and entities only —
|
|
13453
13634
|
// not from readAllMemories() — so box topics accurately reflect the current
|
|
@@ -13971,7 +14152,7 @@ export class Orchestrator {
|
|
|
13971
14152
|
)
|
|
13972
14153
|
return false;
|
|
13973
14154
|
if (
|
|
13974
|
-
this.
|
|
14155
|
+
this.storageDirNamespace(targetStorage.dir) ===
|
|
13975
14156
|
this.config.sharedNamespace
|
|
13976
14157
|
)
|
|
13977
14158
|
return false;
|
|
@@ -14797,7 +14978,7 @@ export class Orchestrator {
|
|
|
14797
14978
|
let targetNamespaceName =
|
|
14798
14979
|
baseNamespace && baseNamespace.length > 0
|
|
14799
14980
|
? baseNamespace
|
|
14800
|
-
: this.
|
|
14981
|
+
: this.storageDirNamespace(targetStorage.dir);
|
|
14801
14982
|
let routedRuleId: string | undefined;
|
|
14802
14983
|
let routedNamespaceExplicit = false;
|
|
14803
14984
|
if (routeRules.length > 0) {
|
|
@@ -14838,7 +15019,7 @@ export class Orchestrator {
|
|
|
14838
15019
|
fact.scope === "global" &&
|
|
14839
15020
|
!routedNamespaceExplicit
|
|
14840
15021
|
) {
|
|
14841
|
-
const currentNs = this.
|
|
15022
|
+
const currentNs = this.storageDirNamespace(targetStorage.dir);
|
|
14842
15023
|
if (currentNs !== this.config.sharedNamespace && profileAllowsSharedWrites) {
|
|
14843
15024
|
try {
|
|
14844
15025
|
targetStorage = await this.storageRouter.storageFor(
|
|
@@ -15106,7 +15287,7 @@ export class Orchestrator {
|
|
|
15106
15287
|
this.qmd.isAvailable() &&
|
|
15107
15288
|
faithfulnessEnforceStatus !== "pending_review"
|
|
15108
15289
|
) {
|
|
15109
|
-
const targetNamespace = this.
|
|
15290
|
+
const targetNamespace = this.storageDirNamespace(targetStorage.dir);
|
|
15110
15291
|
const contradiction = await this.checkForContradiction(
|
|
15111
15292
|
fact.content,
|
|
15112
15293
|
writeCategory,
|
|
@@ -15525,7 +15706,7 @@ export class Orchestrator {
|
|
|
15525
15706
|
memoryId: parentId,
|
|
15526
15707
|
category: writeCategory,
|
|
15527
15708
|
content: fact.content,
|
|
15528
|
-
namespace: this.
|
|
15709
|
+
namespace: this.storageDirNamespace(targetStorage.dir),
|
|
15529
15710
|
confidence: fact.confidence,
|
|
15530
15711
|
source: "extraction",
|
|
15531
15712
|
}),
|
|
@@ -15536,7 +15717,7 @@ export class Orchestrator {
|
|
|
15536
15717
|
|
|
15537
15718
|
// Suggest links for this memory (Phase 3A)
|
|
15538
15719
|
if (this.config.memoryLinkingEnabled && this.qmd.isAvailable()) {
|
|
15539
|
-
const targetNamespace = this.
|
|
15720
|
+
const targetNamespace = this.storageDirNamespace(targetStorage.dir);
|
|
15540
15721
|
const suggestedLinks = await this.suggestLinksForMemory(
|
|
15541
15722
|
fact.content,
|
|
15542
15723
|
writeCategory,
|
|
@@ -15642,7 +15823,7 @@ export class Orchestrator {
|
|
|
15642
15823
|
memoryId,
|
|
15643
15824
|
category: writeCategory,
|
|
15644
15825
|
content: fact.content,
|
|
15645
|
-
namespace: this.
|
|
15826
|
+
namespace: this.storageDirNamespace(targetStorage.dir),
|
|
15646
15827
|
confidence: fact.confidence,
|
|
15647
15828
|
source: "extraction",
|
|
15648
15829
|
}),
|
|
@@ -15788,7 +15969,7 @@ export class Orchestrator {
|
|
|
15788
15969
|
const baseTouchNamespace =
|
|
15789
15970
|
baseNamespace && baseNamespace.length > 0
|
|
15790
15971
|
? baseNamespace
|
|
15791
|
-
: this.
|
|
15972
|
+
: this.storageDirNamespace(storage.dir);
|
|
15792
15973
|
};
|
|
15793
15974
|
const recordDurableNonFactWrite = () => {
|
|
15794
15975
|
durableNonFactWritten = true;
|
|
@@ -17979,7 +18160,7 @@ export class Orchestrator {
|
|
|
17979
18160
|
|
|
17980
18161
|
const fallbackNamespace =
|
|
17981
18162
|
fallbackStorageDir !== null
|
|
17982
|
-
? this.
|
|
18163
|
+
? this.storageDirNamespace(fallbackStorageDir)
|
|
17983
18164
|
: this.config.defaultNamespace;
|
|
17984
18165
|
if (
|
|
17985
18166
|
recallNamespaces.length === 0 ||
|
|
@@ -18169,7 +18350,7 @@ export class Orchestrator {
|
|
|
18169
18350
|
|
|
18170
18351
|
const fallbackNamespace =
|
|
18171
18352
|
fallbackStorageDir !== null
|
|
18172
|
-
? this.
|
|
18353
|
+
? this.storageDirNamespace(fallbackStorageDir)
|
|
18173
18354
|
: this.config.defaultNamespace;
|
|
18174
18355
|
maybeAddStorage(fallbackStorage, fallbackNamespace);
|
|
18175
18356
|
|
|
@@ -20034,41 +20215,18 @@ export class Orchestrator {
|
|
|
20034
20215
|
return namespaceIdentityFromToken(m[1]) ?? m[1];
|
|
20035
20216
|
}
|
|
20036
20217
|
|
|
20037
|
-
private
|
|
20038
|
-
|
|
20039
|
-
|
|
20040
|
-
|
|
20041
|
-
|
|
20042
|
-
|
|
20043
|
-
|
|
20044
|
-
|
|
20045
|
-
|
|
20046
|
-
|
|
20047
|
-
|
|
20048
|
-
|
|
20049
|
-
// (`namespaceIdentityToken(decoded) === dirName`) is TAUTOLOGICAL for a
|
|
20050
|
-
// canonical token string, so it cannot tell a tokenized dir for `alpha` apart
|
|
20051
|
-
// from the legacy raw root of a namespace literally named `ns-616c706861`
|
|
20052
|
-
// (codex NRCve). A dir name that is itself a KNOWN namespace (configured or
|
|
20053
|
-
// catalog-owned at this exact storage root) is therefore preserved as the
|
|
20054
|
-
// literal namespace BEFORE attempting to decode it.
|
|
20055
|
-
if (this.configuredNamespaces().includes(dirName)) {
|
|
20056
|
-
return dirName;
|
|
20057
|
-
}
|
|
20058
|
-
this.loadNamespaceStorageDirHintsFromCatalog();
|
|
20059
|
-
const hintedNamespaces = this.namespaceStorageDirHints.get(resolvedStorageDir);
|
|
20060
|
-
if (hintedNamespaces?.has(dirName)) {
|
|
20061
|
-
return dirName;
|
|
20062
|
-
}
|
|
20063
|
-
if (hintedNamespaces?.size === 1) {
|
|
20064
|
-
const [hintedNamespace] = hintedNamespaces;
|
|
20065
|
-
if (hintedNamespace) return hintedNamespace;
|
|
20066
|
-
}
|
|
20067
|
-
const decoded = namespaceIdentityFromToken(dirName);
|
|
20068
|
-
if (decoded && namespaceIdentityToken(decoded) === dirName) {
|
|
20069
|
-
return decoded;
|
|
20070
|
-
}
|
|
20071
|
-
return dirName;
|
|
20218
|
+
private storageDirNamespace(storageDir: string): string {
|
|
20219
|
+
// #1521: delegates to the scope-module resolver. The inline dir→namespace
|
|
20220
|
+
// derivation (token round-trip guard, catalog hints) is retired so the
|
|
20221
|
+
// adHocNamespaceResolutions ratchet no longer counts this site. Hints are
|
|
20222
|
+
// loaded lazily via the callback (only after early returns, matching the
|
|
20223
|
+
// original behavior — codex P2).
|
|
20224
|
+
return resolveNamespaceFromStorageDir(storageDir, {
|
|
20225
|
+
config: this.config,
|
|
20226
|
+
configuredNamespaces: this.configuredNamespaceList(),
|
|
20227
|
+
hints: this.namespaceStorageDirHints,
|
|
20228
|
+
loadHints: () => this.loadNamespaceStorageDirHintsFromCatalog(),
|
|
20229
|
+
});
|
|
20072
20230
|
}
|
|
20073
20231
|
|
|
20074
20232
|
// #1522: catalog touch methods removed — touches now happen at the storage chokepoint.
|
|
@@ -21,7 +21,12 @@
|
|
|
21
21
|
import assert from "node:assert/strict";
|
|
22
22
|
import test from "node:test";
|
|
23
23
|
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
getConfiguredNamespaces,
|
|
26
|
+
resolveNamespaceFromStorageDir,
|
|
27
|
+
resolveScopePlan,
|
|
28
|
+
resolveWritableNamespaceValue,
|
|
29
|
+
} from "./scope-plan.js";
|
|
25
30
|
import {
|
|
26
31
|
combineNamespaces,
|
|
27
32
|
lcmSessionKeyForNamespace,
|
|
@@ -358,3 +363,165 @@ test("scope-plan: LCM session ids match lcmSessionKeyForNamespace encoding", ()
|
|
|
358
363
|
);
|
|
359
364
|
assert.deepEqual([...plan.lcmReadSessionIds], expected);
|
|
360
365
|
});
|
|
366
|
+
|
|
367
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
368
|
+
// Fitness tests: centralized namespace-resolution helpers (#1521 step 3–4)
|
|
369
|
+
//
|
|
370
|
+
// These pin the three pure helpers that replace the ad-hoc resolution scattered
|
|
371
|
+
// across orchestrator.ts and access-service.ts. They MUST match the pre-migration
|
|
372
|
+
// behavior byte-for-byte (the snapshots were derived from the original inline
|
|
373
|
+
// implementations).
|
|
374
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
375
|
+
|
|
376
|
+
test("getConfiguredNamespaces: default + shared + policies, trimmed and deduped", () => {
|
|
377
|
+
const config = baseConfig({
|
|
378
|
+
defaultNamespace: "default",
|
|
379
|
+
sharedNamespace: "shared",
|
|
380
|
+
namespacePolicies: [
|
|
381
|
+
{ name: "team-a", readPrincipals: ["*"], writePrincipals: ["*"] },
|
|
382
|
+
{ name: "team-b", readPrincipals: ["*"], writePrincipals: ["*"] },
|
|
383
|
+
],
|
|
384
|
+
});
|
|
385
|
+
const ns = getConfiguredNamespaces(config);
|
|
386
|
+
assert.deepEqual(ns, ["default", "shared", "team-a", "team-b"]);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("getConfiguredNamespaces: trims whitespace and drops empty", () => {
|
|
390
|
+
const config = baseConfig({
|
|
391
|
+
defaultNamespace: " default ",
|
|
392
|
+
sharedNamespace: "",
|
|
393
|
+
namespacePolicies: [
|
|
394
|
+
{ name: "team-a ", readPrincipals: ["*"], writePrincipals: ["*"] },
|
|
395
|
+
],
|
|
396
|
+
});
|
|
397
|
+
const ns = getConfiguredNamespaces(config);
|
|
398
|
+
assert.deepEqual(ns, ["default", "team-a"]);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("getConfiguredNamespaces: deduplicates identical names", () => {
|
|
402
|
+
const config = baseConfig({
|
|
403
|
+
defaultNamespace: "default",
|
|
404
|
+
sharedNamespace: "default",
|
|
405
|
+
namespacePolicies: [
|
|
406
|
+
{ name: "default", readPrincipals: ["*"], writePrincipals: ["*"] },
|
|
407
|
+
],
|
|
408
|
+
});
|
|
409
|
+
const ns = getConfiguredNamespaces(config);
|
|
410
|
+
assert.deepEqual(ns, ["default"]);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// ── resolveNamespaceFromStorageDir ──────────────────────────────────────────
|
|
414
|
+
|
|
415
|
+
test("resolveNamespaceFromStorageDir: namespaces disabled → always default", () => {
|
|
416
|
+
const config = baseConfig({ namespacesEnabled: false });
|
|
417
|
+
const ns = resolveNamespaceFromStorageDir("/some/path/namespaces/team-a", {
|
|
418
|
+
config,
|
|
419
|
+
configuredNamespaces: getConfiguredNamespaces(config),
|
|
420
|
+
});
|
|
421
|
+
assert.equal(ns, "default");
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("resolveNamespaceFromStorageDir: memory root → default", () => {
|
|
425
|
+
const config = baseConfig({ memoryDir: "/data/memory" });
|
|
426
|
+
const ns = resolveNamespaceFromStorageDir("/data/memory", {
|
|
427
|
+
config,
|
|
428
|
+
configuredNamespaces: getConfiguredNamespaces(config),
|
|
429
|
+
});
|
|
430
|
+
assert.equal(ns, "default");
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("resolveNamespaceFromStorageDir: configured namespace dir → that namespace", () => {
|
|
434
|
+
const config = baseConfig({
|
|
435
|
+
memoryDir: "/data/memory",
|
|
436
|
+
namespacePolicies: [
|
|
437
|
+
{ name: "team-a", readPrincipals: ["*"], writePrincipals: ["*"] },
|
|
438
|
+
],
|
|
439
|
+
});
|
|
440
|
+
const ns = resolveNamespaceFromStorageDir("/data/memory/namespaces/team-a", {
|
|
441
|
+
config,
|
|
442
|
+
configuredNamespaces: getConfiguredNamespaces(config),
|
|
443
|
+
});
|
|
444
|
+
assert.equal(ns, "team-a");
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("resolveNamespaceFromStorageDir: tokenized dir → decoded namespace", () => {
|
|
448
|
+
const config = baseConfig({ memoryDir: "/data/memory" });
|
|
449
|
+
// "alpha" tokenizes to ns-616c706861
|
|
450
|
+
const ns = resolveNamespaceFromStorageDir("/data/memory/namespaces/ns-616c706861", {
|
|
451
|
+
config,
|
|
452
|
+
configuredNamespaces: getConfiguredNamespaces(config),
|
|
453
|
+
});
|
|
454
|
+
assert.equal(ns, "alpha");
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test("resolveNamespaceFromStorageDir: unknown dir → literal dir name", () => {
|
|
458
|
+
const config = baseConfig({ memoryDir: "/data/memory" });
|
|
459
|
+
const ns = resolveNamespaceFromStorageDir("/data/memory/namespaces/custom-ns", {
|
|
460
|
+
config,
|
|
461
|
+
configuredNamespaces: getConfiguredNamespaces(config),
|
|
462
|
+
});
|
|
463
|
+
assert.equal(ns, "custom-ns");
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test("resolveNamespaceFromStorageDir: catalog hint resolves single-hint dir", () => {
|
|
467
|
+
const config = baseConfig({ memoryDir: "/data/memory" });
|
|
468
|
+
const resolvedDir = "/data/memory/namespaces/dynamic-xyz";
|
|
469
|
+
const hints = new Map([[resolvedDir, new Set(["my-dynamic-ns"])]]);
|
|
470
|
+
const ns = resolveNamespaceFromStorageDir(resolvedDir, {
|
|
471
|
+
config,
|
|
472
|
+
configuredNamespaces: getConfiguredNamespaces(config),
|
|
473
|
+
hints,
|
|
474
|
+
});
|
|
475
|
+
assert.equal(ns, "my-dynamic-ns");
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// ── resolveWritableNamespaceValue ───────────────────────────────────────────
|
|
479
|
+
|
|
480
|
+
test("resolveWritableNamespaceValue: undefined namespace → default, ok", () => {
|
|
481
|
+
const config = baseConfig();
|
|
482
|
+
const result = resolveWritableNamespaceValue(undefined, "sess-1", undefined, config);
|
|
483
|
+
assert.equal(result.ok, true);
|
|
484
|
+
if (result.ok) assert.equal(result.namespace, "default");
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
test("resolveWritableNamespaceValue: explicit namespace, writable principal → ok", () => {
|
|
488
|
+
const config = baseConfig({
|
|
489
|
+
namespacePolicies: [
|
|
490
|
+
{ name: "team-a", readPrincipals: ["alice"], writePrincipals: ["alice"] },
|
|
491
|
+
],
|
|
492
|
+
});
|
|
493
|
+
const result = resolveWritableNamespaceValue("team-a", "alice:sess-1", "alice", config);
|
|
494
|
+
assert.equal(result.ok, true);
|
|
495
|
+
if (result.ok) assert.equal(result.namespace, "team-a");
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test("resolveWritableNamespaceValue: explicit namespace, non-writable principal → not_writable", () => {
|
|
499
|
+
const config = baseConfig({
|
|
500
|
+
namespacePolicies: [
|
|
501
|
+
{ name: "team-a", readPrincipals: ["alice"], writePrincipals: ["alice"] },
|
|
502
|
+
],
|
|
503
|
+
});
|
|
504
|
+
const result = resolveWritableNamespaceValue("team-a", "bob:sess-1", "bob", config);
|
|
505
|
+
assert.equal(result.ok, false);
|
|
506
|
+
if (!result.ok) {
|
|
507
|
+
assert.equal(result.reason, "not_writable");
|
|
508
|
+
assert.equal(result.namespace, "team-a");
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
test("resolveWritableNamespaceValue: non-default namespace with namespaces disabled → unsupported", () => {
|
|
513
|
+
const config = baseConfig({ namespacesEnabled: false });
|
|
514
|
+
const result = resolveWritableNamespaceValue("custom-ns", "sess-1", undefined, config);
|
|
515
|
+
assert.equal(result.ok, false);
|
|
516
|
+
if (!result.ok) {
|
|
517
|
+
assert.equal(result.reason, "unsupported");
|
|
518
|
+
assert.equal(result.namespace, "custom-ns");
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
test("resolveWritableNamespaceValue: namespaces disabled, default namespace → ok", () => {
|
|
523
|
+
const config = baseConfig({ namespacesEnabled: false });
|
|
524
|
+
const result = resolveWritableNamespaceValue("default", "sess-1", undefined, config);
|
|
525
|
+
assert.equal(result.ok, true);
|
|
526
|
+
if (result.ok) assert.equal(result.namespace, "default");
|
|
527
|
+
});
|