@shadowforge0/aquifer-memory 1.8.1 → 1.9.1

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 (57) hide show
  1. package/.env.example +1 -0
  2. package/README.md +82 -26
  3. package/README_CN.md +33 -23
  4. package/README_TW.md +25 -24
  5. package/aquifer.config.example.json +2 -1
  6. package/consumers/cli.js +587 -33
  7. package/consumers/codex-active-checkpoint.js +3 -1
  8. package/consumers/codex-current-memory.js +10 -6
  9. package/consumers/codex.js +6 -3
  10. package/consumers/default/daily-entries.js +2 -2
  11. package/consumers/default/index.js +40 -30
  12. package/consumers/default/prompts/summary.js +2 -2
  13. package/consumers/mcp.js +56 -46
  14. package/consumers/openclaw-ext/index.js +65 -7
  15. package/consumers/openclaw-ext/openclaw.plugin.json +1 -1
  16. package/consumers/openclaw-ext/package.json +1 -1
  17. package/consumers/openclaw-install.js +326 -0
  18. package/consumers/openclaw-plugin.js +105 -24
  19. package/consumers/shared/compat-recall.js +101 -0
  20. package/consumers/shared/config.js +2 -0
  21. package/consumers/shared/openclaw-product-tools.js +130 -0
  22. package/consumers/shared/recall-format.js +2 -2
  23. package/core/aquifer.js +553 -41
  24. package/core/backends/local.js +169 -1
  25. package/core/doctor.js +924 -0
  26. package/core/finalization-inspector.js +164 -0
  27. package/core/finalization-review.js +88 -42
  28. package/core/interface.js +629 -0
  29. package/core/mcp-manifest.js +11 -3
  30. package/core/memory-bootstrap.js +25 -27
  31. package/core/memory-consolidation.js +564 -42
  32. package/core/memory-explain.js +593 -0
  33. package/core/memory-promotion.js +392 -55
  34. package/core/memory-recall.js +75 -71
  35. package/core/memory-records.js +107 -108
  36. package/core/memory-review.js +891 -0
  37. package/core/memory-serving.js +61 -4
  38. package/core/memory-type-policy.js +298 -0
  39. package/core/operator-observability.js +249 -0
  40. package/core/postgres-migrations.js +22 -0
  41. package/core/session-checkpoint-producer.js +3 -1
  42. package/core/session-checkpoints.js +1 -1
  43. package/core/session-finalization.js +78 -3
  44. package/core/storage.js +124 -8
  45. package/docs/getting-started.md +50 -4
  46. package/docs/setup.md +163 -24
  47. package/package.json +5 -4
  48. package/schema/004-completion.sql +4 -4
  49. package/schema/010-v1-finalization-review.sql +72 -0
  50. package/schema/019-v1-memory-review-resolutions.sql +53 -0
  51. package/schema/020-v1-assistant-shaping-memory.sql +30 -0
  52. package/scripts/backfill-canonical-key.js +1 -1
  53. package/scripts/codex-checkpoint-commands.js +28 -0
  54. package/scripts/codex-checkpoint-runtime.js +109 -0
  55. package/scripts/codex-recovery.js +16 -4
  56. package/scripts/diagnose-fts-zh.js +1 -1
  57. package/scripts/extract-insights-from-recent-sessions.js +4 -4
@@ -1,49 +1,16 @@
1
1
  'use strict';
2
2
 
3
3
  const crypto = require('crypto');
