@remnic/core 9.3.656 → 9.3.658

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 (77) hide show
  1. package/dist/access-cli.js +12 -12
  2. package/dist/access-http.js +6 -6
  3. package/dist/access-mcp.js +5 -5
  4. package/dist/access-service.js +4 -4
  5. package/dist/briefing.d.ts +1 -1
  6. package/dist/briefing.js +2 -2
  7. package/dist/causal-consolidation.js +3 -3
  8. package/dist/{chunk-YEZHZCUO.js → chunk-2VCTTEJM.js} +3 -3
  9. package/dist/{chunk-DR67OK4E.js → chunk-3R6OP33G.js} +3 -3
  10. package/dist/{chunk-JSVFEHLL.js → chunk-46RXRASB.js} +2 -2
  11. package/dist/{chunk-NXCK7DO7.js → chunk-4PLOQDBB.js} +54 -18
  12. package/dist/chunk-4PLOQDBB.js.map +1 -0
  13. package/dist/{chunk-54XF2FY7.js → chunk-5PFIMBJJ.js} +12 -12
  14. package/dist/{chunk-4UL7VPTD.js → chunk-6M4LYWA2.js} +4 -4
  15. package/dist/{chunk-DIBWFCLA.js → chunk-7KSPKZIQ.js} +3 -3
  16. package/dist/{chunk-RDW5G6DO.js → chunk-7VWDC7AD.js} +10 -11
  17. package/dist/chunk-7VWDC7AD.js.map +1 -0
  18. package/dist/{chunk-SWDHVH2P.js → chunk-BKRIAXTU.js} +2 -2
  19. package/dist/{chunk-WWMHAMAY.js → chunk-BNUAOLDK.js} +2 -2
  20. package/dist/{chunk-GCYFUTUC.js → chunk-FIS5RT6K.js} +2 -2
  21. package/dist/{chunk-IOZ5WBWD.js → chunk-G2VVBWFU.js} +7 -5
  22. package/dist/chunk-G2VVBWFU.js.map +1 -0
  23. package/dist/{chunk-GSHW5VVD.js → chunk-GGL7R2L2.js} +4 -4
  24. package/dist/{chunk-WIKMCJUR.js → chunk-JI3LQFJH.js} +2 -2
  25. package/dist/{chunk-Z6UDTNY6.js → chunk-KI6QM5AV.js} +6 -6
  26. package/dist/chunk-KI6QM5AV.js.map +1 -0
  27. package/dist/{chunk-2BD7DG37.js → chunk-MBZAESQ3.js} +2 -2
  28. package/dist/{chunk-VAEAGTEQ.js → chunk-QFKRE7AU.js} +3 -3
  29. package/dist/{chunk-AGJKWOKV.js → chunk-RVT6U6PV.js} +2 -2
  30. package/dist/{chunk-TFFZUFEP.js → chunk-VJYFXDCZ.js} +2 -2
  31. package/dist/{chunk-QZRKNA5F.js → chunk-ZCMO46YY.js} +2 -2
  32. package/dist/cli.js +15 -15
  33. package/dist/compounding/engine.js +2 -2
  34. package/dist/connectors/codex-materialize-runner.js +2 -2
  35. package/dist/connectors/index.js +2 -2
  36. package/dist/entity-retrieval.js +2 -2
  37. package/dist/index.js +20 -20
  38. package/dist/maintenance/memory-governance.js +2 -2
  39. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +2 -2
  40. package/dist/maintenance/rebuild-memory-projection.js +3 -3
  41. package/dist/namespaces/migrate.js +3 -3
  42. package/dist/namespaces/storage.js +2 -2
  43. package/dist/operator-toolkit.js +5 -5
  44. package/dist/orchestrator.js +10 -10
  45. package/dist/schemas.d.ts +22 -22
  46. package/dist/semantic-consolidation.js +3 -3
  47. package/dist/semantic-rule-promotion.js +2 -2
  48. package/dist/semantic-rule-verifier.js +2 -2
  49. package/dist/storage.d.ts +27 -5
  50. package/dist/storage.js +1 -1
  51. package/dist/transfer/types.d.ts +12 -12
  52. package/dist/verified-recall.js +2 -2
  53. package/package.json +1 -1
  54. package/src/briefing.ts +8 -4
  55. package/src/entity-retrieval.ts +8 -3
  56. package/src/orchestrator.ts +1 -2
  57. package/src/storage.ts +79 -26
  58. package/dist/chunk-IOZ5WBWD.js.map +0 -1
  59. package/dist/chunk-NXCK7DO7.js.map +0 -1
  60. package/dist/chunk-RDW5G6DO.js.map +0 -1
  61. package/dist/chunk-Z6UDTNY6.js.map +0 -1
  62. /package/dist/{chunk-YEZHZCUO.js.map → chunk-2VCTTEJM.js.map} +0 -0
  63. /package/dist/{chunk-DR67OK4E.js.map → chunk-3R6OP33G.js.map} +0 -0
  64. /package/dist/{chunk-JSVFEHLL.js.map → chunk-46RXRASB.js.map} +0 -0
  65. /package/dist/{chunk-54XF2FY7.js.map → chunk-5PFIMBJJ.js.map} +0 -0
  66. /package/dist/{chunk-4UL7VPTD.js.map → chunk-6M4LYWA2.js.map} +0 -0
  67. /package/dist/{chunk-DIBWFCLA.js.map → chunk-7KSPKZIQ.js.map} +0 -0
  68. /package/dist/{chunk-SWDHVH2P.js.map → chunk-BKRIAXTU.js.map} +0 -0
  69. /package/dist/{chunk-WWMHAMAY.js.map → chunk-BNUAOLDK.js.map} +0 -0
  70. /package/dist/{chunk-GCYFUTUC.js.map → chunk-FIS5RT6K.js.map} +0 -0
  71. /package/dist/{chunk-GSHW5VVD.js.map → chunk-GGL7R2L2.js.map} +0 -0
  72. /package/dist/{chunk-WIKMCJUR.js.map → chunk-JI3LQFJH.js.map} +0 -0
  73. /package/dist/{chunk-2BD7DG37.js.map → chunk-MBZAESQ3.js.map} +0 -0
  74. /package/dist/{chunk-VAEAGTEQ.js.map → chunk-QFKRE7AU.js.map} +0 -0
  75. /package/dist/{chunk-AGJKWOKV.js.map → chunk-RVT6U6PV.js.map} +0 -0
  76. /package/dist/{chunk-TFFZUFEP.js.map → chunk-VJYFXDCZ.js.map} +0 -0
  77. /package/dist/{chunk-QZRKNA5F.js.map → chunk-ZCMO46YY.js.map} +0 -0
