@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.
Files changed (54) hide show
  1. package/dist/access-cli.js +3 -3
  2. package/dist/access-http.d.ts +2 -2
  3. package/dist/access-http.js +4 -4
  4. package/dist/access-mcp.d.ts +2 -2
  5. package/dist/access-mcp.js +3 -3
  6. package/dist/{access-service-CZfksQuS.d.ts → access-service-D2J9dh_9.d.ts} +3 -1
  7. package/dist/access-service.d.ts +2 -2
  8. package/dist/access-service.js +2 -2
  9. package/dist/active-memory-bridge.d.ts +1 -1
  10. package/dist/active-memory-bridge.js +2 -2
  11. package/dist/bootstrap.d.ts +1 -1
  12. package/dist/{chunk-GSFFWOWU.js → chunk-65OLPXBU.js} +5 -4
  13. package/dist/chunk-65OLPXBU.js.map +1 -0
  14. package/dist/{chunk-DEGH5BUP.js → chunk-BUUYY2H2.js} +4 -3
  15. package/dist/chunk-BUUYY2H2.js.map +1 -0
  16. package/dist/{chunk-2R6MP5C6.js → chunk-GEL3F3RV.js} +4 -4
  17. package/dist/{chunk-YN3WHXBG.js → chunk-KS7WO6EQ.js} +36 -9
  18. package/dist/chunk-KS7WO6EQ.js.map +1 -0
  19. package/dist/{chunk-IC4GELZE.js → chunk-NINRTFSV.js} +5 -2
  20. package/dist/chunk-NINRTFSV.js.map +1 -0
  21. package/dist/{chunk-XDMLTNIG.js → chunk-NYV435HC.js} +9 -2
  22. package/dist/chunk-NYV435HC.js.map +1 -0
  23. package/dist/{chunk-HWVTS5NO.js → chunk-UZYLX7M6.js} +7 -3
  24. package/dist/chunk-UZYLX7M6.js.map +1 -0
  25. package/dist/{cli-9pwA_PXm.d.ts → cli-OrfKXNU4.d.ts} +2 -2
  26. package/dist/cli.d.ts +3 -3
  27. package/dist/cli.js +5 -5
  28. package/dist/explicit-capture.d.ts +1 -1
  29. package/dist/index.d.ts +3 -3
  30. package/dist/index.js +7 -7
  31. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  32. package/dist/namespaces/principal.d.ts +5 -5
  33. package/dist/namespaces/principal.js +1 -1
  34. package/dist/{orchestrator-Co9nxRLF.d.ts → orchestrator-DTRQG75J.d.ts} +1 -1
  35. package/dist/orchestrator.d.ts +1 -1
  36. package/dist/orchestrator.js +2 -2
  37. package/dist/schemas.d.ts +22 -22
  38. package/dist/transfer/types.d.ts +12 -12
  39. package/package.json +1 -1
  40. package/src/access-http.ts +1 -0
  41. package/src/access-mcp.ts +1 -0
  42. package/src/access-service.ts +46 -8
  43. package/src/active-memory-bridge.test.ts +28 -0
  44. package/src/active-memory-bridge.ts +5 -2
  45. package/src/namespaces/principal.test.ts +20 -0
  46. package/src/namespaces/principal.ts +13 -7
  47. package/src/orchestrator.ts +13 -1
  48. package/dist/chunk-DEGH5BUP.js.map +0 -1
  49. package/dist/chunk-GSFFWOWU.js.map +0 -1
  50. package/dist/chunk-HWVTS5NO.js.map +0 -1
  51. package/dist/chunk-IC4GELZE.js.map +0 -1
  52. package/dist/chunk-XDMLTNIG.js.map +0 -1
  53. package/dist/chunk-YN3WHXBG.js.map +0 -1
  54. /package/dist/{chunk-2R6MP5C6.js.map → chunk-GEL3F3RV.js.map} +0 -0
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.3.551",
3
+ "version": "9.3.552",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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.
@@ -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
- return this.withBudgetLock(principal, async () => {
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 principal = this.resolveRequestPrincipal(request.sessionKey, authenticatedPrincipal);
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 namespace = requestedNamespace ?? snapshot?.namespace ?? this.orchestrator.config.defaultNamespace;
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 (!snapshot && !intent && !graph) return { found: false };
2188
- return { found: true, snapshot: snapshot ?? undefined, intent, graph };
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 "default";
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)) {
@@ -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,