@remnic/core 9.3.761 → 9.3.762

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 (123) hide show
  1. package/dist/access-admin-ops-surface.js +9 -8
  2. package/dist/access-boundary.js +10 -9
  3. package/dist/access-cli.js +44 -43
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.js +16 -15
  6. package/dist/access-identity-continuity-surface.js +9 -8
  7. package/dist/access-lcm-surface.js +9 -8
  8. package/dist/access-mcp.js +13 -12
  9. package/dist/access-observe-write-surface.js +9 -8
  10. package/dist/access-operations-batch.js +11 -10
  11. package/dist/access-operations.js +12 -11
  12. package/dist/access-recall-surface.js +9 -8
  13. package/dist/access-service.js +9 -8
  14. package/dist/adapters/index.js +4 -4
  15. package/dist/adapters/registry.js +2 -2
  16. package/dist/briefing.js +4 -3
  17. package/dist/causal-consolidation.js +5 -4
  18. package/dist/causal-consolidation.js.map +1 -1
  19. package/dist/{chunk-4WOXD56B.js → chunk-2KYXXMTE.js} +5 -5
  20. package/dist/{chunk-HP23UD6W.js → chunk-2PGB63VU.js} +2 -2
  21. package/dist/{chunk-2B3BTQN4.js → chunk-4HYJETVO.js} +2 -2
  22. package/dist/{chunk-APJQ6UEA.js → chunk-AGNBY3VG.js} +4 -4
  23. package/dist/{chunk-ICVNVSKN.js → chunk-BRYPUVJU.js} +2 -2
  24. package/dist/{chunk-IRFHFRHP.js → chunk-DB647OZ4.js} +24 -24
  25. package/dist/{chunk-LG2MCUBC.js → chunk-DPZ5GJMD.js} +2 -2
  26. package/dist/chunk-DQC3ZZYM.js +35 -0
  27. package/dist/chunk-DQC3ZZYM.js.map +1 -0
  28. package/dist/{chunk-W4ADKEIY.js → chunk-EDC7RZCW.js} +22 -22
  29. package/dist/{chunk-DUU4VLOW.js → chunk-ES3GAMSQ.js} +2 -2
  30. package/dist/{chunk-5AZQZFJG.js → chunk-ESDBHQY6.js} +5 -5
  31. package/dist/{chunk-IV4INTAU.js → chunk-HDFS6RWQ.js} +2 -2
  32. package/dist/{chunk-P7UA66OG.js → chunk-HDYFNXVO.js} +2 -2
  33. package/dist/{chunk-YU4CLTZY.js → chunk-HMUEAKSD.js} +2 -2
  34. package/dist/{chunk-XDFWMY4X.js → chunk-IIURIWJG.js} +102 -12
  35. package/dist/chunk-IIURIWJG.js.map +1 -0
  36. package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
  37. package/dist/{chunk-Y3XEN5TL.js → chunk-JU6BKZHX.js} +3 -3
  38. package/dist/{chunk-NPZTEHR7.js → chunk-KZWUJC7Y.js} +2 -2
  39. package/dist/{chunk-3CBSKDRL.js → chunk-LAJIXABF.js} +8 -8
  40. package/dist/{chunk-3QLPBCFG.js → chunk-NKP6II6G.js} +4 -4
  41. package/dist/{chunk-742MWWVL.js → chunk-OQ6F3WBX.js} +2 -2
  42. package/dist/{chunk-3DAIQSVS.js → chunk-P7W4CDX6.js} +13 -6
  43. package/dist/chunk-P7W4CDX6.js.map +1 -0
  44. package/dist/{chunk-HHCMZEUC.js → chunk-PNFN7SIN.js} +2 -2
  45. package/dist/{chunk-P7N2DQLC.js → chunk-QPFO54VC.js} +3 -3
  46. package/dist/{chunk-ZAIU7ZEA.js → chunk-QWIE43BS.js} +2 -2
  47. package/dist/chunk-TEKTCF2V.js +118 -0
  48. package/dist/chunk-TEKTCF2V.js.map +1 -0
  49. package/dist/{chunk-CFHDI5CV.js → chunk-TIWRP5NW.js} +4 -4
  50. package/dist/{chunk-CXW6Y7SI.js → chunk-V5RVMULT.js} +4 -4
  51. package/dist/{chunk-6S2C5FQ4.js → chunk-VHYFY3JI.js} +2 -2
  52. package/dist/{chunk-JYH3XYWM.js → chunk-WAIMHMO3.js} +3 -3
  53. package/dist/{chunk-OCPVA4TH.js → chunk-ZTBH2OHD.js} +2 -2
  54. package/dist/cli.js +33 -32
  55. package/dist/compounding/engine.js +4 -3
  56. package/dist/connectors/codex-materialize-runner.js +4 -3
  57. package/dist/connectors/index.js +4 -3
  58. package/dist/entity-retrieval.js +4 -3
  59. package/dist/{forget-OEE5OELK.js → forget-WYLR4ZFV.js} +2 -2
  60. package/dist/index.js +46 -45
  61. package/dist/lcm/engine.js +2 -2
  62. package/dist/lcm/index.js +4 -4
  63. package/dist/maintenance/memory-governance.js +4 -3
  64. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +11 -1
  65. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -3
  66. package/dist/maintenance/rebuild-memory-projection.d.ts +22 -1
  67. package/dist/maintenance/rebuild-memory-projection.js +5 -4
  68. package/dist/namespaces/migrate.js +8 -7
  69. package/dist/namespaces/search.js +3 -3
  70. package/dist/namespaces/storage.js +4 -3
  71. package/dist/operator-toolkit.js +11 -10
  72. package/dist/orchestration/maintenance.js +6 -5
  73. package/dist/orchestrator.js +44 -43
  74. package/dist/schemas.d.ts +22 -22
  75. package/dist/search/factory.js +2 -2
  76. package/dist/search/index.js +4 -4
  77. package/dist/semantic-consolidation.js +5 -4
  78. package/dist/semantic-rule-promotion.js +4 -3
  79. package/dist/semantic-rule-verifier.js +4 -3
  80. package/dist/storage-guards.d.ts +9 -0
  81. package/dist/storage-guards.js +11 -0
  82. package/dist/storage-guards.js.map +1 -0
  83. package/dist/storage.js +3 -2
  84. package/dist/transfer/import-sqlite.js +2 -2
  85. package/dist/transfer/types.d.ts +12 -12
  86. package/dist/verified-recall.js +4 -3
  87. package/package.json +2 -2
  88. package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +59 -8
  89. package/src/maintenance/rebuild-memory-projection.ts +149 -9
  90. package/src/storage-guards.ts +30 -0
  91. package/src/storage.ts +8 -8
  92. package/dist/chunk-3DAIQSVS.js.map +0 -1
  93. package/dist/chunk-GYJESQS5.js +0 -77
  94. package/dist/chunk-GYJESQS5.js.map +0 -1
  95. package/dist/chunk-XDFWMY4X.js.map +0 -1
  96. /package/dist/{chunk-4WOXD56B.js.map → chunk-2KYXXMTE.js.map} +0 -0
  97. /package/dist/{chunk-HP23UD6W.js.map → chunk-2PGB63VU.js.map} +0 -0
  98. /package/dist/{chunk-2B3BTQN4.js.map → chunk-4HYJETVO.js.map} +0 -0
  99. /package/dist/{chunk-APJQ6UEA.js.map → chunk-AGNBY3VG.js.map} +0 -0
  100. /package/dist/{chunk-ICVNVSKN.js.map → chunk-BRYPUVJU.js.map} +0 -0
  101. /package/dist/{chunk-IRFHFRHP.js.map → chunk-DB647OZ4.js.map} +0 -0
  102. /package/dist/{chunk-LG2MCUBC.js.map → chunk-DPZ5GJMD.js.map} +0 -0
  103. /package/dist/{chunk-W4ADKEIY.js.map → chunk-EDC7RZCW.js.map} +0 -0
  104. /package/dist/{chunk-DUU4VLOW.js.map → chunk-ES3GAMSQ.js.map} +0 -0
  105. /package/dist/{chunk-5AZQZFJG.js.map → chunk-ESDBHQY6.js.map} +0 -0
  106. /package/dist/{chunk-IV4INTAU.js.map → chunk-HDFS6RWQ.js.map} +0 -0
  107. /package/dist/{chunk-P7UA66OG.js.map → chunk-HDYFNXVO.js.map} +0 -0
  108. /package/dist/{chunk-YU4CLTZY.js.map → chunk-HMUEAKSD.js.map} +0 -0
  109. /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
  110. /package/dist/{chunk-Y3XEN5TL.js.map → chunk-JU6BKZHX.js.map} +0 -0
  111. /package/dist/{chunk-NPZTEHR7.js.map → chunk-KZWUJC7Y.js.map} +0 -0
  112. /package/dist/{chunk-3CBSKDRL.js.map → chunk-LAJIXABF.js.map} +0 -0
  113. /package/dist/{chunk-3QLPBCFG.js.map → chunk-NKP6II6G.js.map} +0 -0
  114. /package/dist/{chunk-742MWWVL.js.map → chunk-OQ6F3WBX.js.map} +0 -0
  115. /package/dist/{chunk-HHCMZEUC.js.map → chunk-PNFN7SIN.js.map} +0 -0
  116. /package/dist/{chunk-P7N2DQLC.js.map → chunk-QPFO54VC.js.map} +0 -0
  117. /package/dist/{chunk-ZAIU7ZEA.js.map → chunk-QWIE43BS.js.map} +0 -0
  118. /package/dist/{chunk-CFHDI5CV.js.map → chunk-TIWRP5NW.js.map} +0 -0
  119. /package/dist/{chunk-CXW6Y7SI.js.map → chunk-V5RVMULT.js.map} +0 -0
  120. /package/dist/{chunk-6S2C5FQ4.js.map → chunk-VHYFY3JI.js.map} +0 -0
  121. /package/dist/{chunk-JYH3XYWM.js.map → chunk-WAIMHMO3.js.map} +0 -0
  122. /package/dist/{chunk-OCPVA4TH.js.map → chunk-ZTBH2OHD.js.map} +0 -0
  123. /package/dist/{forget-OEE5OELK.js.map → forget-WYLR4ZFV.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
  version: string;
325
325
  description: string;
@@ -334,13 +334,13 @@ declare const CapsuleBlockSchema: z.ZodObject<{
334
334
  forkRoot: string;
335
335
  } | null;
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
  version: string;
346
346
  description: 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
  version: string;
476
476
  description: string;
@@ -485,13 +485,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
485
485
  forkRoot: string;
486
486
  } | null;
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
  version: string;