@@ -7,14 +7,14 @@ import {
7
7
  materializeAfterSemanticConsolidation,
8
8
  parseConsolidationResponse,
9
9
  parseOperatorAwareConsolidationResponse
10
- } from "./chunk-SWDHVH2P.js";
11
- import "./chunk-WWMHAMAY.js";
10
+ } from "./chunk-BKRIAXTU.js";
11
+ import "./chunk-BNUAOLDK.js";
12
12
  import "./chunk-LN4YGHTM.js";
13
13
  import {
14
14
  resolveExtensionsRoot
15
15
  } from "./chunk-JLNBQWZ2.js";
16
16
  import "./chunk-3UXOZBHV.js";
17
- import "./chunk-NXCK7DO7.js";
17
+ import "./chunk-4PLOQDBB.js";
18
18
  import "./chunk-M7XQSUBB.js";
19
19
  import "./chunk-5UZXUTVO.js";
20
20
  import "./chunk-J6A3CX5N.js";
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  promoteSemanticRuleFromMemory,
3
3
  setSemanticRulePromotionTestHooks
4
- } from "./chunk-GCYFUTUC.js";
5
- import "./chunk-NXCK7DO7.js";
4
+ } from "./chunk-FIS5RT6K.js";
5
+ import "./chunk-4PLOQDBB.js";
6
6
  import "./chunk-M7XQSUBB.js";
