@devflow-tools/memory-engine 0.17.8 → 0.18.0

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 (155) hide show
  1. package/LICENSE +21 -0
  2. package/dist/consolidator.d.ts.map +1 -1
  3. package/dist/consolidator.js +2 -0
  4. package/dist/consolidator.js.map +1 -1
  5. package/dist/embedding-model-store.d.ts +1 -4
  6. package/dist/embedding-model-store.d.ts.map +1 -1
  7. package/dist/embedding-model-store.js +3 -75
  8. package/dist/embedding-model-store.js.map +1 -1
  9. package/dist/embedding-provider.d.ts +3 -2
  10. package/dist/embedding-provider.d.ts.map +1 -1
  11. package/dist/embedding-provider.js +151 -43
  12. package/dist/embedding-provider.js.map +1 -1
  13. package/dist/hybrid-search.d.ts +5 -1
  14. package/dist/hybrid-search.d.ts.map +1 -1
  15. package/dist/hybrid-search.js +92 -56
  16. package/dist/hybrid-search.js.map +1 -1
  17. package/dist/index.d.ts +16 -3
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +8 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/lease-store.d.ts.map +1 -1
  22. package/dist/lease-store.js +30 -0
  23. package/dist/lease-store.js.map +1 -1
  24. package/dist/memory-command-service.d.ts +73 -0
  25. package/dist/memory-command-service.d.ts.map +1 -0
  26. package/dist/memory-command-service.js +283 -0
  27. package/dist/memory-command-service.js.map +1 -0
  28. package/dist/memory-engine.d.ts +19 -15
  29. package/dist/memory-engine.d.ts.map +1 -1
  30. package/dist/memory-engine.js +207 -158
  31. package/dist/memory-engine.js.map +1 -1
  32. package/dist/memory-episode-repository.d.ts +77 -0
  33. package/dist/memory-episode-repository.d.ts.map +1 -0
  34. package/dist/memory-episode-repository.js +262 -0
  35. package/dist/memory-episode-repository.js.map +1 -0
  36. package/dist/memory-fact-provider-port.d.ts +83 -0
  37. package/dist/memory-fact-provider-port.d.ts.map +1 -0
  38. package/dist/memory-fact-provider-port.js +22 -0
  39. package/dist/memory-fact-provider-port.js.map +1 -0
  40. package/dist/memory-fact-validator.d.ts +16 -0
  41. package/dist/memory-fact-validator.d.ts.map +1 -0
  42. package/dist/memory-fact-validator.js +52 -0
  43. package/dist/memory-fact-validator.js.map +1 -0
  44. package/dist/memory-gate.d.ts +14 -17
  45. package/dist/memory-gate.d.ts.map +1 -1
  46. package/dist/memory-gate.js +369 -329
  47. package/dist/memory-gate.js.map +1 -1
  48. package/dist/memory-identity-compiler.d.ts +18 -0
  49. package/dist/memory-identity-compiler.d.ts.map +1 -0
  50. package/dist/memory-identity-compiler.js +26 -0
  51. package/dist/memory-identity-compiler.js.map +1 -0
  52. package/dist/memory-index-projector.d.ts +22 -0
  53. package/dist/memory-index-projector.d.ts.map +1 -0
  54. package/dist/memory-index-projector.js +11 -0
  55. package/dist/memory-index-projector.js.map +1 -0
  56. package/dist/memory-lifecycle-reducer.d.ts +21 -0
  57. package/dist/memory-lifecycle-reducer.d.ts.map +1 -0
  58. package/dist/memory-lifecycle-reducer.js +34 -0
  59. package/dist/memory-lifecycle-reducer.js.map +1 -0
  60. package/dist/memory-maintenance.d.ts +1 -4
  61. package/dist/memory-maintenance.d.ts.map +1 -1
  62. package/dist/memory-maintenance.js +3 -20
  63. package/dist/memory-maintenance.js.map +1 -1
  64. package/dist/memory-query-service.d.ts +24 -0
  65. package/dist/memory-query-service.d.ts.map +1 -0
  66. package/dist/memory-query-service.js +180 -0
  67. package/dist/memory-query-service.js.map +1 -0
  68. package/dist/memory-relevance.d.ts +16 -1
  69. package/dist/memory-relevance.d.ts.map +1 -1
  70. package/dist/memory-relevance.js +83 -139
  71. package/dist/memory-relevance.js.map +1 -1
  72. package/dist/memory-store.d.ts +52 -36
  73. package/dist/memory-store.d.ts.map +1 -1
  74. package/dist/memory-store.js +406 -704
  75. package/dist/memory-store.js.map +1 -1
  76. package/dist/semantic-memory-compiler.d.ts +0 -2
  77. package/dist/semantic-memory-compiler.d.ts.map +1 -1
  78. package/dist/semantic-memory-compiler.js +4 -4
  79. package/dist/semantic-memory-compiler.js.map +1 -1
  80. package/dist/session-summarizer.d.ts +3 -0
  81. package/dist/session-summarizer.d.ts.map +1 -1
  82. package/dist/session-summarizer.js.map +1 -1
  83. package/dist/signal-analyzer.d.ts +2 -2
  84. package/dist/signal-analyzer.d.ts.map +1 -1
  85. package/dist/signal-analyzer.js +10 -16
  86. package/dist/signal-analyzer.js.map +1 -1
  87. package/package.json +14 -5
  88. package/dist/lease-store.test.d.ts +0 -2
  89. package/dist/lease-store.test.d.ts.map +0 -1
  90. package/dist/lease-store.test.js +0 -182
  91. package/dist/lease-store.test.js.map +0 -1
  92. package/dist/migrations/v10-to-v11.d.ts +0 -3
  93. package/dist/migrations/v10-to-v11.d.ts.map +0 -1
  94. package/dist/migrations/v10-to-v11.js +0 -21
  95. package/dist/migrations/v10-to-v11.js.map +0 -1
  96. package/dist/migrations/v11-to-v12.d.ts +0 -66
  97. package/dist/migrations/v11-to-v12.d.ts.map +0 -1
  98. package/dist/migrations/v11-to-v12.js +0 -577
  99. package/dist/migrations/v11-to-v12.js.map +0 -1
  100. package/dist/migrations/v12-to-v13.d.ts +0 -3
  101. package/dist/migrations/v12-to-v13.d.ts.map +0 -1
  102. package/dist/migrations/v12-to-v13.js +0 -50
  103. package/dist/migrations/v12-to-v13.js.map +0 -1
  104. package/dist/migrations/v2-to-v3.d.ts +0 -3
  105. package/dist/migrations/v2-to-v3.d.ts.map +0 -1
  106. package/dist/migrations/v2-to-v3.js +0 -122
  107. package/dist/migrations/v2-to-v3.js.map +0 -1
  108. package/dist/migrations/v2-to-v3.test.d.ts +0 -2
  109. package/dist/migrations/v2-to-v3.test.d.ts.map +0 -1
  110. package/dist/migrations/v2-to-v3.test.js +0 -88
  111. package/dist/migrations/v2-to-v3.test.js.map +0 -1
  112. package/dist/migrations/v3-to-v4.d.ts +0 -3
  113. package/dist/migrations/v3-to-v4.d.ts.map +0 -1
  114. package/dist/migrations/v3-to-v4.js +0 -97
  115. package/dist/migrations/v3-to-v4.js.map +0 -1
  116. package/dist/migrations/v4-to-v5.d.ts +0 -3
  117. package/dist/migrations/v4-to-v5.d.ts.map +0 -1
  118. package/dist/migrations/v4-to-v5.js +0 -34
  119. package/dist/migrations/v4-to-v5.js.map +0 -1
  120. package/dist/migrations/v4-to-v5.test.d.ts +0 -2
  121. package/dist/migrations/v4-to-v5.test.d.ts.map +0 -1
  122. package/dist/migrations/v4-to-v5.test.js +0 -32
  123. package/dist/migrations/v4-to-v5.test.js.map +0 -1
  124. package/dist/migrations/v6-to-v7.d.ts +0 -3
  125. package/dist/migrations/v6-to-v7.d.ts.map +0 -1
  126. package/dist/migrations/v6-to-v7.js +0 -18
  127. package/dist/migrations/v6-to-v7.js.map +0 -1
  128. package/dist/migrations/v6-to-v7.test.d.ts +0 -2
  129. package/dist/migrations/v6-to-v7.test.d.ts.map +0 -1
  130. package/dist/migrations/v6-to-v7.test.js +0 -33
  131. package/dist/migrations/v6-to-v7.test.js.map +0 -1
  132. package/dist/migrations/v7-to-v8.d.ts +0 -3
  133. package/dist/migrations/v7-to-v8.d.ts.map +0 -1
  134. package/dist/migrations/v7-to-v8.js +0 -20
  135. package/dist/migrations/v7-to-v8.js.map +0 -1
  136. package/dist/migrations/v8-to-v9.d.ts +0 -3
  137. package/dist/migrations/v8-to-v9.d.ts.map +0 -1
  138. package/dist/migrations/v8-to-v9.js +0 -18
  139. package/dist/migrations/v8-to-v9.js.map +0 -1
  140. package/dist/migrations/v9-to-v10.d.ts +0 -3
  141. package/dist/migrations/v9-to-v10.d.ts.map +0 -1
  142. package/dist/migrations/v9-to-v10.js +0 -45
  143. package/dist/migrations/v9-to-v10.js.map +0 -1
  144. package/dist/migrations/v9-to-v10.test.d.ts +0 -2
  145. package/dist/migrations/v9-to-v10.test.d.ts.map +0 -1
  146. package/dist/migrations/v9-to-v10.test.js +0 -40
  147. package/dist/migrations/v9-to-v10.test.js.map +0 -1
  148. package/dist/preference-conflicts.test.d.ts +0 -2
  149. package/dist/preference-conflicts.test.d.ts.map +0 -1
  150. package/dist/preference-conflicts.test.js +0 -80
  151. package/dist/preference-conflicts.test.js.map +0 -1
  152. package/dist/semantic-memory.test.d.ts +0 -2
  153. package/dist/semantic-memory.test.d.ts.map +0 -1
  154. package/dist/semantic-memory.test.js +0 -61
  155. package/dist/semantic-memory.test.js.map +0 -1
