@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
@@ -1,3 +1,4 @@
1
+ import { type LinkCommand, type LinkReceipt, type StoredLink } from './links.js';
1
2
  import type { DatabaseInstance } from '../db-manager.js';
2
3
  import { type JudgmentAccess, type JudgmentKnowledgeOptions } from './judgments.js';
3
4
  import type { CommitmentPage, CreateWorkCommand, ReviseWorkCommand, WithdrawWorkCommand, WorkRead, WorkWriteResult } from './commitments.js';
@@ -7,7 +8,8 @@ export type { JudgmentAccess } from './judgments.js';
7
8
  export { judgmentRecordId } from './judgments.js';
8
9
  export type { IdentityCorrection, IdentityCorrectionReceipt, JudgmentAmendment, JudgmentCommand, JudgmentEventMeta, JudgmentProjections, JudgmentReceipt, JudgmentRecordFields, JsonValue, OwnerWorkPatch, RecordLink, WorkGraphPage, WorkGraphQuery, WorkReclassifyDisposition, WorkReference, } from '../memory/judgment-types.js';
9
10
  export { WORK_RECLASSIFY_DISPOSITIONS } from '../memory/judgment-types.js';
10
- export { JudgmentError, appendJudgment, insertTwinEdge, getTwinEdge, listTwinEdgesForRefs, mapTwinEdgeRow, } from './judgments.js';
11
+ export { JudgmentError, appendJudgment, getTwinEdge, listTwinEdgesForRefs, mapTwinEdgeRow, } from './judgments.js';
12
+ export { appendLink, findLink, linkEdgeId, type LinkCommand, type LinkReceipt, type StoredLink, } from './links.js';
11
13
  export { assertTwinRefsVisible, channelGrantClause, isChannelGranted, listVisibleTwinEdgesForRefs, visibleTwinRefKeys, visibleTwinRefKeysRecursive, TwinRefNotVisibleError, } from './access.js';
12
14
  export type { ChannelGrant, ChannelGrantClause } from './access.js';
13
15
  export { TWIN_EDGE_SOURCES, TWIN_EDGE_TYPES, TWIN_REF_KINDS, type InsertTwinEdgeInput, type ListVisibleTwinEdgesOptions, type TwinEdgeInsert, type TwinEdgeRecord, type TwinEdgeSource, type TwinEdgeSubjectRef, type TwinEdgeType, type TwinProjectRef, type TwinRef, type TwinRefKind, type TwinScopeRef, type TwinVisibility, } from './twin-edge-types.js';
@@ -18,9 +20,8 @@ export { createWork, readWork, reviseWork, withdrawWork } from './commitments.js
18
20
  export { parseExactDueAt, assertIsoDate, assertWorkPatchValues, WORK_PATCH_DEADLINE_PATTERN, WORK_PATCH_DUE_AT_PATTERN, type ParsedExactDueAt, } from './work-dates.js';
19
21
  export type { CommitmentPage, CommitmentChainEntry, CommitmentRevision, CommitmentView, CreateWorkCommand, ReviseWorkCommand, WithdrawWorkCommand, WorkRead, WorkWriteResult, } from './commitments.js';
20
22
  export { vectorSearch, fts5Search } from './search.js';
21
- export { queryDecisionGraph, querySemanticEdges, getGraphNeighborhood, getGraphPaths, getGraphTimeline, queryGraph, AgentGraphValidationError, type AgentGraphAdapter, type AgentGraphEdgeFilters, type AgentGraphResult, type AgentGraphCurrentProjection, type AgentGraphPath, type AgentGraphTimelineEvent, type AgentGraphTimelineMemoryEvent, type AgentGraphTimelineCaseEvent, type AgentGraphTimelineRawEvent, type AgentGraphTimelineEdgeEvent, type GraphNeighborhoodInput, type GraphPathsInput, type GraphPathsResult, type GraphTimelineInput, type GraphTimelineResult, } from './graph-query.js';
23
+ export { queryDecisionGraph, querySemanticEdges, STATED_DECISION_EDGES, getGraphNeighborhood, getGraphPaths, getGraphTimeline, queryGraph, AgentGraphValidationError, type AgentGraphAdapter, type AgentGraphEdgeFilters, type AgentGraphResult, type AgentGraphCurrentProjection, type AgentGraphPath, type AgentGraphTimelineEvent, type AgentGraphTimelineMemoryEvent, type AgentGraphTimelineCaseEvent, type AgentGraphTimelineRawEvent, type AgentGraphTimelineEdgeEvent, type GraphNeighborhoodInput, type GraphPathsInput, type GraphPathsResult, type GraphTimelineInput, type GraphTimelineResult, } from './graph-query.js';
22
24
  export type { SourceIngestCommand, SourceIngestReceipt } from './source-ingest.js';
23
- export { upsertDecisionEdge, proposeDecisionEdge, approveDecisionEdge, rejectDecisionEdge, deprecateAutoDecisionEdges, deleteDecisionEdgesWithAudit, type DecisionEdgeKey, type DecisionEdgeRow, type DecisionEdgeDeleteFailure, } from './decision-edges.js';
24
25
  export interface KnowledgeOptions extends JudgmentKnowledgeOptions {
25
26
  adapter: DatabaseInstance;
26
27
  }
@@ -42,6 +43,13 @@ export interface Knowledge {
42
43
  * paths, timelines, and hydrated details — all under the caller's authority.
43
44
  */
44
45
  queryGraph(query: WorkGraphQuery, access: JudgmentAccess): WorkGraphPage;
46
+ /**
47
+ * Append one edge between existing records, with its reason; nothing is edited. A link to an
48
+ * edge contradicts it.
49
+ */
50
+ appendLink(command: LinkCommand, access: JudgmentAccess): LinkReceipt;
51
+ /** The link this principal already wrote under a command id, if any. */
52
+ findLink(commandId: string, access: JudgmentAccess): StoredLink | null;
45
53
  }
