memorix 1.4.1 → 1.4.2

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.
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Append-only evidence about whether a context artifact helped or was
3
+ * contradicted. Signals never replace the artifact they describe.
4
+ */
5
+ export const MEMORY_OUTCOME_CANDIDATE_KINDS = ['claim', 'durable-memory', 'workflow'] as const;
6
+ export type MemoryOutcomeCandidateKind = typeof MEMORY_OUTCOME_CANDIDATE_KINDS[number];
7
+
8
+ export const MEMORY_OUTCOME_SIGNAL_KINDS = [
9
+ 'verification-passed',
10
+ 'verification-failed',
11
+ 'verified-reuse',
12
+ 'user-pin',
13
+ 'user-correction',
14
+ 'source-changed',
15
+ 'conflict-confirmed',
16
+ 'manual-review',
17
+ ] as const;
18
+ export type MemoryOutcomeSignalKind = typeof MEMORY_OUTCOME_SIGNAL_KINDS[number];
19
+
20
+ export interface MemoryOutcomeSignal {
21
+ id: string;
22
+ projectId: string;
23
+ candidateKind: MemoryOutcomeCandidateKind;
24
+ candidateId: string;
25
+ kind: MemoryOutcomeSignalKind;
26
+ sourceRef: string;
27
+ snapshotId?: string;
28
+ detail?: string;
29
+ observedAt: string;
30
+ }
31
+
32
+ export type OutcomeQuality = 'verified' | 'probationary' | 'degraded';
33
+
34
+ /** The newest auditable signal wins; missing signals leave lifecycle state in charge. */
35
+ export function qualityFromOutcome(signal?: Pick<MemoryOutcomeSignal, 'kind'>): OutcomeQuality | undefined {
36
+ if (!signal) return undefined;
37
+ if (signal.kind === 'verification-failed'
38
+ || signal.kind === 'user-correction'
39
+ || signal.kind === 'source-changed'
40
+ || signal.kind === 'conflict-confirmed') {
41
+ return 'degraded';
42
+ }
43
+ if (signal.kind === 'verification-passed'
44
+ || signal.kind === 'verified-reuse'
45
+ || signal.kind === 'user-pin') {
46
+ return 'verified';
47
+ }
48
+ return undefined;
49
+ }
@@ -7,6 +7,8 @@ import { atomicWriteFile, withFileLock } from '../store/file-lock.js';
7
7
  import { WorkflowSyncer } from '../workspace/workflow-sync.js';
8
8
  import { getKnowledgeWorkspacePaths, resolveKnowledgeWorkspaceFile } from './workspace.js';
9
9
  import { WorkflowStore } from './workflow-store.js';
10
+ import { OutcomeStore } from './outcome-store.js';
11
+ import type { MemoryOutcomeCandidateKind, MemoryOutcomeSignalKind } from './outcome-types.js';
10
12
  import { containsTaskKeyword } from '../codegraph/task-lens.js';
