@remnic/core 9.3.551 → 9.3.552
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-cli.js +3 -3
- package/dist/access-http.d.ts +2 -2
- package/dist/access-http.js +4 -4
- package/dist/access-mcp.d.ts +2 -2
- package/dist/access-mcp.js +3 -3
- package/dist/{access-service-CZfksQuS.d.ts → access-service-D2J9dh_9.d.ts} +3 -1
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +2 -2
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-memory-bridge.js +2 -2
- package/dist/bootstrap.d.ts +1 -1
- package/dist/{chunk-GSFFWOWU.js → chunk-65OLPXBU.js} +5 -4
- package/dist/chunk-65OLPXBU.js.map +1 -0
- package/dist/{chunk-DEGH5BUP.js → chunk-BUUYY2H2.js} +4 -3
- package/dist/chunk-BUUYY2H2.js.map +1 -0
- package/dist/{chunk-2R6MP5C6.js → chunk-GEL3F3RV.js} +4 -4
- package/dist/{chunk-YN3WHXBG.js → chunk-KS7WO6EQ.js} +36 -9
- package/dist/chunk-KS7WO6EQ.js.map +1 -0
- package/dist/{chunk-IC4GELZE.js → chunk-NINRTFSV.js} +5 -2
- package/dist/chunk-NINRTFSV.js.map +1 -0
- package/dist/{chunk-XDMLTNIG.js → chunk-NYV435HC.js} +9 -2
- package/dist/chunk-NYV435HC.js.map +1 -0
- package/dist/{chunk-HWVTS5NO.js → chunk-UZYLX7M6.js} +7 -3
- package/dist/chunk-UZYLX7M6.js.map +1 -0
- package/dist/{cli-9pwA_PXm.d.ts → cli-OrfKXNU4.d.ts} +2 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +5 -5
- package/dist/explicit-capture.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +7 -7
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/namespaces/principal.d.ts +5 -5
- package/dist/namespaces/principal.js +1 -1
- package/dist/{orchestrator-Co9nxRLF.d.ts → orchestrator-DTRQG75J.d.ts} +1 -1
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +2 -2
- package/dist/schemas.d.ts +22 -22
- package/dist/transfer/types.d.ts +12 -12
- package/package.json +1 -1
- package/src/access-http.ts +1 -0
- package/src/access-mcp.ts +1 -0
- package/src/access-service.ts +46 -8
- package/src/active-memory-bridge.test.ts +28 -0
- package/src/active-memory-bridge.ts +5 -2
- package/src/namespaces/principal.test.ts +20 -0
- package/src/namespaces/principal.ts +13 -7
- package/src/orchestrator.ts +13 -1
- package/dist/chunk-DEGH5BUP.js.map +0 -1
- package/dist/chunk-GSFFWOWU.js.map +0 -1
- package/dist/chunk-HWVTS5NO.js.map +0 -1
- package/dist/chunk-IC4GELZE.js.map +0 -1
- package/dist/chunk-XDMLTNIG.js.map +0 -1
- package/dist/chunk-YN3WHXBG.js.map +0 -1
- /package/dist/{chunk-2R6MP5C6.js.map → chunk-GEL3F3RV.js.map} +0 -0
package/dist/transfer/types.d.ts
CHANGED
|
@@ -313,13 +313,13 @@ declare const CapsuleBlockSchema: z.ZodObject<{
|
|
|
313
313
|
peerProfiles: boolean;
|
|
314
314
|
}>;
|
|
315
315
|
}, "strip", z.ZodTypeAny, {
|
|
316
|
+
schemaVersion: string;
|
|
316
317
|
includes: {
|
|
317
318
|
procedural: boolean;
|
|
318
319
|
taxonomy: boolean;
|
|
319
320
|
identityAnchors: boolean;
|
|
320
321
|
peerProfiles: boolean;
|
|
321
322
|
};
|
|
322
|
-
schemaVersion: string;
|
|
323
323
|
id: string;
|
|
324
324
|
description: string;
|
|
325
325
|
version: string;
|
|
@@ -334,13 +334,13 @@ declare const CapsuleBlockSchema: z.ZodObject<{
|
|
|
334
334
|
directAnswerEnabled: boolean;
|
|
335
335
|
};
|
|
336
336
|
}, {
|
|
337
|
+
schemaVersion: string;
|
|
337
338
|
includes: {
|
|
338
339
|
procedural: boolean;
|
|
339
340
|
taxonomy: boolean;
|
|
340
341
|
identityAnchors: boolean;
|
|
341
342
|
peerProfiles: boolean;
|
|
342
343
|
};
|
|
343
|
-
schemaVersion: string;
|
|
344
344
|
id: string;
|
|
345
345
|
description: string;
|
|
346
346
|
version: string;
|
|
@@ -464,13 +464,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
464
464
|
peerProfiles: boolean;
|
|
465
465
|
}>;
|
|
466
466
|
}, "strip", z.ZodTypeAny, {
|
|
467
|
+
schemaVersion: string;
|
|
467
468
|
includes: {
|
|
468
469
|
procedural: boolean;
|
|
469
470
|
taxonomy: boolean;
|
|
470
471
|
identityAnchors: boolean;
|
|
471
472
|
peerProfiles: boolean;
|
|
472
473
|
};
|
|
473
|
-
schemaVersion: string;
|
|
474
474
|
id: string;
|
|
475
475
|
description: string;
|
|
476
476
|
version: string;
|
|
@@ -485,13 +485,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
485
485
|
directAnswerEnabled: boolean;
|
|
486
486
|
};
|
|
487
487
|
}, {
|
|
488
|
+
schemaVersion: string;
|
|
488
489
|
includes: {
|
|
489
490
|
procedural: boolean;
|
|
490
491
|
taxonomy: boolean;
|
|
491
492
|
identityAnchors: boolean;
|
|
492
493
|
peerProfiles: boolean;
|
|
493
494
|
};
|
|
494
|
-
schemaVersion: string;
|
|
495
495
|
id: string;
|
|
496
496
|
description: string;
|
|
497
497
|
version: string;
|
|
@@ -518,13 +518,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
518
518
|
pluginVersion: string;
|
|
519
519
|
includesTranscripts: boolean;
|
|
520
520
|
capsule: {
|
|
521
|
+
schemaVersion: string;
|
|
521
522
|
includes: {
|
|
522
523
|
procedural: boolean;
|
|
523
524
|
taxonomy: boolean;
|
|
524
525
|
identityAnchors: boolean;
|
|
525
526
|
peerProfiles: boolean;
|
|
526
527
|
};
|
|
527
|
-
schemaVersion: string;
|
|
528
528
|
id: string;
|
|
529
529
|
description: string;
|
|
530
530
|
version: string;
|
|
@@ -551,13 +551,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
551
551
|
pluginVersion: string;
|
|
552
552
|
includesTranscripts: boolean;
|
|
553
553
|
capsule: {
|
|
554
|
+
schemaVersion: string;
|
|
554
555
|
includes: {
|
|
555
556
|
procedural: boolean;
|
|
556
557
|
taxonomy: boolean;
|
|
557
558
|
identityAnchors: boolean;
|
|
558
559
|
peerProfiles: boolean;
|
|
559
560
|
};
|
|
560
|
-
schemaVersion: string;
|
|
561
561
|
id: string;
|
|
562
562
|
description: string;
|
|
563
563
|
version: string;
|
|
@@ -683,13 +683,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
683
683
|
peerProfiles: boolean;
|
|
684
684
|
}>;
|
|
685
685
|
}, "strip", z.ZodTypeAny, {
|
|
686
|
+
schemaVersion: string;
|
|
686
687
|
includes: {
|
|
687
688
|
procedural: boolean;
|
|
688
689
|
taxonomy: boolean;
|
|
689
690
|
identityAnchors: boolean;
|
|
690
691
|
peerProfiles: boolean;
|
|
691
692
|
};
|
|
692
|
-
schemaVersion: string;
|
|
693
693
|
id: string;
|
|
694
694
|
description: string;
|
|
695
695
|
version: string;
|
|
@@ -704,13 +704,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
704
704
|
directAnswerEnabled: boolean;
|
|
705
705
|
};
|
|
706
706
|
}, {
|
|
707
|
+
schemaVersion: string;
|
|
707
708
|
includes: {
|
|
708
709
|
procedural: boolean;
|
|
709
710
|
taxonomy: boolean;
|
|
710
711
|
identityAnchors: boolean;
|
|
711
712
|
peerProfiles: boolean;
|
|
712
713
|
};
|
|
713
|
-
schemaVersion: string;
|
|
714
714
|
id: string;
|
|
715
715
|
description: string;
|
|
716
716
|
version: string;
|
|
@@ -737,13 +737,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
737
737
|
pluginVersion: string;
|
|
738
738
|
includesTranscripts: boolean;
|
|
739
739
|
capsule: {
|
|
740
|
+
schemaVersion: string;
|
|
740
741
|
includes: {
|
|
741
742
|
procedural: boolean;
|
|
742
743
|
taxonomy: boolean;
|
|
743
744
|
identityAnchors: boolean;
|
|
744
745
|
peerProfiles: boolean;
|
|
745
746
|
};
|
|
746
|
-
schemaVersion: string;
|
|
747
747
|
id: string;
|
|
748
748
|
description: string;
|
|
749
749
|
version: string;
|
|
@@ -770,13 +770,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
770
770
|
pluginVersion: string;
|
|
771
771
|
includesTranscripts: boolean;
|
|
772
772
|
capsule: {
|
|
773
|
+
schemaVersion: string;
|
|
773
774
|
includes: {
|
|
774
775
|
procedural: boolean;
|
|
775
776
|
taxonomy: boolean;
|
|
776
777
|
identityAnchors: boolean;
|
|
777
778
|
peerProfiles: boolean;
|
|
778
779
|
};
|
|
779
|
-
schemaVersion: string;
|
|
780
780
|
id: string;
|
|
781
781
|
description: string;
|
|
782
782
|
version: string;
|
|
@@ -815,13 +815,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
815
815
|
pluginVersion: string;
|
|
816
816
|
includesTranscripts: boolean;
|
|
817
817
|
capsule: {
|
|
818
|
+
schemaVersion: string;
|
|
818
819
|
includes: {
|
|
819
820
|
procedural: boolean;
|
|
820
821
|
taxonomy: boolean;
|
|
821
822
|
identityAnchors: boolean;
|
|
822
823
|
peerProfiles: boolean;
|
|
823
824
|
};
|
|
824
|
-
schemaVersion: string;
|
|
825
825
|
id: string;
|
|
826
826
|
description: string;
|
|
827
827
|
version: string;
|
|
@@ -854,13 +854,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
854
854
|
pluginVersion: string;
|
|
855
855
|
includesTranscripts: boolean;
|
|
856
856
|
capsule: {
|
|
857
|
+
schemaVersion: string;
|
|
857
858
|
includes: {
|
|
858
859
|
procedural: boolean;
|
|
859
860
|
taxonomy: boolean;
|
|
860
861
|
identityAnchors: boolean;
|
|
861
862
|
peerProfiles: boolean;
|
|
862
863
|
};
|
|
863
|
-
schemaVersion: string;
|
|
864
864
|
id: string;
|
|
865
865
|
description: string;
|
|
866
866
|
version: string;
|
package/package.json
CHANGED
package/src/access-http.ts
CHANGED
|
@@ -861,6 +861,7 @@ export class EngramAccessHttpServer {
|
|
|
861
861
|
const response = await this.service.recallExplain({
|
|
862
862
|
sessionKey: body.sessionKey,
|
|
863
863
|
namespace: this.resolveNamespace(req, body.namespace),
|
|
864
|
+
authenticatedPrincipal: this.resolveRequestPrincipal(req),
|
|
864
865
|
});
|
|
865
866
|
this.respondJson(res, 200, response);
|
|
866
867
|
return;
|
package/src/access-mcp.ts
CHANGED
|
@@ -2136,6 +2136,7 @@ export class EngramMcpServer {
|
|
|
2136
2136
|
return this.service.recallExplain({
|
|
2137
2137
|
sessionKey: typeof args.sessionKey === "string" ? args.sessionKey : undefined,
|
|
2138
2138
|
namespace: typeof args.namespace === "string" ? args.namespace : undefined,
|
|
2139
|
+
authenticatedPrincipal: effectivePrincipal,
|
|
2139
2140
|
});
|
|
2140
2141
|
case "engram.set_coding_context": {
|
|
2141
2142
|
// Issue #569 PR 7 — MCP tool for clients that don't ship cwd.
|
package/src/access-service.ts
CHANGED
|
@@ -354,6 +354,8 @@ export interface EngramAccessRecallResponse {
|
|
|
354
354
|
export interface EngramAccessRecallExplainRequest {
|
|
355
355
|
sessionKey?: string;
|
|
356
356
|
namespace?: string;
|
|
357
|
+
/** Caller principal for namespace access checks. Transport-bound; never from untrusted payloads. */
|
|
358
|
+
authenticatedPrincipal?: string;
|
|
357
359
|
}
|
|
358
360
|
|
|
359
361
|
export interface EngramAccessRecallExplainResponse {
|
|
@@ -1094,7 +1096,7 @@ export class EngramAccessService {
|
|
|
1094
1096
|
return resolved;
|
|
1095
1097
|
}
|
|
1096
1098
|
|
|
1097
|
-
private resolveRequestPrincipal(sessionKey: string | undefined, authenticatedPrincipal?: string): string {
|
|
1099
|
+
private resolveRequestPrincipal(sessionKey: string | undefined, authenticatedPrincipal?: string): string | undefined {
|
|
1098
1100
|
const trusted = authenticatedPrincipal?.trim();
|
|
1099
1101
|
if (trusted) return trusted;
|
|
1100
1102
|
return resolvePrincipal(sessionKey, this.orchestrator.config);
|
|
@@ -1763,7 +1765,13 @@ export class EngramAccessService {
|
|
|
1763
1765
|
const normalizedRequest = { ...request, query };
|
|
1764
1766
|
const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
|
|
1765
1767
|
const principal = this.resolveRequestPrincipal(request.sessionKey, authenticatedPrincipal);
|
|
1766
|
-
|
|
1768
|
+
if (this.orchestrator.config.namespacesEnabled && !principal) {
|
|
1769
|
+
throw new EngramAccessInputError(
|
|
1770
|
+
"authentication required: namespaces are enabled and no principal was supplied",
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
const budgetLockPrincipal = principal ?? "default";
|
|
1774
|
+
return this.withBudgetLock(budgetLockPrincipal, async () => {
|
|
1767
1775
|
let budgetRecordPrincipal: string | null = null;
|
|
1768
1776
|
const response = await this.handleIdempotentRead({
|
|
1769
1777
|
operation: "recall",
|
|
@@ -1839,7 +1847,13 @@ export class EngramAccessService {
|
|
|
1839
1847
|
// Normalize mode early so that no_recall / invalid modes skip budget
|
|
1840
1848
|
// accounting (Codex P1: budget recorded before mode validation).
|
|
1841
1849
|
const mode = this.normalizeRecallMode(request.mode);
|
|
1842
|
-
const
|
|
1850
|
+
const maybePrincipal = this.resolveRequestPrincipal(request.sessionKey, authenticatedPrincipal);
|
|
1851
|
+
if (this.orchestrator.config.namespacesEnabled && !maybePrincipal) {
|
|
1852
|
+
throw new EngramAccessInputError(
|
|
1853
|
+
"authentication required: namespaces are enabled and no principal was supplied",
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
const principal = maybePrincipal ?? "default";
|
|
1843
1857
|
const principalNamespace = defaultNamespaceForPrincipal(principal, this.orchestrator.config);
|
|
1844
1858
|
// Skip budget checks for modes that never perform a cross-namespace read.
|
|
1845
1859
|
const modeSkipsBudget = mode === "no_recall";
|
|
@@ -2096,7 +2110,7 @@ export class EngramAccessService {
|
|
|
2096
2110
|
let auditAnomalies: AccessAuditResult["anomalies"] | undefined;
|
|
2097
2111
|
if (this.auditAdapter) {
|
|
2098
2112
|
try {
|
|
2099
|
-
const resolvedAgentId = principal;
|
|
2113
|
+
const resolvedAgentId = principal ?? "__anonymous__";
|
|
2100
2114
|
const auditEntry = {
|
|
2101
2115
|
ts: new Date().toISOString(),
|
|
2102
2116
|
sessionKey: request.sessionKey ?? "",
|
|
@@ -2160,11 +2174,19 @@ export class EngramAccessService {
|
|
|
2160
2174
|
const requestedNamespace = request.namespace?.trim()
|
|
2161
2175
|
? this.resolveNamespace(request.namespace)
|
|
2162
2176
|
: undefined;
|
|
2177
|
+
const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
|
|
2178
|
+
const principal =
|
|
2179
|
+
authenticatedPrincipal
|
|
2180
|
+
|| resolvePrincipal(request.sessionKey, this.orchestrator.config);
|
|
2163
2181
|
if (requestedNamespace) {
|
|
2164
|
-
const principal = resolvePrincipal(request.sessionKey, this.orchestrator.config);
|
|
2165
2182
|
if (!canReadNamespace(principal, requestedNamespace, this.orchestrator.config)) {
|
|
2166
2183
|
return { found: false };
|
|
2167
2184
|
}
|
|
2185
|
+
} else if (
|
|
2186
|
+
this.orchestrator.config.namespacesEnabled
|
|
2187
|
+
&& !principal
|
|
2188
|
+
) {
|
|
2189
|
+
return { found: false };
|
|
2168
2190
|
}
|
|
2169
2191
|
const snapshot = request.sessionKey
|
|
2170
2192
|
? (() => {
|
|
@@ -2179,13 +2201,29 @@ export class EngramAccessService {
|
|
|
2179
2201
|
if (!requestedNamespace) return candidate;
|
|
2180
2202
|
return candidate.namespace === requestedNamespace ? candidate : null;
|
|
2181
2203
|
})();
|
|
2182
|
-
const
|
|
2204
|
+
const readableSnapshot = (() => {
|
|
2205
|
+
if (!snapshot || !this.orchestrator.config.namespacesEnabled) return snapshot;
|
|
2206
|
+
const snapshotNamespace = snapshot.namespace ?? this.orchestrator.config.defaultNamespace;
|
|
2207
|
+
return canReadNamespace(principal, snapshotNamespace, this.orchestrator.config)
|
|
2208
|
+
? snapshot
|
|
2209
|
+
: null;
|
|
2210
|
+
})();
|
|
2211
|
+
const namespace = (() => {
|
|
2212
|
+
if (requestedNamespace) return requestedNamespace;
|
|
2213
|
+
if (readableSnapshot?.namespace) return readableSnapshot.namespace;
|
|
2214
|
+
const fallbackNamespace = this.orchestrator.config.defaultNamespace;
|
|
2215
|
+
if (!this.orchestrator.config.namespacesEnabled) return fallbackNamespace;
|
|
2216
|
+
return canReadNamespace(principal, fallbackNamespace, this.orchestrator.config)
|
|
2217
|
+
? fallbackNamespace
|
|
2218
|
+
: null;
|
|
2219
|
+
})();
|
|
2220
|
+
if (!namespace) return { found: false };
|
|
2183
2221
|
const [intent, graph] = await Promise.all([
|
|
2184
2222
|
this.orchestrator.getLastIntentSnapshot(namespace),
|
|
2185
2223
|
this.orchestrator.getLastGraphRecallSnapshot(namespace),
|
|
2186
2224
|
]);
|
|
2187
|
-
if (!
|
|
2188
|
-
return { found: true, snapshot:
|
|
2225
|
+
if (!readableSnapshot && !intent && !graph) return { found: false };
|
|
2226
|
+
return { found: true, snapshot: readableSnapshot ?? undefined, intent, graph };
|
|
2189
2227
|
}
|
|
2190
2228
|
|
|
2191
2229
|
async recallTierExplain(
|
|
@@ -134,6 +134,34 @@ test("recallForActiveMemory ignores explicit namespace filters when namespaces a
|
|
|
134
134
|
assert.deepEqual(receivedNamespaces, ["self-namespace"]);
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
+
test("recallForActiveMemory denies blank session keys when namespaces are enabled", async () => {
|
|
138
|
+
const orchestrator = {
|
|
139
|
+
config: {
|
|
140
|
+
namespacesEnabled: true,
|
|
141
|
+
defaultNamespace: "default",
|
|
142
|
+
sharedNamespace: "shared",
|
|
143
|
+
namespacePolicies: [],
|
|
144
|
+
principalFromSessionKeyMode: "disabled",
|
|
145
|
+
principalFromSessionKeyRules: [],
|
|
146
|
+
},
|
|
147
|
+
searchAcrossNamespaces: async () => {
|
|
148
|
+
throw new Error("search should not run without an authenticated principal");
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
await assert.rejects(
|
|
153
|
+
() =>
|
|
154
|
+
recallForActiveMemory(orchestrator as never, {
|
|
155
|
+
query: "api docs",
|
|
156
|
+
sessionKey: " ",
|
|
157
|
+
filters: {
|
|
158
|
+
namespace: "default",
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
/authentication required/,
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
137
165
|
test("recallForActiveMemory marks results truncated when the underlying recall exceeds the requested limit", async () => {
|
|
138
166
|
const orchestrator = {
|
|
139
167
|
resolveSelfNamespace: () => "session-namespace",
|
|
@@ -38,7 +38,7 @@ export interface ActiveMemoryRecallParams {
|
|
|
38
38
|
|
|
39
39
|
interface ActiveMemoryScopedOrchestrator {
|
|
40
40
|
config?: PluginConfig;
|
|
41
|
-
resolvePrincipal?: (sessionKey?: string) => string;
|
|
41
|
+
resolvePrincipal?: (sessionKey?: string) => string | undefined;
|
|
42
42
|
resolveSelfNamespace?: (sessionKey?: string) => string;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -105,6 +105,9 @@ function resolveActiveMemoryNamespace(
|
|
|
105
105
|
typeof orchestrator.resolvePrincipal === "function"
|
|
106
106
|
? orchestrator.resolvePrincipal(sessionKey)
|
|
107
107
|
: resolvePrincipal(sessionKey, config);
|
|
108
|
+
if (config.namespacesEnabled && !principal) {
|
|
109
|
+
throw new Error("authentication required: namespaces are enabled and no principal was supplied");
|
|
110
|
+
}
|
|
108
111
|
if (explicitNamespace) {
|
|
109
112
|
if (!canReadNamespace(principal, explicitNamespace, config)) {
|
|
110
113
|
throw new Error(`namespace ${explicitNamespace} is not readable for principal ${principal}`);
|
|
@@ -120,7 +123,7 @@ function resolveActiveMemoryNamespace(
|
|
|
120
123
|
export async function recallForActiveMemory(
|
|
121
124
|
orchestrator: {
|
|
122
125
|
config?: PluginConfig;
|
|
123
|
-
resolvePrincipal?: (sessionKey?: string) => string;
|
|
126
|
+
resolvePrincipal?: (sessionKey?: string) => string | undefined;
|
|
124
127
|
resolveSelfNamespace?: (sessionKey?: string) => string;
|
|
125
128
|
searchAcrossNamespaces: (params: {
|
|
126
129
|
query: string;
|
|
@@ -196,6 +196,26 @@ test("canReadNamespace: namespacesEnabled=true + no policy for namespace → all
|
|
|
196
196
|
assert.equal(canReadNamespace("alice", "unknown-ns", config), false, "unknown namespace without policy is denied");
|
|
197
197
|
});
|
|
198
198
|
|
|
199
|
+
test("resolvePrincipal: missing session key cannot read implicit namespaces when namespaces are enabled", () => {
|
|
200
|
+
const config = makeConfig(true, []);
|
|
201
|
+
|
|
202
|
+
const principal = resolvePrincipal(undefined, config);
|
|
203
|
+
|
|
204
|
+
assert.equal(principal, undefined);
|
|
205
|
+
assert.equal(canReadNamespace(principal, "default", config), false);
|
|
206
|
+
assert.equal(canReadNamespace(principal, "shared", config), false);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("resolvePrincipal: blank session key cannot read implicit namespaces when namespaces are enabled", () => {
|
|
210
|
+
const config = makeConfig(true, []);
|
|
211
|
+
|
|
212
|
+
const principal = resolvePrincipal(" ", config);
|
|
213
|
+
|
|
214
|
+
assert.equal(principal, undefined);
|
|
215
|
+
assert.equal(canReadNamespace(principal, "default", config), false);
|
|
216
|
+
assert.equal(canReadNamespace(principal, "shared", config), false);
|
|
217
|
+
});
|
|
218
|
+
|
|
199
219
|
test("resolvePrincipal: safe regex rules can resolve a principal", () => {
|
|
200
220
|
const config = makeConfig(true, [], {
|
|
201
221
|
principalFromSessionKeyMode: "regex",
|
|
@@ -12,13 +12,15 @@ function compileSafePrincipalRegex(pattern: string): RegExp | null {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function resolvePrincipal(sessionKey: string | undefined, config: PluginConfig): string {
|
|
15
|
+
export function resolvePrincipal(sessionKey: string | undefined, config: PluginConfig): string | undefined {
|
|
16
16
|
if (!config.namespacesEnabled) return "default";
|
|
17
|
-
const sk = sessionKey
|
|
17
|
+
const sk = typeof sessionKey === "string" && sessionKey.trim().length > 0
|
|
18
|
+
? sessionKey
|
|
19
|
+
: "";
|
|
18
20
|
const mode = config.principalFromSessionKeyMode;
|
|
19
21
|
const rules = config.principalFromSessionKeyRules ?? [];
|
|
20
22
|
|
|
21
|
-
if (!sk) return
|
|
23
|
+
if (!sk) return undefined;
|
|
22
24
|
|
|
23
25
|
if (mode === "prefix") {
|
|
24
26
|
for (const r of rules) {
|
|
@@ -48,15 +50,17 @@ export function resolvePrincipal(sessionKey: string | undefined, config: PluginC
|
|
|
48
50
|
return "default";
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
export function canReadNamespace(principal: string, namespace: string, config: PluginConfig): boolean {
|
|
53
|
+
export function canReadNamespace(principal: string | undefined, namespace: string, config: PluginConfig): boolean {
|
|
52
54
|
if (!config.namespacesEnabled) return true;
|
|
55
|
+
if (!principal) return false;
|
|
53
56
|
const policy = config.namespacePolicies.find((p) => p.name === namespace);
|
|
54
57
|
if (!policy) return namespace === config.defaultNamespace || namespace === config.sharedNamespace;
|
|
55
58
|
return policy.readPrincipals.includes(principal) || policy.readPrincipals.includes("*");
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
export function canWriteNamespace(principal: string, namespace: string, config: PluginConfig): boolean {
|
|
61
|
+
export function canWriteNamespace(principal: string | undefined, namespace: string, config: PluginConfig): boolean {
|
|
59
62
|
if (!config.namespacesEnabled) return true;
|
|
63
|
+
if (!principal) return false;
|
|
60
64
|
const policy = config.namespacePolicies.find((p) => p.name === namespace);
|
|
61
65
|
if (!policy) return namespace === config.defaultNamespace;
|
|
62
66
|
return policy.writePrincipals.includes(principal) || policy.writePrincipals.includes("*");
|
|
@@ -69,15 +73,17 @@ export function canWriteNamespace(principal: string, namespace: string, config:
|
|
|
69
73
|
* - If there's a namespace policy with the same name as the principal, use it.
|
|
70
74
|
* - Otherwise use config.defaultNamespace.
|
|
71
75
|
*/
|
|
72
|
-
export function defaultNamespaceForPrincipal(principal: string, config: PluginConfig): string {
|
|
76
|
+
export function defaultNamespaceForPrincipal(principal: string | undefined, config: PluginConfig): string {
|
|
73
77
|
if (!config.namespacesEnabled) return config.defaultNamespace;
|
|
78
|
+
if (!principal) return config.defaultNamespace;
|
|
74
79
|
const exists = config.namespacePolicies.some((p) => p.name === principal);
|
|
75
80
|
return exists ? principal : config.defaultNamespace;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
export function recallNamespacesForPrincipal(principal: string, config: PluginConfig): string[] {
|
|
83
|
+
export function recallNamespacesForPrincipal(principal: string | undefined, config: PluginConfig): string[] {
|
|
79
84
|
const out: string[] = [];
|
|
80
85
|
if (!config.namespacesEnabled) return [config.defaultNamespace];
|
|
86
|
+
if (!principal) return out;
|
|
81
87
|
|
|
82
88
|
const selfNs = defaultNamespaceForPrincipal(principal, config);
|
|
83
89
|
if (config.defaultRecallNamespaces.includes("self") && canReadNamespace(principal, selfNs, config)) {
|
package/src/orchestrator.ts
CHANGED
|
@@ -1686,7 +1686,7 @@ export class Orchestrator {
|
|
|
1686
1686
|
this._recallWorkspaceOverrides.delete(sessionKey);
|
|
1687
1687
|
}
|
|
1688
1688
|
|
|
1689
|
-
resolvePrincipal(sessionKey?: string): string {
|
|
1689
|
+
resolvePrincipal(sessionKey?: string): string | undefined {
|
|
1690
1690
|
return resolvePrincipal(sessionKey, this.config);
|
|
1691
1691
|
}
|
|
1692
1692
|
|
|
@@ -4841,6 +4841,15 @@ export class Orchestrator {
|
|
|
4841
4841
|
options.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
4842
4842
|
}
|
|
4843
4843
|
|
|
4844
|
+
const principal =
|
|
4845
|
+
typeof options.principalOverride === "string" &&
|
|
4846
|
+
options.principalOverride.length > 0
|
|
4847
|
+
? options.principalOverride
|
|
4848
|
+
: resolvePrincipal(sessionKey, this.config);
|
|
4849
|
+
if (this.config.namespacesEnabled && !principal) {
|
|
4850
|
+
throw new Error("authentication required: namespaces are enabled and no principal was supplied");
|
|
4851
|
+
}
|
|
4852
|
+
|
|
4844
4853
|
// Wait for initialization to complete before attempting recall. The timeout
|
|
4845
4854
|
// is configurable so OpenClaw's per-hook budget and Remnic's internal init
|
|
4846
4855
|
// gate can stay aligned during cold starts.
|
|
@@ -6451,6 +6460,9 @@ export class Orchestrator {
|
|
|
6451
6460
|
&& options.principalOverride.length > 0
|
|
6452
6461
|
? options.principalOverride
|
|
6453
6462
|
: resolvePrincipal(sessionKey, this.config);
|
|
6463
|
+
if (this.config.namespacesEnabled && !principal) {
|
|
6464
|
+
throw new Error("authentication required: namespaces are enabled and no principal was supplied");
|
|
6465
|
+
}
|
|
6454
6466
|
const namespaceOverride = options.namespace?.trim() || undefined;
|
|
6455
6467
|
const readableRecallNamespaces = recallNamespacesForPrincipal(
|
|
6456
6468
|
principal,
|