@shadowforge0/aquifer-memory 1.9.0 → 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 (43) hide show
  1. package/README.md +33 -4
  2. package/README_CN.md +9 -1
  3. package/README_TW.md +5 -2
  4. package/consumers/cli.js +55 -34
  5. package/consumers/codex-active-checkpoint.js +3 -1
  6. package/consumers/codex-current-memory.js +10 -6
  7. package/consumers/codex.js +5 -2
  8. package/consumers/default/daily-entries.js +2 -2
  9. package/consumers/default/index.js +40 -30
  10. package/consumers/default/prompts/summary.js +2 -2
  11. package/consumers/mcp.js +56 -49
  12. package/consumers/openclaw-ext/index.js +1 -1
  13. package/consumers/openclaw-ext/openclaw.plugin.json +1 -1
  14. package/consumers/openclaw-ext/package.json +1 -1
  15. package/consumers/openclaw-plugin.js +66 -23
  16. package/consumers/shared/compat-recall.js +101 -0
  17. package/consumers/shared/openclaw-product-tools.js +130 -0
  18. package/consumers/shared/recall-format.js +2 -2
  19. package/core/aquifer.js +385 -20
  20. package/core/backends/local.js +60 -1
  21. package/core/finalization-review.js +88 -42
  22. package/core/interface.js +629 -0
  23. package/core/mcp-manifest.js +11 -3
  24. package/core/memory-bootstrap.js +25 -27
  25. package/core/memory-consolidation.js +564 -42
  26. package/core/memory-explain.js +20 -51
  27. package/core/memory-promotion.js +392 -55
  28. package/core/memory-recall.js +26 -48
  29. package/core/memory-records.js +91 -103
  30. package/core/memory-type-policy.js +298 -0
  31. package/core/postgres-migrations.js +9 -0
  32. package/core/session-checkpoint-producer.js +3 -1
  33. package/core/session-checkpoints.js +1 -1
  34. package/core/session-finalization.js +2 -2
  35. package/docs/getting-started.md +16 -3
  36. package/docs/setup.md +61 -2
  37. package/package.json +2 -2
  38. package/schema/004-completion.sql +4 -4
  39. package/schema/010-v1-finalization-review.sql +72 -0
  40. package/schema/020-v1-assistant-shaping-memory.sql +30 -0
  41. package/scripts/backfill-canonical-key.js +1 -1
  42. package/scripts/diagnose-fts-zh.js +1 -1
  43. package/scripts/extract-insights-from-recent-sessions.js +4 -4
@@ -10,6 +10,49 @@ const OPERATOR_CADENCES = new Set(['manual', 'daily', 'weekly', 'monthly']);
10
10
  const DEFAULT_CLAIM_LEASE_SECONDS = 600;
11
11
  const DEFAULT_OPERATOR_SNAPSHOT_LIMIT = 1000;
12
12
  const MAX_OPERATOR_SNAPSHOT_LIMIT = 5000;
13
+ const TEMPORAL_DISTILLATION_STANDARD_VERSION = 'temporal_distillation_standard_v1';
14
+ const ASSISTANT_BEHAVIOR_LANGUAGE_LEVELS = new Set(['user_behavior', 'assistant_behavior']);
15
+ const TEMPORAL_CADENCE_POLICY = {
16
+ daily: {
17
+ tier: 'daily',
18
+ windowKind: 'closed_utc_day',
19
+ outputSemantics: 'Carry forward still-current state, decisions, constraints, preferences, facts, conclusions, and open loops from a closed day.',
20
+ promotionRule: 'reviewed_synthesis_required',
21
+ aggregateCandidateRole: 'source_rollup_proposal',
22
+ promptGuidance: [
23
+ 'Daily output may keep concrete same-day state and open loops only when they remain true after the day closes.',
24
+ 'Do not promote transient test output, tool logs, progress narration, or resolved debugging paths.',
25
+ 'Every returned item must include mergeKey, scopeClass, durability, and promotionTarget; temporal runtime state must include staleAfter or validTo.',
26
+ ],
27
+ preferredMemoryTypes: ['state', 'open_loop', 'decision', 'constraint', 'preference', 'fact', 'conclusion'],
28
+ },
29
+ weekly: {
30
+ tier: 'weekly',
31
+ windowKind: 'closed_utc_week',
32
+ outputSemantics: 'Promote only cross-day patterns, durable decisions, continuing risks, direction, and unresolved open loops from a closed week.',
33
+ promotionRule: 'reviewed_synthesis_required',
34
+ aggregateCandidateRole: 'source_rollup_proposal',
35
+ promptGuidance: [
36
+ 'Weekly output must not preserve one-off daily details unless they still affect future work.',
37
+ 'Prefer durable decisions, constraints, preferences, stable facts, conclusions, risks, and still-open loops.',
38
+ 'Do not return temporary runtime state unless it has an explicit expiry and still affects future work.',
39
+ ],
40
+ preferredMemoryTypes: ['decision', 'constraint', 'preference', 'fact', 'conclusion', 'open_loop'],
41
+ },
42
+ monthly: {
43
+ tier: 'monthly',
44
+ windowKind: 'closed_utc_month',
45
+ outputSemantics: 'Keep only long-term principles, stable preferences, durable constraints, stable facts, architectural direction, and long-lived open loops from a closed month.',
46
+ promotionRule: 'reviewed_synthesis_required',
47
+ aggregateCandidateRole: 'source_rollup_proposal',
48
+ promptGuidance: [
49
+ 'Monthly output must drop daily operational detail and stale project state.',
50
+ 'Only durable principles, stable preferences, constraints, facts, direction, and long-lived open loops should remain.',
51
+ 'Monthly output should not return workspace policy, process state, or one-off operator progress.',
52
+ ],
53
+ preferredMemoryTypes: ['constraint', 'preference', 'decision', 'fact', 'open_loop'],
54
+ },
55
+ };
13
56
 
