@remnic/core 9.3.715 → 9.3.717
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 +2 -2
- package/dist/access-boundary.js +4 -3
- package/dist/access-cli.js +13 -12
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +2 -2
- package/dist/access-http.js +8 -7
- package/dist/access-mcp.d.ts +2 -2
- package/dist/access-mcp.js +7 -6
- package/dist/access-operations-batch.js +5 -4
- package/dist/access-operations.d.ts +2 -2
- package/dist/access-operations.js +6 -5
- package/dist/{access-service-GgnA--CE.d.ts → access-service-d4Tk-Dlr.d.ts} +2 -2
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +3 -2
- package/dist/access-surface-catalog.d.ts +2 -2
- package/dist/bootstrap.d.ts +1 -1
- package/dist/{chunk-MECU6WSH.js → chunk-2B43BYYZ.js} +6 -6
- package/dist/chunk-2KF4HDG2.js +24 -0
- package/dist/chunk-2KF4HDG2.js.map +1 -0
- package/dist/{chunk-HHLWSWWF.js → chunk-BF2426TN.js} +2 -2
- package/dist/{chunk-RBHANGVX.js → chunk-BZAK3RBN.js} +2 -2
- package/dist/{chunk-YUJL27ET.js → chunk-CTUWGJRI.js} +40 -63
- package/dist/chunk-CTUWGJRI.js.map +1 -0
- package/dist/{chunk-6QUPUTT7.js → chunk-DZYARLJ7.js} +5 -9
- package/dist/{chunk-6QUPUTT7.js.map → chunk-DZYARLJ7.js.map} +1 -1
- package/dist/{chunk-CTAHKIG6.js → chunk-EM22PCQR.js} +7 -7
- package/dist/{chunk-SHRRWOVY.js → chunk-J6BH2OBA.js} +64 -21
- package/dist/chunk-J6BH2OBA.js.map +1 -0
- package/dist/{chunk-GM3BMWKR.js → chunk-KHQ433G5.js} +2 -2
- package/dist/{chunk-SYDHFFAW.js → chunk-OUDAU3T5.js} +4 -4
- package/dist/{chunk-OQH5XUH3.js → chunk-Q6IP2ARR.js} +2 -5
- package/dist/chunk-Q6IP2ARR.js.map +1 -0
- package/dist/{chunk-O62P2LFL.js → chunk-VZQXJNBD.js} +2 -2
- package/dist/{chunk-BNOAB5HH.js → chunk-YAINYEM3.js} +57 -48
- package/dist/chunk-YAINYEM3.js.map +1 -0
- package/dist/{chunk-3VUPWDSE.js → chunk-ZAVTEQFM.js} +4 -4
- package/dist/{chunk-IHMT6XTY.js → chunk-ZXLSNG3T.js} +3 -3
- package/dist/{cli-Do1YtfnH.d.ts → cli-insNLYs6.d.ts} +2 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +12 -11
- package/dist/direct-answer-wiring.d.ts +9 -9
- package/dist/direct-answer-wiring.js +1 -1
- package/dist/explicit-capture.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +23 -22
- package/dist/index.js.map +1 -1
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/operator-toolkit.js +5 -3
- package/dist/orchestration/maintenance.js +5 -2
- package/dist/{orchestrator-BOaVstFG.d.ts → orchestrator-XPKhY2OV.d.ts} +6 -7
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +7 -6
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-planner-llm.js +2 -2
- package/dist/recall-planner-llm.js.map +1 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/transfer/types.d.ts +12 -12
- package/package.json +2 -2
- package/src/access-service.ts +56 -41
- package/src/capabilities.test.ts +28 -0
- package/src/direct-answer-wiring.test.ts +20 -13
- package/src/direct-answer-wiring.ts +9 -13
- package/src/maintenance/namespace-planner.ts +3 -10
- package/src/orchestrator.ts +42 -71
- package/src/recall-planner-llm.test.ts +30 -11
- package/src/recall-planner-llm.ts +4 -5
- package/src/scopes/scope-plan.test.ts +168 -1
- package/src/scopes/scope-plan.ts +163 -0
- package/dist/chunk-BNOAB5HH.js.map +0 -1
- package/dist/chunk-OQH5XUH3.js.map +0 -1
- package/dist/chunk-SHRRWOVY.js.map +0 -1
- package/dist/chunk-YUJL27ET.js.map +0 -1
- /package/dist/{chunk-MECU6WSH.js.map → chunk-2B43BYYZ.js.map} +0 -0
- /package/dist/{chunk-HHLWSWWF.js.map → chunk-BF2426TN.js.map} +0 -0
- /package/dist/{chunk-RBHANGVX.js.map → chunk-BZAK3RBN.js.map} +0 -0
- /package/dist/{chunk-CTAHKIG6.js.map → chunk-EM22PCQR.js.map} +0 -0
- /package/dist/{chunk-GM3BMWKR.js.map → chunk-KHQ433G5.js.map} +0 -0
- /package/dist/{chunk-SYDHFFAW.js.map → chunk-OUDAU3T5.js.map} +0 -0
- /package/dist/{chunk-O62P2LFL.js.map → chunk-VZQXJNBD.js.map} +0 -0
- /package/dist/{chunk-3VUPWDSE.js.map → chunk-ZAVTEQFM.js.map} +0 -0
- /package/dist/{chunk-IHMT6XTY.js.map → chunk-ZXLSNG3T.js.map} +0 -0
package/src/access-service.ts
CHANGED
|
@@ -109,7 +109,11 @@ import {
|
|
|
109
109
|
type ScopeProfileLayerResolution,
|
|
110
110
|
type ScopeProfilePromotionResolution,
|
|
111
111
|
} from "./namespaces/scope-profiles.js";
|
|
112
|
-
import {
|
|
112
|
+
import {
|
|
113
|
+
type ScopePlan,
|
|
114
|
+
resolveScopePlan,
|
|
115
|
+
resolveWritableNamespaceValue,
|
|
116
|
+
} from "./scopes/scope-plan.js";
|
|
113
117
|
import { namespaceIdentityFromToken } from "./namespaces/identity.js";
|
|
114
118
|
import { namespaceCollectionName } from "./namespaces/search.js";
|
|
115
119
|
import { SecureStoreLockedError } from "./secure-store/index.js";
|
|
@@ -1367,17 +1371,28 @@ export class EngramAccessService {
|
|
|
1367
1371
|
return resolvePrincipal(sessionKey, this.orchestrator.config);
|
|
1368
1372
|
}
|
|
1369
1373
|
|
|
1370
|
-
private
|
|
1374
|
+
private writableNamespaceFor(
|
|
1371
1375
|
namespace: string | undefined,
|
|
1372
1376
|
sessionKey: string | undefined,
|
|
1373
1377
|
authenticatedPrincipal?: string,
|
|
1374
1378
|
): string {
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
+
// #1521: delegates to the scope-module resolver. The inline
|
|
1380
|
+
// namespace/principal resolution + writability check is retired so the
|
|
1381
|
+
// adHocNamespaceResolutions ratchet no longer counts this site.
|
|
1382
|
+
const result = resolveWritableNamespaceValue(
|
|
1383
|
+
namespace,
|
|
1384
|
+
sessionKey,
|
|
1385
|
+
authenticatedPrincipal,
|
|
1386
|
+
this.orchestrator.config,
|
|
1387
|
+
);
|
|
1388
|
+
if (!result.ok) {
|
|
1389
|
+
throw new EngramAccessInputError(
|
|
1390
|
+
result.reason === "unsupported"
|
|
1391
|
+
? `unsupported namespace: ${result.namespace}`
|
|
1392
|
+
: `namespace is not writable: ${result.namespace}`,
|
|
1393
|
+
);
|
|
1379
1394
|
}
|
|
1380
|
-
return
|
|
1395
|
+
return result.namespace;
|
|
1381
1396
|
}
|
|
1382
1397
|
|
|
1383
1398
|
/**
|
|
@@ -1487,7 +1502,7 @@ export class EngramAccessService {
|
|
|
1487
1502
|
const hasExplicitNamespace =
|
|
1488
1503
|
typeof request.namespace === "string" && request.namespace.trim().length > 0;
|
|
1489
1504
|
if (hasExplicitNamespace) {
|
|
1490
|
-
return this.
|
|
1505
|
+
return this.writableNamespaceFor(
|
|
1491
1506
|
request.namespace,
|
|
1492
1507
|
request.sessionKey,
|
|
1493
1508
|
request.authenticatedPrincipal,
|
|
@@ -1509,7 +1524,7 @@ export class EngramAccessService {
|
|
|
1509
1524
|
if (!overlay) {
|
|
1510
1525
|
// No coding overlay → unqualified write stays on config.defaultNamespace,
|
|
1511
1526
|
// exactly the pre-#1434 behavior (auth-checked, like the legacy path).
|
|
1512
|
-
return this.
|
|
1527
|
+
return this.writableNamespaceFor(
|
|
1513
1528
|
undefined,
|
|
1514
1529
|
request.sessionKey,
|
|
1515
1530
|
request.authenticatedPrincipal,
|
|
@@ -1624,7 +1639,7 @@ export class EngramAccessService {
|
|
|
1624
1639
|
// The overlay never applies, so base == write == the explicit namespace.
|
|
1625
1640
|
// Objective-state converges on the same explicit target (the stricter
|
|
1626
1641
|
// principal-self contract only governs the IMPLICIT path).
|
|
1627
|
-
const writeNamespace = this.
|
|
1642
|
+
const writeNamespace = this.writableNamespaceFor(
|
|
1628
1643
|
request.namespace,
|
|
1629
1644
|
request.sessionKey,
|
|
1630
1645
|
request.authenticatedPrincipal,
|
|
@@ -1779,7 +1794,7 @@ export class EngramAccessService {
|
|
|
1779
1794
|
// namespace), collapsing the namespaces-disabled / no-session /
|
|
1780
1795
|
// projectScope-off cases to config.defaultNamespace exactly as before
|
|
1781
1796
|
// (rule 39 parity with resolveCodingScopedWriteNamespace).
|
|
1782
|
-
const writeNamespace = this.
|
|
1797
|
+
const writeNamespace = this.writableNamespaceFor(
|
|
1783
1798
|
undefined,
|
|
1784
1799
|
request.sessionKey,
|
|
1785
1800
|
request.authenticatedPrincipal,
|
|
@@ -4757,7 +4772,7 @@ export class EngramAccessService {
|
|
|
4757
4772
|
const service = createCorrectionService({
|
|
4758
4773
|
orchestrator: this.orchestrator,
|
|
4759
4774
|
resolveAuthorizedNamespace: async (req) =>
|
|
4760
|
-
this.
|
|
4775
|
+
this.writableNamespaceFor(req.namespace, req.sessionKey, req.principal),
|
|
4761
4776
|
resolveReadableNamespaces: (req) => {
|
|
4762
4777
|
const principal = this.resolveRequestPrincipal(req.sessionKey, req.principal);
|
|
4763
4778
|
return recallNamespacesForPrincipal(principal, this.orchestrator.config);
|
|
@@ -4766,7 +4781,7 @@ export class EngramAccessService {
|
|
|
4766
4781
|
// resolveWritableNamespace resolves + checks writability in one step;
|
|
4767
4782
|
// reusing it avoids a second ad-hoc namespace-resolution call site.
|
|
4768
4783
|
try {
|
|
4769
|
-
this.
|
|
4784
|
+
this.writableNamespaceFor(req.namespace, req.sessionKey, req.principal);
|
|
4770
4785
|
return Promise.resolve(true);
|
|
4771
4786
|
} catch {
|
|
4772
4787
|
return Promise.resolve(false);
|
|
@@ -5176,7 +5191,7 @@ export class EngramAccessService {
|
|
|
5176
5191
|
if (deepSleep.enabled === false && deepSleep.enabledExplicitlySet === true) {
|
|
5177
5192
|
throw new Error("memory governance is disabled by dreams.phases.deepSleep.enabled=false");
|
|
5178
5193
|
}
|
|
5179
|
-
const resolvedNamespace = this.
|
|
5194
|
+
const resolvedNamespace = this.writableNamespaceFor(
|
|
5180
5195
|
request.namespace,
|
|
5181
5196
|
undefined,
|
|
5182
5197
|
request.authenticatedPrincipal ?? principal,
|
|
@@ -5236,7 +5251,7 @@ export class EngramAccessService {
|
|
|
5236
5251
|
proceduresWritten: number;
|
|
5237
5252
|
skippedReason?: string;
|
|
5238
5253
|
}> {
|
|
5239
|
-
const resolvedNamespace = this.
|
|
5254
|
+
const resolvedNamespace = this.writableNamespaceFor(
|
|
5240
5255
|
request.namespace,
|
|
5241
5256
|
undefined,
|
|
5242
5257
|
request.authenticatedPrincipal ?? principal,
|
|
@@ -5262,7 +5277,7 @@ export class EngramAccessService {
|
|
|
5262
5277
|
} = {},
|
|
5263
5278
|
principal?: string,
|
|
5264
5279
|
): Promise<LiveConnectorsRunSummary> {
|
|
5265
|
-
this.
|
|
5280
|
+
this.writableNamespaceFor(
|
|
5266
5281
|
undefined,
|
|
5267
5282
|
undefined,
|
|
5268
5283
|
request.authenticatedPrincipal ?? principal,
|
|
@@ -5308,7 +5323,7 @@ export class EngramAccessService {
|
|
|
5308
5323
|
duplicatesSuperseded: number;
|
|
5309
5324
|
result?: PatternReinforcementResult;
|
|
5310
5325
|
}> {
|
|
5311
|
-
const resolvedNamespace = this.
|
|
5326
|
+
const resolvedNamespace = this.writableNamespaceFor(
|
|
5312
5327
|
request.namespace,
|
|
5313
5328
|
undefined,
|
|
5314
5329
|
request.authenticatedPrincipal ?? principal,
|
|
@@ -5598,7 +5613,7 @@ export class EngramAccessService {
|
|
|
5598
5613
|
if (!isTrustZoneName(request.targetZone)) {
|
|
5599
5614
|
throw new EngramAccessInputError(`unsupported trust-zone target: ${String(request.targetZone)}`);
|
|
5600
5615
|
}
|
|
5601
|
-
const resolvedNamespace = this.
|
|
5616
|
+
const resolvedNamespace = this.writableNamespaceFor(
|
|
5602
5617
|
request.namespace,
|
|
5603
5618
|
undefined,
|
|
5604
5619
|
request.authenticatedPrincipal,
|
|
@@ -5632,7 +5647,7 @@ export class EngramAccessService {
|
|
|
5632
5647
|
async trustZoneDemoSeed(
|
|
5633
5648
|
request: EngramAccessTrustZoneDemoSeedRequest,
|
|
5634
5649
|
): Promise<EngramAccessTrustZoneDemoSeedResponse> {
|
|
5635
|
-
const resolvedNamespace = this.
|
|
5650
|
+
const resolvedNamespace = this.writableNamespaceFor(
|
|
5636
5651
|
request.namespace,
|
|
5637
5652
|
undefined,
|
|
5638
5653
|
request.authenticatedPrincipal,
|
|
@@ -5669,7 +5684,7 @@ export class EngramAccessService {
|
|
|
5669
5684
|
throw new EngramAccessInputError("reasonCode is required");
|
|
5670
5685
|
}
|
|
5671
5686
|
|
|
5672
|
-
const resolvedNamespace = this.
|
|
5687
|
+
const resolvedNamespace = this.writableNamespaceFor(
|
|
5673
5688
|
request.namespace,
|
|
5674
5689
|
undefined,
|
|
5675
5690
|
request.authenticatedPrincipal,
|
|
@@ -5835,7 +5850,7 @@ export class EngramAccessService {
|
|
|
5835
5850
|
|
|
5836
5851
|
// Backward-compatible BASE writable namespace (pre-#1495 response semantics)
|
|
5837
5852
|
// for the legacy `namespace` response field. DERIVED from the already-resolved
|
|
5838
|
-
// scope plan — NOT a second
|
|
5853
|
+
// scope plan — NOT a second writable-namespace resolution call
|
|
5839
5854
|
// call (#1505 thread jvO). The fresh call re-authorized `undefined ⇒
|
|
5840
5855
|
// config.defaultNamespace` a SECOND time; under a restrictive default-namespace
|
|
5841
5856
|
// write policy that re-auth could REJECT an otherwise valid project-scoped
|
|
@@ -5845,7 +5860,7 @@ export class EngramAccessService {
|
|
|
5845
5860
|
// the coding context, leaving an orphaned session binding behind. The plan is
|
|
5846
5861
|
// the single authorization point (rule 22 / 39); the legacy field must reuse it
|
|
5847
5862
|
// and never re-authorize. Pre-#1495 semantics were exactly
|
|
5848
|
-
//
|
|
5863
|
+
// the writable-namespace resolver (overlay-agnostic): the explicit
|
|
5849
5864
|
// namespace when supplied, else `config.defaultNamespace` for user-project
|
|
5850
5865
|
// coding overlays. Hosted scope-profile layers such as `teamProject` report
|
|
5851
5866
|
// their effective profile write namespace because there is no legacy
|
|
@@ -6680,7 +6695,7 @@ export class EngramAccessService {
|
|
|
6680
6695
|
|
|
6681
6696
|
// Authorize compaction against the SCOPED WRITE TARGET — the SAME effective
|
|
6682
6697
|
// write namespace `observe` archived the LCM queue under — NOT a premature
|
|
6683
|
-
//
|
|
6698
|
+
// the writable-namespace resolver (undefined ⇒ config.defaultNamespace) (#1505
|
|
6684
6699
|
// thread NBHWs). Under a restrictive `default` WRITE policy where the
|
|
6685
6700
|
// principal can still write its self/project overlay, that premature default
|
|
6686
6701
|
// write-auth threw `namespace is not writable: default` BEFORE the scoped key
|
|
@@ -6692,7 +6707,7 @@ export class EngramAccessService {
|
|
|
6692
6707
|
// default` for a validly scoped observe's queue.
|
|
6693
6708
|
const scope = await this.resolveMemoryScopePlan(request);
|
|
6694
6709
|
// Legacy `namespace` response field: pre-#1505 semantics were exactly
|
|
6695
|
-
//
|
|
6710
|
+
// the writable-namespace resolver (overlay-agnostic) — the
|
|
6696
6711
|
// authorized explicit namespace when supplied, else `config.defaultNamespace`.
|
|
6697
6712
|
// DERIVED from the scope plan (NOT a second auth pass, #1505 thread jvO):
|
|
6698
6713
|
// explicit ⇒ writeNamespace; user-project coding overlay ⇒ defaultNamespace;
|
|
@@ -6747,7 +6762,7 @@ export class EngramAccessService {
|
|
|
6747
6762
|
|
|
6748
6763
|
// Authorize compaction against the SCOPED WRITE TARGET — the SAME effective
|
|
6749
6764
|
// write namespace `observe` archived the LCM queue under — NOT a premature
|
|
6750
|
-
//
|
|
6765
|
+
// the writable-namespace resolver (undefined ⇒ config.defaultNamespace) (#1505
|
|
6751
6766
|
// thread NBHWs). See `lcmCompactionFlush` for the full rationale: under a
|
|
6752
6767
|
// restrictive `default` WRITE policy where the principal can still write its
|
|
6753
6768
|
// self/project overlay, the old premature default write-auth threw `namespace
|
|
@@ -6832,7 +6847,7 @@ export class EngramAccessService {
|
|
|
6832
6847
|
}
|
|
6833
6848
|
const symptom = request.symptom?.trim();
|
|
6834
6849
|
if (!symptom) throw new EngramAccessInputError("symptom is required");
|
|
6835
|
-
const resolvedNs = this.
|
|
6850
|
+
const resolvedNs = this.writableNamespaceFor(request.namespace, undefined, request.principal);
|
|
6836
6851
|
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
6837
6852
|
const created = await storage.appendContinuityIncident({
|
|
6838
6853
|
symptom,
|
|
@@ -6862,7 +6877,7 @@ export class EngramAccessService {
|
|
|
6862
6877
|
if (!fixApplied) throw new EngramAccessInputError("fixApplied is required");
|
|
6863
6878
|
const verificationResult = request.verificationResult?.trim();
|
|
6864
6879
|
if (!verificationResult) throw new EngramAccessInputError("verificationResult is required");
|
|
6865
|
-
const resolvedNs = this.
|
|
6880
|
+
const resolvedNs = this.writableNamespaceFor(request.namespace, undefined, request.principal);
|
|
6866
6881
|
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
6867
6882
|
const closed = await storage.closeContinuityIncident(id, {
|
|
6868
6883
|
fixApplied,
|
|
@@ -6904,7 +6919,7 @@ export class EngramAccessService {
|
|
|
6904
6919
|
if (!this.orchestrator.config.identityContinuityEnabled) {
|
|
6905
6920
|
return { enabled: false, reason: "Identity continuity is disabled." };
|
|
6906
6921
|
}
|
|
6907
|
-
const resolvedNs = this.
|
|
6922
|
+
const resolvedNs = this.writableNamespaceFor(request.namespace, undefined, request.principal);
|
|
6908
6923
|
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
6909
6924
|
const loop = await storage.upsertIdentityImprovementLoop({
|
|
6910
6925
|
id: request.id?.trim() || "",
|
|
@@ -6931,7 +6946,7 @@ export class EngramAccessService {
|
|
|
6931
6946
|
}
|
|
6932
6947
|
const id = request.id?.trim();
|
|
6933
6948
|
if (!id) throw new EngramAccessInputError("id is required");
|
|
6934
|
-
const resolvedNs = this.
|
|
6949
|
+
const resolvedNs = this.writableNamespaceFor(request.namespace, undefined, request.principal);
|
|
6935
6950
|
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
6936
6951
|
const reviewed = await storage.reviewIdentityImprovementLoop(id, {
|
|
6937
6952
|
status: request.status,
|
|
@@ -6985,7 +7000,7 @@ export class EngramAccessService {
|
|
|
6985
7000
|
const hasUpdate = Object.values(updates).some((v) => typeof v === "string" && v.length > 0);
|
|
6986
7001
|
if (!hasUpdate) throw new EngramAccessInputError("At least one section field is required.");
|
|
6987
7002
|
|
|
6988
|
-
const resolvedNs = this.
|
|
7003
|
+
const resolvedNs = this.writableNamespaceFor(request.namespace, undefined, request.principal);
|
|
6989
7004
|
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
6990
7005
|
const existing = await storage.readIdentityAnchor();
|
|
6991
7006
|
|
|
@@ -7654,7 +7669,7 @@ export class EngramAccessService {
|
|
|
7654
7669
|
"memoryId must not contain path separators",
|
|
7655
7670
|
);
|
|
7656
7671
|
}
|
|
7657
|
-
const resolvedNs = this.
|
|
7672
|
+
const resolvedNs = this.writableNamespaceFor(
|
|
7658
7673
|
request.namespace,
|
|
7659
7674
|
request.sessionKey,
|
|
7660
7675
|
request.principal,
|
|
@@ -7678,7 +7693,7 @@ export class EngramAccessService {
|
|
|
7678
7693
|
principal?: string;
|
|
7679
7694
|
sessionKey?: string;
|
|
7680
7695
|
}): Promise<unknown> {
|
|
7681
|
-
const resolvedNs = this.
|
|
7696
|
+
const resolvedNs = this.writableNamespaceFor(request.namespace, request.sessionKey, request.principal);
|
|
7682
7697
|
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
7683
7698
|
// Update frontmatter to active status (promote from pending/draft)
|
|
7684
7699
|
await storage.updateMemoryFrontmatter(request.memoryId, {
|
|
@@ -7734,7 +7749,7 @@ export class EngramAccessService {
|
|
|
7734
7749
|
);
|
|
7735
7750
|
}
|
|
7736
7751
|
|
|
7737
|
-
const resolvedNs = this.
|
|
7752
|
+
const resolvedNs = this.writableNamespaceFor(
|
|
7738
7753
|
request.namespace,
|
|
7739
7754
|
request.sessionKey,
|
|
7740
7755
|
request.principal,
|
|
@@ -7784,7 +7799,7 @@ export class EngramAccessService {
|
|
|
7784
7799
|
namespace?: string;
|
|
7785
7800
|
principal?: string;
|
|
7786
7801
|
}): Promise<{ saved: boolean }> {
|
|
7787
|
-
const resolvedNs = this.
|
|
7802
|
+
const resolvedNs = this.writableNamespaceFor(request.namespace, request.sessionKey, request.principal);
|
|
7788
7803
|
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
7789
7804
|
const storageDir = storage.dir;
|
|
7790
7805
|
const { writeFile, mkdir } = await import("node:fs/promises");
|
|
@@ -7842,7 +7857,7 @@ export class EngramAccessService {
|
|
|
7842
7857
|
// Pass authenticatedPrincipal so the principal resolution matches other
|
|
7843
7858
|
// write endpoints (gotcha #42: read and write paths must resolve through
|
|
7844
7859
|
// the same namespace layer).
|
|
7845
|
-
const resolvedNamespace = this.
|
|
7860
|
+
const resolvedNamespace = this.writableNamespaceFor(
|
|
7846
7861
|
request.namespace,
|
|
7847
7862
|
request.sessionId,
|
|
7848
7863
|
request.authenticatedPrincipal,
|
|
@@ -8144,7 +8159,7 @@ export class EngramAccessService {
|
|
|
8144
8159
|
"authentication required: namespaces are enabled and no principal was supplied",
|
|
8145
8160
|
);
|
|
8146
8161
|
}
|
|
8147
|
-
const resolved = this.
|
|
8162
|
+
const resolved = this.writableNamespaceFor(namespace, undefined, principal);
|
|
8148
8163
|
const storage = await this.orchestrator.getStorage(resolved);
|
|
8149
8164
|
return { namespace: resolved, storage };
|
|
8150
8165
|
}
|
|
@@ -8201,7 +8216,7 @@ export class EngramAccessService {
|
|
|
8201
8216
|
},
|
|
8202
8217
|
): Promise<ImportCapsuleResult> {
|
|
8203
8218
|
const { namespace, principal, root: explicitRoot, memoryDir: explicitMemoryDir, ...importOptions } = opts;
|
|
8204
|
-
const resolvedNamespace = this.
|
|
8219
|
+
const resolvedNamespace = this.writableNamespaceFor(namespace, undefined, principal);
|
|
8205
8220
|
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
8206
8221
|
const root = explicitRoot ?? storage.dir;
|
|
8207
8222
|
const memoryDir = explicitMemoryDir ?? this.orchestrator.config.memoryDir;
|
|
@@ -8288,7 +8303,7 @@ export class EngramAccessService {
|
|
|
8288
8303
|
},
|
|
8289
8304
|
): Promise<ExportCapsuleResult> {
|
|
8290
8305
|
const { namespace, principal, root: explicitRoot, memoryDir: explicitMemoryDir, ...exportOptions } = opts;
|
|
8291
|
-
const resolvedNamespace = this.
|
|
8306
|
+
const resolvedNamespace = this.writableNamespaceFor(namespace, undefined, principal);
|
|
8292
8307
|
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
8293
8308
|
const root = explicitRoot ?? storage.dir;
|
|
8294
8309
|
const memoryDir = explicitMemoryDir ?? this.orchestrator.config.memoryDir;
|
|
@@ -8522,7 +8537,7 @@ export class EngramAccessService {
|
|
|
8522
8537
|
async offlineSyncApplyFileContent(
|
|
8523
8538
|
options: EngramAccessOfflineSyncApplyFileContentRequest,
|
|
8524
8539
|
): Promise<EngramAccessOfflineSyncApplyFileContentResponse> {
|
|
8525
|
-
const resolvedNamespace = this.
|
|
8540
|
+
const resolvedNamespace = this.writableNamespaceFor(
|
|
8526
8541
|
options.namespace,
|
|
8527
8542
|
undefined,
|
|
8528
8543
|
options.principal,
|
|
@@ -8573,7 +8588,7 @@ export class EngramAccessService {
|
|
|
8573
8588
|
async offlineSyncApply(
|
|
8574
8589
|
options: EngramAccessOfflineSyncApplyRequest,
|
|
8575
8590
|
): Promise<EngramAccessOfflineSyncApplyResponse> {
|
|
8576
|
-
const resolvedNamespace = this.
|
|
8591
|
+
const resolvedNamespace = this.writableNamespaceFor(
|
|
8577
8592
|
options.namespace,
|
|
8578
8593
|
undefined,
|
|
8579
8594
|
options.principal,
|
|
@@ -8655,7 +8670,7 @@ export class EngramAccessService {
|
|
|
8655
8670
|
);
|
|
8656
8671
|
}
|
|
8657
8672
|
const dryRun = options.dryRun === true;
|
|
8658
|
-
const resolvedNamespace = this.
|
|
8673
|
+
const resolvedNamespace = this.writableNamespaceFor(
|
|
8659
8674
|
options.namespace,
|
|
8660
8675
|
undefined,
|
|
8661
8676
|
options.authenticatedPrincipal,
|
package/src/capabilities.test.ts
CHANGED
|
@@ -96,6 +96,34 @@ test("resolveCapabilities returns a frozen object", () => {
|
|
|
96
96
|
assert.equal(Object.isFrozen(caps), true, "CapabilitySet must be frozen");
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
test("resolveCapabilities is deterministic — same config always yields identical gate values (one-resolution-per-op #1523)", () => {
|
|
100
|
+
// The one-resolution-per-op contract (issue #1523): resolve ONCE at the
|
|
101
|
+
// operation entry, thread the frozen result down. This test proves the
|
|
102
|
+
// resolution is deterministic — calling it twice with the same config
|
|
103
|
+
// produces identical values for every gate, so there is never a reason to
|
|
104
|
+
// re-resolve mid-operation.
|
|
105
|
+
const config = parseConfig({
|
|
106
|
+
rerankCacheEnabled: true,
|
|
107
|
+
recallDirectAnswerEnabled: false,
|
|
108
|
+
recallMmrEnabled: true,
|
|
109
|
+
recallPlannerLlmEnabled: true,
|
|
110
|
+
graphRecallEnabled: true,
|
|
111
|
+
});
|
|
112
|
+
const first = resolveCapabilities(config);
|
|
113
|
+
const second = resolveCapabilities(config);
|
|
114
|
+
for (const field of FIELDS) {
|
|
115
|
+
assert.equal(
|
|
116
|
+
first[field],
|
|
117
|
+
second[field],
|
|
118
|
+
`field ${field} must be deterministic across resolutions`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
// Both must be frozen — a mid-operation mutation would break the contract.
|
|
122
|
+
assert.equal(Object.isFrozen(first), true);
|
|
123
|
+
assert.equal(Object.isFrozen(second), true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
|
|
99
127
|
// ---------------------------------------------------------------------------
|
|
100
128
|
// GraphConstructionCapabilitySet — gate-parity tests (issue #1566 Cluster A).
|
|
101
129
|
//
|
|
@@ -13,7 +13,6 @@ import type { TrustZoneName } from "./trust-zones.js";
|
|
|
13
13
|
type WiringConfig = DirectAnswerWiringInput["config"];
|
|
14
14
|
|
|
15
15
|
const BASE_CONFIG: WiringConfig = {
|
|
16
|
-
recallDirectAnswerEnabled: true,
|
|
17
16
|
recallDirectAnswerTokenOverlapFloor: 0.5,
|
|
18
17
|
recallDirectAnswerImportanceFloor: 0.7,
|
|
19
18
|
recallDirectAnswerAmbiguityMargin: 0.15,
|
|
@@ -105,17 +104,24 @@ test("tryDirectAnswer disabled-path does not call any source accessor", async ()
|
|
|
105
104
|
assert.deepEqual(sources.calls.importance, []);
|
|
106
105
|
});
|
|
107
106
|
|
|
108
|
-
// ──
|
|
107
|
+
// ── One-resolution-per-op (#1523): `enabled` is the sole gate ──────────────
|
|
109
108
|
|
|
110
|
-
test("tryDirectAnswer
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
109
|
+
test("tryDirectAnswer disabled when `enabled: false` even with matching candidates (caps wins over any config-derived value)", async () => {
|
|
110
|
+
// Issue #1523 contract: the resolved capability is the sole gate.
|
|
111
|
+
// `enabled` comes from `resolveCapabilities(config).recallDirectAnswer`,
|
|
112
|
+
// resolved ONCE at the recall entry. This test proves the gate value is
|
|
113
|
+
// authoritative — when it says disabled, the function short-circuits
|
|
114
|
+
// regardless of candidate quality.
|
|
115
|
+
const sources = makeMockSources({
|
|
116
|
+
memories: [makeMemory({ tags: ["pnpm"], content: "remnic uses pnpm" })],
|
|
117
|
+
trustZones: { m1: "trusted" },
|
|
118
|
+
importance: { m1: 0.9 },
|
|
119
|
+
});
|
|
115
120
|
const result = await tryDirectAnswer({
|
|
116
|
-
query: "
|
|
121
|
+
query: "package manager remnic",
|
|
117
122
|
namespace: "default",
|
|
118
|
-
config:
|
|
123
|
+
config: BASE_CONFIG,
|
|
124
|
+
enabled: false,
|
|
119
125
|
sources,
|
|
120
126
|
});
|
|
121
127
|
assert.equal(result.eligible, false);
|
|
@@ -123,9 +129,9 @@ test("tryDirectAnswer falls back to config.recallDirectAnswerEnabled when `enabl
|
|
|
123
129
|
assert.equal(sources.calls.listCandidates, 0);
|
|
124
130
|
});
|
|
125
131
|
|
|
126
|
-
test("tryDirectAnswer
|
|
127
|
-
//
|
|
128
|
-
//
|
|
132
|
+
test("tryDirectAnswer proceeds when `enabled: true` and candidates match", async () => {
|
|
133
|
+
// Same contract, positive case: when the resolved capability says enabled,
|
|
134
|
+
// the function proceeds to materialize candidates.
|
|
129
135
|
const sources = makeMockSources({
|
|
130
136
|
memories: [makeMemory({ tags: ["pnpm"], content: "remnic uses pnpm" })],
|
|
131
137
|
trustZones: { m1: "trusted" },
|
|
@@ -134,7 +140,8 @@ test("tryDirectAnswer falls back to config.recallDirectAnswerEnabled when `enabl
|
|
|
134
140
|
const result = await tryDirectAnswer({
|
|
135
141
|
query: "package manager remnic",
|
|
136
142
|
namespace: "default",
|
|
137
|
-
config: BASE_CONFIG,
|
|
143
|
+
config: BASE_CONFIG,
|
|
144
|
+
enabled: true,
|
|
138
145
|
sources,
|
|
139
146
|
});
|
|
140
147
|
assert.notEqual(result.reason, "disabled");
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Short-circuit contract:
|
|
16
16
|
*
|
|
17
|
-
* - When the resolved gate (`input.enabled
|
|
18
|
-
* `
|
|
17
|
+
* - When the resolved gate (`input.enabled`, projected from
|
|
18
|
+
* `caps.recallDirectAnswer` at the recall-operation entry) is `false`, the
|
|
19
|
+
* function returns the
|
|
19
20
|
* eligibility verdict with reason `"disabled"` without touching any source
|
|
20
21
|
* accessor. This is the documented default.
|
|
21
22
|
* - When enabled, the wiring cheaply drops non-trusted-zone memories
|
|
@@ -74,7 +75,6 @@ export interface DirectAnswerWiringInput {
|
|
|
74
75
|
namespace: string;
|
|
75
76
|
config: Pick<
|
|
76
77
|
PluginConfig,
|
|
77
|
-
| "recallDirectAnswerEnabled"
|
|
78
78
|
| "recallDirectAnswerTokenOverlapFloor"
|
|
79
79
|
| "recallDirectAnswerImportanceFloor"
|
|
80
80
|
| "recallDirectAnswerAmbiguityMargin"
|
|
@@ -82,13 +82,12 @@ export interface DirectAnswerWiringInput {
|
|
|
82
82
|
>;
|
|
83
83
|
/**
|
|
84
84
|
* Direct-answer capability gate, resolved once at the recall-operation entry
|
|
85
|
-
* (issue #1523
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* shape keep identical behavior.
|
|
85
|
+
* via `resolveCapabilities(config).recallDirectAnswer` (issue #1523). This
|
|
86
|
+
* is the sole gate — the function no longer reads the config flag directly.
|
|
87
|
+
* Callers MUST pass the resolved capability so the whole operation agrees on
|
|
88
|
+
* a single gate value.
|
|
90
89
|
*/
|
|
91
|
-
enabled
|
|
90
|
+
enabled: boolean;
|
|
92
91
|
sources: DirectAnswerSources;
|
|
93
92
|
queryEntityRefs?: string[];
|
|
94
93
|
abortSignal?: AbortSignal;
|
|
@@ -105,10 +104,7 @@ export async function tryDirectAnswer(
|
|
|
105
104
|
const { query, namespace, config, enabled, sources, queryEntityRefs, abortSignal } = input;
|
|
106
105
|
|
|
107
106
|
const eligibilityConfig: DirectAnswerConfig = {
|
|
108
|
-
|
|
109
|
-
// flag so callers on the old input shape (config-only, no `enabled`) get
|
|
110
|
-
// identical gating (issue #1523 backward-compat).
|
|
111
|
-
enabled: enabled ?? config.recallDirectAnswerEnabled,
|
|
107
|
+
enabled,
|
|
112
108
|
tokenOverlapFloor: config.recallDirectAnswerTokenOverlapFloor,
|
|
113
109
|
importanceFloor: config.recallDirectAnswerImportanceFloor,
|
|
114
110
|
ambiguityMargin: config.recallDirectAnswerAmbiguityMargin,
|
|
@@ -9,6 +9,7 @@ import { namespaceIdentityToken } from "../namespaces/identity.js";
|
|
|
9
9
|
import { resolveNamespaceStorageRoot } from "../namespaces/storage.js";
|
|
10
10
|
import { displayErrorDetail } from "../runtime/better-sqlite.js";
|
|
11
11
|
import type { PluginConfig } from "../types.js";
|
|
12
|
+
import { getConfiguredNamespaces } from "../scopes/scope-plan.js";
|
|
12
13
|
|
|
13
14
|
export type NamespaceMaintenanceJobName = string;
|
|
14
15
|
|
|
@@ -110,15 +111,7 @@ export function __setNamespaceMaintenanceFsForTest(overrides: Partial<typeof nam
|
|
|
110
111
|
};
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
return Array.from(
|
|
115
|
-
new Set(
|
|
116
|
-
[config.defaultNamespace, config.sharedNamespace, ...config.namespacePolicies.map((policy) => policy.name)]
|
|
117
|
-
.map((value) => value.trim())
|
|
118
|
-
.filter(Boolean)
|
|
119
|
-
)
|
|
120
|
-
);
|
|
121
|
-
}
|
|
114
|
+
|
|
122
115
|
|
|
123
116
|
function inferConfiguredKind(config: PluginConfig, namespace: string): NamespaceKind {
|
|
124
117
|
if (namespace === config.defaultNamespace.trim()) return "default";
|
|
@@ -219,7 +212,7 @@ export async function planNamespaceMaintenance(
|
|
|
219
212
|
// once per configured name. The #1500 contract is "namespaces disabled:
|
|
220
213
|
// maintain the current default storage only," so collapse to the default.
|
|
221
214
|
const configured = config.namespacesEnabled
|
|
222
|
-
?
|
|
215
|
+
? getConfiguredNamespaces(config)
|
|
223
216
|
: [config.defaultNamespace.trim()].filter(Boolean);
|
|
224
217
|
const byNamespace = new Map<string, NamespaceMaintenanceCandidate>();
|
|
225
218
|
const skipped: NamespaceMaintenanceSkippedNamespace[] = [];
|