497
497
  description: 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
  version: string;
530
530
  description: 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
  version: string;
563
563
  description: 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
  version: string;
695
695
  description: string;
@@ -704,13 +704,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
704
704
  forkRoot: string;
705
705
  } | null;
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
  version: string;
716
716
  description: 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
  version: string;
749
749
  description: 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
  version: string;
782
782
  description: 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
  version: string;
827
827
  description: 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
  version: string;
866
866
  description: string;
@@ -1,18 +1,19 @@
1
1
  import {
2
2
  compareVerifiedEpisodeResults,
3
3
  searchVerifiedEpisodes
4
- } from "./chunk-2B3BTQN4.js";
4
+ } from "./chunk-4HYJETVO.js";
5
5
  import "./chunk-7FL4CNPV.js";
6
- import "./chunk-3DAIQSVS.js";
6
+ import "./chunk-P7W4CDX6.js";
7
7
  import "./chunk-M7XQSUBB.js";
8
8
  import "./chunk-L2B3EE7B.js";
9
9
  import "./chunk-5UZXUTVO.js";
10
10
  import "./chunk-5GPPACXK.js";
11
+ import "./chunk-LTJAMRGI.js";
11
12
  import "./chunk-TKZ3AL5V.js";
12
13
  import "./chunk-6CIAGZ6X.js";