4
-
5
- const ACTIVE_V1_TYPES = new Set([
6
- 'fact',
7
- 'state',
8
- 'decision',
9
- 'preference',
10
- 'constraint',
11
- 'entity_note',
12
- 'open_loop',
13
- 'conclusion',
14
- ]);
15
-
16
- const AUTHORITY_RANK = {
17
- raw_transcript: 0,
18
- llm_inference: 1,
19
- verified_summary: 2,
20
- manual: 3,
21
- system: 4,
22
- executable_evidence: 5,
23
- user_explicit: 6,
24
- };
25
-
26
- const DEFAULT_INHERITANCE = {
27
- constraint: 'additive',
28
- preference: 'defaultable',
29
- state: 'defaultable',
30
- fact: 'defaultable',
31
- conclusion: 'defaultable',
32
- entity_note: 'defaultable',
33
- decision: 'non_inheritable',
34
- open_loop: 'non_inheritable',
35
- };
36
-
37
- const DEFAULT_ASPECT = {
38
- fact: 'fact',
39
- state: 'state',
40
- decision: 'decision',
41
- preference: 'preference',
42
- constraint: 'constraint',
43
- entity_note: 'entity_note',
44
- open_loop: 'open_loop',
45
- conclusion: 'conclusion',
46
- };
4
+ const { assessTextForEnrich } = require('./memory-safety-gate');
5
+ const {
6
+ assistantShapingKindAllowed,
7
+ assistantShapingServingImpactAllowed,
8
+ assistantShapingServingImpactKnown,
9
+ authorityStrength,
10
+ defaultAspectForMemoryType,
11
+ defaultInheritanceForMemoryType,
12
+ isActiveMemoryType,
13
+ } = require('./memory-type-policy');
47
14
 