7
7
  import "./chunk-5UZXUTVO.js";
8
8
  import "./chunk-J6A3CX5N.js";
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  compareVerifiedSemanticRuleResults,
3
3
  searchVerifiedSemanticRules
4
- } from "./chunk-TFFZUFEP.js";
5
- import "./chunk-NXCK7DO7.js";
4
+ } from "./chunk-VJYFXDCZ.js";
5
+ import "./chunk-4PLOQDBB.js";
6
6
  import "./chunk-M7XQSUBB.js";
7
7
  import "./chunk-5UZXUTVO.js";
8
8
  import "./chunk-J6A3CX5N.js";
package/dist/storage.d.ts CHANGED
@@ -27,9 +27,13 @@ interface MemoryLifecycleEventWriteOptions {
27
27
  * Strips non-alphanumeric chars, collapses hyphens, removes type prefix duplication.
28
28
  * e.g. "My Project" → "my-project"
29
29
  *
30
- * Checks user-defined aliases (from config/aliases.json) first, then built-in aliases.
30
+ * Checks caller-provided user aliases first, then built-in aliases. Alias
31
+ * tables are instance state on StorageManager (issue #1534) — use
32
+ * `storageManager.normalizeEntityName(raw, type)` when normalizing within a
33
+ * store, or pass `storageManager.entityAliases` explicitly. Without an
34
+ * aliases argument only the built-in structural aliases apply.
31
35
  */
32
- declare function normalizeEntityName(raw: string, type: string): string;
36
+ declare function normalizeEntityName(raw: string, type: string, aliases?: Readonly<Record<string, string>>): string;
33
37
  /**
34
38
  * Content-hash dedup index for facts.
35
39
  * Normalizes content (lowercase, strip punctuation, collapse whitespace),
@@ -347,11 +351,29 @@ declare class StorageManager {
347
351
  */
348
352
  private get bufferSurpriseLedgerPath();
349
353
  /**
350
- * Load user-defined entity aliases from config/aliases.json in the memory store.
351
- * File format: { "variant": "canonical", "variant2": "canonical", ... }
352
- * Call this once at startup (e.g. from orchestrator.initialize()).
354
+ * Entity alias table loaded from THIS store's config/aliases.json.
355
+ * Instance-scoped on purpose (issue #1534): multiple StorageManager
356
+ * instances in one process (namespaces, hosted profiles, tenants) must
357
+ * never share alias state — the previous module-level table let whichever
358
+ * store loaded last rewrite every other store's canonical entity ids.
359
+ */
360
+ private userAliases;
361
+ /** Normalize an entity name using this store's alias table. */
362
+ normalizeEntityName(raw: string, type: string): string;
363
+ /**
364
+ * Read-only view of this store's user alias table, for call sites that
365
+ * normalize outside the manager (pass it to the free `normalizeEntityName`).
366
+ */
367
+ get entityAliases(): Readonly<Record<string, string>>;
368
+ /**
369
+ * Reload user-defined entity aliases from config/aliases.json in the memory
370
+ * store. File format: { "variant": "canonical", ... }. The constructor
371
+ * already loads aliases, so this is only needed to pick up file changes
372
+ * (e.g. orchestrator.initialize() re-running on a live instance).
373
+ * Non-object payloads and non-string or empty alias values are ignored.
353
374
  */
354
375
  loadAliases(): Promise<void>;
376
+ private loadAliasesSync;
355
377
  ensureDirectories(): Promise<void>;
356
378
  writeMemory(category: MemoryCategory, content: string, options?: {
357
379
  actor?: string;
package/dist/storage.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  parseEntityFile,
10
10
  serializeEntityFile,
11
11
  stripAttributesSuffix
12
- } from "./chunk-NXCK7DO7.js";
12
+ } from "./chunk-4PLOQDBB.js";
13
13
  import "./chunk-M7XQSUBB.js";
14
14
  import "./chunk-5UZXUTVO.js";
15
15
  import "./chunk-J6A3CX5N.js";
@@ -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;
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  compareVerifiedEpisodeResults,
3
3
  searchVerifiedEpisodes
4
- } from "./chunk-JSVFEHLL.js";
4
+ } from "./chunk-46RXRASB.js";
5
5
  import "./chunk-HQ6NIBL6.js";