46
54
  export declare function createKnowledge(options: KnowledgeOptions): Knowledge;
47
55
  //# sourceMappingURL=index.d.ts.map
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deprecateAutoDecisionEdges = exports.rejectDecisionEdge = exports.approveDecisionEdge = exports.proposeDecisionEdge = exports.upsertDecisionEdge = exports.AgentGraphValidationError = exports.queryGraph = exports.getGraphTimeline = exports.getGraphPaths = exports.getGraphNeighborhood = exports.querySemanticEdges = exports.queryDecisionGraph = exports.fts5Search = exports.vectorSearch = exports.WORK_PATCH_DUE_AT_PATTERN = exports.WORK_PATCH_DEADLINE_PATTERN = exports.assertWorkPatchValues = exports.assertIsoDate = exports.parseExactDueAt = exports.withdrawWork = exports.reviseWork = exports.readWork = exports.createWork = exports.isObservationVisibilityRowVisible = exports.isObservationVersionVisible = exports.readObservationVersion = exports.searchOwnerObservationVersions = exports.getObservationVersion = exports.appendObservationVersion = exports.observationVersionId = exports.correctIdentity = exports.ingestSource = exports.TWIN_REF_KINDS = exports.TWIN_EDGE_TYPES = exports.TWIN_EDGE_SOURCES = exports.TwinRefNotVisibleError = exports.visibleTwinRefKeysRecursive = exports.visibleTwinRefKeys = exports.listVisibleTwinEdgesForRefs = exports.isChannelGranted = exports.channelGrantClause = exports.assertTwinRefsVisible = exports.mapTwinEdgeRow = exports.listTwinEdgesForRefs = exports.getTwinEdge = exports.insertTwinEdge = exports.appendJudgment = exports.JudgmentError = exports.WORK_RECLASSIFY_DISPOSITIONS = exports.judgmentRecordId = void 0;
4
- exports.deleteDecisionEdgesWithAudit = void 0;
3
+ exports.AgentGraphValidationError = exports.queryGraph = exports.getGraphTimeline = exports.getGraphPaths = exports.getGraphNeighborhood = exports.STATED_DECISION_EDGES = exports.querySemanticEdges = exports.queryDecisionGraph = exports.fts5Search = exports.vectorSearch = exports.WORK_PATCH_DUE_AT_PATTERN = exports.WORK_PATCH_DEADLINE_PATTERN = exports.assertWorkPatchValues = exports.assertIsoDate = exports.parseExactDueAt = exports.withdrawWork = exports.reviseWork = exports.readWork = exports.createWork = exports.isObservationVisibilityRowVisible = exports.isObservationVersionVisible = exports.readObservationVersion = exports.searchOwnerObservationVersions = exports.getObservationVersion = exports.appendObservationVersion = exports.observationVersionId = exports.correctIdentity = exports.ingestSource = exports.TWIN_REF_KINDS = exports.TWIN_EDGE_TYPES = exports.TWIN_EDGE_SOURCES = exports.TwinRefNotVisibleError = exports.visibleTwinRefKeysRecursive = exports.visibleTwinRefKeys = exports.listVisibleTwinEdgesForRefs = exports.isChannelGranted = exports.channelGrantClause = exports.assertTwinRefsVisible = exports.linkEdgeId = exports.findLink = exports.appendLink = exports.mapTwinEdgeRow = exports.listTwinEdgesForRefs = exports.getTwinEdge = exports.appendJudgment = exports.JudgmentError = exports.WORK_RECLASSIFY_DISPOSITIONS = exports.judgmentRecordId = void 0;
5
4
  exports.createKnowledge = createKnowledge;
5
+ const links_js_1 = require("./links.js");
6
6
  const judgments_js_1 = require("./judgments.js");
7
7
  const source_ingest_js_1 = require("./source-ingest.js");
8
8
  const commitments_js_1 = require("./commitments.js");
