@jungjaehoon/mama-core 4.1.0 → 5.0.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 (38) hide show
  1. package/README.md +10 -10
  2. package/db/migrations/099-commitment-revision-graph.sql +3 -0
  3. package/dist/api/catalog.js +3 -6
  4. package/dist/api/dispatch.js +12 -1
  5. package/dist/db-adapter/node-sqlite-adapter.d.ts +5 -1
  6. package/dist/db-adapter/node-sqlite-adapter.js +27 -24
  7. package/dist/db-manager.d.ts +2 -13
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.js +3 -6
  10. package/dist/knowledge/commitments.d.ts +2 -0
  11. package/dist/knowledge/commitments.js +1 -0
  12. package/dist/knowledge/graph-query.d.ts +10 -1
  13. package/dist/knowledge/graph-query.js +118 -25
  14. package/dist/knowledge/index.d.ts +11 -3
  15. package/dist/knowledge/index.js +9 -10
  16. package/dist/knowledge/judgments.d.ts +3 -1
  17. package/dist/knowledge/judgments.js +46 -26
  18. package/dist/knowledge/links.d.ts +50 -0
  19. package/dist/knowledge/links.js +152 -0
  20. package/dist/mama-api.d.ts +22 -7
  21. package/dist/mama-api.js +64 -15
  22. package/dist/memory/api.d.ts +16 -11
  23. package/dist/memory/api.js +376 -400
  24. package/dist/memory/decision-links.d.ts +54 -0
  25. package/dist/memory/decision-links.js +195 -0
  26. package/dist/memory/graph-read.d.ts +0 -16
  27. package/dist/memory/graph-read.js +10 -51
  28. package/dist/memory/judgment-types.d.ts +1 -20
  29. package/dist/memory/types.d.ts +13 -0
  30. package/dist/memory/write-adapters.d.ts +1 -39
  31. package/dist/memory/write-adapters.js +0 -98
  32. package/package.json +1 -1
  33. package/dist/knowledge/commitment-revision-migration.d.ts +0 -4
  34. package/dist/knowledge/commitment-revision-migration.js +0 -50
  35. package/dist/knowledge/decision-edges.d.ts +0 -70
  36. package/dist/knowledge/decision-edges.js +0 -123
  37. package/dist/memory/evolution-engine.d.ts +0 -22
  38. package/dist/memory/evolution-engine.js +0 -133
package/README.md CHANGED
@@ -4,7 +4,7 @@ The shared engine for storage, records and revisions, evidence links, memory, se
4
4
  runtime drivers. Consumers supply their own database, principals, source access and product
5
5
  vocabulary through public exports. Core does not require the MAMA OS daemon.
6
6
 
7
- Version **4.1.0**; Node.js 22.13+. This README describes the current rebuild checkout.
7
+ Version **5.0.0**; Node.js 22.13+. This README describes the current rebuild checkout.
8
8
  See [architecture](../../docs/explanation/architecture.md) and
9
9
  [the shared-engine goal](../../INTENT.md).
10
10
 
@@ -13,15 +13,15 @@ See [architecture](../../docs/explanation/architecture.md) and
13
13
  [package.json](package.json) defines supported import paths; [src/index.ts](src/index.ts)
14
14
  defines root exports. Representative root exports:
15
15
 
16
- | Area | Root exports |
17
- | --------- | ------------------------------------------------------------------------------------------------- |
18
- | Storage | `createAdapter`, `SQLiteAdapter`, `initDB`, `getDB`, `getAdapter`, `closeDB` |
19
- | Records | `createKnowledge`, `appendJudgment`, `ingestSource` |
20
- | Memory | `mama`, `createMamaApi`, `saveMemory`, `recallMemory`, `ingestConversation`, `MEMORY_KINDS` |
21
- | Search | `generateEmbedding`, `generateEnhancedEmbedding`, `cosineSimilarity`, `EmbeddingCache` |
22
- | Evidence | `getMemoryProvenance`, `resolveMemoryProvenance`, `insertTwinEdge`, `listVisibleTwinEdgesForRefs` |
23
- | Execution | `createCatalog`, `createDispatcher`, `createClient`, `startRuntime`, `createActionIpcServer` |
24
- | Traces | `beginModelRun`, `commitModelRun`, `appendToolTrace`, `listToolTracesForRun` |
16
+ | Area | Root exports |
17
+ | --------- | -------------------------------------------------------------------------------------------- |
18
+ | Storage | `createAdapter`, `SQLiteAdapter`, `initDB`, `getDB`, `getAdapter`, `closeDB` |
19
+ | Records | `createKnowledge`, `appendJudgment`, `ingestSource` |
20
+ | Memory | `mama`, `createMamaApi`, `saveMemory`, `recallMemory`, `ingestConversation`, `MEMORY_KINDS` |
21
+ | Search | `generateEmbedding`, `generateEnhancedEmbedding`, `cosineSimilarity`, `EmbeddingCache` |
22
+ | Evidence | `getMemoryProvenance`, `resolveMemoryProvenance`, `listVisibleTwinEdgesForRefs` |
23
+ | Execution | `createCatalog`, `createDispatcher`, `createClient`, `startRuntime`, `createActionIpcServer` |
24
+ | Traces | `beginModelRun`, `commitModelRun`, `appendToolTrace`, `listToolTracesForRun` |
25
25
 
26
26
  Every public subpath below is relative to `@jungjaehoon/mama-core`:
27
27
 
@@ -1,2 +1,5 @@
1
+ -- 099 once backfilled a host builds_on from each commitment revision to the one before it. The
2
+ -- host no longer writes edges the agent did not state (owner, 2026-09-30); the order of revisions
3
+ -- lives in commitment_assignments. The version is kept so databases that crossed it stay aligned.
1
4
  INSERT OR IGNORE INTO schema_version (version, description)
2
5
  VALUES (99, 'Commitment revision graph links');