14
57
  function stableJson(value) {
15
58
  if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
@@ -186,6 +229,27 @@ function aggregateCandidateCadence(cadence) {
186
229
  return cadence === 'daily' || cadence === 'weekly' || cadence === 'monthly';
187
230
  }
188
231
 
232
+ function temporalPolicyForCadence(cadence) {
233
+ return TEMPORAL_CADENCE_POLICY[cadence] || null;
234
+ }
235
+
236
+ function isAggregateRollupCandidate(candidate = {}) {
237
+ const payload = candidate.payload && typeof candidate.payload === 'object' ? candidate.payload : {};
238
+ return payload.kind === 'compaction_rollup'
239
+ || payload.aggregateCandidateRole === 'source_rollup_proposal';
240
+ }
241
+
242
+ function assertPromotableTemporalCandidates(candidates = [], input = {}) {
243
+ if (input.promoteCandidates !== true) return;
244
+ const aggregateCandidates = candidates.filter(isAggregateRollupCandidate);
245
+ if (aggregateCandidates.length === 0) return;
246
+ if (input.unsafePromoteAggregateCandidates === true) return;
247
+ throw new Error(
248
+ 'Temporal aggregate rollup candidates are ledger/prompt material only. '
249
+ + 'Attach a reviewed timer synthesis summary before promoting temporal candidates.'
250
+ );
251
+ }
252
+
189
253
  function safeKeyPart(value, fallback) {
190
254
  const text = String(value || fallback || '').trim().toLowerCase();
191
255
  return text.replace(/\s+/g, '-').replace(/[^a-z0-9:._/-]/g, '-');
@@ -197,9 +261,272 @@ function compactSummary(record) {
197
261
  return summary.length > 240 ? `${summary.slice(0, 237)}...` : summary;
198
262
  }
199
263
 
264
+ function normalizeClassifierValue(value) {
265
+ return String(value || '').trim().toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9:_./-]/g, '');
266
+ }
267
+
268
+ function normalizeSummaryKey(value) {
269
+ return String(value || '').trim().toLowerCase().replace(/\s+/g, ' ');
270
+ }
271
+
272
+ function payloadValue(payload = {}, ...keys) {
273
+ for (const key of keys) {
274
+ if (payload[key] !== undefined && payload[key] !== null && payload[key] !== '') return payload[key];
275
+ }
276
+ return null;
277
+ }
278
+
279
+ function extractAbsolutePaths(text) {
280
+ return String(text || '').match(/\/(?:home|mnt|etc|var|tmp|opt|root|usr)\/[^\s"'<>),]+/g) || [];
281
+ }
282
+
283
+ function normalizePathPrefix(value) {
284
+ return String(value || '').trim().replace(/\/+$/, '');
285
+ }
286
+
287
+ function detectScopeClassFromContent(candidate = {}, opts = {}) {
288
+ const text = normalizeSummaryKey(`${candidate.title || ''} ${candidate.summary || ''}`);
289
+ const productRepoPaths = normalizeStringList(
290
+ opts.productRepoPaths || opts.productRepoPath || opts.repoPath || opts.allowedProductPath,
291
+ ).map(normalizePathPrefix).filter(Boolean);
292
+ const paths = extractAbsolutePaths(text);
293
+ if (paths.some(path => /(^|\/)(agents\.md|\.codex|\.openclaw)(\/|$)/.test(path.toLowerCase()))) {
294
+ return 'workspace_policy';
295
+ }
296
+ if (productRepoPaths.length > 0 && paths.some(path => (
297
+ !productRepoPaths.some(prefix => path === prefix || path.startsWith(`${prefix}/`))
298
+ ))) {
299
+ return 'workspace_policy';
300
+ }
301
+ if (/\bdirty worktree\b|\brevert\b.*\b(current task|user changes|outside the current task)\b/.test(text)) {
302
+ return 'workspace_policy';
303
+ }
304
+ if (/\b(restart|running process|mcp process|gateway|latest|published|release|tag|npm latest|github release|local install|runtime)\b/.test(text)) {
305
+ return 'runtime_state';
306
+ }
307
+ if (/\b(package surface|evidence_items|memory_records|postgresql|schema|query contract|release gate)\b/.test(text)) {
308
+ return 'product_memory';
309
+ }
310
+ return null;
311
+ }
312
+
313
+ function inferScopeClass(candidate = {}, opts = {}) {
314
+ const payload = candidate.payload && typeof candidate.payload === 'object' ? candidate.payload : {};
315
+ const explicit = normalizeClassifierValue(payloadValue(payload, 'scopeClass', 'scope_class'));
316
+ const detected = detectScopeClassFromContent(candidate, opts);
317
+ if (detected === 'workspace_policy') return detected;
318
+ if (explicit) return explicit;
319
+ return detected || 'product_memory';
320
+ }
321
+
322
+ function inferDurability(candidate = {}, scopeClass) {
323
+ const payload = candidate.payload && typeof candidate.payload === 'object' ? candidate.payload : {};
324
+ const explicit = normalizeClassifierValue(payloadValue(payload, 'durability'));
325
+ if (explicit) return explicit;
326
+ if (scopeClass === 'runtime_state') return 'temporal';
327
+ const memoryType = normalizeClassifierValue(candidate.memoryType || candidate.memory_type);
328
+ if (memoryType === 'open_loop') return 'bounded';
329
+ return 'durable';
330
+ }
331
+
332
+ function candidateExpiry(candidate = {}) {
333
+ const payload = candidate.payload && typeof candidate.payload === 'object' ? candidate.payload : {};
334
+ const staleness = payload.staleness && typeof payload.staleness === 'object' ? payload.staleness : {};
335
+ return payloadValue(candidate, 'staleAfter', 'stale_after', 'validTo', 'valid_to')
336
+ || payloadValue(payload, 'staleAfter', 'stale_after', 'validTo', 'valid_to', 'expiresAt', 'expires_at')
337
+ || payloadValue(staleness, 'staleAfter', 'stale_after', 'validTo', 'valid_to', 'expiresAt', 'expires_at');
338
+ }
339
+
340
+ function payloadObject(payload = {}, ...keys) {
341
+ for (const key of keys) {
342
+ const value = payload[key];
343
+ if (value && typeof value === 'object' && !Array.isArray(value)) return value;
344
+ }
345
+ return {};
346
+ }
347
+
348
+ function explicitBoolean(value) {
349
+ if (value === true || value === false) return value;
350
+ if (typeof value === 'string') {
351
+ const normalized = value.trim().toLowerCase();
352
+ if (normalized === 'true') return true;
353
+ if (normalized === 'false') return false;
354
+ }
355
+ return null;
356
+ }
357
+
358
+ function assistantBehaviorAbstraction(payload = {}) {
359
+ const abstraction = payloadObject(payload, 'abstraction', 'generalization', 'assistantBehaviorAbstraction');
360
+ const appliesBeyondSource = explicitBoolean(
361
+ abstraction.appliesBeyondSource
362
+ ?? abstraction.applies_beyond_source
363
+ ?? payload.appliesBeyondSource
364
+ ?? payload.applies_beyond_source,
365
+ );
366
+ const sourceBound = explicitBoolean(
367
+ abstraction.sourceBound
368
+ ?? abstraction.source_bound
369
+ ?? payload.sourceBound
370
+ ?? payload.source_bound,
371
+ );
372
+ const principle = String(
373
+ abstraction.principle
374
+ || abstraction.behaviorPrinciple
375
+ || abstraction.behavior_principle
376
+ || payload.behaviorPrinciple
377
+ || payload.behavior_principle
378
+ || '',
379
+ ).trim();
380
+ const languageLevel = normalizeClassifierValue(
381
+ abstraction.languageLevel
382
+ || abstraction.language_level
383
+ || payload.languageLevel
384
+ || payload.language_level,
385
+ );
386
+ const generalized = appliesBeyondSource === true && sourceBound === false && Boolean(principle);
387
+ const behaviorLanguage = ASSISTANT_BEHAVIOR_LANGUAGE_LEVELS.has(languageLevel);
388
+ return {
389
+ appliesBeyondSource,
390
+ sourceBound,
391
+ principle,
392
+ languageLevel,
393
+ declared: appliesBeyondSource !== null || sourceBound !== null || Boolean(principle) || Boolean(languageLevel),
394
+ generalized,
395
+ behaviorLanguage,
396
+ eligible: generalized && behaviorLanguage,
397
+ };
398
+ }
399
+
400
+ function evaluateTemporalDistillationCandidate(candidate = {}, opts = {}) {
401
+ const payload = candidate.payload && typeof candidate.payload === 'object' ? candidate.payload : {};
402
+ const cadence = normalizeClassifierValue(opts.cadence || candidate.payload?.cadence || 'manual');
403
+ const memoryType = normalizeClassifierValue(candidate.memoryType || candidate.memory_type);
404
+ const scopeClass = inferScopeClass(candidate, opts);
405
+ const promotionTarget = normalizeClassifierValue(payloadValue(payload, 'promotionTarget', 'promotion_target'))
406
+ || (memoryType === 'assistant_shaping'
407
+ ? 'assistant_behavior_memory'
408
+ : (scopeClass === 'product_memory' || scopeClass === 'runtime_state' ? 'project_current_memory' : scopeClass));
409
+ const durability = inferDurability(candidate, scopeClass);
410
+ const mergeKey = normalizeClassifierValue(payloadValue(payload, 'mergeKey', 'merge_key'));
411
+ const expiry = candidateExpiry(candidate);
412
+ const reasons = [];
413
+ const sourceCanonicalKeys = Array.isArray(candidate.sourceCanonicalKeys) ? candidate.sourceCanonicalKeys : [];
414
+ const sourceMemoryIds = Array.isArray(candidate.sourceMemoryIds) ? candidate.sourceMemoryIds : [];
415
+ const assistantBehaviorTarget = memoryType === 'assistant_shaping' && promotionTarget === 'assistant_behavior_memory';
416
+ const projectMemoryTarget = promotionTarget === 'project_current_memory';
417
+
418
+ if (!mergeKey) reasons.push('missing_merge_key');
419
+ if (candidate.sourceLineageError) reasons.push(candidate.sourceLineageError);
420
+ if (opts.requireSourceLineage === true && sourceCanonicalKeys.length === 0) reasons.push('missing_source_lineage');
421
+ if (sourceMemoryIds.length !== sourceCanonicalKeys.length) reasons.push('invalid_source_lineage');
422
+ if (scopeClass === 'workspace_policy' || promotionTarget === 'workspace_policy') reasons.push('workspace_policy_not_project_memory');
423
+ if (!assistantBehaviorTarget && !projectMemoryTarget) reasons.push('unsupported_promotion_target');
424
+ if (memoryType !== 'assistant_shaping' && promotionTarget === 'assistant_behavior_memory') {
425
+ reasons.push('assistant_behavior_target_requires_assistant_shaping');
426
+ }
427
+ if (assistantBehaviorTarget) {
428
+ const abstraction = assistantBehaviorAbstraction(payload);
429
+ if (!abstraction.generalized) reasons.push('assistant_behavior_requires_generalized_abstraction');
430
+ if (!abstraction.behaviorLanguage) reasons.push('assistant_behavior_requires_behavior_level_language');
431
+ }
432
+ if (durability === 'transient') reasons.push('transient_memory_not_promotable');
433
+ if (scopeClass === 'runtime_state' && !expiry) reasons.push('runtime_state_requires_expiry');
434
+ if ((cadence === 'weekly' || cadence === 'monthly') && scopeClass === 'runtime_state') reasons.push('runtime_state_not_promotable_for_cadence');
435
+ if (cadence === 'monthly' && memoryType === 'state') reasons.push('monthly_state_requires_long_lived_type');
436
+
437
+ return {
438
+ eligible: reasons.length === 0,
439
+ reasons,
440
+ standardVersion: TEMPORAL_DISTILLATION_STANDARD_VERSION,
441
+ mergeKey: mergeKey || `derived:${memoryType}:${hashSnapshot(candidate.summary || candidate.title || candidate.canonicalKey || '')}`,
442
+ scopeClass,
443
+ promotionTarget,
444
+ durability,
445
+ staleAfter: payloadValue(payload, 'staleAfter', 'stale_after') || payloadValue(candidate, 'staleAfter', 'stale_after') || null,
446
+ validTo: payloadValue(payload, 'validTo', 'valid_to') || payloadValue(candidate, 'validTo', 'valid_to') || null,
447
+ };
448
+ }
449
+
450
+ function temporalCandidatePriority(candidate = {}) {
451
+ const type = normalizeClassifierValue(candidate.memoryType || candidate.memory_type);
452
+ const priority = {
453
+ constraint: 80,
454
+ decision: 75,
455
+ fact: 70,
456
+ preference: 65,
457
+ open_loop: 60,
458
+ conclusion: 50,
459
+ state: 40,
460
+ entity_note: 30,
461
+ };
462
+ return priority[type] || 0;
463
+ }
464
+
465
+ function summarizeSafetyGate(safetyGate = {}) {
466
+ const stats = safetyGate.stats && typeof safetyGate.stats === 'object' ? safetyGate.stats : safetyGate;
467
+ return {
468
+ redacted: Number(stats.redacted || 0),
469
+ dropped: Number(stats.dropped || 0),
470
+ };
471
+ }
472
+
473
+ function applyTemporalDistillationStandard(candidates = [], opts = {}) {
474
+ const acceptedByMergeKey = new Map();
475
+ const rejected = [];
476
+ const acceptedOrder = [];
477
+
478
+ for (const candidate of candidates) {
479
+ const evaluation = evaluateTemporalDistillationCandidate(candidate, opts);
480
+ const payload = candidate.payload && typeof candidate.payload === 'object' ? candidate.payload : {};
481
+ const next = {
482
+ ...candidate,
483
+ staleAfter: candidate.staleAfter || candidate.stale_after || evaluation.staleAfter || null,
484
+ validTo: candidate.validTo || candidate.valid_to || evaluation.validTo || null,
485
+ payload: {
486
+ ...payload,
487
+ distillation: {
488
+ standardVersion: evaluation.standardVersion,
489
+ mergeKey: evaluation.mergeKey,
490
+ scopeClass: evaluation.scopeClass,
491
+ promotionTarget: evaluation.promotionTarget,
492
+ durability: evaluation.durability,
493
+ staleAfter: evaluation.staleAfter,
494
+ validTo: evaluation.validTo,
495
+ },
496
+ },
497
+ };
498
+ if (!evaluation.eligible) {
499
+ rejected.push({ candidate: next, reasons: evaluation.reasons });
500
+ continue;
501
+ }
502
+
503
+ const existing = acceptedByMergeKey.get(evaluation.mergeKey);
504
+ if (!existing) {
505
+ acceptedByMergeKey.set(evaluation.mergeKey, next);
506
+ acceptedOrder.push(evaluation.mergeKey);
507
+ continue;
508
+ }
509
+
510
+ const incomingPriority = temporalCandidatePriority(next);
511
+ const existingPriority = temporalCandidatePriority(existing);
512
+ if (incomingPriority > existingPriority) {
513
+ acceptedByMergeKey.set(evaluation.mergeKey, next);
514
+ rejected.push({ candidate: existing, reasons: ['duplicate_merge_key_superseded'] });
515
+ } else {
516
+ rejected.push({ candidate: next, reasons: ['duplicate_merge_key'] });
517
+ }
518
+ }
519
+
520
+ return {
521
+ candidates: acceptedOrder.map(key => acceptedByMergeKey.get(key)).filter(Boolean),
522
+ rejected,
523
+ };
524
+ }
525
+
200
526
  function buildAggregateCandidates(normalized, statusUpdates, opts) {
201
527
  const { cadence, periodStart, periodEnd } = opts;
202
528
  if (!aggregateCandidateCadence(cadence)) return [];
529
+ const temporalPolicy = temporalPolicyForCadence(cadence);
203
530
 
204
531
  const staleIds = new Set(statusUpdates.map(update => String(update.memoryId)));
205
532
  const staleKeys = new Set(statusUpdates.map(update => String(update.canonicalKey || '')));
@@ -298,8 +625,11 @@ function buildAggregateCandidates(normalized, statusUpdates, opts) {
298
625
  payload: {
299
626
  kind: 'compaction_rollup',
300
627
  synthesisKind: 'timer_current_memory_synthesis_v1',
301
- currentMemoryRole: `${cadence}_timer_synthesis_candidate`,
302
- promotionGate: 'operator_required',
628
+ currentMemoryRole: `${cadence}_source_rollup_proposal`,
629
+ temporalTier: cadence,
630
+ temporalPolicy,
631
+ aggregateCandidateRole: 'source_rollup_proposal',
632
+ promotionGate: 'reviewed_synthesis_required',
303
633
  cadence,
304
634
  policyVersion,
305
635
  periodStart: windowStart,
@@ -324,8 +654,8 @@ function buildAggregateCandidates(normalized, statusUpdates, opts) {
324
654
  canonicalKey: record.canonicalKey,
325
655
  },
326
656
  })),
327
- visibleInBootstrap: true,
328
- visibleInRecall: true,
657
+ visibleInBootstrap: false,
658
+ visibleInRecall: false,
329
659
  });