@@ -15,10 +15,13 @@ Object.defineProperty(exports, "WORK_RECLASSIFY_DISPOSITIONS", { enumerable: tru
15
15
  var judgments_js_3 = require("./judgments.js");
16
16
  Object.defineProperty(exports, "JudgmentError", { enumerable: true, get: function () { return judgments_js_3.JudgmentError; } });
17
17
  Object.defineProperty(exports, "appendJudgment", { enumerable: true, get: function () { return judgments_js_3.appendJudgment; } });
18
- Object.defineProperty(exports, "insertTwinEdge", { enumerable: true, get: function () { return judgments_js_3.insertTwinEdge; } });
19
18
  Object.defineProperty(exports, "getTwinEdge", { enumerable: true, get: function () { return judgments_js_3.getTwinEdge; } });
20
19
  Object.defineProperty(exports, "listTwinEdgesForRefs", { enumerable: true, get: function () { return judgments_js_3.listTwinEdgesForRefs; } });
21
20
  Object.defineProperty(exports, "mapTwinEdgeRow", { enumerable: true, get: function () { return judgments_js_3.mapTwinEdgeRow; } });
21
+ var links_js_2 = require("./links.js");
22
+ Object.defineProperty(exports, "appendLink", { enumerable: true, get: function () { return links_js_2.appendLink; } });
23
+ Object.defineProperty(exports, "findLink", { enumerable: true, get: function () { return links_js_2.findLink; } });
24
+ Object.defineProperty(exports, "linkEdgeId", { enumerable: true, get: function () { return links_js_2.linkEdgeId; } });
22
25
  var access_js_1 = require("./access.js");
23
26
  Object.defineProperty(exports, "assertTwinRefsVisible", { enumerable: true, get: function () { return access_js_1.assertTwinRefsVisible; } });
24
27
  Object.defineProperty(exports, "channelGrantClause", { enumerable: true, get: function () { return access_js_1.channelGrantClause; } });
@@ -62,18 +65,12 @@ Object.defineProperty(exports, "fts5Search", { enumerable: true, get: function (
62
65
  var graph_query_js_2 = require("./graph-query.js");
63
66
  Object.defineProperty(exports, "queryDecisionGraph", { enumerable: true, get: function () { return graph_query_js_2.queryDecisionGraph; } });
64
67
  Object.defineProperty(exports, "querySemanticEdges", { enumerable: true, get: function () { return graph_query_js_2.querySemanticEdges; } });
68
+ Object.defineProperty(exports, "STATED_DECISION_EDGES", { enumerable: true, get: function () { return graph_query_js_2.STATED_DECISION_EDGES; } });
65
69
  Object.defineProperty(exports, "getGraphNeighborhood", { enumerable: true, get: function () { return graph_query_js_2.getGraphNeighborhood; } });
66
70
  Object.defineProperty(exports, "getGraphPaths", { enumerable: true, get: function () { return graph_query_js_2.getGraphPaths; } });
67
71
  Object.defineProperty(exports, "getGraphTimeline", { enumerable: true, get: function () { return graph_query_js_2.getGraphTimeline; } });
68
72
  Object.defineProperty(exports, "queryGraph", { enumerable: true, get: function () { return graph_query_js_2.queryGraph; } });
69
73
  Object.defineProperty(exports, "AgentGraphValidationError", { enumerable: true, get: function () { return graph_query_js_2.AgentGraphValidationError; } });
70
- var decision_edges_js_1 = require("./decision-edges.js");
71
- Object.defineProperty(exports, "upsertDecisionEdge", { enumerable: true, get: function () { return decision_edges_js_1.upsertDecisionEdge; } });
72
- Object.defineProperty(exports, "proposeDecisionEdge", { enumerable: true, get: function () { return decision_edges_js_1.proposeDecisionEdge; } });
73
- Object.defineProperty(exports, "approveDecisionEdge", { enumerable: true, get: function () { return decision_edges_js_1.approveDecisionEdge; } });
74
- Object.defineProperty(exports, "rejectDecisionEdge", { enumerable: true, get: function () { return decision_edges_js_1.rejectDecisionEdge; } });
75
- Object.defineProperty(exports, "deprecateAutoDecisionEdges", { enumerable: true, get: function () { return decision_edges_js_1.deprecateAutoDecisionEdges; } });
76
- Object.defineProperty(exports, "deleteDecisionEdgesWithAudit", { enumerable: true, get: function () { return decision_edges_js_1.deleteDecisionEdgesWithAudit; } });
77
74
  function createKnowledge(options) {
78
75
  const writer = (0, judgments_js_1.createJudgmentWriter)(options);
79
76
  return {
@@ -85,6 +82,8 @@ function createKnowledge(options) {
85
82
  withdrawWork: (command, access) => (0, commitments_js_1.withdrawWork)(command, access, options),
86
83
  readWork: (query, access) => (0, commitments_js_1.readWork)(options.adapter, query, access),
87
84
  queryGraph: (query, access) => (0, graph_query_js_1.queryGraph)(options.adapter, query, access),
85
+ appendLink: (command, access) => (0, links_js_1.appendLink)(options.adapter, command, access),
86
+ findLink: (commandId, access) => (0, links_js_1.findLink)(options.adapter, commandId, access),
88
87
  };
89
88
  }
90
89
  //# sourceMappingURL=index.js.map
@@ -1,6 +1,6 @@
1
1
  import type { DatabaseAdapter, DatabaseInstance } from '../db-manager.js';
2
2
  import { type TwinEdgeInsert, type TwinEdgeRecord, type TwinEdgeSource, type TwinEdgeType, type TwinProjectRef, type TwinRef } from './twin-edge-types.js';
3
- import type { JudgmentCommand, JudgmentReceipt } from '../memory/judgment-types.js';
3
+ import type { JudgmentCommand, JudgmentReceipt, WorkReference } from '../memory/judgment-types.js';
4
4
  import type { MemoryScopeRef } from '../memory/types.js';
5
5
  export interface JudgmentAccess {
6
6
  principalId: string;
@@ -88,6 +88,8 @@ export declare function admittedScopeIds(access: JudgmentAccess): string[];
88
88
  export declare function boundScopeIdsFor(access: JudgmentAccess, command: {
89
89
  scopes?: MemoryScopeRef[];
90
90
  }): string[];
91
+ /** Whether a link may name this target under the caller's admitted write scopes. */
92
+ export declare function referenceExists(adapter: Pick<DatabaseAdapter, 'prepare'>, reference: WorkReference, admittedScopeIds: readonly string[]): boolean;
91
93
  export declare function appendJudgment(command: JudgmentCommand, access: JudgmentAccess, options: JudgmentKnowledgeOptions): Promise<JudgmentReceipt>;
92
94
  export declare function createJudgmentWriter(options: JudgmentKnowledgeOptions): {
93
95
  appendJudgment: (command: JudgmentCommand, access: JudgmentAccess) => Promise<JudgmentReceipt>;
@@ -7,6 +7,7 @@ exports.JudgmentError = void 0;
7
7
  exports.judgmentRecordId = judgmentRecordId;
8
8
  exports.admittedScopeIds = admittedScopeIds;
9
9
  exports.boundScopeIdsFor = boundScopeIdsFor;
10
+ exports.referenceExists = referenceExists;
10
11
  exports.appendJudgment = appendJudgment;
11
12
  exports.createJudgmentWriter = createJudgmentWriter;
12
13
  exports.mapTwinEdgeRow = mapTwinEdgeRow;
@@ -130,6 +131,12 @@ const AMEND_FIELDS = [
130
131
  /** Public-save parity fields carried by the command are validated up front so a
131
132
  * malformed command fails before any write or embedder call. */
132
133
  function validateCommandFields(command) {
134
+ // A replacement is a stated act: without its own reason the supersedes link would carry the
135
+ // whole record's reasoning instead.
136
+ for (const replacement of command.replaces ?? []) {
137
+ requireText(replacement.id, 'replaces id');
138
+ requireText(replacement.reason, 'replaces reason');
139
+ }
133
140
  if (command.confidence !== undefined &&
134
141
  (!Number.isFinite(command.confidence) || command.confidence < 0 || command.confidence > 1)) {
135
142
  throw new JudgmentError('INVALID_COMMAND', 'confidence must be a number between 0 and 1');
@@ -163,14 +170,8 @@ function validateCommandFields(command) {
163
170
  throw new JudgmentError('INVALID_COMMAND', 'amends requires at least one projection field to set');
164
171
  }
165
172
  }
166
- for (const edge of command.projections?.decisionEdges ?? []) {
167
- if (edge.fromId !== undefined) {
168
- requireText(edge.fromId, 'projection edge source');
169
- }
170
- requireText(edge.targetId, 'projection edge target');
171
- requireText(edge.relationship, 'projection edge relationship');
172
- }
173
173
  }
174
+ /** Whether a link may name this target under the caller's admitted write scopes. */
174
175
  function referenceExists(adapter, reference, admittedScopeIds) {
175
176
  if (reference.kind === 'memory') {
176
177
  const row = adapter
@@ -207,7 +208,13 @@ function referenceExists(adapter, reference, admittedScopeIds) {
207
208
  .get(reference.id) !== undefined);
208
209
  }
209
210
  if (reference.kind === 'edge') {
210
- return (adapter.prepare('SELECT 1 FROM twin_edges WHERE edge_id = ?').get(reference.id) !== undefined);
211
+ // A correction reads the link it corrects, so both of that link's ends must be reachable.
212
+ const edge = adapter
213
+ .prepare('SELECT subject_kind, subject_id, object_kind, object_id FROM twin_edges WHERE edge_id = ?')
214
+ .get(reference.id);
215
+ return (edge !== undefined &&
216
+ referenceExists(adapter, { kind: edge.subject_kind, id: edge.subject_id }, admittedScopeIds) &&
217
+ referenceExists(adapter, { kind: edge.object_kind, id: edge.object_id }, admittedScopeIds));
211
218
  }
212
219
  return false;
213
220
  }
@@ -255,6 +262,26 @@ const AMEND_COLUMN_MAP = {
255
262
  supersedes: 'supersedes',
256
263
  supersededBy: 'superseded_by',
257
264
  };
265
+ /** The column values an amendment or a replacement is about to overwrite, per target. */
266
+ function priorValuesFor(adapter, command) {
267
+ const read = (id, columns) => adapter.prepare(`SELECT ${columns.join(', ')} FROM decisions WHERE id = ?`).get(id);
268
+ const replaced = (command.replaces ?? []).map((replacement) => {
269
+ const prior = read(replacement.id, ['status', 'superseded_by']);
270
+ return {
271
+ target: replacement.id,
272
+ values: { status: prior?.status ?? null, supersededBy: prior?.superseded_by ?? null },
273
+ };
274
+ });
275
+ const amended = (command.amends ?? []).map((amendment) => {
276
+ const fields = AMEND_FIELDS.filter((field) => field in amendment);
277
+ const prior = read(amendment.target.id, fields.map((field) => AMEND_COLUMN_MAP[field]));
278
+ return {
279
+ target: amendment.target.id,
280
+ values: Object.fromEntries(fields.map((field) => [field, prior?.[AMEND_COLUMN_MAP[field]] ?? null])),
281
+ };
282
+ });
283
+ return [...replaced, ...amended];
284
+ }
258
285
  function applyAmendment(adapter, amendment, now) {
259
286
  const sets = ['updated_at = ?'];
260
287
  const params = [now];
@@ -268,7 +295,7 @@ function applyAmendment(adapter, amendment, now) {
268
295
  .prepare(`UPDATE decisions SET ${sets.join(', ')} WHERE id = ?`)
269
296
  .run(...params, amendment.target.id);
270
297
  }
271
- function applyProjections(adapter, command, recordId, effectiveScopes, now) {
298
+ function applyProjections(adapter, command, recordId, effectiveScopes) {
272
299
  const projections = command.projections;
273
300
  if (!projections)
274
301
  return;
@@ -280,17 +307,6 @@ function applyProjections(adapter, command, recordId, effectiveScopes, now) {
280
307
  scopes: effectiveScopes,
281
308
  });
282
309
  }
283
- for (const targetId of projections.supersedeTargets ?? []) {
284
- adapter
285
- .prepare("UPDATE decisions SET superseded_by = ?, status = 'superseded', updated_at = ? WHERE id = ?")
286
- .run(recordId, now, targetId);
287
- }
288
- const edgeInsert = adapter.prepare(`INSERT INTO decision_edges
289
- (from_id, to_id, relationship, reason, weight, created_at, created_by, approved_by_user)
290
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
291
- for (const edge of projections.decisionEdges ?? []) {
292
- edgeInsert.run(edge.fromId ?? recordId, edge.targetId, edge.relationship, edge.reason ?? null, edge.weight ?? 1, now, edge.createdBy ?? 'user', edge.approvedByUser ?? 1);
293
- }
294
310
  }
295
311
  function parseReceipt(value) {
296
312
  let parsed;
@@ -339,7 +355,6 @@ function insertLink(adapter, command, recordId, link, index, access, now) {
339
355
  agent_id: Object.hasOwn(command, 'agentId') ? (command.agentId ?? undefined) : access.agentId,
340
356
  model_run_id: command.modelRunId ?? undefined,
341
357
  reason_text: typeof link.attrs?.reason === 'string' ? link.attrs.reason : (command.reasoning ?? undefined),
342
- evidence_refs: command.replaces?.length ? command.replaces : undefined,
343
358
  content_hash: contentHash,
344
359
  created_at: now,
345
360
  });
@@ -405,6 +420,9 @@ async function appendJudgmentOnAdapter(adapter, command, access, embedder) {
405
420
  }
406
421
  return;
407
422
  }
423
+ // What the amendments and replacements will overwrite, kept in this record: the target
424
+ // columns are a projection, and the appended records are the history.
425
+ const replacedValues = priorValuesFor(adapter, command);
408
426
  const decisionRowId = (0, db_manager_js_1.insertPreparedDecision)(adapter, {
409
427
  id: recordId,
410
428
  topic: command.topic,
@@ -443,7 +461,9 @@ async function appendJudgmentOnAdapter(adapter, command, access, embedder) {
443
461
  kind = COALESCE(?, kind), status = COALESCE(?, status),
444
462
  summary = COALESCE(?, summary), is_static = COALESCE(?, is_static)
445
463
  WHERE id = ?`)
446
- .run(command.recordKind, (0, canonicalize_js_1.canonicalizeJSON)(command.payload ?? {}), command.appliesFrom ?? null, command.appliesUntil ?? null, command.record?.kind ?? null, command.record?.status ?? null,
464
+ .run(command.recordKind, (0, canonicalize_js_1.canonicalizeJSON)(replacedValues.length > 0
465
+ ? { ...(command.payload ?? {}), replacedValues }
466
+ : (command.payload ?? {})), command.appliesFrom ?? null, command.appliesUntil ?? null, command.record?.kind ?? null, command.record?.status ?? null,
447
467
  // Never leave summary NULL: legacy readers (evolution candidates,
448
468
  // recall) assume it is text. The command summary is the authored
449
469
  // summary when the record does not carry a distinct one.
@@ -477,7 +497,7 @@ async function appendJudgmentOnAdapter(adapter, command, access, embedder) {
477
497
  }
478
498
  applyAmendment(adapter, amendment, domainNow);
479
499
  }
480
- applyProjections(adapter, command, recordId, effectiveScopes, domainNow);
500
+ applyProjections(adapter, command, recordId, effectiveScopes);
481
501
  // A commitment's own clock is domain time, like the record's: `updated_at`
482
502
  // is what the board sorts and filters on, so a caller that states when a
483
503
  // revision happened must see that time on the row, not the wall clock the
@@ -547,9 +567,9 @@ async function appendJudgmentOnAdapter(adapter, command, access, embedder) {
547
567
  (commitment_id, revision, record_id, operation, set_json, clear_json, applies_from, applies_until, created_at, agent_id, model_run_id)
548
568
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
549
569
  .run(work.commitmentId, revision, recordId, work.operation, (0, canonicalize_js_1.canonicalizeJSON)(workPatch(work)), (0, canonicalize_js_1.canonicalizeJSON)(work.clear ?? []), command.eventDatetime ?? null, command.appliesUntil ?? null, domainNow, access.agentId, command.modelRunId ?? null);
550
- edgeIds.push(insertLink(adapter, command, recordId, { relation: 'builds_on', target: { kind: 'memory', id: current.head_record_id } }, command.links?.length ?? 0,
551
- // The host links a revision to the record it revises; the agent did not judge it.
552
- { ...access, edgeSource: 'code' }, now));
570
+ // No host edge to the previous revision: the order lives in commitment_assignments, and
571
+ // how a revision relates to an earlier record is the agent's link to state (owner,
572
+ // 2026-09-30).
553
573
  adapter
554
574
  .prepare('UPDATE commitments SET current_revision = ?, head_record_id = ?, withdrawn = ?, updated_at = ?, agent_id = ?, model_run_id = ? WHERE commitment_id = ?')
555
575
  .run(revision, recordId, work.operation === 'withdraw' ? 1 : 0, domainNow, access.agentId, command.modelRunId ?? null, work.commitmentId);
@@ -0,0 +1,50 @@
1
+ import type { DatabaseAdapter } from '../db-manager.js';
2
+ import type { RecordLink } from '../memory/judgment-types.js';
3
+ import type { JudgmentAccess } from './judgments.js';
4
+ import type { TwinRef } from './twin-edge-types.js';
5
+ /**
6
+ * One edge the agent appends between records that already exist, with the sentence of why.
7
+ *
8
+ * Nothing is edited: a link is a new row, and a wrong link is answered by a newer one that
9
+ * contradicts it. The row is its own receipt, so no record or revision is written for it.
10
+ */
11
+ export interface LinkCommand {
12
+ commandId: string;
13
+ /** The record the statement is made from; for a work item, its head revision. */
14
+ from: {
15
+ kind: 'memory';
16
+ id: string;
17
+ };
18
+ /** An edge target is a correction and takes `contradicts`. */
19
+ to: TwinRef;
20
+ relation: RecordLink['relation'];
21
+ reason: string;
22
+ /** Observations or records the reason rests on. */
23
+ evidenceRefs?: TwinRef[];
24
+ /** Extra relation attributes, such as a person's role. */
25
+ attrs?: Record<string, unknown>;
26
+ agentId?: string | null;
27
+ modelRunId?: string | null;
28
+ }
29
+ export interface LinkReceipt {
30
+ edgeId: string;
31
+ createdAt: number;
32
+ /** True when the command id was already written with this same link. */
33
+ replayed: boolean;
34
+ }
35
+ /** A stored link, found by the command id that wrote it. */
36
+ export interface StoredLink extends LinkReceipt {
37
+ from: TwinRef;
38
+ to: TwinRef;
39
+ relation: string;
40
+ reason: string | null;
41
+ }
42
+ /** A link's id is fixed by who asked and under which command id, so a retry finds its row. */
43
+ export declare function linkEdgeId(principalId: string, commandId: string): string;
44
+ /**
45
+ * The link a principal already wrote under this command id, if any: a retry finds it here, with
46
+ * what it states, so the caller can tell a retry from another link under the same id.
47
+ */
48
+ export declare function findLink(adapter: Pick<DatabaseAdapter, 'prepare'>, commandId: string, access: JudgmentAccess): StoredLink | null;
49
+ export declare function appendLink(adapter: Pick<DatabaseAdapter, 'prepare' | 'transaction' | 'transactionImmediate'>, command: LinkCommand, access: JudgmentAccess): LinkReceipt;
50
+ //# sourceMappingURL=links.d.ts.map
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.linkEdgeId = linkEdgeId;
7
+ exports.findLink = findLink;
8
+ exports.appendLink = appendLink;
9
+ const node_crypto_1 = __importDefault(require("node:crypto"));
10
+ const canonicalize_js_1 = require("../canonicalize.js");
11
+ const judgments_js_1 = require("./judgments.js");
12
+ const TARGET_KINDS = new Set(['memory', 'registry', 'observation', 'edge']);
13
+ const EVIDENCE_KINDS = new Set(['memory', 'observation']);
14
+ /**
15
+ * Relations a link states. Replacing (supersedes) and amending change the target's state, so they
16
+ * are acts written with a record (`replaces`, an amendment), and a source citation (derived_from)
17
+ * is written with the record that rests on it.
18
+ */
19
+ const RELATIONS = new Set([
20
+ 'refines',
21
+ 'contradicts',
22
+ 'mentions',
23
+ 'builds_on',
24
+ 'debates',
25
+ 'synthesizes',
26
+ 'blocks',
27
+ 'next_action_for',
28
+ ]);
29
+ function nonblank(value, field) {
30
+ if (typeof value !== 'string' || value.trim() === '') {
31
+ throw new judgments_js_1.JudgmentError('INVALID_COMMAND', `${field} must be nonblank`);
32
+ }
33
+ return value;
34
+ }
35
+ /** A link's id is fixed by who asked and under which command id, so a retry finds its row. */
36
+ function linkEdgeId(principalId, commandId) {
37
+ return `link_${node_crypto_1.default
38
+ .createHash('sha256')
39
+ .update((0, canonicalize_js_1.canonicalizeJSON)({ principalId, commandId }))
40
+ .digest('hex')
41
+ .slice(0, 24)}`;
42
+ }
43
+ function linkContentHash(edgeId, command) {
44
+ return node_crypto_1.default
45
+ .createHash('sha256')
46
+ .update((0, canonicalize_js_1.canonicalizeJSON)({
47
+ edgeId,
48
+ from: command.from,
49
+ to: command.to,
50
+ relation: command.relation,
51
+ reason: command.reason,
52
+ evidenceRefs: command.evidenceRefs ?? [],
53
+ attrs: command.attrs ?? {},
54
+ }))
55
+ .digest();
56
+ }
57
+ function assertReachable(adapter, refs, access) {
58
+ // The same rule a record's own links follow (judgments.ts): the ends sit in the caller's
59
+ // admitted scopes, and an edge target has both of its ends there.
60
+ const scopeIds = (0, judgments_js_1.admittedScopeIds)(access);
61
+ for (const ref of refs) {
62
+ if (!(0, judgments_js_1.referenceExists)(adapter, ref, scopeIds)) {
63
+ // Echo only the caller's own input: an unavailable id reads the same whether wrong or outside scope.
64
+ throw new judgments_js_1.JudgmentError('REFERENCE_NOT_FOUND', `A link end is unavailable: ${ref.kind} ${ref.id}`);
65
+ }
66
+ }
67
+ }
68
+ /**
69
+ * The link a principal already wrote under this command id, if any: a retry finds it here, with
70
+ * what it states, so the caller can tell a retry from another link under the same id.
71
+ */
72
+ function findLink(adapter, commandId, access) {
73
+ const existing = (0, judgments_js_1.getTwinEdge)(adapter, linkEdgeId(access.principalId, commandId));
74
+ return existing
75
+ ? {
76
+ edgeId: existing.edge_id,
77
+ createdAt: existing.created_at,
78
+ replayed: true,
79
+ from: existing.subject_ref,
80
+ to: existing.object_ref,
81
+ relation: existing.edge_type,
82
+ reason: existing.reason_text,
83
+ }
84
+ : null;
85
+ }
86
+ function appendLink(adapter, command, access) {
87
+ nonblank(command.commandId, 'commandId');
88
+ nonblank(access.principalId, 'principalId');
89
+ const reason = nonblank(command.reason, 'reason').trim();
90
+ if (command.from?.kind !== 'memory') {
91
+ throw new judgments_js_1.JudgmentError('INVALID_COMMAND', 'A link starts from a memory record');
92
+ }
93
+ nonblank(command.from.id, 'from.id');
94
+ if (!command.to || !TARGET_KINDS.has(command.to.kind)) {
95
+ throw new judgments_js_1.JudgmentError('INVALID_COMMAND', 'A link targets a memory record, a registry node, an observation or an edge');
96
+ }
97
+ nonblank(command.to.id, 'to.id');
98
+ if (!RELATIONS.has(command.relation)) {
99
+ throw new judgments_js_1.JudgmentError('INVALID_COMMAND', `Unknown link relation: ${command.relation}`);
100
+ }
101
+ if (command.to.kind === 'edge' && command.relation !== 'contradicts') {
102
+ throw new judgments_js_1.JudgmentError('INVALID_COMMAND', 'A link to an edge is a correction and takes the relation contradicts');
103
+ }
104
+ for (const ref of command.evidenceRefs ?? []) {
105
+ if (!EVIDENCE_KINDS.has(ref.kind)) {
106
+ throw new judgments_js_1.JudgmentError('INVALID_COMMAND', 'Link evidence is an observation or a record');
107
+ }
108
+ nonblank(ref.id, 'evidence id');
109
+ }
110
+ const edgeId = linkEdgeId(access.principalId, command.commandId);
111
+ const contentHash = linkContentHash(edgeId, { ...command, reason });
112
+ const replay = () => {
113
+ const existing = (0, judgments_js_1.getTwinEdge)(adapter, edgeId);
114
+ if (!existing)
115
+ return null;
116
+ if (!existing.content_hash.equals(contentHash)) {
117
+ throw new judgments_js_1.JudgmentError('COMMAND_CONFLICT', 'The command id is already bound to another link');
118
+ }
119
+ return { edgeId, createdAt: existing.created_at, replayed: true };
120
+ };
121
+ assertReachable(adapter, [command.from, command.to, ...(command.evidenceRefs ?? [])], access);
122
+ const earlier = replay();
123
+ if (earlier)
124
+ return earlier;
125
+ const now = Date.now();
126
+ const transaction = adapter.transactionImmediate
127
+ ? adapter.transactionImmediate.bind(adapter)
128
+ : adapter.transaction.bind(adapter);
129
+ return transaction(() => {
130
+ const raced = replay();
131
+ if (raced)
132
+ return raced;
133
+ (0, judgments_js_1.insertTwinEdge)(adapter, {
134
+ edge_id: edgeId,
135
+ edge_type: command.relation,
136
+ subject_ref: command.from,
137
+ object_ref: command.to,
138
+ relation_attrs: { ...(command.attrs ?? {}), reason },
139
+ confidence: 1.0,
140
+ source: access.edgeSource ?? 'agent',
141
+ agent_id: Object.hasOwn(command, 'agentId') ? (command.agentId ?? undefined) : access.agentId,
142
+ model_run_id: command.modelRunId ?? undefined,
143
+ request_idempotency_key: command.commandId,
144
+ reason_text: reason,
145
+ evidence_refs: command.evidenceRefs?.length ? command.evidenceRefs : undefined,
146
+ content_hash: contentHash,
147
+ created_at: now,
148
+ });
149
+ return { edgeId, createdAt: now, replayed: false };
150
+ });
151
+ }
152
+ //# sourceMappingURL=links.js.map
@@ -20,16 +20,16 @@
20
20
  import { type CheckpointRow, type ListDecisionsOptions, type SearchCandidate, type SuggestFunctionOptions, type UpdateOutcomeParams } from './memory/api.js';
21
21
  import { DecisionRecord, type DatabaseInstance } from './db-manager.js';
22
22
  import { SemanticEdges } from './decision-formatter.js';
23
- import { saveMemory as saveMemoryInAdapter, recallMemory as recallMemoryInAdapter, buildProfile as buildProfileInAdapter, ingestMemory as ingestMemoryInAdapter, ingestConversation as ingestConversationInAdapter, evolveMemory, buildMemoryBootstrap as buildMemoryBootstrapInAdapter, createAuditAck, recordMemoryAudit as recordMemoryAuditInAdapter, upsertChannelSummary as upsertChannelSummaryInAdapter, getChannelSummary as getChannelSummaryInAdapter } from './memory/api.js';
23
+ import { saveMemory as saveMemoryInAdapter, recallMemory as recallMemoryInAdapter, buildProfile as buildProfileInAdapter, ingestMemory as ingestMemoryInAdapter, ingestConversation as ingestConversationInAdapter, buildMemoryBootstrap as buildMemoryBootstrapInAdapter, createAuditAck, recordMemoryAudit as recordMemoryAuditInAdapter, upsertChannelSummary as upsertChannelSummaryInAdapter, getChannelSummary as getChannelSummaryInAdapter } from './memory/api.js';
24
24
  import { createAuditFinding as createAuditFindingInAdapter, listOpenAuditFindings as listOpenAuditFindingsInAdapter } from './memory/finding-store.js';
25
25
  import { listMemoryEventsForMemory as listMemoryEventsForMemoryInAdapter, listRecentMemoryEvents as listRecentMemoryEventsInAdapter } from './memory/event-store.js';
26
26
  import { getMemoryProvenance as getMemoryProvenanceInAdapter, listMemoriesByEnvelopeHash as listMemoriesByEnvelopeHashInAdapter, listMemoriesByGatewayCallId as listMemoriesByGatewayCallIdInAdapter, listMemoriesByModelRunId as listMemoriesByModelRunIdInAdapter } from './memory/provenance-query.js';
27
27
  import { beginModelRun as beginModelRunInAdapter, commitModelRun as commitModelRunInAdapter, failModelRun as failModelRunInAdapter, getModelRun as getModelRunInAdapter, listModelRunNativeInputs as listModelRunNativeInputsInAdapter } from './runtime/model-run-store.js';
28
28
  import { appendToolTrace as appendToolTraceInAdapter, listToolTracesForRun as listToolTracesForRunInAdapter, listToolTraces as listToolTracesInAdapter, readToolTrace as readToolTraceInAdapter } from './runtime/tool-trace-store.js';
29
29
  import { type SearchHitDiagnostics } from './knowledge/search-quality.js';
30
- /**
31
- * Parameters for mama.save()
32
- */
30
+ import type { RecordLink } from './memory/judgment-types.js';
31
+ import { type DecisionLinkInput, type DecisionWithEdges } from './memory/decision-links.js';
32
+ import type { LinkReceipt } from './knowledge/links.js';
33
33
  interface SaveParams {
34
34
  topic: string;
35
35
  decision: string;
@@ -52,6 +52,17 @@ interface SaveParams {
52
52
  }>;
53
53
  /** ISO 8601 date string for when the event actually occurred (e.g. "2023-01-15") */
54
54
  event_date?: string | null;
55
+ /** Decisions this one relates to, each with the relation and the reason the caller judged. */
56
+ links?: Array<{
57
+ id: string;
58
+ relation: RecordLink['relation'];
59
+ reason: string;
60
+ }>;
61
+ /** Decisions this one replaces, each with the reason. */
62
+ replaces?: Array<{
63
+ id: string;
64
+ reason: string;
65
+ }>;
55
66
  }
56
67
  /**
57
68
  * Similar decision result from search
@@ -294,6 +305,10 @@ declare function suggest(userQuestion: string, options?: SuggestFunctionOptions)
294
305
  declare function recall(topic: string, options?: RecallOptions): Promise<string | RecallGraphResult>;
295
306
  declare function expandWithGraph(candidates: SearchCandidate[]): Promise<SearchCandidate[]>;
296
307
  declare function updateOutcome(decisionId: string, outcome: UpdateOutcomeParams): Promise<void>;
308
+ /** Link one decision to another, or correct a link, with the reason the caller judged. */
309
+ declare function link(input: DecisionLinkInput): Promise<LinkReceipt>;
310
+ /** One decision with every edge in and out, each with its reason and who wrote it. */
311
+ declare function getDecision(id: string): Promise<DecisionWithEdges | null>;
297
312
  declare function listDecisions(options?: ListDecisionsOptions): Promise<DecisionRecord[] | string>;
298
313
  declare function saveCheckpoint(summary: string, openFiles?: string[], nextSteps?: string, recentConversation?: any[]): Promise<number | bigint>;
299
314
  declare function loadCheckpoint(): Promise<CheckpointRow | null>;
@@ -368,7 +383,6 @@ export declare function createMamaApi(adapter: DatabaseInstance): {
368
383
  id: string;
369
384
  }>;
370
385
  ingestConversation: (input: Parameters<typeof ingestConversationInAdapter>[1]) => Promise<import("./index.js").IngestConversationResult>;
371
- evolveMemory: (input: Parameters<typeof evolveMemory>[0]) => Promise<import("./memory/evolution-engine.js").EvolutionResult>;
372
386
  buildMemoryBootstrap: (params: Parameters<typeof buildMemoryBootstrapInAdapter>[1]) => Promise<import("./memory/types.js").MemoryAgentBootstrap>;
373
387
  createAuditAck: (input: Parameters<typeof createAuditAck>[0]) => import("./memory/types.js").MemoryAuditAck;
374
388
  recordMemoryAudit: (input: Parameters<typeof recordMemoryAuditInAdapter>[1]) => Promise<{
@@ -408,6 +422,8 @@ export declare function createMamaApi(adapter: DatabaseInstance): {
408
422
  export type MamaApi = ReturnType<typeof createMamaApi>;
409
423
  declare const mama: {
410
424
  save: typeof save;
425
+ link: typeof link;
426
+ getDecision: typeof getDecision;
411
427
  suggest: typeof suggest;
412
428
  saveMemory: typeof saveMemory;
413
429
  recallMemory: typeof recallMemory;
@@ -417,7 +433,6 @@ declare const mama: {
417
433
  buildProfile: typeof buildProfile;
418
434
  ingestMemory: typeof ingestMemory;
419
435
  ingestConversation: typeof ingestConversation;
420
- evolveMemory: typeof evolveMemory;
421
436
  buildMemoryBootstrap: typeof buildMemoryBootstrap;
422
437
  createAuditAck: typeof createAuditAck;
423
438
  recordMemoryAudit: typeof recordMemoryAudit;
@@ -445,6 +460,6 @@ declare const mama: {
445
460
  recall: typeof recall;
446
461
  expandWithGraph: typeof expandWithGraph;
447
462
  };
448
- export { save, suggest, saveMemory, recallMemory, listDecisions as list, listCheckpoints, updateOutcome, buildProfile, ingestMemory, ingestConversation, evolveMemory, buildMemoryBootstrap, createAuditAck, recordMemoryAudit, upsertChannelSummary, getChannelSummary, listOpenAuditFindings, createAuditFinding, getMemoryProvenance, listMemoriesByEnvelopeHash, listMemoriesByGatewayCallId, listMemoriesByModelRunId, listMemoryEventsForMemory, listRecentMemoryEvents, beginModelRun, commitModelRun, failModelRun, getModelRun, appendToolTrace, listToolTracesForRun, listToolTraces, readToolTrace, saveCheckpoint, loadCheckpoint, recall, expandWithGraph, };
463
+ export { save, link, getDecision, suggest, saveMemory, recallMemory, listDecisions as list, listCheckpoints, updateOutcome, buildProfile, ingestMemory, ingestConversation, buildMemoryBootstrap, createAuditAck, recordMemoryAudit, upsertChannelSummary, getChannelSummary, listOpenAuditFindings, createAuditFinding, getMemoryProvenance, listMemoriesByEnvelopeHash, listMemoriesByGatewayCallId, listMemoriesByModelRunId, listMemoryEventsForMemory, listRecentMemoryEvents, beginModelRun, commitModelRun, failModelRun, getModelRun, appendToolTrace, listToolTracesForRun, listToolTraces, readToolTrace, saveCheckpoint, loadCheckpoint, recall, expandWithGraph, };
449
464
  export default mama;
450
465
  //# sourceMappingURL=mama-api.d.ts.map