@@ -899,7 +899,7 @@ function coreActionRegistrations(knowledge, adapter, deps) {
899
899
  {
900
900
  contract: {
901
901
  name: 'memory.search',
902
- summary: 'Search or list memory records under the caller authority. Omitted scopes read the admitted corpus; explicit scopes must be a subset of it. With a query this is the semantic recall path (vector + lexical fusion, learned ranker when enabled); without one it is the exact topic-prefix ledger read.',
902
+ summary: 'Search or list memory records under the caller authority. Omitted scopes read the admitted corpus; explicit scopes must be a subset of it. With a query this is the semantic recall path (vector + lexical fusion, learned ranker when enabled), each hit lists the records its stated links reach (links: id, topic, relation, reason, corrected_by), and a result reached through a link names the hit it came from (related_to), the relation (graph_source), the reason (edge_reason) and any correction (edge_corrected_by); a hit that is one revision of a work item names it (work_item: commitment_id, revision, head_revision), and an earlier revision can rank above the head that corrected it, so open the head before answering from it; without one it is the exact topic-prefix ledger read.',
903
903
  inputSchema: memorySearchSchema,
904
904
  examples: [
905
905
  {
@@ -1078,14 +1078,14 @@ function coreActionRegistrations(knowledge, adapter, deps) {
1078
1078
  {
1079
1079
  contract: {
1080
1080
  name: 'memory.read:graph',
1081
- summary: 'The decision graph this caller may see: `view: graph` returns a bounded page of nodes with the edges joining admitted nodes and the admitted total; `nodes` returns the named ids; `detail` returns one record in full; `similarity` returns what the vector index says is near, over the admitted window. Every view is bounded by the admitted scopes.',
1081
+ summary: 'The decision graph this caller may see: `view: graph` returns a bounded page of nodes with the edges joining admitted nodes and the admitted total; `nodes` returns the named ids; `detail` returns one record in full. Every view is bounded by the admitted scopes.',
1082
1082
  inputSchema: {
1083
1083
  type: 'object',
1084
1084
  additionalProperties: false,
1085
1085
  properties: {
1086
1086
  view: {
1087
1087
  type: 'string',
1088
- enum: ['graph', 'nodes', 'detail', 'similarity'],
1088
+ enum: ['graph', 'nodes', 'detail'],
1089
1089
  description: 'Memory graph view, e.g. "detail".',
1090
1090
  },
1091
1091
  id: { type: 'string', description: 'One memory id for detail, e.g. "mem_123".' },
@@ -1132,9 +1132,6 @@ function coreActionRegistrations(knowledge, adapter, deps) {
1132
1132
  }
1133
1133
  return { nodes: await (0, graph_read_js_1.readGraphNodes)(adapter, scopes, { ids, limit: null }) };
1134
1134
  }
1135
- if (view === 'similarity') {
1136
- return { edges: await (0, graph_read_js_1.readGraphSimilarityEdges)(adapter, scopes) };
1137
- }
1138
1135
  const [nodes, edges, total] = await Promise.all([
1139
1136
  (0, graph_read_js_1.readGraphNodes)(adapter, scopes, { limit }),
1140
1137
  (0, graph_read_js_1.readGraphEdges)(adapter, scopes),
@@ -272,7 +272,11 @@ function validateInput(schema, value, path) {
272
272
  if (schema.oneOf !== undefined) {
273
273
  const matches = schema.oneOf.filter((sub) => validateInput(sub, value, path) === null).length;
274
274
  if (matches !== 1) {
275
- return `${path} must match exactly one allowed shape (${matches} matched).`;
275
+ // A refusal names what is allowed: "(0 matched)" alone once left a caller dropping the
276
+ // field and the write with it.
277
+ const shapes = schema.oneOf.map(shapeName).join(', ');
278
+ const described = schema.description === undefined ? '' : ` ${schema.description}`;
279
+ return `${path} must match exactly one of: ${shapes} (${matches} matched).${described}`;
276
280
  }
277
281
  }
278
282
  if (typeof value === 'number') {
@@ -349,6 +353,13 @@ function validateInput(schema, value, path) {
349
353
  }
350
354
  return null;
351
355
  }
356
+ function shapeName(schema) {
357
+ if (schema.const !== undefined)
358
+ return JSON.stringify(schema.const);
359
+ if (schema.enum !== undefined)
360
+ return schema.enum.map((value) => JSON.stringify(value)).join(' | ');
361
+ return schema.type ?? 'a described shape';
362
+ }
352
363
  function typeMismatch(type, value) {
353
364
  switch (type) {
354
365
  case 'object':
@@ -94,7 +94,11 @@ export declare class NodeSQLiteAdapter implements DatabaseInstance {
94
94
  private rebuildRegistry069;
95
95
  private rebuildRecordActors070;
96
96
  private applyRepairMigration;
97
- private rebuildWorkflowMemoryKind098;
97
+ /**
98
+ * A migration that drops and recreates a referenced table: foreign keys go off outside the
99
+ * transaction (a PRAGMA inside it does nothing), so the drop cascades to no row.
100
+ */
101
+ private applyWithForeignKeysOff;
98
102
  private operatorMemoryCommitIntentTableSql;
99
103
  private hasOperatorMemoryCommitIntentClaimInvariant;
100
104
  private assertMigration040BaseComplete;
@@ -14,7 +14,6 @@ const fs_1 = __importDefault(require("fs"));
14
14
  const node_sqlite_statement_js_1 = require("./node-sqlite-statement.js");
15
15
  const debug_logger_js_1 = require("../debug-logger.js");
16
16
  const embedder_js_1 = require("../embedding/embedder.js");
17
- const commitment_revision_migration_js_1 = require("../knowledge/commitment-revision-migration.js");
18
17
  const SQLITE_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
19
18
  // Migration 071 origin invariant, kept in ONE place so the DDL the rebuild
20
19
  // writes and the shape verification that admits a table cannot drift. A row is
@@ -322,7 +321,7 @@ class NodeSQLiteAdapter {
322
321
  }
323
322
  // Re-read one decision's effective status into the cache. MUST be called after
324
323
  // any status transition that can move a row OUT of an excluded state (e.g.
325
- // promoteMemoryStatus staging->active): the vectorSearch pre-filter drops
324
+ // a status amendment back to active): the vectorSearch pre-filter drops
326
325
  // excluded rowids before the api post-filter ever sees them, so a stale
327
326
  // excluded entry would make an active row unrecallable until restart.
328
327
  refreshDecisionStatusCache(rowid) {
@@ -675,22 +674,6 @@ class NodeSQLiteAdapter {
675
674
  if (version <= currentVersion) {
676
675
  continue;
677
676
  }
678
- if (isCore && version === 99) {
679
- try {
680
- this.exec('BEGIN TRANSACTION');
681
- (0, commitment_revision_migration_js_1.backfillCommitmentRevisionGraph)(this);
682
- this.exec(fs_1.default.readFileSync(path_1.default.join(migrationsDir, file), 'utf8'));
683
- this.stampMigration(sourceName, version);
684
- this.exec('COMMIT');
685
- (0, debug_logger_js_1.info)(`[node-sqlite-adapter] Migration ${file} applied successfully`);
686
- }
687
- catch (err) {
688
- this.exec('ROLLBACK');
689
- const message = err instanceof Error ? err.message : String(err);
690
- throw new Error(`Migration ${file} failed: ${message}`);
691
- }
692
- continue;
693
- }
694
677
  if (isCore && version === 98) {
695
678
  const decisionColumns = this.tableColumns('decisions');
696
679
  if (!this.tableExists('decisions_fts') ||
@@ -698,11 +681,27 @@ class NodeSQLiteAdapter {
698
681
  (0, debug_logger_js_1.warn)(`[node-sqlite-adapter] Migration ${file} deferred: the full decisions projection is not present`);
699
682
  continue;
700
683
  }
701
- this.rebuildWorkflowMemoryKind098(migrationsDir);
684
+ this.applyWithForeignKeysOff(migrationsDir, file);
702
685
  this.stampMigration(sourceName, version);
703
686
  (0, debug_logger_js_1.info)(`[node-sqlite-adapter] Migration ${file} reconciled successfully`);
704
687
  continue;
705
688
  }
689
+ // 084 rebuilds memory_scopes. Its own PRAGMA foreign_keys = OFF is a no-op inside the
690
+ // migration transaction, so dropping the table cascaded to every memory scope binding
691
+ // (a development memory at schema 80 lost all 414 on the way to 099).
692
+ if (isCore && version === 84) {
693
+ if (!this.tableExists('memory_scopes')) {
694
+ // As the plain path does for an ALTER TABLE on a missing table: a legacy database whose
695
+ // scope tables come later from the structural repair.
696
+ (0, debug_logger_js_1.warn)(`[node-sqlite-adapter] Migration ${file} skipped: memory_scopes does not exist`);
697
+ this.stampMigration(sourceName, version);
698
+ continue;
699
+ }
700
+ this.applyWithForeignKeysOff(migrationsDir, file);
701
+ this.stampMigration(sourceName, version);
702
+ (0, debug_logger_js_1.info)(`[node-sqlite-adapter] Migration ${file} applied successfully`);
703
+ continue;
704
+ }
706
705
  // Runtime MetricsStore may already own project_id/channel_id. Reconcile
707
706
  // this additive migration atomically instead of accepting a duplicate skip.
708
707
  if (isCore && version === 68) {
@@ -1347,15 +1346,19 @@ class NodeSQLiteAdapter {
1347
1346
  throw new Error(`Repair migration ${fileName} failed: ${message}`);
1348
1347
  }
1349
1348
  }
1350
- rebuildWorkflowMemoryKind098(migrationsDir) {
1351
- const migrationPath = path_1.default.join(migrationsDir, '098-workflow-memory-kind.sql');
1349
+ /**
1350
+ * A migration that drops and recreates a referenced table: foreign keys go off outside the
1351
+ * transaction (a PRAGMA inside it does nothing), so the drop cascades to no row.
1352
+ */
1353
+ applyWithForeignKeysOff(migrationsDir, file) {
1354
+ const migrationPath = path_1.default.join(migrationsDir, file);
1352
1355
  if (!fs_1.default.existsSync(migrationPath)) {
1353
- throw new Error('Migration 098 workflow-kind rebuild SQL is missing');
1356
+ throw new Error(`Migration ${file} SQL is missing`);
1354
1357
  }
1355
1358
  const previousForeignKeys = this.readForeignKeysEnabled();
1356
1359
  this.exec('PRAGMA foreign_keys = OFF');
1357
1360
  if (this.readForeignKeysEnabled()) {
1358
- throw new Error('Migration 098 could not disable foreign_keys before rebuilding decisions');
1361
+ throw new Error(`Migration ${file} could not disable foreign_keys before its rebuild`);
1359
1362
  }
1360
1363
  try {
1361
1364
  this.transaction(() => {
@@ -1363,7 +1366,7 @@ class NodeSQLiteAdapter {
1363
1366
  });
1364
1367
  const violations = this.prepare('PRAGMA foreign_key_check').all();
1365
1368
  if (violations.length > 0) {
1366
- throw new Error('Migration 098 left foreign key violations');
1369
+ throw new Error(`Migration ${file} left foreign key violations`);
1367
1370
  }
1368
1371
  }
1369
1372
  finally {
@@ -81,7 +81,6 @@ export interface DecisionRecord {
81
81
  refined_from?: string | string[] | null;
82
82
  created_at: number;
83
83
  updated_at?: number;
84
- edges?: DecisionEdgeRow[];
85
84
  /** ISO 8601 date when the event actually occurred. Null if not set. */
86
85
  event_date?: string | null;
87
86
  /** Source event timestamp in milliseconds when known. Null if not set. */
@@ -94,19 +93,9 @@ export interface OutcomeData {
94
93
  duration_days?: number | null;
95
94
  confidence?: number | null;
96
95
  }
97
- export interface DecisionEdgeRow {
98
- from_id: string;
99
- to_id: string;
100
- relationship: string;
101
- reason?: string | null;
102
- weight?: number;
103
- created_at?: number;
104
- created_by?: string;
105
- approved_by_user?: number | null;
106
- decision_id?: string | null;
107
- evidence?: string | null;
108
- }
109
96
  export interface SemanticEdgeItem {
97
+ /** The link's id when it is a `twin_edges` row; null for a legacy `decision_edges` row. */
98
+ edge_id?: string | null;
110
99
  from_id: string;
111
100
  to_id: string;
112
101
  relationship: string;
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ import mama from './mama-api.js';
14
14
  export { mama };
15
15
  export { createMamaApi, type MamaApi } from './mama-api.js';
16
16
  export { MEMORY_SCOPE_KINDS, MEMORY_KINDS, MEMORY_STATUSES, MEMORY_EDGE_TYPES, createEmptyRecallBundle, type MemoryScopeKind, type MemoryKind, type MemoryKindFilter, type MemoryStatus, type MemoryEdgeType, type MemoryScopeRef, type MemorySourceRef, type MemoryRecord, type MemoryWriteProvenance, type MemoryProvenanceRecord, type PublicSaveMemoryInput, type PublicIngestMemoryInput, type PublicIngestConversationInput, type MemorySearchResultHit, type MemoryEdge, type ProfileSnapshot, type RecallBundle, type ConversationMessage, type IngestConversationInput, type ExtractedMemoryUnit, type IngestConversationResult, type AuditFindingRecord, canonicalizeContextScopes, } from './memory/types.js';
17
- export { saveMemory, saveJudgmentRecord, saveLegacyMemory, promoteMemoryStatus, readMemoryRecordById, readMemoryRecordsInScopes, retireMemoryRecord, recallMemory, buildProfile, ingestMemory, evolveMemory, buildMemoryBootstrap, createAuditAck, recordMemoryAudit, ingestConversation, upsertChannelSummary, getChannelSummary, type MemoryRetirementStatus, type ReadMemoryRecordsOptions, } from './memory/api.js';
17
+ export { saveMemory, saveJudgmentRecord, saveLegacyMemory, readMemoryRecordById, readMemoryRecordsInScopes, retireMemoryRecord, recallMemory, buildProfile, ingestMemory, buildMemoryBootstrap, createAuditAck, recordMemoryAudit, ingestConversation, upsertChannelSummary, getChannelSummary, type MemoryRetirementStatus, type ReadMemoryRecordsOptions, } from './memory/api.js';
18
18
  export { createKnowledge, appendJudgment, ingestSource, JudgmentError, type Knowledge, type KnowledgeOptions, type JudgmentAccess, type SourceIngestCommand, type SourceIngestReceipt, type WorkGraphPage, type WorkGraphQuery, type WorkReference, } from './knowledge/index.js';
19
19
  export { queryRelevantTruth } from './memory/truth-store.js';
20
20
  export { createAuditFinding, listOpenAuditFindings } from './memory/finding-store.js';
@@ -30,7 +30,7 @@ export { MODEL_RUN_STATUSES, type ModelRunStatus, type BeginModelRunInput, type
30
30
  export { beginModelRun, commitModelRun, failModelRun, getModelRun, } from './runtime/model-run-store.js';
31
31
  export { appendToolTrace, appendOperationToolTrace, listToolTracesForRun, listToolTraces, readToolTrace, } from './runtime/tool-trace-store.js';
32
32
  export { TWIN_EDGE_SOURCES, TWIN_EDGE_TYPES, TWIN_REF_KINDS, type InsertTwinEdgeInput, type ListVisibleTwinEdgesOptions, type TwinEdgeInsert, type TwinEdgeRecord, type TwinEdgeSource, type TwinEdgeType, type TwinRef, type TwinRefKind, type TwinScopeRef, } from './knowledge/twin-edge-types.js';
33
- export { getTwinEdge, insertTwinEdge, listTwinEdgesForRefs, mapTwinEdgeRow, } from './knowledge/judgments.js';
33
+ export { getTwinEdge, listTwinEdgesForRefs, mapTwinEdgeRow } from './knowledge/judgments.js';
34
34
  export { listVisibleTwinEdgesForRefs } from './knowledge/access.js';
35
35
  export type { ActionCall, ActionContract, ActionExample, ActionFailure, ActionFailureKind, ActionResult, ActionSchemaObject, ActionSessionFacts, } from './action-contracts.js';
36
36
  export { createCatalog, coreActionRegistrations, UnknownActionError, type ActionCatalog, type ActionContext, type ActionExec, type ActionRegistration, type MemoryReadAllowance, } from './api/catalog.js';
package/dist/index.js CHANGED
@@ -25,9 +25,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
25
25
  return (mod && mod.__esModule) ? mod : { "default": mod };
26
26
  };
27
27
  Object.defineProperty(exports, "__esModule", { value: true });
28
- exports.insertMemoryEventInTransaction = exports.appendMemoryEvent = exports.listOpenAuditFindings = exports.createAuditFinding = exports.queryRelevantTruth = exports.JudgmentError = exports.ingestSource = exports.appendJudgment = exports.createKnowledge = exports.getChannelSummary = exports.upsertChannelSummary = exports.ingestConversation = exports.recordMemoryAudit = exports.createAuditAck = exports.buildMemoryBootstrap = exports.evolveMemory = exports.ingestMemory = exports.buildProfile = exports.recallMemory = exports.retireMemoryRecord = exports.readMemoryRecordsInScopes = exports.readMemoryRecordById = exports.promoteMemoryStatus = exports.saveLegacyMemory = exports.saveJudgmentRecord = exports.saveMemory = exports.canonicalizeContextScopes = exports.createEmptyRecallBundle = exports.MEMORY_EDGE_TYPES = exports.MEMORY_STATUSES = exports.MEMORY_KINDS = exports.MEMORY_SCOPE_KINDS = exports.createMamaApi = exports.mama = exports.SQLiteAdapter = exports.createAdapter = exports.getDbPath = exports.updateDecisionOutcome = exports.closeDB = exports.getAdapter = exports.getDB = exports.initDB = exports.EmbeddingCache = exports.EMBEDDING_PREFIX_SCHEME = exports.MODEL_NAME = exports.EMBEDDING_DIM = exports.embeddingCache = exports.cosineSimilarity = exports.generateEnhancedEmbedding = exports.generateEmbedding = void 0;
29
- exports.newRequestId = exports.IpcTransportError = exports.encodeFrame = exports.createActionIpcServer = exports.startRuntime = exports.createClient = exports.withCallReceipt = exports.validateInput = exports.createDispatcher = exports.UnknownActionError = exports.coreActionRegistrations = exports.createCatalog = exports.listVisibleTwinEdgesForRefs = exports.mapTwinEdgeRow = exports.listTwinEdgesForRefs = exports.insertTwinEdge = exports.getTwinEdge = exports.TWIN_REF_KINDS = exports.TWIN_EDGE_TYPES = exports.TWIN_EDGE_SOURCES = exports.readToolTrace = exports.listToolTraces = exports.listToolTracesForRun = exports.appendOperationToolTrace = exports.appendToolTrace = exports.getModelRun = exports.failModelRun = exports.commitModelRun = exports.beginModelRun = exports.MODEL_RUN_STATUSES = exports.scanMemoryWriteInput = exports.scanForSecrets = exports.sanitizeRecallText = exports.sanitizeRecallBundle = exports.resolveMemoryProvenance = exports.toIndexedEvent = exports.resolveMemoryProvenanceLive = exports.parseSourceRef = exports.isMessageRefVisible = exports.isEventVisibleNow = exports.OBSERVATION_COLUMNS = exports.listMemoryProvenanceAudit = exports.getMemoryProvenanceAudit = exports.listMemoriesByModelRunId = exports.listMemoriesByGatewayCallId = exports.listMemoriesByEnvelopeHash = exports.getMemoryProvenance = exports.normalizeMemoryWriteProvenance = exports.listRecentMemoryEvents = exports.listMemoryEventsForMemory = void 0;
30
- exports.FALLBACK_MODEL = exports.DEFAULT_MODEL = exports.listModels = exports.isAvailable = exports.generate = exports.extractCodeExample = exports.extractQuickAnswer = exports.estimateTokens = exports.ensureTokenBudget = exports.formatTrustContext = exports.formatList = exports.formatRecall = exports.formatLegacyContext = exports.formatContext = exports.isMAMAError = exports.wrapError = exports.ErrorCodes = exports.TimeoutError = exports.RateLimitError = exports.LinkError = exports.ConfigurationError = exports.EmbeddingError = exports.DatabaseError = exports.ValidationError = exports.NotFoundError = exports.MAMAError = exports.DebugLogger = exports.error = exports.warn = exports.info = exports.debug = exports.logSearching = exports.logLoading = exports.logError = exports.logFailed = exports.logComplete = exports.logProgress = exports.formatTopNContext = exports.selectTopDecisions = exports.calculateRelevance = exports.CanonicalizeError = exports.targetRefHash = exports.canonicalizeJSON = exports.IPC_MAX_FRAME_BYTES = exports.sendIpcRequest = void 0;
28
+ exports.listRecentMemoryEvents = exports.listMemoryEventsForMemory = exports.insertMemoryEventInTransaction = exports.appendMemoryEvent = exports.listOpenAuditFindings = exports.createAuditFinding = exports.queryRelevantTruth = exports.JudgmentError = exports.ingestSource = exports.appendJudgment = exports.createKnowledge = exports.getChannelSummary = exports.upsertChannelSummary = exports.ingestConversation = exports.recordMemoryAudit = exports.createAuditAck = exports.buildMemoryBootstrap = exports.ingestMemory = exports.buildProfile = exports.recallMemory = exports.retireMemoryRecord = exports.readMemoryRecordsInScopes = exports.readMemoryRecordById = exports.saveLegacyMemory = exports.saveJudgmentRecord = exports.saveMemory = exports.canonicalizeContextScopes = exports.createEmptyRecallBundle = exports.MEMORY_EDGE_TYPES = exports.MEMORY_STATUSES = exports.MEMORY_KINDS = exports.MEMORY_SCOPE_KINDS = exports.createMamaApi = exports.mama = exports.SQLiteAdapter = exports.createAdapter = exports.getDbPath = exports.updateDecisionOutcome = exports.closeDB = exports.getAdapter = exports.getDB = exports.initDB = exports.EmbeddingCache = exports.EMBEDDING_PREFIX_SCHEME = exports.MODEL_NAME = exports.EMBEDDING_DIM = exports.embeddingCache = exports.cosineSimilarity = exports.generateEnhancedEmbedding = exports.generateEmbedding = void 0;
29
+ exports.canonicalizeJSON = exports.IPC_MAX_FRAME_BYTES = exports.sendIpcRequest = exports.newRequestId = exports.IpcTransportError = exports.encodeFrame = exports.createActionIpcServer = exports.startRuntime = exports.createClient = exports.withCallReceipt = exports.validateInput = exports.createDispatcher = exports.UnknownActionError = exports.coreActionRegistrations = exports.createCatalog = exports.listVisibleTwinEdgesForRefs = exports.mapTwinEdgeRow = exports.listTwinEdgesForRefs = exports.getTwinEdge = exports.TWIN_REF_KINDS = exports.TWIN_EDGE_TYPES = exports.TWIN_EDGE_SOURCES = exports.readToolTrace = exports.listToolTraces = exports.listToolTracesForRun = exports.appendOperationToolTrace = exports.appendToolTrace = exports.getModelRun = exports.failModelRun = exports.commitModelRun = exports.beginModelRun = exports.MODEL_RUN_STATUSES = exports.scanMemoryWriteInput = exports.scanForSecrets = exports.sanitizeRecallText = exports.sanitizeRecallBundle = exports.resolveMemoryProvenance = exports.toIndexedEvent = exports.resolveMemoryProvenanceLive = exports.parseSourceRef = exports.isMessageRefVisible = exports.isEventVisibleNow = exports.OBSERVATION_COLUMNS = exports.listMemoryProvenanceAudit = exports.getMemoryProvenanceAudit = exports.listMemoriesByModelRunId = exports.listMemoriesByGatewayCallId = exports.listMemoriesByEnvelopeHash = exports.getMemoryProvenance = exports.normalizeMemoryWriteProvenance = void 0;
30
+ exports.FALLBACK_MODEL = exports.DEFAULT_MODEL = exports.listModels = exports.isAvailable = exports.generate = exports.extractCodeExample = exports.extractQuickAnswer = exports.estimateTokens = exports.ensureTokenBudget = exports.formatTrustContext = exports.formatList = exports.formatRecall = exports.formatLegacyContext = exports.formatContext = exports.isMAMAError = exports.wrapError = exports.ErrorCodes = exports.TimeoutError = exports.RateLimitError = exports.LinkError = exports.ConfigurationError = exports.EmbeddingError = exports.DatabaseError = exports.ValidationError = exports.NotFoundError = exports.MAMAError = exports.DebugLogger = exports.error = exports.warn = exports.info = exports.debug = exports.logSearching = exports.logLoading = exports.logError = exports.logFailed = exports.logComplete = exports.logProgress = exports.formatTopNContext = exports.selectTopDecisions = exports.calculateRelevance = exports.CanonicalizeError = exports.targetRefHash = void 0;
31
31
  var embedder_js_1 = require("./embedding/embedder.js");
32
32
  Object.defineProperty(exports, "generateEmbedding", { enumerable: true, get: function () { return embedder_js_1.generateEmbedding; } });
33
33
  Object.defineProperty(exports, "generateEnhancedEmbedding", { enumerable: true, get: function () { return embedder_js_1.generateEnhancedEmbedding; } });
@@ -63,14 +63,12 @@ var api_js_1 = require("./memory/api.js");
63
63
  Object.defineProperty(exports, "saveMemory", { enumerable: true, get: function () { return api_js_1.saveMemory; } });
64
64
  Object.defineProperty(exports, "saveJudgmentRecord", { enumerable: true, get: function () { return api_js_1.saveJudgmentRecord; } });
65
65
  Object.defineProperty(exports, "saveLegacyMemory", { enumerable: true, get: function () { return api_js_1.saveLegacyMemory; } });
66
- Object.defineProperty(exports, "promoteMemoryStatus", { enumerable: true, get: function () { return api_js_1.promoteMemoryStatus; } });
67
66
  Object.defineProperty(exports, "readMemoryRecordById", { enumerable: true, get: function () { return api_js_1.readMemoryRecordById; } });
68
67
  Object.defineProperty(exports, "readMemoryRecordsInScopes", { enumerable: true, get: function () { return api_js_1.readMemoryRecordsInScopes; } });
69
68
  Object.defineProperty(exports, "retireMemoryRecord", { enumerable: true, get: function () { return api_js_1.retireMemoryRecord; } });
70
69
  Object.defineProperty(exports, "recallMemory", { enumerable: true, get: function () { return api_js_1.recallMemory; } });
71
70
  Object.defineProperty(exports, "buildProfile", { enumerable: true, get: function () { return api_js_1.buildProfile; } });
72
71
  Object.defineProperty(exports, "ingestMemory", { enumerable: true, get: function () { return api_js_1.ingestMemory; } });
73
- Object.defineProperty(exports, "evolveMemory", { enumerable: true, get: function () { return api_js_1.evolveMemory; } });
74
72
  Object.defineProperty(exports, "buildMemoryBootstrap", { enumerable: true, get: function () { return api_js_1.buildMemoryBootstrap; } });
75
73
  Object.defineProperty(exports, "createAuditAck", { enumerable: true, get: function () { return api_js_1.createAuditAck; } });
76
74
  Object.defineProperty(exports, "recordMemoryAudit", { enumerable: true, get: function () { return api_js_1.recordMemoryAudit; } });
@@ -136,7 +134,6 @@ Object.defineProperty(exports, "TWIN_EDGE_TYPES", { enumerable: true, get: funct
136
134
  Object.defineProperty(exports, "TWIN_REF_KINDS", { enumerable: true, get: function () { return twin_edge_types_js_1.TWIN_REF_KINDS; } });
137
135
  var judgments_js_1 = require("./knowledge/judgments.js");
138
136
  Object.defineProperty(exports, "getTwinEdge", { enumerable: true, get: function () { return judgments_js_1.getTwinEdge; } });
139
- Object.defineProperty(exports, "insertTwinEdge", { enumerable: true, get: function () { return judgments_js_1.insertTwinEdge; } });
140
137
  Object.defineProperty(exports, "listTwinEdgesForRefs", { enumerable: true, get: function () { return judgments_js_1.listTwinEdgesForRefs; } });
141
138
  Object.defineProperty(exports, "mapTwinEdgeRow", { enumerable: true, get: function () { return judgments_js_1.mapTwinEdgeRow; } });
142
139
  var access_js_1 = require("./knowledge/access.js");
@@ -36,6 +36,8 @@ export interface CommitmentChainEntry {
36
36
  revision: number;
37
37
  operation: CommitmentRevision['operation'];
38
38
  eventDatetime: number | null;
39
+ /** When the revision was written; the day of a revision with no event time. */
40
+ createdAt: number;
39
41
  status: string | null;
40
42
  stage: string | null;
41
43
  summary: string | null;
@@ -106,6 +106,7 @@ function buildChain(adapter, revisions, admitted) {
106
106
  revision: revision.revision,
107
107
  operation: revision.operation,
108
108
  eventDatetime: revision.eventDatetime,
109
+ createdAt: revision.createdAt,
109
110
  status: revision.operation === 'withdraw' ? 'cancelled' : stringField(values, 'status'),
110
111
  stage: stringField(values, 'stage'),
111
112
  summary: readJudgmentSummary(adapter, revision.recordRef.id, admitted),
@@ -13,13 +13,22 @@ import type { DatabaseAdapter, DecisionRecord, SemanticEdges } from '../db-manag
13
13
  import { type JudgmentAccess } from './judgments.js';
14
14
  import type { WorkGraphPage, WorkGraphQuery } from '../memory/judgment-types.js';
15
15
  import type { TwinEdgeRecord, TwinEdgeType, TwinProjectRef, TwinRef, TwinScopeRef } from '../knowledge/twin-edge-types.js';
16
+ /** The reasons the host's evolution rules gave the legacy `decision_edges` rows they wrote. */
17
+ export declare const HOST_EDGE_REASON_PREFIXES: readonly ["Semantically similar", "Updated fact", "Related but distinct"];
18
+ /**
19
+ * The edges an agent stated between decisions, in the `decision_edges` columns plus `edge_id`:
20
+ * links in `twin_edges` between two memories that the host did not write, and the legacy
21
+ * `decision_edges` rows whose reason is not one the host gave. Search expansion follows these and
22
+ * no host edge (owner, 2026-09-30): a similarity is no ground that two records are the same case.
23
+ */
24
+ export declare const STATED_DECISION_EDGES: string;
16
25
  /**
17
26
  * Walk a topic's supersedes chain with a recursive CTE.
18
27
  *
19
28
  * @param adapter - Database to read through
20
29
  * @param topic - Decision topic to query
21
30
  * @param anchorId - Start from this decision instead of the topic's current head
22
- * @returns Decisions ordered by recency, each carrying its approved edges
31
+ * @returns Decisions ordered by recency
23
32
  */
24
33
  export declare function queryDecisionGraph(adapter: DatabaseAdapter, topic: string, anchorId?: string): Promise<DecisionRecord[]>;
25
34
  /**
@@ -11,7 +11,7 @@
11
11
  * @module knowledge/graph-query
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.AgentGraphValidationError = void 0;
14
+ exports.AgentGraphValidationError = exports.STATED_DECISION_EDGES = exports.HOST_EDGE_REASON_PREFIXES = void 0;
15
15
  exports.queryDecisionGraph = queryDecisionGraph;
16
16
  exports.querySemanticEdges = querySemanticEdges;
17
17
  exports.getGraphNeighborhood = getGraphNeighborhood;
@@ -44,13 +44,38 @@ function parseRefinedFrom(decision) {
44
44
  throw new Error(`decision ${decision.id} has unreadable refined_from: ${message}`);
45
45
  }
46
46
  }
47
+ /** The reasons the host's evolution rules gave the legacy `decision_edges` rows they wrote. */
48
+ exports.HOST_EDGE_REASON_PREFIXES = [
49
+ 'Semantically similar',
50
+ 'Updated fact',
51
+ 'Related but distinct',
52
+ ];
53
+ /**
54
+ * The edges an agent stated between decisions, in the `decision_edges` columns plus `edge_id`:
55
+ * links in `twin_edges` between two memories that the host did not write, and the legacy
56
+ * `decision_edges` rows whose reason is not one the host gave. Search expansion follows these and
57
+ * no host edge (owner, 2026-09-30): a similarity is no ground that two records are the same case.
58
+ */
59
+ exports.STATED_DECISION_EDGES = `(
60
+ SELECT NULL AS edge_id, from_id, to_id, relationship, reason, weight, created_at, created_by,
61
+ approved_by_user, decision_id, evidence
62
+ FROM decision_edges
63
+ WHERE ${exports.HOST_EDGE_REASON_PREFIXES.map((prefix) => `COALESCE(reason, '') NOT LIKE '${prefix}%'`).join(' AND ')}
64
+ UNION ALL
65
+ SELECT edge_id, subject_id, object_id, edge_type,
66
+ COALESCE(json_extract(relation_attrs_json, '$.reason'), reason_text), confidence,
67
+ created_at, source, NULL, NULL, NULL
68
+ FROM twin_edges
69
+ WHERE subject_kind = 'memory' AND object_kind = 'memory' AND source <> 'code'
70
+ AND edge_type <> 'derived_from'
71
+ )`;
47
72
  /**
48
73
  * Walk a topic's supersedes chain with a recursive CTE.
49
74
  *
50
75
  * @param adapter - Database to read through
51
76
  * @param topic - Decision topic to query
52
77
  * @param anchorId - Start from this decision instead of the topic's current head
53
- * @returns Decisions ordered by recency, each carrying its approved edges
78
+ * @returns Decisions ordered by recency
54
79
  */
55
80
  async function queryDecisionGraph(adapter, topic, anchorId) {
56
81
  try {
@@ -66,13 +91,7 @@ async function queryDecisionGraph(adapter, topic, anchorId) {
66
91
  SELECT * FROM decision_chain ORDER BY created_at DESC, id DESC
67
92
  `)
68
93
  .all(topic);
69
- const edgesStmt = adapter.prepare(`
70
- SELECT * FROM decision_edges
71
- WHERE from_id = ?
72
- AND (approved_by_user = 1 OR approved_by_user IS NULL)
73
- `);
74
94
  for (const decision of decisions) {
75
- decision.edges = edgesStmt.all(decision.id);
76
95
  decision.refined_from = parseRefinedFrom(decision);
77
96
  }
78
97
  return decisions;
@@ -96,15 +115,7 @@ async function queryDecisionGraph(adapter, topic, anchorId) {
96
115
  ORDER BY created_at DESC
97
116
  `);
98
117
  const decisions = stmt.all(anchorId);
99
- // Join with decision_edges to include relationships
100
- // Prepare statement once outside loop for performance
101
- const edgesStmt = adapter.prepare(`
102
- SELECT * FROM decision_edges
103
- WHERE from_id = ?
104
- AND (approved_by_user = 1 OR approved_by_user IS NULL)
105
- `);
106
118
  for (const decision of decisions) {
107
- decision.edges = edgesStmt.all(decision.id);
108
119
  decision.refined_from = parseRefinedFrom(decision);
109
120
  }
110
121
  return decisions;
@@ -146,7 +157,7 @@ async function querySemanticEdges(adapter, decisionIds) {
146
157
  // Query outgoing edges (from_id = decision)
147
158
  const outgoingStmt = adapter.prepare(`
148
159
  SELECT e.*, d.topic, d.decision, d.confidence, d.created_at
149
- FROM decision_edges e
160
+ FROM ${exports.STATED_DECISION_EDGES} e
150
161
  JOIN decisions d ON e.to_id = d.id
151
162
  WHERE e.from_id IN (${placeholders})
152
163
  AND e.relationship IN (${edgeTypePlaceholders})
@@ -157,7 +168,7 @@ async function querySemanticEdges(adapter, decisionIds) {
157
168
  // Query incoming edges (to_id = decision)
158
169
  const incomingStmt = adapter.prepare(`
159
170
  SELECT e.*, d.topic, d.decision, d.confidence, d.created_at
160
- FROM decision_edges e
171
+ FROM ${exports.STATED_DECISION_EDGES} e
161
172
  JOIN decisions d ON e.from_id = d.id
162
173
  WHERE e.to_id IN (${placeholders})
163
174
  AND e.relationship IN (${edgeTypePlaceholders})
@@ -547,6 +558,29 @@ function loadTimelineRecordEvent(adapter, ref) {
547
558
  }
548
559
  return null;
549
560
  }
561
+ /**
562
+ * Edges as the reader may see them. Both ends being visible does not make a link's evidence
563
+ * visible, so evidence refs the reader cannot see are left out. Entries without a kind are the
564
+ * link entries ({id, reason}) an earlier version copied into a record's own edges; they repeat the
565
+ * edge's target and reason.
566
+ */
567
+ function withVisibleEvidence(adapter, edges, visibility) {
568
+ const refsOf = (edge) => Array.isArray(edge.evidence_refs)
569
+ ? edge.evidence_refs.filter((ref) => typeof ref?.id === 'string' &&
570
+ twin_edge_types_js_1.TWIN_REF_KINDS.includes(ref.kind))
571
+ : [];
572
+ const visible = (0, access_js_1.visibleTwinRefKeysRecursive)(adapter, edges.flatMap(refsOf), visibility);
573
+ return edges.map((edge) => {
574
+ if (!Array.isArray(edge.evidence_refs))
575
+ return edge;
576
+ const evidence = refsOf(edge).filter((ref) => visible.has(`${ref.kind}\0${ref.id}`));
577
+ return {
578
+ ...edge,
579
+ evidence_refs: evidence.length > 0 ? evidence : null,
580
+ evidence_refs_json: evidence.length > 0 ? JSON.stringify(evidence) : null,
581
+ };
582
+ });
583
+ }
550
584
  function getGraphNeighborhood(adapter, input) {
551
585
  const depth = normalizeDepth(input.depth, 1);
552
586
  const limit = normalizeLimit(input.limit, DEFAULT_GRAPH_LIMIT);
@@ -598,7 +632,11 @@ function getGraphNeighborhood(adapter, input) {
598
632
  }
599
633
  frontier = nextFrontier;
600
634
  }
601
- return { nodes, edges, current_projection: projectCurrentEdges(adapter, edges, visibility) };
635
+ return {
636
+ nodes,
637
+ edges: withVisibleEvidence(adapter, edges, { ...visibility, asOfMs: input.as_of_ms }),
638
+ current_projection: projectCurrentEdges(adapter, edges, visibility),
639
+ };
602
640
  }
603
641
  function getGraphPaths(adapter, input) {
604
642
  const maxDepth = normalizeDepth(input.max_depth, 3);
@@ -665,8 +703,12 @@ function getGraphPaths(adapter, input) {
665
703
  const pathEdges = [
666
704
  ...new Map(paths.flatMap((path) => path.edges).map((edge) => [edge.edge_id, edge])).values(),
667
705
  ];
706
+ const shown = new Map(withVisibleEvidence(adapter, pathEdges, visibility).map((edge) => [edge.edge_id, edge]));
668
707
  return {
669
- paths,
708
+ paths: paths.map((path) => ({
709
+ ...path,
710
+ edges: path.edges.map((edge) => shown.get(edge.edge_id)),
711
+ })),
670
712
  limit_reached: limitReached || paths.length >= limit,
671
713
  current_projection: projectCurrentEdges(adapter, pathEdges, visibility),
672
714
  };
@@ -698,11 +740,11 @@ function getGraphTimeline(adapter, input) {
698
740
  const limit = normalizeLimit(input.limit, DEFAULT_GRAPH_LIMIT);
699
741
  const window = timelineWindow(input);
700
742
  const seeds = timelineSeeds(adapter, input.ref, visibility);
701
- const edges = listFilteredEdges(adapter, seeds, {
743
+ const edges = withVisibleEvidence(adapter, listFilteredEdges(adapter, seeds, {
702
744
  visibility,
703
745
  edge_filters: input.edge_filters,
704
746
  as_of_ms: input.as_of_ms,
705
- }).filter((edge) => isInTimelineWindow(edge.created_at, window));
747
+ }).filter((edge) => isInTimelineWindow(edge.created_at, window)), { ...visibility, asOfMs: input.as_of_ms });
706
748
  const refs = [];
707
749
  const seenRefs = new Set();
708
750
  for (const seed of seeds) {
@@ -765,6 +807,50 @@ function graphAccessInput(access) {
765
807
  max_source_ms: access.maxSourceMs,
766
808
  };
767
809
  }
810
+ /**
811
+ * Later links that contradict an edge, each with its reason, and the links that contradict those:
812
+ * the edge row is never edited, so a correction is read from the edges that point at it.
813
+ */
814
+ function edgeCorrections(adapter, edgeIds, visibility, asOfMs) {
815
+ const byTarget = new Map();
816
+ const seen = new Set(edgeIds);
817
+ let frontier = [...edgeIds];
818
+ while (frontier.length > 0) {
819
+ const placeholders = frontier.map(() => '?').join(', ');
820
+ const rows = adapter
821
+ .prepare(`SELECT edge_id, subject_kind, subject_id, object_id, reason_text, created_at
822
+ FROM twin_edges
823
+ WHERE object_kind = 'edge' AND edge_type = 'contradicts'
824
+ AND object_id IN (${placeholders}) AND created_at <= ?
825
+ ORDER BY created_at, edge_id`)
826
+ .all(...frontier, asOfMs);
827
+ const subjects = rows.map((row) => ({ kind: row.subject_kind, id: row.subject_id }));
828
+ const visible = (0, access_js_1.visibleTwinRefKeysRecursive)(adapter, subjects, visibility);
829
+ frontier = [];
830
+ for (const row of rows) {
831
+ if (!visible.has(`${row.subject_kind}\0${row.subject_id}`))
832
+ continue;
833
+ const list = byTarget.get(row.object_id) ?? [];
834
+ list.push({ edgeId: row.edge_id, reason: row.reason_text, at: row.created_at });
835
+ byTarget.set(row.object_id, list);
836
+ if (!seen.has(row.edge_id)) {
837
+ seen.add(row.edge_id);
838
+ frontier.push(row.edge_id);
839
+ }
840
+ }
841
+ }
842
+ const nest = (edgeId) => byTarget.get(edgeId)?.map((correction) => {
843
+ const further = nest(correction.edgeId);
844
+ return further ? { ...correction, correctedBy: further } : correction;
845
+ });
846
+ const corrections = new Map();
847
+ for (const edgeId of edgeIds) {
848
+ const list = nest(edgeId);
849
+ if (list)
850
+ corrections.set(edgeId, list);
851
+ }
852
+ return corrections;
853
+ }
768
854
  /**
769
855
  * Name-free entry: registry alias, decisions FTS, observation metadata. Search
770
856
  * seeds are candidates, not citations — refs the caller cannot see are dropped
@@ -1127,8 +1213,9 @@ function hydrateNode(adapter, ref, ctx, access) {
1127
1213
  }
1128
1214
  return hydrateRecordNode(adapter, ref);
1129
1215
  }
1130
- function pageEdgeAttrs(edge) {
1216
+ function pageEdgeAttrs(edge, correctedBy) {
1131
1217
  return {
1218
+ ...(correctedBy?.length ? { corrected_by: correctedBy } : {}),
1132
1219
  edge_type: edge.edge_type,
1133
1220
  relation_attrs: (edge.relation_attrs ?? null),
1134
1221
  confidence: edge.confidence,
@@ -1137,6 +1224,7 @@ function pageEdgeAttrs(edge) {
1137
1224
  model_run_id: edge.model_run_id,
1138
1225
  reason_classification: edge.reason_classification,
1139
1226
  reason_text: edge.reason_text,
1227
+ evidence_refs: (edge.evidence_refs ?? null),
1140
1228
  created_at: edge.created_at,
1141
1229
  content_hash: edge.content_hash.toString('hex'),
1142
1230
  };
@@ -1556,7 +1644,12 @@ function queryGraph(adapter, query, access) {
1556
1644
  nodes.sort((left, right) => (nodeOrder.get(`${left.ref.kind}:${left.ref.id}`) ?? Number.MAX_SAFE_INTEGER) -
1557
1645
  (nodeOrder.get(`${right.ref.kind}:${right.ref.id}`) ?? Number.MAX_SAFE_INTEGER));
1558
1646
  }
1559
- const pageEdges = collectedEdges.map((edge) => {
1647
+ const corrections = edgeCorrections(adapter, collectedEdges.map((edge) => edge.edge_id), ctx.visibility, asOf);
1648
+ const shownEdges = withVisibleEvidence(adapter, collectedEdges, {
1649
+ ...ctx.visibility,
1650
+ asOfMs: asOf,
1651
+ });
1652
+ const pageEdges = shownEdges.map((edge) => {
1560
1653
  const fromProjection = projections.get(projectionKey(edge.edge_id, 'from'));
1561
1654
  const toProjection = projections.get(projectionKey(edge.edge_id, 'to'));
1562
1655
  return {
@@ -1566,7 +1659,7 @@ function queryGraph(adapter, query, access) {
1566
1659
  to: edge.object_ref,
1567
1660
  resolvedFrom: fromProjection?.current_ref ?? edge.subject_ref,
1568
1661
  resolvedTo: toProjection?.current_ref ?? edge.object_ref,
1569
- attrs: pageEdgeAttrs(edge),
1662
+ attrs: pageEdgeAttrs(edge, corrections.get(edge.edge_id)),
1570
1663
  };
1571
1664
  });
1572
1665
  return {