6
- import "./chunk-NXCK7DO7.js";
6
+ import "./chunk-4PLOQDBB.js";
7
7
  import "./chunk-M7XQSUBB.js";
8
8
  import "./chunk-5UZXUTVO.js";
9
9
  import "./chunk-J6A3CX5N.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.3.656",
3
+ "version": "9.3.658",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/briefing.ts CHANGED
@@ -183,7 +183,11 @@ export function parseBriefingFocus(token: string | undefined): BriefingFocus | n
183
183
  * an entity only via `frontmatter.entityRef` (no body / tag mention) would
184
184
  * silently drop out of an untyped focus filter (codex P2 review on #695).
185
185
  */
186
- export function focusMatchesMemory(memory: MemoryFile, focus: BriefingFocus): boolean {
186
+ export function focusMatchesMemory(
187
+ memory: MemoryFile,
188
+ focus: BriefingFocus,
189
+ entityAliases?: Readonly<Record<string, string>>,
190
+ ): boolean {
187
191
  const needle = focus.value.toLowerCase();
188
192
  const entityRef = (memory.frontmatter.entityRef ?? "").toLowerCase();
189
193
 
@@ -216,7 +220,7 @@ export function focusMatchesMemory(memory: MemoryFile, focus: BriefingFocus): bo
216
220
  // `Project-Alpha` of type `project` canonicalizes to `project-alpha`,
217
221
  // and a focus on `project:Project-Alpha` does the same.
218
222
  if (!entityRef) return false;
219
- const focusCanonical = normalizeEntityName(focus.value, focus.type);
223
+ const focusCanonical = normalizeEntityName(focus.value, focus.type, entityAliases);
220
224
  // Strip a leading `<type><delimiter>` prefix from a non-canonical
221
225
  // entityRef before normalizing — `normalizeEntityName` only strips
222
226
  // `<type>-` so other valid verbatim formats (`person:Alice-Test`,
@@ -232,7 +236,7 @@ export function focusMatchesMemory(memory: MemoryFile, focus: BriefingFocus): bo
232
236
  if (typeDelimMatch) {
233
237
  refForNormalize = refForNormalize.slice(typeDelimMatch[0].length);
234
238
  }
235
- const refCanonical = normalizeEntityName(refForNormalize, focus.type);
239
+ const refCanonical = normalizeEntityName(refForNormalize, focus.type, entityAliases);
236
240
  return refCanonical === focusCanonical;
237
241
  }
238
242
 
@@ -834,7 +838,7 @@ export async function buildBriefing(options: BuildBriefingOptions): Promise<Brie
834
838
 
835
839
  const memoriesInWindow = filterMemoriesByWindow(allMemories, window);
836
840
  const focusedMemories = focus
837
- ? memoriesInWindow.filter((m) => focusMatchesMemory(m, focus))
841
+ ? memoriesInWindow.filter((m) => focusMatchesMemory(m, focus, options.storage.entityAliases))
838
842
  : memoriesInWindow;
839
843
 
840
844
  const activeThreads = buildActiveThreads(focusedMemories);
@@ -457,12 +457,17 @@ async function buildEntityMentionIndex(
457
457
  Promise.all(storages.map((scopedStorage) => scopedStorage.readAllMemories())),
458
458
  readNativeChunks(config, recallNamespaces),
459
459
  ]);
460
- const entityFiles = entityFileSets.flat();
460
+ // Pair each entity with the alias table of the store it came from (#1534):
461
+ // canonical ids must reflect the owning store's aliases, never another
462
+ // namespace's.
463
+ const entityRecords = entityFileSets.flatMap((set, index) =>
464
+ set.map((entity) => ({ entity, aliases: storages[index]!.entityAliases })),
465
+ );
461
466
  const memories = memorySets.flat();
462
467
 
463
468
  const entities = new Map<string, EntityMentionIndexEntry>();
464
- for (const entity of entityFiles) {
465
- const canonicalId = normalizeEntityName(entity.name, entity.type);
469
+ for (const { entity, aliases } of entityRecords) {
470
+ const canonicalId = normalizeEntityName(entity.name, entity.type, aliases);
466
471
  const rawStructuredSections = entity.structuredSections ?? [];
467
472
  const rawBeliefLedgerFactKeys = beliefLedgerFactKeys(rawStructuredSections);
468
473
  const sanitizedFacts = entity.facts
@@ -65,7 +65,6 @@ import {
65
65
  StorageManager,
66
66
  ContentHashIndex,
67
67
  fingerprintEntityStructuredFacts,
68
- normalizeEntityName,
69
68
  normalizeAttributePairs,
70
69
  parseEntityFile,
71
70
  } from "./storage.js";
@@ -15791,7 +15790,7 @@ export class Orchestrator {
15791
15790
  const type = (entity as any)?.type;
15792
15791
  if (typeof name !== "string" || typeof type !== "string") continue;
15793
15792
  try {
15794
- const normalized = normalizeEntityName(name, type);
15793
+ const normalized = storage.normalizeEntityName(name, type);
15795
15794
  await storage.addEntityActivity(
15796
15795
  normalized,
15797
15796
  { date: today, note: "Mentioned in conversation" },
package/src/storage.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { access, readdir, readFile, stat, writeFile, mkdir, unlink, rename, appendFile, open } from "node:fs/promises";
2
- import { appendFileSync, createReadStream, mkdirSync, statSync } from "node:fs";
2
+ import { appendFileSync, createReadStream, mkdirSync, readFileSync, statSync } from "node:fs";
3
3
  import { createHash } from "node:crypto";
4
4
  import path from "node:path";
5
5
  import { log } from "./logger.js";
@@ -935,13 +935,6 @@ function inferCurrentStateStatus(
935
935
  return inferMemoryStatus(frontmatter, pathRel, fallbackStatus);
936
936
  }
937
937
 
938
- /**
939
- * Entity alias table loaded from the user's local config.
940
- * Populated by StorageManager.loadAliases() at startup.
941
- * Falls back to built-in structural aliases (e.g. "open-claw" → "openclaw").
942
- */
943
- let userAliases: Record<string, string> = {};
944
-
945
938
  /** Built-in aliases for common structural normalizations (no personal data) */
946
939
  const BUILTIN_ALIASES: Record<string, string> = {
947
940
  openclaw: "openclaw",
@@ -953,9 +946,17 @@ const BUILTIN_ALIASES: Record<string, string> = {
953
946
  * Strips non-alphanumeric chars, collapses hyphens, removes type prefix duplication.
954
947
  * e.g. "My Project" → "my-project"
955
948
  *
956
- * Checks user-defined aliases (from config/aliases.json) first, then built-in aliases.
949
+ * Checks caller-provided user aliases first, then built-in aliases. Alias
950
+ * tables are instance state on StorageManager (issue #1534) — use
951
+ * `storageManager.normalizeEntityName(raw, type)` when normalizing within a
952
+ * store, or pass `storageManager.entityAliases` explicitly. Without an
953
+ * aliases argument only the built-in structural aliases apply.
957
954
  */
958
- export function normalizeEntityName(raw: string, type: string): string {
955
+ export function normalizeEntityName(
956
+ raw: string,
957
+ type: string,
958
+ aliases?: Readonly<Record<string, string>>,
959
+ ): string {
959
960
  // Strip type prefix if present (e.g. name="person-jane-doe", type="person")
960
961
  const rawStr = typeof raw === "string" ? raw : "";
961
962
  const typeStr = typeof type === "string" && type.trim().length > 0 ? type : "entity";
@@ -972,10 +973,14 @@ export function normalizeEntityName(raw: string, type: string): string {
972
973
  .replace(/-+/g, "-")
973
974
  .replace(/^-|-$/g, "");
974
975
 
975
- // Check user aliases first, then built-in
976
- if (userAliases[normalized]) {
977
- normalized = userAliases[normalized];
978
- } else if (BUILTIN_ALIASES[normalized]) {
976
+ // Check caller-provided user aliases first, then built-in. Own-property and
977
+ // string guards keep inherited object keys (e.g. an entity literally named
978
+ // "constructor") and malformed alias values from corrupting canonical ids.
979
+ const userAlias =
980
+ aliases !== undefined && Object.hasOwn(aliases, normalized) ? aliases[normalized] : undefined;
981
+ if (typeof userAlias === "string" && userAlias.length > 0) {
982
+ normalized = userAlias;
983
+ } else if (Object.hasOwn(BUILTIN_ALIASES, normalized)) {
979
984
  normalized = BUILTIN_ALIASES[normalized];
980
985
  }
981
986
 
@@ -2443,7 +2448,14 @@ export class StorageManager {
2443
2448
  constructor(
2444
2449
  private readonly baseDir: string,
2445
2450
  private readonly entitySchemas?: PluginConfig["entitySchemas"],
2446
- ) {}
2451
+ ) {
2452
+ // Load this store's alias table at construction (#1534): StorageManager
2453
+ // is created in a dozen places (namespace router, operator toolkit,
2454
+ // compounding engine, cold storage, ...) and most never call
2455
+ // loadAliases() explicitly — every creation path must still get the
2456
+ // store's own aliases, never an empty or foreign table.
2457
+ this.loadAliasesSync();
2458
+ }
2447
2459
 
2448
2460
  /** The root directory of this storage instance. */
2449
2461
  get dir(): string {
@@ -3197,18 +3209,59 @@ export class StorageManager {
3197
3209
  }
3198
3210
 
3199
3211
  /**
3200
- * Load user-defined entity aliases from config/aliases.json in the memory store.
3201
- * File format: { "variant": "canonical", "variant2": "canonical", ... }
3202
- * Call this once at startup (e.g. from orchestrator.initialize()).
3212
+ * Entity alias table loaded from THIS store's config/aliases.json.
3213
+ * Instance-scoped on purpose (issue #1534): multiple StorageManager
3214
+ * instances in one process (namespaces, hosted profiles, tenants) must
3215
+ * never share alias state — the previous module-level table let whichever
3216
+ * store loaded last rewrite every other store's canonical entity ids.
3217
+ */
3218
+ private userAliases: Record<string, string> = {};
3219
+
3220
+ /** Normalize an entity name using this store's alias table. */
3221
+ normalizeEntityName(raw: string, type: string): string {
3222
+ return normalizeEntityName(raw, type, this.userAliases);
3223
+ }
3224
+
3225
+ /**
3226
+ * Read-only view of this store's user alias table, for call sites that
3227
+ * normalize outside the manager (pass it to the free `normalizeEntityName`).
3228
+ */
3229
+ get entityAliases(): Readonly<Record<string, string>> {
3230
+ return this.userAliases;
3231
+ }
3232
+
3233
+ /**
3234
+ * Reload user-defined entity aliases from config/aliases.json in the memory
3235
+ * store. File format: { "variant": "canonical", ... }. The constructor
3236
+ * already loads aliases, so this is only needed to pick up file changes
3237
+ * (e.g. orchestrator.initialize() re-running on a live instance).
3238
+ * Non-object payloads and non-string or empty alias values are ignored.
3203
3239
  */
3204
3240
  async loadAliases(): Promise<void> {
3241
+ this.loadAliasesSync();
3242
+ }
3243
+
3244
+ private loadAliasesSync(): void {
3205
3245
  const aliasPath = path.join(this.baseDir, "config", "aliases.json");
3246
+ // Re-derive from the file on every call: a reload after the file was
3247
+ // fixed, emptied, or removed must never leave a previous table active.
3248
+ this.userAliases = {};
3206
3249
  try {
3207
- const raw = await readFile(aliasPath, "utf-8");
3250
+ const raw = readFileSync(aliasPath, "utf-8");
3208
3251
  const parsed = JSON.parse(raw);
3209
- if (typeof parsed === "object" && parsed !== null) {
3210
- userAliases = parsed as Record<string, string>;
3211
- log.debug(`loaded ${Object.keys(userAliases).length} entity aliases from ${aliasPath}`);
3252
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
3253
+ const cleaned: Record<string, string> = {};
3254
+ for (const [key, value] of Object.entries(parsed)) {
3255
+ if (typeof value === "string" && value.trim().length > 0) {
3256
+ cleaned[key] = value;
3257
+ }
3258
+ }
3259
+ this.userAliases = cleaned;
3260
+ log.debug(`loaded ${Object.keys(cleaned).length} entity aliases from ${aliasPath}`);
3261
+ } else {
3262
+ log.warn(
3263
+ `ignoring ${aliasPath}: payload must be a JSON object mapping variant → canonical strings`,
3264
+ );
3212
3265
  }
3213
3266
  } catch {
3214
3267
  // No aliases file — that's fine, use built-in only
@@ -3679,7 +3732,7 @@ export class StorageManager {
3679
3732
  .filter((fact) => fact.length > 0),
3680
3733
  )]
3681
3734
  : [];
3682
- let normalized = normalizeEntityName(name, type);
3735
+ let normalized = this.normalizeEntityName(name, type);
3683
3736
 
3684
3737
  // Check for fuzzy match against existing entities before creating a new file
3685
3738
  const match = await this.findMatchingEntity(name, type);
@@ -4648,7 +4701,7 @@ export class StorageManager {
4648
4701
 
4649
4702
  const typePrefix = `${type.toLowerCase()}-`;
4650
4703
  // Extract the name part from the proposed normalized name
4651
- const proposedFull = normalizeEntityName(proposedName, type);
4704
+ const proposedFull = this.normalizeEntityName(proposedName, type);
4652
4705
  const proposedNamePart = proposedFull.startsWith(typePrefix)
4653
4706
  ? proposedFull.slice(typePrefix.length)
4654
4707
  : proposedFull;
@@ -6104,7 +6157,7 @@ export class StorageManager {
6104
6157
  entity.updated = entityUpdatedAt;
6105
6158
  await this.writeStorageSecureFile(filePath, serializeEntityFile(entity, this.entitySchemas));
6106
6159
  await this.removeEntitySynthesisQueueEntries([
6107
- ...new Set([name, normalizeEntityName(entity.name, entity.type)]),
6160
+ ...new Set([name, this.normalizeEntityName(entity.name, entity.type)]),
6108
6161
  ]);
6109
6162
  this.invalidateKnowledgeIndexCache();
6110
6163
  this.bumpMemoryStatusVersion(); // invalidate entity cache
@@ -6425,7 +6478,7 @@ export class StorageManager {
6425
6478
  if (dashIdx === -1) continue;
6426
6479
  const type = baseName.slice(0, dashIdx);
6427
6480
  const restOfName = baseName.slice(dashIdx + 1);
6428
- const canonical = normalizeEntityName(restOfName, type);
6481
+ const canonical = this.normalizeEntityName(restOfName, type);
6429
6482
 
6430
6483
  if (!groups.has(canonical)) groups.set(canonical, []);
6431
6484
  groups.get(canonical)!.push(file);