48
15
  const FORBIDDEN_TAGS = new Set([
49
16
  'commentary',
@@ -59,6 +26,28 @@ const FORBIDDEN_TAGS = new Set([
59
26
  'env_dump',
60
27
  ]);
61
28
 
29
+ const ASSISTANT_BEHAVIOR_LANGUAGE_LEVELS = new Set(['user_behavior', 'assistant_behavior']);
30
+
31
+ const CANDIDATE_TEXT_FIELDS = [
32
+ 'title',
33
+ 'summary',
34
+ 'evidenceText',
35
+ 'evidence_text',
36
+ 'evidenceExcerpt',
37
+ 'evidence_excerpt',
38
+ 'sourceText',
39
+ 'source_text',
40
+ 'quote',
41
+ ];
42
+
43
+ const CANDIDATE_STRUCTURED_TEXT_FIELDS = [
44
+ 'payload',
45
+ 'evidenceItems',
46
+ 'evidence_items',
47
+ 'evidenceTexts',
48
+ 'evidence_texts',
49
+ ];
50
+
62
51
  function normalizeText(value) {
63
52
  return String(value || '').trim().replace(/\s+/g, ' ');
64
53
  }
@@ -67,6 +56,185 @@ function normalizeType(value) {
67
56
  return normalizeText(value).toLowerCase();
68
57
  }
69
58
 
59
+ function normalizePolicyToken(value) {
60
+ return normalizeText(value)
61
+ .toLowerCase()
62
+ .replace(/[\s-]+/g, '_');
63
+ }
64
+
65
+ function candidatePayload(candidate = {}) {
66
+ return candidate.payload && typeof candidate.payload === 'object' && !Array.isArray(candidate.payload)
67
+ ? candidate.payload
68
+ : {};
69
+ }
70
+
71
+ function pickPolicyToken(candidate = {}, aliases = []) {
72
+ const payload = candidatePayload(candidate);
73
+ for (const key of aliases) {
74
+ const value = payload[key] ?? candidate[key];
75
+ const normalized = normalizePolicyToken(value);
76
+ if (normalized) return normalized;
77
+ }
78
+ return '';
79
+ }
80
+
81
+ function pickPolicyText(candidate = {}, aliases = []) {
82
+ const payload = candidatePayload(candidate);
83
+ for (const key of aliases) {
84
+ const value = normalizeText(payload[key] ?? candidate[key]);
85
+ if (value) return value;
86
+ }
87
+ return '';
88
+ }
89
+
90
+ function numericPolicyValue(value) {
91
+ const parsed = Number(value);
92
+ return Number.isFinite(parsed) ? parsed : 0;
93
+ }
94
+
95
+ function arrayPolicyCount(value) {
96
+ return Array.isArray(value) ? value.filter(Boolean).length : 0;
97
+ }
98
+
99
+ function temporalSupportForAssistantShaping(candidate = {}) {
100
+ const payload = candidatePayload(candidate);
101
+ const support = payload.temporalSupport || payload.temporal_support || candidate.temporalSupport || candidate.temporal_support || {};
102
+ const refs = normalizeEvidenceRefs(candidate);
103
+ const distinctRefs = new Set(
104
+ (Array.isArray(refs) ? refs : [])
105
+ .map(ref => `${ref.sourceKind || ref.source_kind || 'source'}:${ref.sourceRef || ref.source_ref || ''}`)
106
+ .filter(value => !value.endsWith(':')),
107
+ );
108
+ const countSignals = [
109
+ support.sessionCount,
110
+ support.session_count,
111
+ support.observationCount,
112
+ support.observation_count,
113
+ payload.sessionCount,
114
+ payload.session_count,
115
+ payload.observationCount,
116
+ payload.observation_count,
117
+ candidate.sessionCount,
118
+ candidate.session_count,
119
+ candidate.observationCount,
120
+ candidate.observation_count,
121
+ ].map(numericPolicyValue);
122
+ const arraySignals = [
123
+ support.sessions,
124
+ support.sessionIds,
125
+ support.session_ids,
126
+ support.observations,
127
+ support.evidence,
128
+ payload.observations,
129
+ payload.temporalObservations,
130
+ payload.temporal_observations,
131
+ candidate.observations,
132
+ ].map(arrayPolicyCount);
133
+ const maxCount = Math.max(0, distinctRefs.size, ...countSignals, ...arraySignals);
134
+ const consolidationWindow = normalizeText(
135
+ support.window ||
136
+ support.cadence ||
137
+ support.period ||
138
+ payload.distillationWindow ||
139
+ payload.distillation_window ||
140
+ candidate.distillationWindow ||
141
+ candidate.distillation_window,
142
+ );
143
+ const repeated = support.repeated === true
144
+ || payload.repeated === true
145
+ || candidate.repeated === true
146
+ || support.pattern === true
147
+ || payload.pattern === true
148
+ || candidate.pattern === true;
149
+ return {
150
+ supported: maxCount >= 2 || repeated || Boolean(consolidationWindow),
151
+ observationCount: maxCount,
152
+ consolidationWindow: consolidationWindow || null,
153
+ repeated,
154
+ };
155
+ }
156
+
157
+ function assistantBehaviorAbstraction(candidate = {}) {
158
+ const payload = candidatePayload(candidate);
159
+ const distillation = payload.distillation && typeof payload.distillation === 'object' ? payload.distillation : {};
160
+ const promotionTarget = normalizePolicyToken(
161
+ distillation.promotionTarget
162
+ || distillation.promotion_target
163
+ || payload.promotionTarget
164
+ || payload.promotion_target,
165
+ );
166
+ if (promotionTarget !== 'assistant_behavior_memory') {
167
+ return { required: false, eligible: true };
168
+ }
169
+ const abstraction = payload.abstraction && typeof payload.abstraction === 'object'
170
+ ? payload.abstraction
171
+ : (payload.generalization && typeof payload.generalization === 'object' ? payload.generalization : {});
172
+ const boolValue = value => {
173
+ if (value === true || value === false) return value;
174
+ if (typeof value === 'string') {
175
+ const normalized = value.trim().toLowerCase();
176
+ if (normalized === 'true') return true;
177
+ if (normalized === 'false') return false;
178
+ }
179
+ return null;
180
+ };
181
+ const appliesBeyondSource = boolValue(
182
+ abstraction.appliesBeyondSource
183
+ ?? abstraction.applies_beyond_source
184
+ ?? payload.appliesBeyondSource
185
+ ?? payload.applies_beyond_source,
186
+ );
187
+ const sourceBound = boolValue(
188
+ abstraction.sourceBound
189
+ ?? abstraction.source_bound
190
+ ?? payload.sourceBound
191
+ ?? payload.source_bound,
192
+ );
193
+ const principle = normalizeText(
194
+ abstraction.principle
195
+ || abstraction.behaviorPrinciple
196
+ || abstraction.behavior_principle
197
+ || payload.behaviorPrinciple
198
+ || payload.behavior_principle,
199
+ );
200
+ const languageLevel = normalizePolicyToken(
201
+ abstraction.languageLevel
202
+ || abstraction.language_level
203
+ || payload.languageLevel
204
+ || payload.language_level,
205
+ );
206
+ const generalized = appliesBeyondSource === true && sourceBound === false && Boolean(principle);
207
+ const behaviorLanguage = ASSISTANT_BEHAVIOR_LANGUAGE_LEVELS.has(languageLevel);
208
+ return {
209
+ required: true,
210
+ eligible: generalized && behaviorLanguage,
211
+ generalized,
212
+ behaviorLanguage,
213
+ appliesBeyondSource,
214
+ sourceBound,
215
+ principle,
216
+ languageLevel,
217
+ };
218
+ }
219
+
220
+ function authorityBypassesTemporalGate(authority) {
221
+ return authorityStrength(authority) >= authorityStrength('manual');
222
+ }
223
+
224
+ function buildAssistantShapingAspect(candidate = {}, subject = '') {
225
+ const shapingKind = pickPolicyToken(candidate, ['shapingKind', 'shaping_kind', 'kind', 'category']);
226
+ const servingImpact = pickPolicyToken(candidate, ['servingImpact', 'serving_impact', 'impact']);
227
+ const policyKey = pickPolicyToken(candidate, ['policyKey', 'policy_key', 'ruleKey', 'rule_key', 'policyId', 'policy_id']);
228
+ const explicitAspect = pickPolicyToken(candidate, ['aspect', 'attribute', 'predicate']);
229
+ const stableSubject = normalizePolicyToken(subject) || 'session';
230
+ const identityToken = policyKey || explicitAspect || stableSubject;
231
+ return [
232
+ shapingKind || defaultAspectForMemoryType('assistant_shaping'),
233
+ servingImpact || 'assistant_behavior',
234
+ identityToken,
235
+ ].filter(Boolean).join(':');
236
+ }
237
+
70
238
  function stableHash(value) {
71
239
  return crypto
72
240
  .createHash('sha256')
@@ -92,11 +260,11 @@ function assertionHash(value) {
92
260
  }
93
261
 
94
262
  function defaultInheritanceForType(memoryType) {
95
- return DEFAULT_INHERITANCE[normalizeType(memoryType)] || 'defaultable';
263
+ return defaultInheritanceForMemoryType(memoryType);
96
264
  }
97
265
 
98
266
  function authorityRank(authority) {
99
- return AUTHORITY_RANK[String(authority || 'llm_inference').toLowerCase()] ?? 0;
267
+ return authorityStrength(authority || 'llm_inference');
100
268
  }
101
269
 
102
270
  function buildCanonicalKey({ memoryType, scopeKey, contextKey, topicKey, subject, aspect }) {
@@ -106,7 +274,7 @@ function buildCanonicalKey({ memoryType, scopeKey, contextKey, topicKey, subject
106
274
  const topic = normalizeType(topicKey || '');
107
275
  const scopePart = [scope, context, topic].filter(Boolean).join('|');
108
276
  const subj = normalizeType(subject || 'session');
109
- const asp = normalizeType(aspect || DEFAULT_ASPECT[type] || type);
277
+ const asp = normalizeType(aspect || defaultAspectForMemoryType(type));
110
278
  return [type, scopePart, subj, asp].join(':');
111
279
  }
112
280
 
@@ -267,6 +435,87 @@ function sameClaim(a, b) {
267
435
  === normalizeText(b && (b.summary || b.title)).toLowerCase();
268
436
  }
269
437
 
438
+ function rowValue(obj, camel, snake = null) {
439
+ if (!obj || typeof obj !== 'object') return null;
440
+ if (obj[camel] !== undefined && obj[camel] !== null && obj[camel] !== '') return obj[camel];
441
+ if (snake && obj[snake] !== undefined && obj[snake] !== null && obj[snake] !== '') return obj[snake];
442
+ return null;
443
+ }
444
+
445
+ function activeIdentityMismatch(candidate = {}, existing = {}) {
446
+ const candidateType = normalizeType(rowValue(candidate, 'memoryType', 'memory_type'));
447
+ const existingType = normalizeType(rowValue(existing, 'memoryType', 'memory_type'));
448
+ if (candidateType && existingType && candidateType !== existingType) return 'memory_type';
449
+
450
+ const candidateScopeId = rowValue(candidate, 'scopeId', 'scope_id');
451
+ const existingScopeId = rowValue(existing, 'scopeId', 'scope_id');
452
+ if (candidateScopeId && existingScopeId && String(candidateScopeId) !== String(existingScopeId)) return 'scope_id';
453
+
454
+ const candidateScopeKey = rowValue(candidate, 'scopeKey', 'scope_key');
455
+ const existingScopeKey = rowValue(existing, 'scopeKey', 'scope_key');
456
+ if (candidateScopeKey && existingScopeKey && String(candidateScopeKey) !== String(existingScopeKey)) return 'scope_key';
457
+
458
+ return null;
459
+ }
460
+
461
+ function sanitizeCandidateText(value, tags) {
462
+ if (typeof value === 'string') {
463
+ const assessment = assessTextForEnrich(value, 'assistant');
464
+ for (const tag of assessment.tags || []) tags.add(tag);
465
+ if (assessment.action === 'drop') return undefined;
466
+ return assessment.text;
467
+ }
468
+
469
+ if (Array.isArray(value)) {
470
+ return value
471
+ .map(item => sanitizeCandidateText(item, tags))
472
+ .filter(item => item !== undefined);
473
+ }
474
+
475
+ if (value && typeof value === 'object') {
476
+ const out = {};
477
+ for (const [key, child] of Object.entries(value)) {
478
+ const sanitized = sanitizeCandidateText(child, tags);
479
+ if (sanitized !== undefined) out[key] = sanitized;
480
+ }
481
+ return Object.keys(out).length > 0 ? out : undefined;
482
+ }
483
+
484
+ return value;
485
+ }
486
+
487
+ function sanitizePromotionCandidate(candidate = {}) {
488
+ const next = { ...candidate };
489
+ const tags = new Set([
490
+ ...(candidate.pollutionTags || []),
491
+ ...(candidate.tags || []),
492
+ ].map(tag => String(tag || '').trim().toLowerCase()).filter(Boolean));
493
+
494
+ for (const field of CANDIDATE_TEXT_FIELDS) {
495
+ if (next[field] === undefined) continue;
496
+ const sanitized = sanitizeCandidateText(next[field], tags);
497
+ if (sanitized === undefined) delete next[field];
498
+ else next[field] = sanitized;
499
+ }
500
+
501
+ for (const field of CANDIDATE_STRUCTURED_TEXT_FIELDS) {
502
+ if (next[field] === undefined) continue;
503
+ const sanitized = sanitizeCandidateText(next[field], tags);
504
+ if (sanitized === undefined) delete next[field];
505
+ else next[field] = sanitized;
506
+ }
507
+
508
+ if (tags.size > 0) {
509
+ next.pollutionTags = [...tags];
510
+ next._safetyGate = {
511
+ tags: [...tags],
512
+ sanitized: true,
513
+ };
514
+ }
515
+
516
+ return next;
517
+ }
518
+
270
519
  function pushStructuredCandidates(candidates, items, spec) {
271
520
  for (const item of items) {
272
521
  const text = textFromItem(item, spec.keys);
@@ -285,7 +534,11 @@ function pushStructuredCandidates(candidates, items, spec) {
285
534
  const explicitAspect = normalizeText(itemObj && (itemObj.aspect || itemObj.predicate || itemObj.attribute));
286
535
  const subject = explicitSubject || normalizeText(spec.subject);
287
536
  let aspect = explicitAspect || normalizeText(spec.aspect);
288
- if (!explicitAspect) aspect = `${aspect}:${stableHash(text)}`;
537
+ if (spec.memoryType === 'assistant_shaping') {
538
+ aspect = buildAssistantShapingAspect(itemObj || {}, subject);
539
+ } else if (!explicitAspect) {
540
+ aspect = `${aspect}:${stableHash(text)}`;
541
+ }
289
542
  candidates.push({
290
543
  memoryType: spec.memoryType,
291
544
  canonicalKey: buildCanonicalKey({
@@ -304,7 +557,7 @@ function pushStructuredCandidates(candidates, items, spec) {
304
557
  title: text.slice(0, 120),
305
558
  summary: text,
306
559
  payload: typeof item === 'string' ? { [spec.payloadKey]: text } : { ...item, [spec.payloadKey]: text },
307
- authority: spec.authority,
560
+ authority: normalizeText(itemObj && (itemObj.authority || itemObj.payload?.authority)) || spec.authority,
308
561
  evidenceRefs: spec.evidenceRefs,
309
562
  evidenceText: evidenceText || undefined,
310
563
  visibleInBootstrap: true,
@@ -333,6 +586,7 @@ function extractCandidatesFromStructuredSummary(input = {}) {
333
586
  ['fact', ['important_facts', 'facts'], ['statement', 'fact', 'summary', 'text'], 'statement'],
334
587
  ['preference', ['preferences'], ['preference', 'summary', 'text'], 'preference'],
335
588
  ['constraint', ['constraints'], ['constraint', 'summary', 'text'], 'constraint'],
589
+ ['assistant_shaping', ['assistant_shaping', 'assistant_shaping_memories'], ['policy', 'guidance', 'preference', 'boundary', 'rule', 'summary', 'text'], 'guidance'],
336
590
  ['conclusion', ['conclusions'], ['conclusion', 'summary', 'text'], 'conclusion'],
337
591
  ['state', ['states', 'state'], ['state', 'summary', 'text', 'value'], 'state'],
338
592
  ['entity_note', ['entity_notes'], ['note', 'summary', 'text'], 'note'],
@@ -355,7 +609,7 @@ function extractCandidatesFromStructuredSummary(input = {}) {
355
609
  contextKey,
356
610
  topicKey,
357
611
  subject,
358
- aspect: DEFAULT_ASPECT[memoryType],
612
+ aspect: defaultAspectForMemoryType(memoryType),
359
613
  inheritanceMode,
360
614
  authority,
361
615
  evidenceRefs,
@@ -365,8 +619,66 @@ function extractCandidatesFromStructuredSummary(input = {}) {
365
619
  return candidates;
366
620
  }
367
621
 
622
+ function assessAssistantShapingCandidate(candidate = {}) {
623
+ const shapingKind = pickPolicyToken(candidate, ['shapingKind', 'shaping_kind', 'kind', 'category']);
624
+ if (!shapingKind) return { action: 'quarantine', reason: 'missing_shaping_kind' };
625
+ const knownShapingKind = assistantShapingKindAllowed(shapingKind);
626
+
627
+ const servingImpact = pickPolicyToken(candidate, ['servingImpact', 'serving_impact', 'impact']);
628
+ if (!servingImpact) return { action: 'quarantine', reason: 'missing_serving_impact' };
629
+ const knownServingImpact = assistantShapingServingImpactKnown(servingImpact);
630
+ if (!assistantShapingServingImpactAllowed(servingImpact)) {
631
+ return { action: 'quarantine', reason: 'unsupported_serving_impact' };
632
+ }
633
+
634
+ const userRelevance = pickPolicyText(candidate, ['userRelevance', 'user_relevance', 'whyUserFacing', 'why_user_facing']);
635
+ const payload = candidatePayload(candidate);
636
+ if (!userRelevance && payload.userFacing !== true && candidate.userFacing !== true) {
637
+ return { action: 'quarantine', reason: 'missing_user_relevance' };
638
+ }
639
+
640
+ const authority = String(candidate.authority || 'llm_inference').toLowerCase();
641
+ const temporalSupport = temporalSupportForAssistantShaping(candidate);
642
+ if (!authorityBypassesTemporalGate(authority) && !temporalSupport.supported) {
643
+ return {
644
+ action: 'quarantine',
645
+ reason: 'assistant_shaping_needs_temporal_evidence',
646
+ temporalSupport,
647
+ };
648
+ }
649
+
650
+ const abstraction = assistantBehaviorAbstraction(candidate);
651
+ if (abstraction.required && !abstraction.generalized) {
652
+ return {
653
+ action: 'quarantine',
654
+ reason: 'assistant_behavior_requires_generalized_abstraction',
655
+ abstraction,
656
+ };
657
+ }
658
+ if (abstraction.required && !abstraction.behaviorLanguage) {
659
+ return {
660
+ action: 'quarantine',
661
+ reason: 'assistant_behavior_requires_behavior_level_language',
662
+ abstraction,
663
+ };
664
+ }
665
+
666
+ return {
667
+ action: 'promote',
668
+ reason: 'assistant_shaping_allowed',
669
+ shapingKind,
670
+ servingImpact,
671
+ knownShapingKind,
672
+ knownServingImpact,
673
+ temporalSupport,
674
+ abstraction,
675
+ };
676
+ }
677
+
368
678
  function assessCandidate(candidate = {}, opts = {}) {
369
679
  const memoryType = normalizeType(candidate.memoryType || candidate.memory_type);
680
+ let allowedReason = 'v1_foundation_allowed';
681
+ let allowedDetails = {};
370
682
  const tags = new Set([
371
683
  ...(candidate.pollutionTags || []),
372
684
  ...(candidate.tags || []),
@@ -378,7 +690,7 @@ function assessCandidate(candidate = {}, opts = {}) {
378
690
  }
379
691
  }
380
692
 
381
- if (!ACTIVE_V1_TYPES.has(memoryType)) {
693
+ if (!isActiveMemoryType(memoryType)) {
382
694
  return { action: 'quarantine', reason: 'unsupported_memory_type' };
383
695
  }
384
696
 
@@ -399,9 +711,33 @@ function assessCandidate(candidate = {}, opts = {}) {
399
711
  return { action: 'quarantine', reason: 'missing_scope' };
400
712
  }
401
713
 
714
+ if (memoryType === 'assistant_shaping') {
715
+ const shapingAssessment = assessAssistantShapingCandidate(candidate);
716
+ if (shapingAssessment.action !== 'promote') return shapingAssessment;
717
+ allowedReason = shapingAssessment.reason;
718
+ const { action: _action, reason: _reason, ...details } = shapingAssessment;
719
+ void _action;
720
+ void _reason;
721
+ allowedDetails = details;
722
+ }
723
+
402
724
  const activeConflicts = opts.existingActiveRecords || [];
403
725
  for (const existing of activeConflicts) {
404
- if (sameClaim(candidate, existing)) continue;
726
+ if (sameClaim(candidate, existing)) {
727
+ const mismatch = activeIdentityMismatch(candidate, existing);
728
+ if (mismatch) {
729
+ return {
730
+ action: 'quarantine',
731
+ reason: `active_identity_mismatch_${mismatch}`,
732
+ conflictWith: existing.id || existing.memory_id,
733
+ };
734
+ }
735
+ return {
736
+ action: 'skip',
737
+ reason: 'already_active',
738
+ activeId: existing.id || existing.memory_id,
739
+ };
740
+ }
405
741
  const incomingRank = authorityRank(authority);
406
742
  const existingRank = authorityRank(existing.authority);
407
743
  if (incomingRank < existingRank) {
@@ -414,15 +750,16 @@ function assessCandidate(candidate = {}, opts = {}) {
414
750
  action: 'promote',
415
751
  reason: 'higher_authority_supersedes',
416
752
  supersedeId: existing.id || existing.memory_id,
753
+ ...allowedDetails,
417
754
  };
418
755
  }
419
756
 
420
- return { action: 'promote', reason: 'v1_foundation_allowed' };
757
+ return { action: 'promote', reason: allowedReason, ...allowedDetails };
421
758
  }
422
759
 
423
760
  function createMemoryPromotion({ records, embedFn = null }) {
424
761
  async function prepareCandidates(candidates = []) {
425
- const prepared = candidates.map(candidate => ({ ...candidate }));
762
+ const prepared = candidates.map(candidate => sanitizePromotionCandidate(candidate));
426
763
  if (typeof embedFn !== 'function' || prepared.length === 0) return prepared;
427
764
 
428
765
  const pendingMemoryRows = [];
@@ -663,13 +1000,13 @@ function createMemoryPromotion({ records, embedFn = null }) {
663
1000
  }
664
1001
 
665
1002
  module.exports = {
666
- ACTIVE_V1_TYPES,
667
1003
  FORBIDDEN_TAGS,
668
- AUTHORITY_RANK,
669
1004
  defaultInheritanceForType,
670
1005
  buildCanonicalKey,
671
1006
  buildMemoryEmbeddingText,
672
1007
  extractCandidatesFromStructuredSummary,
673
1008
  assessCandidate,
1009
+ assessAssistantShapingCandidate,
1010
+ sanitizePromotionCandidate,
674
1011
  createMemoryPromotion,
675
1012
  };