@@ -8,12 +8,13 @@ import { GraphStore } from './graph-store.js';
8
8
  import { extractRuleMemories } from './rule-extractor.js';
9
9
  import { isObservationType } from './observation-types.js';
10
10
  import { classifyFailureCategory, evaluateStoredObservationQuality, observationClusterKey, } from './observation-quality.js';
11
- import { buildPreferenceConflictDiagnostics, normalizeExplicitPreferenceContent, } from './preference-conflicts.js';
11
+ import { buildPreferenceConflictDiagnostics, } from './preference-conflicts.js';
12
12
  import { enrichExplicitMemory, } from './explicit-memory-enricher.js';
13
- import { evaluateBootstrapMemory, MEMORY_SELECTION_THRESHOLD, normalizeMemoryEntities, } from './memory-relevance.js';
13
+ import { evaluateBootstrapMemory, MEMORY_SELECTION_THRESHOLD, } from './memory-relevance.js';
14
14
  import { resolveCapabilityRollout } from '@devflow-tools/sdk/capability-rollout';
15
- import { compileSemanticMemory } from './semantic-memory-compiler.js';
15
+ import { authorizeMemoryMutation, compileRetrievalScope, stripMutableIdentityMetadata } from '@devflow-tools/sdk';
16
16
  import { emitSemanticControlLog } from '@devflow-tools/telemetry';