11
13
  import type {
12
14
  WorkflowAdapterPreview,
@@ -751,7 +753,36 @@ export async function recordWorkflowRun(input: {
751
753
  throw new Error('Workflow was not found for this workspace');
752
754
  }
753
755
  if (!input.run.task.trim()) throw new Error('Workflow run task is required');
754
- return store.recordRun(input.run);
756
+ const run = store.recordRun(input.run);
757
+ const kind: MemoryOutcomeSignalKind | undefined = run.outcome === 'passed' && run.verificationVerdict === 'passed'
758
+ ? 'verification-passed'
759
+ : run.outcome === 'failed' || run.verificationVerdict === 'failed'
760
+ ? 'verification-failed'
761
+ : undefined;
762
+ if (!kind) return run;
763
+
764
+ const evidence = new Map<string, MemoryOutcomeCandidateKind>();
765
+ evidence.set(workflow.id, 'workflow');
766
+ for (const reference of run.selectedEvidence) {
767
+ const match = /^(claim|durable):(.+)$/.exec(reference);
768
+ if (!match) continue;
769
+ evidence.set(match[2], match[1] === 'claim' ? 'claim' : 'durable-memory');
770
+ }
771
+ const outcomes = new OutcomeStore();
772
+ await outcomes.init(workflowStoreDataDir(input.workspace));
773
+ for (const [candidateId, candidateKind] of evidence) {
774
+ outcomes.record({
775
+ projectId: run.projectId,
776
+ candidateKind,
777
+ candidateId,
778
+ kind,
779
+ sourceRef: 'workflow-run:' + run.id,
780
+ ...(run.startingSnapshotId ? { snapshotId: run.startingSnapshotId } : {}),
781
+ ...(run.failureReason ? { detail: run.failureReason } : {}),
782
+ observedAt: run.completedAt ?? run.startedAt,
783
+ });
784
+ }
785
+ return run;
755
786
  }
756
787
 
757
788
  export async function selectWorkspaceWorkflows(input: {
@@ -5,8 +5,11 @@ import { selectClaimsForTask } from './claims.js';
5
5
  import { KnowledgeWorkspaceStore } from './workspace-store.js';
6
6
  import { loadKnowledgeWorkspace } from './workspace.js';
7
7
  import { WorkflowStore } from './workflow-store.js';
8
+ import { OutcomeStore } from './outcome-store.js';
9
+ import { qualityFromOutcome, type OutcomeQuality } from './outcome-types.js';
8
10
  import { selectWorkflows } from './workflows.js';
9
11
  import { renderLongTermMemorySummary, selectLongTermMemoriesForTask } from '../memory/long-term.js';
12
+ import { governContextCandidates, type GovernancePlan } from './governor.js';
10
13
  import type {
11
14
  ContextCandidateFreshness,
12
15
  ContextCandidateKind,
@@ -86,6 +89,7 @@ export interface WorksetLongTermMemory {
86
89
  kind: LongTermMemory['kind'];
87
90
  scope: LongTermMemory['scope'];
88
91
  state: LongTermMemory['state'];
92
+ quality: OutcomeQuality;
89
93
  title: string;
90
94
  summary: string;
91
95
  evidenceRefs: string[];
@@ -117,6 +121,17 @@ export interface WorksetContinuation {
117
121
  };
118
122
  }
119
123
 
124
+ /** A small, exact file-level delta between the two latest complete Code State snapshots. */
125
+ export interface WorksetCodeEvolution {
126
+ fromSnapshotId: string;
127
+ toSnapshotId: string;
128
+ changes: Array<{ path: string; kind: 'added' | 'modified' | 'removed' }>;
129
+ directlyConnectedPaths: string[];
130
+ /** Current stale memory bindings directly tied to the changed file paths. */
131
+ affectedMemoryCount?: number;
132
+ truncated: boolean;
133
+ }
134
+
120
135
  export interface TaskWorkset {
121
136
  version: '1.3';
122
137
  task: string;
@@ -124,6 +139,7 @@ export interface TaskWorkset {
124
139
  currentFacts: string[];
125
140
  continuation?: WorksetContinuation;
126
141
  codeState?: string;
142
+ codeEvolution?: WorksetCodeEvolution;
127
143
  startHere: string[];
128
144
  /** Bounded task-specific relations from a validated local semantic graph. */
129
145
  semanticCode?: ExternalCodeGraphOutline;
@@ -161,6 +177,7 @@ export interface BuildTaskWorksetInput {
161
177
  currentFacts?: string[];
162
178
  continuation?: WorksetContinuation;
163
179
  codeState?: string;
180
+ codeEvolution?: WorksetCodeEvolution;
164
181
  startHere: string[];
165
182
  semanticCode?: ExternalCodeGraphOutline;
166
183
  providerQuality?: CodeGraphProviderQuality;
@@ -258,7 +275,7 @@ function pageMatchesClaim(page: KnowledgePageRecord, claimIds: Set<string>): boo
258
275
  && page.claimIds.some(claimId => claimIds.has(claimId));
259
276
  }
260
277
 
261
- function evidenceIdsForClaim(claim: KnowledgeClaim, evidence: ClaimEvidenceRef[]): string[] {
278
+ function evidenceIdsForClaim(claim: Pick<KnowledgeClaim, 'id'>, evidence: ClaimEvidenceRef[]): string[] {
262
279
  return unique([
263
280
  'claim:' + claim.id,
264
281
  ...evidence.map(item => item.evidenceKind + ':' + item.evidenceId),
@@ -353,6 +370,87 @@ function scheduledActions(cautions: WorksetCaution[]): string[] {
353
370
  .map(caution => short(caution.message, 28));
354
371
  }
355
372
 
373
+ function isGovernedDelivery(disposition: GovernancePlan['decisions'][number]['disposition']): boolean {
374
+ return disposition === 'include' || disposition === 'compact';
375
+ }
376
+
377
+ /**
378
+ * Optional knowledge is where stale or over-confident context does the most
379
+ * harm. Qualify it before prompt rendering; current project facts and cautions
380
+ * remain visible so the agent can still inspect live source.
381
+ */
382
+ function governOptionalArtifacts(input: {
383
+ reliableMemory: WorksetMemorySource[];
384
+ claims: WorksetClaim[];
385
+ durableMemory: WorksetLongTermMemory[];
386
+ workflows: WorksetWorkflow[];
387
+ }): {
388
+ plan: GovernancePlan;
389
+ reliableMemory: WorksetMemorySource[];
390
+ claims: WorksetClaim[];
391
+ durableMemory: WorksetLongTermMemory[];
392
+ workflows: WorksetWorkflow[];
393
+ } {
394
+ const candidates = [
395
+ ...input.reliableMemory.map((memory, index) => ({
396
+ kind: 'memory' as const,
397
+ id: 'memory:' + memory.id,
398
+ estimatedTokens: 10,
399
+ relevance: Math.max(0.2, 0.9 - index * 0.1),
400
+ scopeAllowed: true,
401
+ freshness: freshnessForMemory(memory.status),
402
+ trust: 'historical' as const,
403
+ quality: memory.status === 'current' ? 'verified' as const : 'degraded' as const,
404
+ })),
405
+ ...input.claims.map((claim, index) => ({
406
+ kind: 'claim' as const,
407
+ id: 'claim:' + claim.id,
408
+ estimatedTokens: 12,
409
+ relevance: Math.max(0.2, 0.95 - index * 0.1),
410
+ scopeAllowed: true,
411
+ freshness: claim.status === 'active' ? 'current' as const : 'unknown' as const,
412
+ trust: 'source-backed' as const,
413
+ quality: claim.reviewState === 'approved' ? 'verified' as const : 'probationary' as const,
414
+ conflict: claim.status === 'disputed' ? 'confirmed' as const : 'none' as const,
415
+ evidenceCount: claim.evidenceRefs.length,
416
+ })),
417
+ ...input.durableMemory.map((memory, index) => ({
418
+ kind: 'durable-memory' as const,
419
+ id: 'durable:' + memory.id,
420
+ estimatedTokens: 14,
421
+ relevance: Math.max(0.2, 0.8 - index * 0.1),
422
+ scopeAllowed: true,
423
+ freshness: memory.state === 'approved' ? 'current' as const : 'unknown' as const,
424
+ trust: 'source-backed' as const,
425
+ quality: memory.quality,
426
+ evidenceCount: memory.evidenceRefs.length,
427
+ })),
428
+ ...input.workflows.map((workflow, index) => ({
429
+ kind: 'workflow' as const,
430
+ id: 'workflow:' + workflow.id,
431
+ estimatedTokens: 14,
432
+ relevance: Math.max(0.2, 0.85 - index * 0.1),
433
+ scopeAllowed: true,
434
+ freshness: 'current' as const,
435
+ trust: 'source-backed' as const,
436
+ quality: 'verified' as const,
437
+ })),
438
+ ];
439
+ // Prompt rendering owns the final whole-item token budget. This preflight
440
+ // only decides whether optional evidence is safe enough to render at all.
441
+ const plan = governContextCandidates(candidates, Number.MAX_SAFE_INTEGER);
442
+ const permitted = new Set(plan.decisions
443
+ .filter(decision => isGovernedDelivery(decision.disposition))
444
+ .map(decision => decision.candidate.id));
445
+ return {
446
+ plan,
447
+ reliableMemory: input.reliableMemory.filter(memory => permitted.has('memory:' + memory.id)),
448
+ claims: input.claims.filter(claim => permitted.has('claim:' + claim.id)),
449
+ durableMemory: input.durableMemory.filter(memory => permitted.has('durable:' + memory.id)),
450
+ workflows: input.workflows.filter(workflow => permitted.has('workflow:' + workflow.id)),
451
+ };
452
+ }
453
+
356
454
  /**
357
455
  * Render a bounded prompt from whole evidence items. It never cuts a page,
358
456
  * workflow, or claim body into an untraceable partial fragment.
@@ -505,6 +603,57 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
505
603
  });
506
604
  }
507
605
 
606
+ if (input.codeEvolution && input.codeEvolution.changes.length > 0) {
607
+ appendLine(lines, '', maxTokens, omitted, 'code-evolution-heading');
608
+ appendLine(lines, 'Code changes since prior scan', maxTokens, omitted, 'code-evolution-heading');
609
+ for (const change of input.codeEvolution.changes.slice(0, 3)) {
610
+ appendLine(
611
+ lines,
612
+ '- ' + change.kind + ': ' + change.path,
613
+ maxTokens,
614
+ omitted,
615
+ 'code-evolution-change',
616
+ selected,
617
+ {
618
+ kind: 'code-state',
619
+ id: 'snapshot:' + input.codeEvolution.toSnapshotId,
620
+ reason: 'exact file hash change between complete CodeGraph snapshots',
621
+ freshness: 'current',
622
+ trust: 'source-backed',
623
+ },
624
+ );
625
+ }
626
+ for (const path of input.codeEvolution.directlyConnectedPaths.slice(0, 2)) {
627
+ appendLine(
628
+ lines,
629
+ '- connected now: ' + path,
630
+ maxTokens,
631
+ omitted,
632
+ 'code-evolution-impact',
633
+ selected,
634
+ {
635
+ kind: 'code-state',
636
+ id: 'snapshot:' + input.codeEvolution.toSnapshotId,
637
+ reason: 'bounded current CodeGraph impact relation',
638
+ freshness: 'current',
639
+ trust: 'derived',
640
+ },
641
+ );
642
+ }
643
+ if ((input.codeEvolution.affectedMemoryCount ?? 0) > 0) {
644
+ appendLine(
645
+ lines,
646
+ '- ' + input.codeEvolution.affectedMemoryCount + ' stored memory link(s) reference this changed code; reread source before relying on their conclusions.',
647
+ maxTokens,
648
+ omitted,
649
+ 'code-evolution-stale-memory',
650
+ );
651
+ }
652
+ if (input.codeEvolution.truncated) {
653
+ appendLine(lines, '- More connected files exist; inspect CodeGraph before assuming complete impact.', maxTokens, omitted, 'code-evolution-truncated');
654
+ }
655
+ }
656
+
508
657
  if (input.semanticCode && (input.semanticCode.entryPoints.length > 0 || input.semanticCode.relations.length > 0)) {
509
658
  appendLine(lines, '', maxTokens, omitted, 'semantic-code-heading');
510
659
  appendLine(lines, 'Semantic code outline', maxTokens, omitted, 'semantic-code-heading');
@@ -716,6 +865,21 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
716
865
 
717
866
  let durableMemory: WorksetLongTermMemory[] = [];
718
867
  let durableEvidence: LongTermMemoryEvidence[] = [];
868
+ let outcomeStore: OutcomeStore | undefined;
869
+ try {
870
+ outcomeStore = new OutcomeStore();
871
+ await outcomeStore.init(input.dataDir);
872
+ } catch {
873
+ // Outcome evidence enriches the decision; normal current-source context
874
+ // remains usable if an older local database cannot expose it yet.
875
+ }
876
+ const claimQuality = outcomeStore
877
+ ? outcomeStore.latestForCandidates(input.projectId, 'claim', claims.map(claim => claim.id))
878
+ : new Map();
879
+ for (const claim of claims) {
880
+ const outcomeQuality = qualityFromOutcome(claimQuality.get(claim.id));
881
+ if (outcomeQuality === 'degraded') claim.reviewState = 'needs-review';
882
+ }
719
883
  if (task) {
720
884
  try {
721
885
  const selected = await selectLongTermMemoriesForTask({
@@ -726,11 +890,16 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
726
890
  ...(input.reader?.isTeamMember ? { isTeamMember: true } : {}),
727
891
  limit: 3,
728
892
  });
893
+ const durableQuality = outcomeStore
894
+ ? outcomeStore.latestForCandidates(input.projectId, 'durable-memory', selected.map(item => item.memory.id))
895
+ : new Map();
729
896
  durableMemory = selected.map(item => ({
730
897
  id: item.memory.id,
731
898
  kind: item.memory.kind,
732
899
  scope: item.memory.scope,
733
900
  state: item.memory.state,
901
+ quality: qualityFromOutcome(durableQuality.get(item.memory.id))
902
+ ?? (item.memory.state === 'approved' ? 'verified' : 'probationary'),
734
903
  title: item.memory.title,
735
904
  summary: renderLongTermMemorySummary(item.memory, 20),
736
905
  evidenceRefs: item.evidence.map(evidence => evidence.id),
@@ -785,13 +954,6 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
785
954
  // Knowledge is optional. Existing Code Memory remains usable without it.
786
955
  }
787
956
 
788
- const evidenceIds = unique(selection.claims.flatMap(claim => evidenceIdsForClaim(
789
- claim,
790
- evidenceByClaim.get(claim.id) ?? [],
791
- )).concat(
792
- durableMemory.map(memory => 'durable:' + memory.id),
793
- durableEvidence.map(evidence => 'durable-evidence:' + evidence.id),
794
- ));
795
957
  const verification = unique([
796
958
  ...workflows.flatMap(workflow => workflow.verificationGates),
797
959
  ...input.verificationHints,
@@ -799,6 +961,22 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
799
961
  const normalizedCautions = unique(cautions.map(caution => caution.kind))
800
962
  .map(kind => cautions.find(caution => caution.kind === kind)!)
801
963
  .slice(0, 6);
964
+ const governed = governOptionalArtifacts({
965
+ reliableMemory: input.reliableMemory?.slice(0, 3) ?? [],
966
+ claims,
967
+ durableMemory,
968
+ workflows,
969
+ });
970
+ const governedDurableIds = new Set(governed.durableMemory.map(memory => memory.id));
971
+ const evidenceIds = unique(governed.claims.flatMap(claim => evidenceIdsForClaim(
972
+ claim,
973
+ evidenceByClaim.get(claim.id) ?? [],
974
+ )).concat(
975
+ governed.durableMemory.map(memory => 'durable:' + memory.id),
976
+ durableEvidence
977
+ .filter(evidence => governedDurableIds.has(evidence.memoryId))
978
+ .map(evidence => 'durable-evidence:' + evidence.id),
979
+ ));
802
980
  const continuation = input.continuation
803
981
  && (
804
982
  input.continuation.previousSession
@@ -838,15 +1016,16 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
838
1016
  : short(fact, 28)).slice(0, 4) ?? [],
839
1017
  ...(continuation ? { continuation } : {}),
840
1018
  ...(input.codeState ? { codeState: short(input.codeState, 28) } : {}),
1019
+ ...(input.codeEvolution ? { codeEvolution: input.codeEvolution } : {}),
841
1020
  startHere: unique(input.startHere).slice(0, 5),
842
1021
  ...(input.semanticCode ? { semanticCode: input.semanticCode } : {}),
843
- reliableMemory: input.reliableMemory?.slice(0, 3) ?? [],
1022
+ reliableMemory: governed.reliableMemory,
844
1023
  cautionMemory: input.cautionMemory?.slice(0, 3) ?? [],
845
1024
  hiddenCautionMemoryCount: input.hiddenCautionMemoryCount ?? 0,
846
- claims,
847
- pages,
848
- durableMemory,
849
- workflows,
1025
+ claims: governed.claims,
1026
+ pages: pages.filter(page => page.claimIds.some(claimId => governed.claims.some(claim => claim.id === claimId))),
1027
+ durableMemory: governed.durableMemory,
1028
+ workflows: governed.workflows,
850
1029
  cautions: normalizedCautions,
851
1030
  verification,
852
1031
  evidenceIds,
@@ -871,6 +1050,17 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
871
1050
  },
872
1051
  selected: rendered.selected,
873
1052
  omitted: receiptOmissions(rendered.omittedItems, base.hiddenCautionMemoryCount),
1053
+ governance: {
1054
+ scope: 'optional-evidence',
1055
+ mode: governed.plan.mode,
1056
+ decisions: governed.plan.decisions.map(decision => ({
1057
+ kind: decision.candidate.kind,
1058
+ ...(decision.candidate.id ? { id: decision.candidate.id } : {}),
1059
+ disposition: decision.disposition,
1060
+ reasons: decision.reasons,
1061
+ })),
1062
+ cautions: governed.plan.cautions,
1063
+ },
874
1064
  scheduledActions: scheduledActions(normalizedCautions),
875
1065
  };
876
1066
  return {
@@ -321,6 +321,18 @@ const CREATE_CODE_STATE_SNAPSHOTS_TABLE = [
321
321
  ');',
322
322
  ].join('\n');
323
323
 
324
+ const CREATE_CODE_STATE_SNAPSHOT_FILES_TABLE = [
325
+ 'CREATE TABLE IF NOT EXISTS code_state_snapshot_files (',
326
+ ' snapshotId TEXT NOT NULL,',
327
+ ' projectId TEXT NOT NULL,',
328
+ ' fileId TEXT NOT NULL,',
329
+ ' path TEXT NOT NULL,',
330
+ ' contentHash TEXT NOT NULL,',
331
+ ' PRIMARY KEY (snapshotId, path),',
332
+ ' FOREIGN KEY (snapshotId) REFERENCES code_state_snapshots(id) ON DELETE CASCADE',
333
+ ');',
334
+ ].join('\n');
335
+
324
336
  // ── 1.2 Knowledge Claim Ledger ──────────────────────────────────────
325
337
 
326
338
  const CREATE_KNOWLEDGE_CLAIMS_TABLE = `
@@ -553,6 +565,22 @@ CREATE TABLE IF NOT EXISTS long_term_memory_events (
553
565
  );
554
566
  `;
555
567
 
568
+ // ── 1.4 Evidence-governed memory ───────────────────────────────────
569
+
570
+ const CREATE_MEMORY_OUTCOME_SIGNALS_TABLE = `
571
+ CREATE TABLE IF NOT EXISTS memory_outcome_signals (
572
+ id TEXT PRIMARY KEY,
573
+ projectId TEXT NOT NULL,
574
+ candidateKind TEXT NOT NULL,
575
+ candidateId TEXT NOT NULL,
576
+ kind TEXT NOT NULL,
577
+ sourceRef TEXT NOT NULL,
578
+ snapshotId TEXT,
579
+ detail TEXT,
580
+ observedAt TEXT NOT NULL
581
+ );
582
+ `;
583
+
556
584
  // ── 1.3.3 Controlled media assets ─────────────────────────────────
557
585
 
558
586
  const CREATE_MEDIA_ASSETS_TABLE = `
@@ -826,6 +854,20 @@ const SCHEMA_MIGRATIONS: SchemaMigration[] = [
826
854
  db.exec('CREATE INDEX IF NOT EXISTS idx_code_edges_snapshot ON code_edges(projectId, snapshotId)');
827
855
  },
828
856
  },
857
+ {
858
+ id: '1.4-codegraph-snapshot-manifest',
859
+ apply: (db) => {
860
+ db.exec(CREATE_CODE_STATE_SNAPSHOT_FILES_TABLE);
861
+ db.exec('CREATE INDEX IF NOT EXISTS idx_code_snapshot_files_project_path ON code_state_snapshot_files(projectId, path)');
862
+ },
863
+ },
864
+ {
865
+ id: '1.4-memory-outcome-signals',
866
+ apply: (db) => {
867
+ db.exec(CREATE_MEMORY_OUTCOME_SIGNALS_TABLE);
868
+ db.exec('CREATE INDEX IF NOT EXISTS idx_memory_outcomes_candidate ON memory_outcome_signals(projectId, candidateKind, candidateId, observedAt DESC)');
869
+ },
870
+ },
829
871
  {
830
872
  id: '1.2-knowledge-claim-ledger',
831
873
  apply: (db) => {