13
14
  import "./chunk-J6A3CX5N.js";
15
+ import "./chunk-DQC3ZZYM.js";
14
16
  import "./chunk-SVOZFLIQ.js";
15
- import "./chunk-LTJAMRGI.js";
16
17
  import "./chunk-JS2HKKOK.js";
17
18
  import "./chunk-AWJ2FHCF.js";
18
19
  import "./chunk-42JKGUFJ.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.3.761",
3
+ "version": "9.3.762",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3000,7 +3000,7 @@
3000
3000
  "core"
3001
3001
  ],
3002
3002
  "peerDependencies": {
3003
- "@remnic/coding-graph": "^9.3.761"
3003
+ "@remnic/coding-graph": "^9.3.762"
3004
3004
  },
3005
3005
  "peerDependenciesMeta": {
3006
3006
  "@remnic/coding-graph": {
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { stat } from "node:fs/promises";
3
3
  import { StorageManager } from "../storage.js";
4
+ import { log } from "../logger.js";
4
5
  import type { MemoryLifecycleEvent } from "../types.js";
5
6
  import { toBackupStamp } from "./backup-stamp.js";
6
7
  import { writeFileAtomically } from "./atomic-file.js";
@@ -15,12 +16,24 @@ export interface RebuildMemoryLifecycleLedgerOptions {
15
16
  now?: Date;
16
17
  }
17
18
 
19
+ export interface SkippedLifecycleBlankIdMemory {
20
+ path: string;
21
+ }
22
+
23
+ export interface SkippedDuplicateLifecycleEvent {
24
+ eventId: string;
25
+ keptPath: string;
26
+ skippedPath: string;
27
+ }
28
+
18
29
  export interface RebuildMemoryLifecycleLedgerResult {
19
30
  dryRun: boolean;
20
31
  scannedMemories: number;
21
32
  rebuiltRows: number;
22
33
  outputPath: string;
23
34
  backupPath?: string;
35
+ skippedBlankIdMemories: SkippedLifecycleBlankIdMemory[];
36
+ skippedDuplicateEvents: SkippedDuplicateLifecycleEvent[];
24
37
  }
25
38
 
26
39
  function isNodeError(err: unknown): err is NodeJS.ErrnoException {
@@ -59,16 +72,52 @@ export async function rebuildMemoryLifecycleLedger(
59
72
  const now = options.now ?? new Date();
60
73
  const outputPath = path.join(options.memoryDir, "state", "memory-lifecycle-ledger.jsonl");
61
74
  const storage = new StorageManager(options.memoryDir);
62
- const allMemories = [
63
- ...await storage.readAllMemories(),
64
- ...await storage.readAllColdMemories(),
65
- ...await storage.readArchivedMemories(),
66
- ]
67
- .sort((a, b) => a.frontmatter.id.localeCompare(b.frontmatter.id));
75
+ const tiers = [
76
+ await storage.readAllMemories(),
77
+ await storage.readAllColdMemories(),
78
+ await storage.readArchivedMemories(),
79
+ ];
80
+ const allMemories = tiers.flat();
81
+ const skippedBlankIdMemories: SkippedLifecycleBlankIdMemory[] = [];
82
+ const eventCandidates: Array<{ event: MemoryLifecycleEvent; path: string }> = [];
83
+ for (const tier of tiers) {
84
+ for (const memory of [...tier].sort((left, right) => left.path.localeCompare(right.path))) {
85
+ if (!memory.frontmatter.id.trim()) {
86
+ skippedBlankIdMemories.push({ path: memory.path });
87
+ log.warn(`lifecycle ledger rebuild skipped blank memory id: path=${memory.path}`);
88
+ continue;
89
+ }
90
+ for (const event of buildLifecycleEventsForMemory(memory)) {
91
+ eventCandidates.push({ event, path: memory.path });
92
+ }
93
+ }
94
+ }
68
95
 
69
- const events: MemoryLifecycleEvent[] = sortMemoryLifecycleEvents(
70
- allMemories.flatMap((memory) => buildLifecycleEventsForMemory(memory)),
96
+ const orderedEvents = sortMemoryLifecycleEvents(eventCandidates.map((candidate) => candidate.event));
97
+ const candidateByEvent = new Map(
98
+ eventCandidates.map((candidate) => [candidate.event, candidate]),
71
99
  );
100
+ const keptByEventId = new Map<string, { event: MemoryLifecycleEvent; path: string }>();
101
+ const events: MemoryLifecycleEvent[] = [];
102
+ const skippedDuplicateEvents: SkippedDuplicateLifecycleEvent[] = [];
103
+ for (const event of orderedEvents) {
104
+ const candidate = candidateByEvent.get(event)!;
105
+ const kept = keptByEventId.get(event.eventId);
106
+ if (kept) {
107
+ skippedDuplicateEvents.push({
108
+ eventId: event.eventId,
109
+ keptPath: kept.path,
110
+ skippedPath: candidate.path,
111
+ });
112
+ log.warn(
113
+ `lifecycle ledger rebuild skipped duplicate event_id=${event.eventId}: `
114
+ + `kept=${kept.path} skipped=${candidate.path}`,
115
+ );
116
+ continue;
117
+ }
118
+ keptByEventId.set(event.eventId, candidate);
119
+ events.push(event);
120
+ }
72
121
 
73
122
  let backupPath: string | undefined;
74
123
  if (!dryRun) {
@@ -87,5 +136,7 @@ export async function rebuildMemoryLifecycleLedger(
87
136
  rebuiltRows: events.length,
88
137
  outputPath,
89
138
  backupPath,
139
+ skippedBlankIdMemories,
140
+ skippedDuplicateEvents,
90
141
  };
91
142
  }
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { mkdir, rm, stat } from "node:fs/promises";
3
3
  import { StorageManager } from "../storage.js";
4
+ import { log } from "../logger.js";
4
5
  import {
5
6
  listMemoryGovernanceRuns,
6
7
  readMemoryGovernanceRunArtifact,
@@ -51,6 +52,27 @@ export interface RebuildMemoryProjectionOptions {
51
52
  updatedBefore?: string;
52
53
  }
53
54
 
55
+ export interface SkippedDuplicateMemory {
56
+ memoryId: string;
57
+ keptPath: string;
58
+ skippedPath: string;
59
+ }
60
+
61
+ export interface SkippedBlankIdMemory {
62
+ path: string;
63
+ }
64
+
65
+ export interface SkippedDuplicateTimelineEvent {
66
+ eventId: string;
67
+ keptPath: string;
68
+ skippedPath: string;
69
+ }
70
+
71
+ export interface SkippedBlankIdTimelineEvent {
72
+ eventId: string;
73
+ path: string;
74
+ }
75
+
54
76
  export interface RebuildMemoryProjectionResult {
55
77
  dryRun: boolean;
56
78
  scannedMemories: number;
@@ -62,6 +84,10 @@ export interface RebuildMemoryProjectionResult {
62
84
  outputPath: string;
63
85
  backupPath?: string;
64
86
  usedLifecycleLedger: boolean;
87
+ skippedDuplicateMemories: SkippedDuplicateMemory[];
88
+ skippedBlankIdMemories: SkippedBlankIdMemory[];
89
+ skippedDuplicateTimelineEvents: SkippedDuplicateTimelineEvent[];
90
+ skippedBlankIdTimelineEvents: SkippedBlankIdTimelineEvent[];
65
91
  scope: {
66
92
  updatedAfter: string | null;
67
93
  updatedBefore: string | null;
@@ -435,6 +461,91 @@ function serializeGovernanceReviewQueueRow(
435
461
  });
436
462
  }
437
463
 
464
+ function selectProjectionMemories(
465
+ tiers: MemoryFile[][],
466
+ ): {
467
+ allMemories: MemoryFile[];
468
+ memories: MemoryFile[];
469
+ skippedDuplicateMemories: SkippedDuplicateMemory[];
470
+ skippedBlankIdMemories: SkippedBlankIdMemory[];
471
+ } {
472
+ const allMemories = tiers.flat();
473
+ const memories: MemoryFile[] = [];
474
+ const keptById = new Map<string, MemoryFile>();
475
+ const skippedDuplicateMemories: SkippedDuplicateMemory[] = [];
476
+ const skippedBlankIdMemories: SkippedBlankIdMemory[] = [];
477
+
478
+ // Tier order is authoritative: hot wins over cold, and cold wins over archive.
479
+ // Paths break ties within a tier so the winner does not depend on readdir order.
480
+ for (const tier of tiers) {
481
+ for (const memory of [...tier].sort((left, right) => left.path.localeCompare(right.path))) {
482
+ const memoryId = memory.frontmatter.id.trim();
483
+ if (!memoryId) {
484
+ skippedBlankIdMemories.push({ path: memory.path });
485
+ log.warn(`projection rebuild skipped blank memory id: path=${memory.path}`);
486
+ continue;
487
+ }
488
+ const kept = keptById.get(memoryId);
489
+ if (kept) {
490
+ skippedDuplicateMemories.push({
491
+ memoryId,
492
+ keptPath: kept.path,
493
+ skippedPath: memory.path,
494
+ });
495
+ log.warn(
496
+ `projection rebuild skipped duplicate memory id=${memoryId}: kept=${kept.path} skipped=${memory.path}`,
497
+ );
498
+ continue;
499
+ }
500
+ keptById.set(memoryId, memory);
501
+ memories.push(memory);
502
+ }
503
+ }
504
+
505
+ return { allMemories, memories, skippedDuplicateMemories, skippedBlankIdMemories };
506
+ }
507
+
508
+ function deduplicateTimelineEvents(
509
+ events: MemoryLifecycleEvent[],
510
+ sourcePathFor: (event: MemoryLifecycleEvent) => string,
511
+ ): {
512
+ events: MemoryLifecycleEvent[];
513
+ skipped: SkippedDuplicateTimelineEvent[];
514
+ skippedBlankIds: SkippedBlankIdTimelineEvent[];
515
+ } {
516
+ const keptById = new Map<string, { event: MemoryLifecycleEvent; path: string }>();
517
+ const unique: MemoryLifecycleEvent[] = [];
518
+ const skipped: SkippedDuplicateTimelineEvent[] = [];
519
+ const skippedBlankIds: SkippedBlankIdTimelineEvent[] = [];
520
+ for (const event of sortMemoryLifecycleEvents(events)) {
521
+ const sourcePath = sourcePathFor(event);
522
+ if (!event.memoryId.trim()) {
523
+ skippedBlankIds.push({ eventId: event.eventId, path: sourcePath });
524
+ log.warn(
525
+ `projection rebuild skipped timeline event with blank memory id: `
526
+ + `event_id=${event.eventId} path=${sourcePath}`,
527
+ );
528
+ continue;
529
+ }
530
+ const kept = keptById.get(event.eventId);
531
+ if (kept) {
532
+ skipped.push({
533
+ eventId: event.eventId,
534
+ keptPath: kept.path,
535
+ skippedPath: sourcePath,
536
+ });
537
+ log.warn(
538
+ `projection rebuild skipped duplicate timeline event_id=${event.eventId}: `
539
+ + `kept=${kept.path} skipped=${sourcePath}`,
540
+ );
541
+ continue;
542
+ }
543
+ keptById.set(event.eventId, { event, path: sourcePath });
544
+ unique.push(event);
545
+ }
546
+ return { events: unique, skipped, skippedBlankIds };
547
+ }
548
+
438
549
  async function loadAuthoritativeProjectionSnapshot(options: {
439
550
  memoryDir: string;
440
551
  defaultNamespace?: string;
@@ -464,22 +575,43 @@ async function loadAuthoritativeProjectionSnapshot(options: {
464
575
  updatedAfter: string | null;
465
576
  updatedBefore: string | null;
466
577
  };
578
+ skippedDuplicateMemories: SkippedDuplicateMemory[];
579
+ skippedBlankIdMemories: SkippedBlankIdMemory[];
580
+ skippedDuplicateTimelineEvents: SkippedDuplicateTimelineEvent[];
581
+ skippedBlankIdTimelineEvents: SkippedBlankIdTimelineEvent[];
467
582
  }> {
468
583
  const storage = new StorageManager(options.memoryDir);
469
584
  // Force a fresh disk read — projection verify/rebuild must see the true
470
585
  // on-disk state, not a potentially stale in-process cache.
471
586
  storage.invalidateAllMemoriesCacheForDir();
472
- const allMemories = [
473
- ...await storage.readAllMemories(),
474
- ...await storage.readAllColdMemories(),
475
- ...await storage.readArchivedMemories(),
476
- ]
477
- .sort((a, b) => a.frontmatter.id.localeCompare(b.frontmatter.id));
587
+ const selected = selectProjectionMemories([
588
+ await storage.readAllMemories(),
589
+ await storage.readAllColdMemories(),
590
+ await storage.readArchivedMemories(),
591
+ ]);
592
+ const allMemories = selected.allMemories;
593
+ const projectionMemories = selected.memories;
478
594
  const lifecycleEvents = await storage.readAllMemoryLifecycleEvents();
479
- const { events, usedLifecycleLedger } = loadTimelineEvents(allMemories, lifecycleEvents);
480
- const currentRows = allMemories.map((memory) => toCurrentStateRow(options.memoryDir, memory));
595
+ const timeline = loadTimelineEvents(projectionMemories, lifecycleEvents);
596
+ const lifecycleLedgerPath = path.join(
597
+ options.memoryDir,
598
+ "state",
599
+ "memory-lifecycle-ledger.jsonl",
600
+ );
601
+ const memoryPathById = new Map(
602
+ projectionMemories.map((memory) => [memory.frontmatter.id, memory.path]),
603
+ );
604
+ const deduplicatedTimeline = deduplicateTimelineEvents(
605
+ timeline.events,
606
+ (event) => timeline.usedLifecycleLedger
607
+ ? lifecycleLedgerPath
608
+ : memoryPathById.get(event.memoryId) ?? lifecycleLedgerPath,
609
+ );
610
+ const events = deduplicatedTimeline.events;
611
+ const usedLifecycleLedger = timeline.usedLifecycleLedger;
612
+ const currentRows = projectionMemories.map((memory) => toCurrentStateRow(options.memoryDir, memory));
481
613
  const scope = normalizeProjectionScope(options);
482
- const scopedMemories = filterMemoriesForProjectionScope(allMemories, scope);
614
+ const scopedMemories = filterMemoriesForProjectionScope(projectionMemories, scope);
483
615
  const scopedMemoryIds = new Set(scopedMemories.map((memory) => memory.frontmatter.id));
484
616
  const nativeKnowledgeRows = await loadPersistedNativeKnowledgeChunks({
485
617
  memoryDir: options.memoryDir,
@@ -516,6 +648,10 @@ async function loadAuthoritativeProjectionSnapshot(options: {
516
648
  governance,
517
649
  usedLifecycleLedger,
518
650
  scope,
651
+ skippedDuplicateMemories: selected.skippedDuplicateMemories,
652
+ skippedBlankIdMemories: selected.skippedBlankIdMemories,
653
+ skippedDuplicateTimelineEvents: deduplicatedTimeline.skipped,
654
+ skippedBlankIdTimelineEvents: deduplicatedTimeline.skippedBlankIds,
519
655
  };
520
656
  }
521
657
 
@@ -1040,6 +1176,10 @@ export async function rebuildMemoryProjection(
1040
1176
  outputPath,
1041
1177
  backupPath,
1042
1178
  usedLifecycleLedger: snapshot.usedLifecycleLedger,
1179
+ skippedDuplicateMemories: snapshot.skippedDuplicateMemories,
1180
+ skippedBlankIdMemories: snapshot.skippedBlankIdMemories,
1181
+ skippedDuplicateTimelineEvents: snapshot.skippedDuplicateTimelineEvents,
1182
+ skippedBlankIdTimelineEvents: snapshot.skippedBlankIdTimelineEvents,
1043
1183
  scope: snapshot.scope,
1044
1184
  };
1045
1185
  }
@@ -0,0 +1,30 @@
1
+ import { log } from "./logger.js";
2
+
3
+ const PROJECTION_FALLBACK_WARN_INTERVAL_MS = 5 * 60_000;
4
+ const projectionFallbackWarnedAt = new Map<string, number>();
5
+
6
+ export function assertMemoryFrontmatterId(
7
+ fm: { id: string; category?: string; created?: string; entityRef?: string },
8
+ ): void {
9
+ if (typeof fm.id !== "string" || fm.id.trim().length === 0) {
10
+ const context = [
11
+ fm.category ? `category=${fm.category}` : null,
12
+ fm.created ? `created=${fm.created}` : null,
13
+ fm.entityRef ? `entityRef=${fm.entityRef}` : null,
14
+ ].filter(Boolean).join(" ");
15
+ throw new Error(
16
+ `memory frontmatter id must not be blank${context ? ` (${context})` : ""}`,
17
+ );
18
+ }
19
+ }
20
+
21
+ export function warnProjectionFallback(memoryDir: string, consumer: string): null {
22
+ const key = `${memoryDir}\0${consumer}`;
23
+ const now = Date.now();
24
+ const warnedAt = projectionFallbackWarnedAt.get(key) ?? 0;
25
+ if (now - warnedAt >= PROJECTION_FALLBACK_WARN_INTERVAL_MS) {
26
+ projectionFallbackWarnedAt.set(key, now);
27
+ log.warn(`storage.${consumer}: memory projection absent or empty; falling back to full corpus`);
28
+ }
29
+ return null;
30
+ }
package/src/storage.ts CHANGED
@@ -4,6 +4,7 @@ import { createHash } from "node:crypto"
4
4
  import { normalizeContent, computeContentHash } from "./content-hash.js";;
5
5
  import path from "node:path";
6
6
  import { log } from "./logger.js";
7
+ import { assertMemoryFrontmatterId, warnProjectionFallback } from "./storage-guards.js";
7
8
  import { MemoryReadStore } from "./storage/memory-read-store.js";
8
9
  import {
9
10
  readMaybeEncryptedLines,
@@ -308,6 +309,7 @@ function stripCitationMarkersForHashRemoval(value: string, template: string): st
308
309
  }
309
310
 
310
311
  function serializeFrontmatter(fm: MemoryFrontmatter): string {
312
+ assertMemoryFrontmatterId(fm);
311
313
  const lines = [
312
314
  "---",
313
315
  `id: ${fm.id}`,
@@ -6020,8 +6022,8 @@ export class StorageManager {
6020
6022
  lastAccessed: entry.lastAccessed,
6021
6023
  };
6022
6024
 
6023
- const fileContent = `${serializeFrontmatter(newFm)}\n\n${memory.content}\n`;
6024
6025
  try {
6026
+ const fileContent = `${serializeFrontmatter(newFm)}\n\n${memory.content}\n`;
6025
6027
  await this.writeStorageSecureFile(memory.path, fileContent);
6026
6028
  updated++;
6027
6029
  } catch (err) {
@@ -6070,8 +6072,7 @@ export class StorageManager {
6070
6072
 
6071
6073
  async getProjectedMemoryState(id: string): Promise<MemoryProjectionCurrentState | null> {
6072
6074
  const projected = readProjectedMemoryState(this.baseDir, id);
6073
- if (projected) return projected;
6074
-
6075
+ if (projected) return projected; warnProjectionFallback(this.baseDir, "getProjectedMemoryState");
6075
6076
  const active = await this.getMemoryById(id);
6076
6077
  if (active) return this.toProjectedCurrentState(active, "active");
6077
6078
 
@@ -6084,7 +6085,8 @@ export class StorageManager {
6084
6085
  async browseProjectedMemories(
6085
6086
  options: ProjectedMemoryBrowseOptions,
6086
6087
  ): Promise<ProjectedMemoryBrowsePage | null> {
6087
- return readProjectedMemoryBrowse(this.baseDir, options);
6088
+ return readProjectedMemoryBrowse(this.baseDir, options)
6089
+ ?? warnProjectionFallback(this.baseDir, "browseProjectedMemories");
6088
6090
  }
6089
6091
 
6090
6092
  async getProjectedGovernanceRecord(): Promise<ReturnType<typeof readProjectedGovernanceRecord>> {
@@ -6124,8 +6126,7 @@ export class StorageManager {
6124
6126
  if (cappedLimit === 0) return [];
6125
6127
 
6126
6128
  const projected = readProjectedMemoryTimeline(this.baseDir, memoryId, cappedLimit);
6127
- if (projected && projected.length > 0) return projected;
6128
-
6129
+ if (projected && projected.length > 0) return projected; warnProjectionFallback(this.baseDir, "getMemoryTimeline");
6129
6130
  const events = await this.readAllMemoryLifecycleEvents();
6130
6131
  return events.filter((event) => event.memoryId === memoryId).slice(-cappedLimit);
6131
6132
  }
@@ -6382,9 +6383,8 @@ export class StorageManager {
6382
6383
  updated: now,
6383
6384
  };
6384
6385
 
6385
- const fileContent = `${serializeFrontmatter(updatedFm)}\n\n${memory.content}\n`;
6386
-
6387
6386
  try {
6387
+ const fileContent = `${serializeFrontmatter(updatedFm)}\n\n${memory.content}\n`;
6388
6388
  await this.writeStorageSecureFile(memory.path, fileContent);
6389
6389
  await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.archiveMemories", {
6390
6390
  memoryId: id,