17
+ import { MemoryCommandService } from './memory-command-service.js';
17
18
  function estimateMemoryTokens(text) {
18
19
  const cjkCharacters = text.match(/[\u3400-\u9fff\uf900-\ufaff]/g)?.length ?? 0;
19
20
  return Math.max(1, cjkCharacters + Math.ceil((text.length - cjkCharacters) / 4));
@@ -57,25 +58,21 @@ export class MemoryEngine {
57
58
  searcher;
58
59
  graph;
59
60
  rootPath;
60
- constructor(rootPath, embedder) {
61
+ projectIdentity;
62
+ commands;
63
+ constructor(rootPath, embedder, projectIdentity) {
61
64
  this.rootPath = rootPath;
62
- this.store = new MemoryStore(rootPath);
65
+ this.projectIdentity = projectIdentity;
63
66
  this.embedder = embedder ?? new EmbeddingProvider();
67
+ this.store = new MemoryStore(rootPath, this.embedder.getCapability('memory_retrieval').profile, projectIdentity);
64
68
  this.searcher = new HybridSearch(this.store, this.embedder, this);
65
69
  this.graph = new GraphStore(this.store);
66
- this.reconcileHistoricalExplicitPreferences();
70
+ this.commands = new MemoryCommandService(rootPath, this.store, projectIdentity);
67
71
  }
68
72
  _initPromise = null;
69
73
  _embedderAvailable = true;
70
- _explicitPreferencesReconciled = false;
71
- reconcileHistoricalExplicitPreferences() {
72
- if (this._explicitPreferencesReconciled)
73
- return;
74
- this.store.reconcileExplicitPreferenceConflicts(this.rootPath);
75
- this._explicitPreferencesReconciled = true;
76
- }
77
74
  getExplicitPreferenceConflictState() {
78
- const conflicts = buildPreferenceConflictDiagnostics(this.store.listCurrentExplicitPreferences(this.rootPath));
75
+ const conflicts = buildPreferenceConflictDiagnostics(this.store.listCurrentExplicitPreferences(this.projectIdentity.memoryNamespace));
79
76
  return {
80
77
  conflicts,
81
78
  withheldIds: new Set(conflicts.flatMap(conflict => conflict.memoryIds)),
@@ -91,8 +88,25 @@ export class MemoryEngine {
91
88
  async ensureInit() {
92
89
  if (!this._initPromise) {
93
90
  this._initPromise = (async () => {
94
- this.reconcileHistoricalExplicitPreferences();
91
+ const before = this.embedder.getCapability('memory_retrieval');
95
92
  this._embedderAvailable = await this.embedder.warmup();
93
+ const after = this.embedder.getCapability('memory_retrieval');
94
+ if (before.status !== after.status) {
95
+ emitSemanticControlLog({
96
+ event: 'embedding.capability.transitioned',
97
+ identity: { projectRoot: this.rootPath },
98
+ level: after.status === 'ready' ? 'info' : 'warn',
99
+ timestamp: Date.now(),
100
+ data: {
101
+ purpose: 'memory_retrieval',
102
+ profileHash: after.profile.profileHash,
103
+ generation: after.profile.generation,
104
+ from: before.status,
105
+ to: after.status,
106
+ failure: after.failure,
107
+ },
108
+ });
109
+ }
96
110
  })();
97
111
  }
98
112
  return this._initPromise;
@@ -275,6 +289,7 @@ export class MemoryEngine {
275
289
  executionId: normalized.executionId,
276
290
  action: normalized.action,
277
291
  requiredPolicyFacets: normalized.requiredPolicyFacets,
292
+ semanticTask: normalized.semanticTask,
278
293
  scope: normalized.scopes?.length === 1 ? normalized.scopes[0] : undefined,
279
294
  });
280
295
  const results = searchOutcome.results;
@@ -487,7 +502,6 @@ export class MemoryEngine {
487
502
  executionId: normalized.executionId,
488
503
  };
489
504
  this.store.recordRetrievalAccess({ ...access, accessType: 'selected' });
490
- this.store.recordRetrievalAccess({ ...access, accessType: 'exposed' });
491
505
  }
492
506
  catch { /* access reinforcement is non-critical */ }
493
507
  }
@@ -510,72 +524,49 @@ export class MemoryEngine {
510
524
  relevant: [],
511
525
  };
512
526
  }
513
- async saveExplicitMemoryIntent(content, sessionId, sourceEventId) {
514
- const normalizedContent = normalizeExplicitPreferenceContent(content);
515
- if (!normalizedContent)
516
- throw new Error('Explicit memory content must be nonblank');
517
- const observationSessionId = sessionId ?? `explicit:${Date.now()}`;
518
- const compilation = compileSemanticMemory({
519
- projectRoot: this.rootPath,
520
- narrative: normalizedContent,
527
+ async saveExplicitMemoryIntent(content, provenance, sourceEventId) {
528
+ const observationSessionId = provenance.sessionId;
529
+ const event = sourceEventId ? this.store.getEvent(sourceEventId) : null;
530
+ const turnId = eventTurnId(event, sourceEventId ?? `turn:${observationSessionId}`);
531
+ const result = await this.commands.commitExplicitEpisode({
532
+ content, actorId: provenance.actorId, sessionId: observationSessionId, turnId,
533
+ sourceMessageId: sourceEventId ?? `explicit-message:${turnId}`,
521
534
  sourceEventIds: sourceEventId ? [sourceEventId] : [],
522
- existing: this.store.listObservations().map(observation => ({
523
- id: observation.id,
524
- semanticKey: observation.semantic_key,
525
- facetKey: observation.facet_key,
526
- resolutionState: observation.resolution_state,
527
- })),
535
+ createdAt: event?.created_at,
536
+ supersedesEpisodeId: provenance.supersedesEpisodeId,
528
537
  });
529
- if (compilation.facts.length === 0) {
530
- throw new Error(`Explicit memory semantic compilation failed: ${compilation.status}`);
531
- }
532
- const existingIds = sourceEventId
533
- ? new Set(this.store.listObservations().map(observation => observation.id))
534
- : null;
535
- const rows = compilation.facts.map(fact => this.store.acceptExplicitPreference({
536
- sessionId: observationSessionId,
537
- project: this.rootPath,
538
- title: fact.enrichment.title,
539
- content: fact.enrichment.narrative,
540
- sourceEventId,
541
- enrichment: fact.enrichment,
542
- }));
543
- const row = rows[0];
544
- for (let index = 1; index < rows.length; index++) {
545
- this.store.addSemanticObservationRelation(row.id, rows[index].id, compilation.facts[index]?.relationToPrimary ?? 'related_to');
546
- }
547
- if (sourceEventId) {
548
- const event = this.store.getEvent(sourceEventId);
549
- if (!event) {
550
- return {
551
- id: row.id, type: row.type, title: row.title, content: row.narrative,
552
- source: row.source, scope: row.project, confidence: row.confidence,
553
- importance: row.importance, score: 1, createdAt: observationEffectiveTimestamp(row),
554
- indexStatus: row.embedding ? 'indexed' : 'pending',
555
- durable: true,
556
- lexicallyReady: row.quality_status === 'accepted',
557
- policyReady: row.quality_status === 'accepted' && compilation.facts[0].enrichment.facet !== 'generic',
558
- vectorStatus: row.embedding ? 'ready' : 'pending',
559
- relatedMemoryIds: rows.slice(1).map(item => item.id),
560
- semanticDecision: {
561
- status: compilation.status,
562
- operations: compilation.facts.map(fact => fact.operation),
563
- rejectedCount: compilation.rejected.length,
564
- },
565
- };
566
- }
538
+ const rows = result.factIds.flatMap(id => {
539
+ const row = this.store.getObservation(id);
540
+ return row ? [row] : [];
541
+ });
542
+ if (sourceEventId && event) {
567
543
  for (const saved of rows) {
568
544
  this.store.recordPendingTurnObservation({
569
545
  turnEventId: sourceEventId,
570
- turnId: eventTurnId(event, sourceEventId),
546
+ turnId,
571
547
  sessionId: observationSessionId,
572
- project: this.rootPath,
548
+ project: this.projectIdentity.memoryNamespace,
573
549
  observationId: saved.id,
574
- createdByTurn: !existingIds.has(saved.id),
550
+ createdByTurn: result.decisions.some(decision => decision.candidateId && decision.operation === 'add'),
575
551
  createdAt: event.created_at,
576
552
  });
577
553
  }
578
554
  }
555
+ const row = rows[0];
556
+ if (!row) {
557
+ return {
558
+ id: result.episode.episodeId, type: 'cold_episode',
559
+ title: result.episode.content.slice(0, 160), content: result.episode.content,
560
+ source: 'explicit_intent', scope: this.projectIdentity.memoryNamespace, confidence: 1, importance: 10,
561
+ score: 1, createdAt: result.episode.createdAt, indexStatus: 'pending', durable: true,
562
+ lexicallyReady: true, policyReady: false, vectorStatus: 'pending', relatedMemoryIds: [],
563
+ semanticDecision: {
564
+ status: result.status === 'held' ? 'semantic_rejected' : 'semantic_unavailable',
565
+ operations: result.decisions.map(decision => decision.operation),
566
+ rejectedCount: result.status === 'held' ? result.decisions.length : 0,
567
+ },
568
+ };
569
+ }
579
570
  return {
580
571
  id: row.id, type: row.type, title: row.title, content: row.narrative,
581
572
  source: row.source, scope: row.project, confidence: row.confidence,
@@ -583,37 +574,40 @@ export class MemoryEngine {
583
574
  indexStatus: row.embedding ? 'indexed' : 'pending',
584
575
  durable: true,
585
576
  lexicallyReady: row.quality_status === 'accepted',
586
- policyReady: row.quality_status === 'accepted' && compilation.facts[0].enrichment.facet !== 'generic',
577
+ policyReady: row.quality_status === 'accepted' && row.facet_key !== null,
587
578
  vectorStatus: row.embedding ? 'ready' : 'pending',
588
579
  relatedMemoryIds: rows.slice(1).map(item => item.id),
589
580
  semanticDecision: {
590
- status: compilation.status,
591
- operations: compilation.facts.map(fact => fact.operation),
592
- rejectedCount: compilation.rejected.length,
581
+ status: 'semantic_ready',
582
+ operations: result.decisions.map(decision => decision.operation),
583
+ rejectedCount: result.decisions.filter(decision => decision.operation === 'hold').length,
593
584
  },
594
585
  };
595
586
  }
596
587
  enrichExplicitMemoryObservation(observationId) {
597
588
  const observation = this.store.getObservation(observationId);
598
- if (!observation)
599
- throw new Error(`Observation ${observationId} not found`);
589
+ if (!observation) {
590
+ const episode = this.commands.getEpisode(observationId);
591
+ if (!episode)
592
+ throw new Error(`Observation ${observationId} not found`);
593
+ return enrichExplicitMemory({
594
+ projectRoot: this.rootPath,
595
+ content: episode.content,
596
+ sourceEventIds: episode.sourceEventIds,
597
+ });
598
+ }
600
599
  const enrichment = enrichExplicitMemory({
601
600
  projectRoot: this.rootPath,
602
601
  content: observation.narrative,
603
602
  sourceEventIds: parseStringArray(observation.source_event_ids),
604
603
  });
605
- this.store.applyExplicitMemoryEnrichment(observationId, enrichment);
606
604
  return enrichment;
607
605
  }
608
606
  attributeRetrievalBenefits(options) {
609
607
  const selectedRows = this.store.listRetrievalAccessForAttribution(options.sessionId, options.executionId);
610
608
  const unique = new Map(selectedRows.map(row => [row.source_id, row]));
611
- const answerEntities = normalizeMemoryEntities(options.finalAnswer ?? '');
612
- const normalizedAnswer = (options.finalAnswer ?? '').normalize('NFKC').toLocaleLowerCase('en-US');
613
- const authoritativeAdoptedIds = options.adoptedSourceIds
614
- ? new Set(options.adoptedSourceIds)
615
- : null;
616
- let adopted = 0;
609
+ const consumedSourceIds = new Set(options.consumedSourceIds);
610
+ let consumed = 0;
617
611
  let positive = 0;
618
612
  let negative = 0;
619
613
  const rollout = resolveCapabilityRollout(this.rootPath).config.outcomeAttribution;
@@ -622,23 +616,12 @@ export class MemoryEngine {
622
616
  const observation = row.source_type === 'observation' ? this.store.getObservation(row.source_id) : null;
623
617
  if (!memory && !observation)
624
618
  continue;
625
- const title = memory?.title ?? observation.title;
626
- const content = memory?.content ?? observation.narrative;
627
- const concepts = observation ? parseStringArray(observation.concepts) : [];
628
- const sourceEntities = normalizeMemoryEntities(`${title}\n${content}\n${concepts.join(' ')}`);
629
- const entityAdopted = [...sourceEntities].some(entity => answerEntities.has(entity));
630
- const factAdopted = parseStringArray(observation?.facts ?? '[]').some(fact => {
631
- const normalizedFact = fact.normalize('NFKC').toLocaleLowerCase('en-US').trim();
632
- return normalizedFact.length >= 12 && normalizedAnswer.includes(normalizedFact);
633
- });
634
- if (authoritativeAdoptedIds
635
- ? !authoritativeAdoptedIds.has(row.source_id)
636
- : !entityAdopted && !factAdopted)
619
+ if (!consumedSourceIds.has(row.source_id))
637
620
  continue;
638
621
  this.store.recordRetrievalAccess({
639
622
  sourceType: row.source_type,
640
623
  sourceId: row.source_id,
641
- accessType: 'adopted',
624
+ accessType: 'consumed',
642
625
  sessionId: row.session_id ?? options.sessionId,
643
626
  requestId: row.request_id ?? undefined,
644
627
  executionId: row.execution_id ?? options.executionId,
@@ -646,10 +629,27 @@ export class MemoryEngine {
646
629
  purpose: row.purpose ?? 'session_final_answer',
647
630
  rank: row.rank ?? undefined,
648
631
  score: row.score ?? undefined,
649
- idempotencyKey: `memory-adopted:${options.sessionId}:${options.executionId ?? ''}:${row.source_type}:${row.source_id}`,
632
+ idempotencyKey: `memory-consumed:${options.sessionId}:${options.executionId ?? ''}:${row.source_type}:${row.source_id}`,
650
633
  });
651
- adopted += 1;
652
- if (rollout !== 'off' && options.outcome && options.outcome !== 'unknown') {
634
+ consumed += 1;
635
+ if (options.verificationReceipt) {
636
+ this.store.recordRetrievalAccess({
637
+ sourceType: row.source_type,
638
+ sourceId: row.source_id,
639
+ accessType: 'verified',
640
+ sessionId: row.session_id ?? options.sessionId,
641
+ requestId: row.request_id ?? undefined,
642
+ executionId: row.execution_id ?? options.executionId,
643
+ query: row.query ?? undefined,
644
+ purpose: 'same_task_verification',
645
+ rank: row.rank ?? undefined,
646
+ score: row.score ?? undefined,
647
+ verificationReceipt: options.verificationReceipt,
648
+ idempotencyKey: `memory-verified:${options.sessionId}:${options.executionId ?? ''}:${row.source_type}:${row.source_id}`,
649
+ });
650
+ }
651
+ if (rollout !== 'off' && options.verificationReceipt
652
+ && options.outcome && options.outcome !== 'unknown') {
653
653
  const accessType = options.outcome === 'positive' ? 'positive_outcome' : 'negative_outcome';
654
654
  this.store.recordRetrievalAccess({
655
655
  sourceType: row.source_type,
@@ -675,7 +675,7 @@ export class MemoryEngine {
675
675
  negative += 1;
676
676
  }
677
677
  }
678
- return { selected: unique.size, adopted, positive, negative, unknown: Math.max(0, unique.size - adopted) };
678
+ return { selected: unique.size, consumed, positive, negative, unknown: Math.max(0, unique.size - consumed) };
679
679
  }
680
680
  recordContextEnvelopeSelection(options) {
681
681
  const entries = [...new Map(options.entries.map(entry => [entry.id, entry])).values()];
@@ -684,21 +684,19 @@ export class MemoryEngine {
684
684
  const sourceType = this.store.getObservation(id) ? 'observation' : this.store.get(id) ? 'memory' : null;
685
685
  if (!sourceType)
686
686
  continue;
687
- for (const accessType of ['selected', 'exposed']) {
688
- this.store.recordRetrievalAccess({
689
- sourceType,
690
- sourceId: id,
691
- accessType,
692
- sessionId: options.sessionId,
693
- executionId: options.executionId,
694
- requestId: options.requestId,
695
- query: options.query,
696
- purpose: 'task_context_envelope',
697
- rank: index + 1,
698
- score: entry.score,
699
- contextReceipt: options.contextReceipt,
700
- });
701
- }
687
+ this.store.recordRetrievalAccess({
688
+ sourceType,
689
+ sourceId: id,
690
+ accessType: 'selected',
691
+ sessionId: options.sessionId,
692
+ executionId: options.executionId,
693
+ requestId: options.requestId,
694
+ query: options.query,
695
+ purpose: 'task_context_envelope',
696
+ rank: index + 1,
697
+ score: entry.score,
698
+ contextReceipt: options.contextReceipt,
699
+ });
702
700
  }
703
701
  }
704
702
  listObservationMemories(source) {
@@ -729,18 +727,24 @@ export class MemoryEngine {
729
727
  source: m.source,
730
728
  }));
731
729
  }
732
- async approveCandidate(id) {
733
- this.store.update(id, { status: 'approved' });
730
+ async approveCandidate(id, provenance) {
731
+ await this.updateMemory(id, { status: 'approved' }, provenance);
734
732
  }
735
- async rejectCandidate(id) {
736
- this.store.delete(id);
733
+ async rejectCandidate(id, provenance) {
734
+ await this.deleteMemory(id, provenance);
737
735
  }
738
736
  // --- New API methods ---
739
737
  async search(query, options) {
738
+ return (await this.searchWithDiagnostics(query, options)).results;
739
+ }
740
+ async searchWithDiagnostics(query, options) {
740
741
  await this.ensureInit();
741
742
  const { withheldIds } = this.getExplicitPreferenceConflictState();
742
- const results = await this.searcher.search(query, { ...options, excludedIds: withheldIds });
743
- return results.filter(result => this.isSearchResultVisible(result, withheldIds));
743
+ const outcome = await this.searcher.searchWithDiagnostics(query, { ...options, excludedIds: withheldIds });
744
+ return {
745
+ ...outcome,
746
+ results: outcome.results.filter(result => this.isSearchResultVisible(result, withheldIds)),
747
+ };
744
748
  }
745
749
  getRetrievalRevision() {
746
750
  return this.store.getRetrievalRevision();
@@ -754,14 +758,12 @@ export class MemoryEngine {
754
758
  });
755
759
  return rows.map(r => this._rowToRecord(r));
756
760
  }
757
- async addMemory(entry) {
758
- return this.addMemoryWithStatus(entry);
761
+ async addMemory(entry, provenance) {
762
+ return this.addMemoryWithStatus(entry, provenance);
759
763
  }
760
- async addMemoryWithStatus(entry, requestedStatus, options = {}) {
764
+ async addMemoryWithStatus(entry, provenance, requestedStatus, options = {}) {
761
765
  await this.ensureInit();
762
- if (entry.sessionId) {
763
- await this.ensureSession(entry.sessionId, this.rootPath);
764
- }
766
+ await this.ensureSession(provenance.sessionId, this.projectIdentity.memoryNamespace);
765
767
  // 1. SHA-256 content hash exact dedup
766
768
  const normalizedContent = entry.content
767
769
  .replace(/\/Users\/[^/\s]+(\/\S+)?/g, '{PROJECT_ROOT}$1')
@@ -813,21 +815,21 @@ export class MemoryEngine {
813
815
  if (bestSim > 0.85 && bestMatch) {
814
816
  if (entry.content.length > bestMatch.content.length) {
815
817
  const newRow = this.store.add({
818
+ actorId: provenance.actorId,
816
819
  type: bestMatch.type,
817
- category: entry.category,
818
820
  content: entry.content,
819
821
  originalText: entry.originalText,
820
822
  source: entry.source,
821
823
  status: requestedStatus ?? bestMatch.status,
822
824
  confidence: entry.confidence ?? bestMatch.confidence,
823
825
  scope: entry.scope ?? bestMatch.scope,
824
- sessionId: entry.sessionId,
826
+ sessionId: provenance.sessionId,
825
827
  });
826
- this.store.update(newRow.id, {
828
+ this.updateStoredRow(newRow, {
827
829
  version: (bestMatch.version || 1) + 1,
828
830
  supersedes: bestMatch.id,
829
831
  });
830
- this.store.update(bestMatch.id, {
832
+ this.updateStoredRow(bestMatch, {
831
833
  status: 'superseded',
832
834
  superseded_by: newRow.id,
833
835
  });
@@ -840,7 +842,7 @@ export class MemoryEngine {
840
842
  reason: `superseded_by:${newRow.id}`,
841
843
  });
842
844
  emitSemanticControlLog({
843
- event: 'memory.write.completed', identity: { projectRoot: this.rootPath, sessionId: entry.sessionId }, level: 'info', timestamp: Date.now(),
845
+ event: 'memory.write.completed', identity: { projectRoot: this.rootPath, sessionId: provenance.sessionId }, level: 'info', timestamp: Date.now(),
844
846
  data: { stage: 'version', oldMemoryId: bestMatch.id, newMemoryId: newRow.id, version: (bestMatch.version || 1) + 1 },
845
847
  });
846
848
  return this._rowToRecord(newRow);
@@ -861,14 +863,17 @@ export class MemoryEngine {
861
863
  const arbitration = await this.detectAndArbitrateContradiction(entry.content, newEmb);
862
864
  if (arbitration.decision === 'skip') {
863
865
  emitSemanticControlLog({
864
- event: 'memory.write.completed', identity: { projectRoot: this.rootPath, sessionId: entry.sessionId }, level: 'info', timestamp: Date.now(),
866
+ event: 'memory.write.completed', identity: { projectRoot: this.rootPath, sessionId: provenance.sessionId }, level: 'info', timestamp: Date.now(),
865
867
  data: { stage: 'contradiction_arbitration', operation: 'hold', reason: arbitration.reason },
866
868
  });
867
869
  return null;
868
870
  }
869
871
  if (arbitration.decision === 'merge' && arbitration.conflictingId) {
870
872
  const mergedContent = arbitration.mergedContent || `${entry.content}`;
871
- this.store.update(arbitration.conflictingId, { content: mergedContent });
873
+ const conflicting = this.store.get(arbitration.conflictingId);
874
+ if (!conflicting)
875
+ throw new Error('MEMORY_ID_NOT_FOUND');
876
+ this.updateStoredRow(conflicting, { content: mergedContent });
872
877
  this.store.incrementAccess(arbitration.conflictingId);
873
878
  this.store.recordHistory({
874
879
  memoryId: arbitration.conflictingId,
@@ -877,7 +882,7 @@ export class MemoryEngine {
877
882
  reason: `contradiction_merged: ${arbitration.reason ?? 'unknown'}`,
878
883
  });
879
884
  emitSemanticControlLog({
880
- event: 'memory.write.completed', identity: { projectRoot: this.rootPath, sessionId: entry.sessionId }, level: 'info', timestamp: Date.now(),
885
+ event: 'memory.write.completed', identity: { projectRoot: this.rootPath, sessionId: provenance.sessionId }, level: 'info', timestamp: Date.now(),
881
886
  data: { stage: 'contradiction_arbitration', operation: 'merge', conflictingMemoryId: arbitration.conflictingId, reason: arbitration.reason },
882
887
  });
883
888
  const mergedRow = this.store.get(arbitration.conflictingId);
@@ -888,20 +893,21 @@ export class MemoryEngine {
888
893
  }
889
894
  catch (err) {
890
895
  emitSemanticControlLog({
891
- event: 'memory.write.degraded', identity: { projectRoot: this.rootPath, sessionId: entry.sessionId }, level: 'warn', timestamp: Date.now(),
896
+ event: 'memory.write.degraded', identity: { projectRoot: this.rootPath, sessionId: provenance.sessionId }, level: 'warn', timestamp: Date.now(),
892
897
  data: { stage: 'contradiction_detection', reason: memoryEngineErrorReason(err), fallback: 'write_without_arbitration' },
893
898
  });
894
899
  }
895
900
  }
896
901
  const row = this.store.add({
897
- category: entry.category,
902
+ actorId: provenance.actorId,
903
+ type: entry.category,
898
904
  content: entry.content,
899
905
  originalText: entry.originalText,
900
906
  source: entry.source,
901
907
  status: insertStatus,
902
908
  confidence: entry.confidence,
903
909
  scope: entry.scope,
904
- sessionId: entry.sessionId,
910
+ sessionId: provenance.sessionId,
905
911
  });
906
912
  // 4. Audit: record ADD event
907
913
  this.store.recordHistory({
@@ -927,10 +933,42 @@ export class MemoryEngine {
927
933
  }
928
934
  reconcileRuleStatus(row, requestedStatus) {
929
935
  if (requestedStatus && row.source === 'rule_engine' && row.status !== requestedStatus) {
930
- this.store.update(row.id, { status: requestedStatus });
936
+ this.updateStoredRow(row, { status: requestedStatus });
931
937
  row.status = requestedStatus;
932
938
  }
933
939
  }
940
+ authorizeMutation(row, provenance) {
941
+ const requestScope = compileRetrievalScope({
942
+ channel: 'memory', operation: 'write',
943
+ ...this.projectIdentity, ...provenance,
944
+ });
945
+ const authorized = authorizeMemoryMutation({
946
+ id: row.id,
947
+ requestScope,
948
+ storedScope: {
949
+ projectId: row.project_id,
950
+ memoryNamespace: row.memory_namespace,
951
+ actorId: row.actor_id,
952
+ sessionId: row.session_id ?? '',
953
+ },
954
+ });
955
+ return {
956
+ id: authorized.id,
957
+ projectId: authorized.scope.projectId,
958
+ memoryNamespace: authorized.scope.memoryNamespace,
959
+ actorId: authorized.scope.actorId,
960
+ sessionId: authorized.scope.sessionId,
961
+ };
962
+ }
963
+ updateStoredRow(row, updates) {
964
+ this.store.updateScoped({
965
+ id: row.id,
966
+ projectId: row.project_id,
967
+ memoryNamespace: row.memory_namespace,
968
+ actorId: row.actor_id,
969
+ sessionId: row.session_id ?? '',
970
+ }, updates);
971
+ }
934
972
  /** Map internal MemoryRow → SDK MemoryEntryRecord */
935
973
  _rowToRecord(row) {
936
974
  const sdkStatus = row.status === 'active' ? 'approved' :
@@ -938,6 +976,9 @@ export class MemoryEngine {
938
976
  row.status;
939
977
  return {
940
978
  id: row.id,
979
+ projectId: row.project_id,
980
+ memoryNamespace: row.memory_namespace,
981
+ actorId: row.actor_id,
941
982
  category: row.type,
942
983
  content: row.content,
943
984
  originalText: row.original_text ?? undefined,
@@ -950,9 +991,12 @@ export class MemoryEngine {
950
991
  approvedAt: (row.status === 'active' || row.status === 'approved') ? row.updated_at : undefined,
951
992
  };
952
993
  }
953
- async updateMemory(id, updates) {
994
+ async updateMemory(id, updates, provenance) {
954
995
  const old = this.store.get(id);
955
- this.store.update(id, updates);
996
+ if (!old)
997
+ throw new Error('MEMORY_ID_NOT_FOUND');
998
+ const scope = this.authorizeMutation(old, provenance);
999
+ this.store.updateScoped(scope, stripMutableIdentityMetadata(updates));
956
1000
  if (old) {
957
1001
  this.store.recordHistory({
958
1002
  memoryId: id,
@@ -965,31 +1009,32 @@ export class MemoryEngine {
965
1009
  });
966
1010
  }
967
1011
  }
968
- async deleteMemory(id) {
1012
+ async deleteMemory(id, provenance) {
969
1013
  const old = this.store.get(id);
970
- if (old) {
971
- this.store.recordHistory({
972
- memoryId: id,
973
- event: 'DELETE',
974
- oldContent: old.content,
975
- reason: 'manual_delete',
976
- });
977
- }
978
- this.store.delete(id);
1014
+ if (!old)
1015
+ throw new Error('MEMORY_ID_NOT_FOUND');
1016
+ const scope = this.authorizeMutation(old, provenance);
1017
+ this.store.recordHistory({
1018
+ memoryId: id,
1019
+ event: 'DELETE',
1020
+ oldContent: old.content,
1021
+ reason: 'manual_delete',
1022
+ });
1023
+ this.store.deleteScoped(scope);
979
1024
  }
980
1025
  async getPendingMemories() {
981
1026
  return this.listMemories({ status: 'pending' });
982
1027
  }
983
- async approveMemory(id) {
984
- this.store.update(id, { status: 'approved' });
1028
+ async approveMemory(id, provenance) {
1029
+ await this.updateMemory(id, { status: 'approved' }, provenance);
985
1030
  this.store.recordHistory({
986
1031
  memoryId: id,
987
1032
  event: 'REINFORCE',
988
1033
  reason: 'user_approved',
989
1034
  });
990
1035
  }
991
- async rejectMemory(id) {
992
- this.store.delete(id);
1036
+ async rejectMemory(id, provenance) {
1037
+ await this.deleteMemory(id, provenance);
993
1038
  }
994
1039
  // --- Session Consolidation & Contradiction Detection ---
995
1040
  async consolidateSession(sessionId) {
@@ -1014,7 +1059,7 @@ export class MemoryEngine {
1014
1059
  source: 'auto',
1015
1060
  confidence: 0.5,
1016
1061
  scope: sessionId,
1017
- });
1062
+ }, { actorId: 'actor:devflow-consolidator', sessionId });
1018
1063
  const expiresAt = Date.now() + 30 * 24 * 60 * 60 * 1000;
1019
1064
  this.store.setExpires(entry.id, expiresAt);
1020
1065
  return entry;
@@ -1062,6 +1107,7 @@ export class MemoryEngine {
1062
1107
  return { decision: 'keep' };
1063
1108
  }
1064
1109
  close() {
1110
+ this.commands.close();
1065
1111
  this.store.close();
1066
1112
  }
1067
1113
  // --- Event Recording & Extraction Pipeline ---
@@ -1106,7 +1152,7 @@ export class MemoryEngine {
1106
1152
  const existing = existingByContent.get(candidate.content);
1107
1153
  if (existing) {
1108
1154
  if (existing.source === 'rule_engine' && existing.status !== desiredStatus) {
1109
- this.store.update(existing.id, { status: desiredStatus });
1155
+ this.updateStoredRow(existing, { status: desiredStatus });
1110
1156
  existing.status = desiredStatus;
1111
1157
  }
1112
1158
  continue;
@@ -1118,6 +1164,9 @@ export class MemoryEngine {
1118
1164
  source: 'rule_engine',
1119
1165
  confidence: candidate.confidence,
1120
1166
  scope: '',
1167
+ }, {
1168
+ actorId: 'actor:devflow-rule-engine',
1169
+ sessionId: 'session:rule-engine',
1121
1170
  }, desiredStatus, { indexDerived: false });
1122
1171
  saved.push(entry);
1123
1172
  existingByContent.set(candidate.content, this.store.get(entry.id));