330
660
  }
331
661
 
@@ -335,6 +665,7 @@ function buildAggregateCandidates(normalized, statusUpdates, opts) {
335
665
  function buildTimerSynthesisInput(normalized, statusUpdates, candidates, opts) {
336
666
  const { cadence, periodStart, periodEnd } = opts;
337
667
  if (!aggregateCandidateCadence(cadence)) return null;
668
+ const temporalPolicy = temporalPolicyForCadence(cadence);
338
669
 
339
670
  const staleIds = new Set(statusUpdates.map(update => String(update.memoryId)));
340
671
  const staleKeys = new Set(statusUpdates.map(update => String(update.canonicalKey || '')));
@@ -373,8 +704,10 @@ function buildTimerSynthesisInput(normalized, statusUpdates, candidates, opts) {
373
704
  periodEnd: windowEnd,
374
705
  promotion: {
375
706
  default: 'candidate_only',
376
- requires: 'apply=true and promoteCandidates=true',
707
+ requires: 'apply=true, promoteCandidates=true, and reviewed synthesis summary',
708
+ aggregateCandidatePromotion: 'blocked_by_default',
377
709
  },
710
+ temporalPolicy,
378
711
  guards: {
379
712
  rawTranscriptExcluded: true,
380
713
  sessionSummariesExcluded: true,
@@ -419,14 +752,26 @@ function buildTimerSynthesisPrompt(plan = {}, opts = {}) {
419
752
  throw new Error('memory.consolidation.timer_synthesis_prompt requires a timer synthesisInput');
420
753
  }
421
754
  const maxFacts = opts.maxFacts || 12;
755
+ const temporalPolicy = synthesisInput.temporalPolicy || temporalPolicyForCadence(synthesisInput.cadence) || {};
756
+ const policyGuidance = Array.isArray(temporalPolicy.promptGuidance)
757
+ ? temporalPolicy.promptGuidance
758
+ : [];
422
759
  return [
423
760
  'You are producing an Aquifer timer current-memory synthesis proposal.',
424
761
  'Use only the <timer_synthesis_input> block. Do not read raw transcripts, session summaries, tool output, or debug material.',
425
762
  'This is a producer proposal, not an active memory commit. Promotion still requires the normal operator promotion gate.',
426
- 'Return compact JSON with this shape:',
427
- '{"summaryText":"...","structuredSummary":{"facts":[],"decisions":[],"open_loops":[],"preferences":[],"constraints":[],"conclusions":[],"entity_notes":[],"states":[]}}',
763
+ `Temporal tier: ${temporalPolicy.tier || synthesisInput.cadence || 'unknown'}.`,
764
+ `Temporal semantics: ${temporalPolicy.outputSemantics || 'Keep only memory that remains useful after the closed timer window.'}`,
765
+ ...policyGuidance.map(line => `Policy: ${line}`),
766
+ 'Return compact JSON with this shape. Every item must include mergeKey, scopeClass, durability, promotionTarget, and sourceCanonicalKeys; temporal runtime state also needs staleAfter or validTo:',
767
+ '{"summaryText":"...","structuredSummary":{"assistant_shaping":[{"guidance":"...","shapingKind":"memory_policy","servingImpact":"changes_memory_promotion","userRelevance":"...","temporalSupport":{"observationCount":2},"abstraction":{"languageLevel":"user_behavior","appliesBeyondSource":true,"sourceBound":false,"principle":"..."},"mergeKey":"...","scopeClass":"assistant_behavior","durability":"durable","promotionTarget":"assistant_behavior_memory","sourceCanonicalKeys":["memory:canonical:key"]}],"facts":[{"statement":"...","subject":"...","aspect":"...","mergeKey":"...","scopeClass":"product_memory","durability":"durable","promotionTarget":"project_current_memory","sourceCanonicalKeys":["memory:canonical:key"]}],"decisions":[],"open_loops":[],"preferences":[],"constraints":[],"conclusions":[],"entity_notes":[],"states":[]}}',
428
768
  `Keep facts/states/open_loops concrete and scoped. Use at most ${maxFacts} facts.`,
429
769
  'Mark uncertain, resolved, superseded, revoked, or stale items explicitly in payload fields when applicable.',
770
+ 'Use promotionTarget="project_current_memory" only for product-scoped memory that should be eligible for current-memory promotion.',
771
+ 'Use assistant_shaping with promotionTarget="assistant_behavior_memory" only for durable assistant behavior that changes future responses, retrieval, tool routing, or memory promotion for the user.',
772
+ 'For assistant_behavior_memory, put project-specific evidence only in sourceCanonicalKeys and abstraction context; guidance must state the generalized assistant behavior, and abstraction must set languageLevel="user_behavior", appliesBeyondSource=true, sourceBound=false, and principle.',
773
+ 'Use scopeClass="workspace_policy" or promotionTarget="workspace_policy" for workspace/operator rules; those will stay out of project current memory.',
774
+ 'Use the same mergeKey for duplicate claims across memory types so the deterministic gate can keep one candidate.',
430
775
  'Do not copy sourceCurrentMemory unchanged unless the timer window confirms it still carries forward.',
431
776
  '',
432
777
  '<timer_synthesis_input>',
@@ -504,6 +849,93 @@ function sourceLineageFromSynthesisInput(synthesisInput = {}) {
504
849
  };
505
850
  }
506
851
 
852
+ function sourceLookupFromSynthesisInput(synthesisInput = {}) {
853
+ const rows = Array.isArray(synthesisInput.sourceCurrentMemory) ? synthesisInput.sourceCurrentMemory : [];
854
+ const byKey = new Map();
855
+ const byId = new Map();
856
+ for (const row of rows) {
857
+ const id = Number(row.memoryId);
858
+ const key = String(row.canonicalKey || '').trim();
859
+ if (!Number.isSafeInteger(id) || id <= 0 || !key) continue;
860
+ const pair = { id, key };
861
+ byKey.set(key, pair);
862
+ byId.set(id, pair);
863
+ }
864
+ return { byKey, byId };
865
+ }
866
+
867
+ function assistantBehaviorPromotionTarget(payload = {}) {
868
+ return normalizeClassifierValue(payloadValue(payload, 'promotionTarget', 'promotion_target'))
869
+ || normalizeClassifierValue(payloadObject(payload, 'distillation').promotionTarget || payloadObject(payload, 'distillation').promotion_target);
870
+ }
871
+
872
+ function normalizeAssistantBehaviorServingText(candidate = {}) {
873
+ if ((candidate.memoryType || candidate.memory_type) !== 'assistant_shaping') return candidate;
874
+ const payload = candidate.payload && typeof candidate.payload === 'object' ? candidate.payload : {};
875
+ if (assistantBehaviorPromotionTarget(payload) !== 'assistant_behavior_memory') return candidate;
876
+ const abstraction = assistantBehaviorAbstraction(payload);
877
+ if (!abstraction.eligible) return candidate;
878
+ const sourceGuidance = String(payload.guidance || '').trim();
879
+ const nextPayload = {
880
+ ...payload,
881
+ guidance: abstraction.principle,
882
+ };
883
+ if (sourceGuidance && sourceGuidance !== abstraction.principle) {
884
+ nextPayload.sourceGuidance = sourceGuidance;
885
+ }
886
+ return {
887
+ ...candidate,
888
+ title: abstraction.principle.slice(0, 120),
889
+ summary: abstraction.principle,
890
+ payload: nextPayload,
891
+ };
892
+ }
893
+
894
+ function candidatePayloadWithoutLineage(payload = {}) {
895
+ const rest = { ...payload };
896
+ delete rest.sourceMemoryIds;
897
+ delete rest.source_memory_ids;
898
+ delete rest.sourceCanonicalKeys;
899
+ delete rest.source_canonical_keys;
900
+ delete rest.sources;
901
+ delete rest.sourceKeys;
902
+ delete rest.source_keys;
903
+ return rest;
904
+ }
905
+
906
+ function resolveCandidateSourceLineage(payload = {}, lookup = {}) {
907
+ const keys = normalizeStringList(
908
+ payload.sourceCanonicalKeys || payload.source_canonical_keys || payload.sourceKeys || payload.source_keys,
909
+ );
910
+ const ids = normalizeStringList(payload.sourceMemoryIds || payload.source_memory_ids)
911
+ .map(value => Number(value))
912
+ .filter(value => Number.isSafeInteger(value) && value > 0);
913
+ const pairs = new Map();
914
+ for (const key of keys) {
915
+ const pair = lookup.byKey?.get(key);
916
+ if (!pair) {
917
+ return { sourceMemoryIds: [], sourceCanonicalKeys: [], error: 'invalid_source_lineage' };
918
+ }
919
+ pairs.set(pair.key, pair);
920
+ }
921
+ for (const id of ids) {
922
+ const pair = lookup.byId?.get(id);
923
+ if (!pair) {
924
+ return { sourceMemoryIds: [], sourceCanonicalKeys: [], error: 'invalid_source_lineage' };
925
+ }
926
+ pairs.set(pair.key, pair);
927
+ }
928
+ const sorted = [...pairs.values()].sort((a, b) => {
929
+ if (a.key !== b.key) return a.key.localeCompare(b.key);
930
+ return a.id - b.id;
931
+ });
932
+ return {
933
+ sourceMemoryIds: sorted.map(pair => pair.id),
934
+ sourceCanonicalKeys: sorted.map(pair => pair.key),
935
+ error: null,
936
+ };
937
+ }
938
+
507
939
  function buildTimerSynthesisCandidates(plan = {}, synthesisSummary = {}, opts = {}) {
508
940
  const synthesisInput = plan.synthesisInput || plan.meta?.synthesisInput || null;
509
941
  if (!synthesisInput) {
@@ -513,6 +945,8 @@ function buildTimerSynthesisCandidates(plan = {}, synthesisSummary = {}, opts =
513
945
  const structuredSummary = summary.structuredSummary || {};
514
946
  const scope = inferTimerSynthesisScope(synthesisInput, opts);
515
947
  const lineage = sourceLineageFromSynthesisInput(synthesisInput);
948
+ const sourceLookup = sourceLookupFromSynthesisInput(synthesisInput);
949
+ const sourceRecordCount = lineage.sourceMemoryIds.length;
516
950
  const synthesisHash = hashSnapshot({
517
951
  summary,
518
952
  lineage,
@@ -530,7 +964,7 @@ function buildTimerSynthesisCandidates(plan = {}, synthesisSummary = {}, opts =
530
964
  periodStart: canonicalInstant(plan.periodStart),
531
965
  periodEnd: canonicalInstant(plan.periodEnd),
532
966
  policyVersion: plan.policyVersion || 'v1',
533
- sourceCanonicalKeys: lineage.sourceCanonicalKeys,
967
+ sourceRecordCount,
534
968
  },
535
969
  }];
536
970
  const extracted = extractCandidatesFromStructuredSummary({
@@ -543,36 +977,54 @@ function buildTimerSynthesisCandidates(plan = {}, synthesisSummary = {}, opts =
543
977
  authority: opts.authority || 'verified_summary',
544
978
  evidenceRefs,
545
979
  });
546
- const candidates = extracted.map((candidate, index) => ({
547
- ...candidate,
548
- candidateHash: hashSnapshot({
980
+ const prepared = extracted.map((candidate, index) => {
981
+ const normalizedCandidate = normalizeAssistantBehaviorServingText(candidate);
982
+ const itemPayload = normalizedCandidate.payload && typeof normalizedCandidate.payload === 'object'
983
+ ? normalizedCandidate.payload
984
+ : {};
985
+ const itemLineage = resolveCandidateSourceLineage(itemPayload, sourceLookup);
986
+ const candidateHash = hashSnapshot({
549
987
  synthesisHash,
550
988
  index,
551
- canonicalKey: candidate.canonicalKey,
552
- summary: candidate.summary,
553
- }),
554
- payload: {
555
- ...(candidate.payload || {}),
556
- kind: 'timer_synthesis',
557
- synthesisKind: synthesisInput.kind || 'timer_current_memory_synthesis_v1',
558
- currentMemoryRole: `${plan.cadence || 'manual'}_timer_synthesis_candidate`,
559
- promotionGate: 'operator_required',
560
- cadence: plan.cadence,
561
- policyVersion: plan.policyVersion || 'v1',
562
- periodStart: canonicalInstant(plan.periodStart),
563
- periodEnd: canonicalInstant(plan.periodEnd),
564
- synthesisHash,
565
- sourceMemoryIds: lineage.sourceMemoryIds,
566
- sourceCanonicalKeys: lineage.sourceCanonicalKeys,
567
- safetyGate,
568
- },
569
- }));
989
+ canonicalKey: normalizedCandidate.canonicalKey,
990
+ summary: normalizedCandidate.summary,
991
+ });
992
+ return {
993
+ ...normalizedCandidate,
994
+ candidateHash,
995
+ sourceMemoryIds: itemLineage.sourceMemoryIds,
996
+ sourceCanonicalKeys: itemLineage.sourceCanonicalKeys,
997
+ sourceLineageError: itemLineage.error,
998
+ payload: {
999
+ ...candidatePayloadWithoutLineage(itemPayload),
1000
+ kind: 'timer_synthesis',
1001
+ synthesisKind: synthesisInput.kind || 'timer_current_memory_synthesis_v1',
1002
+ currentMemoryRole: `${plan.cadence || 'manual'}_timer_synthesis_candidate`,
1003
+ promotionGate: 'operator_required',
1004
+ cadence: plan.cadence,
1005
+ policyVersion: plan.policyVersion || 'v1',
1006
+ periodStart: canonicalInstant(plan.periodStart),
1007
+ periodEnd: canonicalInstant(plan.periodEnd),
1008
+ synthesisHash,
1009
+ sourceLineageRef: `timer_synthesis:${synthesisHash}:candidate:${candidateHash}`,
1010
+ sourceRecordCount: itemLineage.sourceMemoryIds.length,
1011
+ safetyGateSummary: summarizeSafetyGate(safetyGate),
1012
+ },
1013
+ };
1014
+ });
1015
+ const standardized = applyTemporalDistillationStandard(prepared, {
1016
+ cadence: plan.cadence,
1017
+ periodStart: plan.periodStart,
1018
+ periodEnd: plan.periodEnd,
1019
+ requireSourceLineage: true,
1020
+ });
570
1021
 
571
1022
  return {
572
1023
  summary,
573
1024
  safetyGate,
574
1025
  synthesisHash,
575
- candidates,
1026
+ candidates: standardized.candidates,
1027
+ rejectedCandidates: standardized.rejected,
576
1028
  sourceMemoryIds: lineage.sourceMemoryIds,
577
1029
  sourceCanonicalKeys: lineage.sourceCanonicalKeys,
578
1030
  };
@@ -603,6 +1055,23 @@ function attachTimerSynthesis(plan = {}, synthesisSummary = {}, opts = {}) {
603
1055
  safetyGate: synthesis.safetyGate,
604
1056
  synthesisHash: synthesis.synthesisHash,
605
1057
  candidateCount: synthesis.candidates.length,
1058
+ candidateTrace: synthesis.candidates.map(candidate => ({
1059
+ candidateHash: candidate.candidateHash,
1060
+ memoryType: candidate.memoryType,
1061
+ canonicalKey: candidate.canonicalKey,
1062
+ sourceMemoryIds: candidate.sourceMemoryIds || [],
1063
+ sourceCanonicalKeys: candidate.sourceCanonicalKeys || [],
1064
+ })),
1065
+ rejectedCandidateCount: synthesis.rejectedCandidates.length,
1066
+ rejectedCandidates: synthesis.rejectedCandidates.map(item => ({
1067
+ memoryType: item.candidate.memoryType,
1068
+ canonicalKey: item.candidate.canonicalKey,
1069
+ summary: compactSummary(item.candidate),
1070
+ reasons: item.reasons,
1071
+ distillation: item.candidate.payload?.distillation || null,
1072
+ sourceMemoryIds: item.candidate.sourceMemoryIds || [],
1073
+ sourceCanonicalKeys: item.candidate.sourceCanonicalKeys || [],
1074
+ })),
606
1075
  sourceMemoryIds: synthesis.sourceMemoryIds,
607
1076
  sourceCanonicalKeys: synthesis.sourceCanonicalKeys,
608
1077
  },
@@ -613,6 +1082,7 @@ function attachTimerSynthesis(plan = {}, synthesisSummary = {}, opts = {}) {
613
1082
  synthesisResult: {
614
1083
  synthesisHash: synthesis.synthesisHash,
615
1084
  candidateCount: synthesis.candidates.length,
1085
+ rejectedCandidateCount: synthesis.rejectedCandidates.length,
616
1086
  safetyGate: synthesis.safetyGate,
617
1087
  },
618
1088
  },
@@ -625,14 +1095,28 @@ function buildPromotionReview(input = {}, opts = {}) {
625
1095
  const applyResult = input.applyResult || {};
626
1096
  const candidates = Array.isArray(plan.candidates) ? plan.candidates : [];
627
1097
  const statusUpdates = Array.isArray(plan.statusUpdates) ? plan.statusUpdates : [];
1098
+ const aggregateCandidateCount = candidates.filter(isAggregateRollupCandidate).length;
1099
+ const synthesisResult = plan.synthesisResult || plan.meta?.synthesisResult || null;
1100
+ const rejectedCandidateCount = synthesisResult?.rejectedCandidateCount || 0;
1101
+ const gate = input.promoteCandidates === true
1102
+ ? (aggregateCandidateCount > 0
1103
+ ? 'blocked unless unsafePromoteAggregateCandidates=true; attach reviewed synthesis summary for normal promotion'
1104
+ : 'operator promotion requested for reviewed synthesis candidates')
1105
+ : 'candidate-only unless promoteCandidates=true with reviewed synthesis summary';
628
1106
  const candidateLines = candidates.slice(0, Math.max(0, Math.min(20, opts.limit || 8))).map(candidate => {
629
1107
  const type = candidate.memoryType || candidate.memory_type || 'memory';
630
1108
  const scope = candidate.scopeKey || candidate.scope_key || 'unspecified';
631
1109
  const payload = candidate.payload && typeof candidate.payload === 'object' ? candidate.payload : {};
632
- const sourceKeys = Array.isArray(payload.sourceCanonicalKeys) && payload.sourceCanonicalKeys.length > 0
633
- ? ` | sources=${payload.sourceCanonicalKeys.join(',')}`
1110
+ const candidateSourceKeys = Array.isArray(candidate.sourceCanonicalKeys) && candidate.sourceCanonicalKeys.length > 0
1111
+ ? candidate.sourceCanonicalKeys
1112
+ : (Array.isArray(payload.sourceCanonicalKeys) ? payload.sourceCanonicalKeys : []);
1113
+ const sourceKeys = candidateSourceKeys.length > 0
1114
+ ? ` | sources=${candidateSourceKeys.join(',')}`
634
1115
  : '';
635
- return `- candidate ${type} ${scope}: ${compactSummary(candidate)}${sourceKeys}`;
1116
+ const sourceLineage = !sourceKeys && payload.sourceLineageRef
1117
+ ? ` | lineage=${payload.sourceLineageRef} sourceRecordCount=${payload.sourceRecordCount || 0}`
1118
+ : '';
1119
+ return `- candidate ${type} ${scope}: ${compactSummary(candidate)}${sourceKeys}${sourceLineage}`;
636
1120
  });
637
1121
  const staleLines = statusUpdates.slice(0, Math.max(0, Math.min(20, opts.limit || 8))).map(update => (
638
1122
  `- ${update.status}: ${update.canonicalKey || update.memoryId || 'memory'}${update.reason ? ` (${update.reason})` : ''}`
@@ -642,9 +1126,10 @@ function buildPromotionReview(input = {}, opts = {}) {
642
1126
  'Promotion review:',
643
1127
  `window: ${plan.cadence || 'manual'} ${canonicalInstant(plan.periodStart)} -> ${canonicalInstant(plan.periodEnd)}`,
644
1128
  `source: ${plan.synthesisInput?.sourceOfTruth || plan.meta?.synthesisInput?.sourceOfTruth || 'memory_records'}`,
645
- `gate: ${input.promoteCandidates === true ? 'operator promotion requested' : 'candidate-only unless promoteCandidates=true'}`,
1129
+ `gate: ${gate}`,
1130
+ `temporal synthesis: ${synthesisResult ? `reviewed summary attached (${synthesisResult.candidateCount || 0} candidates, ${rejectedCandidateCount} rejected)` : 'not attached'}`,
646
1131
  `status updates: planned=${statusUpdates.length} applied=${applyResult.applied || 0} skipped=${applyResult.skipped || 0}`,
647
- `candidates: planned=${candidates.length} promoted=${promotionResult.promoted || 0} quarantined=${promotionResult.quarantined || 0} errored=${promotionResult.errored || 0}`,
1132
+ `candidates: planned=${candidates.length} aggregate=${aggregateCandidateCount} promoted=${promotionResult.promoted || 0} quarantined=${promotionResult.quarantined || 0} errored=${promotionResult.errored || 0}`,
648
1133
  'candidate proposals:',
649
1134
  linesOrNone(candidateLines),
650
1135
  'status update proposals:',
@@ -720,12 +1205,37 @@ function summarizePromotionResults(results = []) {
720
1205
  return summary;
721
1206
  }
722
1207
 
723
- function normalizeCandidateLineage(candidate = {}) {
1208
+ function planLineageForCandidate(candidate = {}, plan = {}) {
724
1209
  const payload = candidate.payload && typeof candidate.payload === 'object' ? candidate.payload : {};
725
- const sourceCanonicalKeys = Array.isArray(payload.sourceCanonicalKeys)
726
- ? payload.sourceCanonicalKeys.map(key => String(key || '')).filter(Boolean)
727
- : [];
728
- const rawSourceMemoryIds = Array.isArray(payload.sourceMemoryIds) ? payload.sourceMemoryIds : [];
1210
+ const sourceLineageRef = String(payload.sourceLineageRef || '').trim();
1211
+ if (!sourceLineageRef) return null;
1212
+ const synthesisResult = plan.synthesisResult || plan.meta?.synthesisResult || null;
1213
+ if (!synthesisResult || !synthesisResult.synthesisHash) return null;
1214
+ const baseRef = `timer_synthesis:${synthesisResult.synthesisHash}`;
1215
+ if (sourceLineageRef !== baseRef && !sourceLineageRef.startsWith(`${baseRef}:candidate:`)) return null;
1216
+ return {
1217
+ sourceMemoryIds: Array.isArray(synthesisResult.sourceMemoryIds) ? synthesisResult.sourceMemoryIds : [],
1218
+ sourceCanonicalKeys: Array.isArray(synthesisResult.sourceCanonicalKeys) ? synthesisResult.sourceCanonicalKeys : [],
1219
+ };
1220
+ }
1221
+
1222
+ function normalizeCandidateLineage(candidate = {}, opts = {}) {
1223
+ const payload = candidate.payload && typeof candidate.payload === 'object' ? candidate.payload : {};
1224
+ const planLineage = planLineageForCandidate(candidate, opts.plan || {});
1225
+ const candidateCanonicalKeys = Array.isArray(candidate.sourceCanonicalKeys)
1226
+ ? candidate.sourceCanonicalKeys
1227
+ : (Array.isArray(candidate.source_canonical_keys) ? candidate.source_canonical_keys : null);
1228
+ const candidateMemoryIds = Array.isArray(candidate.sourceMemoryIds)
1229
+ ? candidate.sourceMemoryIds
1230
+ : (Array.isArray(candidate.source_memory_ids) ? candidate.source_memory_ids : null);
1231
+ const sourceCanonicalKeys = Array.isArray(candidateCanonicalKeys)
1232
+ ? candidateCanonicalKeys.map(key => String(key || '')).filter(Boolean)
1233
+ : (Array.isArray(payload.sourceCanonicalKeys)
1234
+ ? payload.sourceCanonicalKeys.map(key => String(key || '')).filter(Boolean)
1235
+ : (planLineage?.sourceCanonicalKeys || []).map(key => String(key || '')).filter(Boolean));
1236
+ const rawSourceMemoryIds = Array.isArray(candidateMemoryIds)
1237
+ ? candidateMemoryIds
1238
+ : (Array.isArray(payload.sourceMemoryIds) ? payload.sourceMemoryIds : (planLineage?.sourceMemoryIds || []));
729
1239
  const sourceMemoryIds = rawSourceMemoryIds
730
1240
  .filter(id => id !== null && id !== undefined)
731
1241
  .map(id => Number(id));
@@ -1086,7 +1596,9 @@ function createMemoryConsolidation({ pool, schema, defaultTenantId, records = nu
1086
1596
  for (let i = 0; i < candidates.length; i++) {
1087
1597
  const candidate = candidates[i] || {};
1088
1598
  const result = results[i] || {};
1089
- const { payload, sourceMemoryIds, sourceCanonicalKeys } = normalizeCandidateLineage(candidate);
1599
+ const { payload, sourceMemoryIds, sourceCanonicalKeys } = normalizeCandidateLineage(candidate, {
1600
+ plan: input.plan,
1601
+ });
1090
1602
  const candidateHash = candidate.candidateHash || payload.candidateHash || hashSnapshot(candidate);
1091
1603
  const inserted = await queryable.query(
1092
1604
  `INSERT INTO ${schema}.compaction_candidates (
@@ -1213,6 +1725,7 @@ function createMemoryConsolidation({ pool, schema, defaultTenantId, records = nu
1213
1725
  tenantId,
1214
1726
  run: claim,
1215
1727
  candidates,
1728
+ plan,
1216
1729
  results: candidates.map(candidate => ({
1217
1730
  candidate,
1218
1731
  action: 'planned',
@@ -1258,6 +1771,8 @@ function createMemoryConsolidation({ pool, schema, defaultTenantId, records = nu
1258
1771
  const promoteCandidates = input.promoteCandidates === true;
1259
1772
  const summary = createApplySummary(statusUpdates);
1260
1773
 
1774
+ assertPromotableTemporalCandidates(candidates, input);
1775
+
1261
1776
  if (!records || typeof records.updateMemoryStatusIfCurrent !== 'function') {
1262
1777
  throw new Error('memory.consolidation.executePlan requires records.updateMemoryStatusIfCurrent');
1263
1778
  }
@@ -1344,6 +1859,7 @@ function createMemoryConsolidation({ pool, schema, defaultTenantId, records = nu
1344
1859
  tenantId,
1345
1860
  run: claim,
1346
1861
  candidates,
1862
+ plan,
1347
1863
  results: promotionResults,
1348
1864
  });
1349
1865
  const promotionResult = summarizePromotionResults(promotionResults);
@@ -1525,6 +2041,7 @@ function createMemoryConsolidation({ pool, schema, defaultTenantId, records = nu
1525
2041
  applyToken: input.applyToken,
1526
2042
  appliedAt: input.appliedAt,
1527
2043
  promoteCandidates: true,
2044
+ unsafePromoteAggregateCandidates: input.unsafePromoteAggregateCandidates,
1528
2045
  claimLeaseSeconds: input.claimLeaseSeconds,
1529
2046
  reclaimStaleClaims: input.reclaimStaleClaims,
1530
2047
  })
@@ -1579,6 +2096,11 @@ module.exports = {
1579
2096
  canonicalInstant,
1580
2097
  normalizeClaimLeaseSeconds,
1581
2098
  resolveOperatorWindow,
2099
+ temporalPolicyForCadence,
2100
+ evaluateTemporalDistillationCandidate,
2101
+ applyTemporalDistillationStandard,
2102
+ isAggregateRollupCandidate,
2103
+ assertPromotableTemporalCandidates,
1582
2104
  planCompaction,
1583
2105
  buildTimerSynthesisInput,
1584
2106
  buildTimerSynthesisPrompt,