@jungjaehoon/mama-core 1.4.2 → 1.6.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 (181) hide show
  1. package/README.md +48 -10
  2. package/db/migrations/026-create-canonical-entity-tables.sql +99 -0
  3. package/db/migrations/027-create-entity-review-support.sql +44 -0
  4. package/db/migrations/028-create-entity-audit-tables.sql +45 -0
  5. package/db/migrations/029-extend-entity-observations-observation-type.sql +60 -0
  6. package/db/migrations/030-case-first-memory-substrate.sql +901 -0
  7. package/db/migrations/031-wiki-page-index-provenance-columns.sql +2 -0
  8. package/db/migrations/032-add-memory-provenance-columns.sql +21 -0
  9. package/db/migrations/033-create-model-runs-and-tool-traces.sql +53 -0
  10. package/db/migrations/034-add-connector-event-scope-columns.sql +14 -0
  11. package/db/migrations/035-create-twin-edges.sql +63 -0
  12. package/db/migrations/036-create-agent-situation-packets.sql +61 -0
  13. package/dist/agent-graph/alias-write.d.ts +3 -0
  14. package/dist/agent-graph/alias-write.js +231 -0
  15. package/dist/agent-graph/entity-resolve.d.ts +3 -0
  16. package/dist/agent-graph/entity-resolve.js +275 -0
  17. package/dist/agent-graph/errors.d.ts +4 -0
  18. package/dist/agent-graph/errors.js +11 -0
  19. package/dist/agent-graph/graph-query.d.ts +5 -0
  20. package/dist/agent-graph/graph-query.js +359 -0
  21. package/dist/agent-graph/index.d.ts +6 -0
  22. package/dist/agent-graph/index.js +22 -0
  23. package/dist/agent-graph/types.d.ts +162 -0
  24. package/dist/agent-graph/types.js +3 -0
  25. package/dist/agent-situation/builder.d.ts +8 -0
  26. package/dist/agent-situation/builder.js +239 -0
  27. package/dist/agent-situation/cache-key.d.ts +4 -0
  28. package/dist/agent-situation/cache-key.js +64 -0
  29. package/dist/agent-situation/index.d.ts +7 -0
  30. package/dist/agent-situation/index.js +28 -0
  31. package/dist/agent-situation/packet-store.d.ts +27 -0
  32. package/dist/agent-situation/packet-store.js +228 -0
  33. package/dist/agent-situation/ranking-policy.d.ts +18 -0
  34. package/dist/agent-situation/ranking-policy.js +75 -0
  35. package/dist/agent-situation/source-readers.d.ts +52 -0
  36. package/dist/agent-situation/source-readers.js +300 -0
  37. package/dist/agent-situation/types.d.ts +195 -0
  38. package/dist/agent-situation/types.js +3 -0
  39. package/dist/canonicalize.d.ts +38 -0
  40. package/dist/canonicalize.js +106 -0
  41. package/dist/cases/case-links.d.ts +116 -0
  42. package/dist/cases/case-links.js +794 -0
  43. package/dist/cases/composition-overrides.d.ts +90 -0
  44. package/dist/cases/composition-overrides.js +724 -0
  45. package/dist/cases/corrections.d.ts +156 -0
  46. package/dist/cases/corrections.js +1045 -0
  47. package/dist/cases/freshness.d.ts +57 -0
  48. package/dist/cases/freshness.js +283 -0
  49. package/dist/cases/live-state.d.ts +48 -0
  50. package/dist/cases/live-state.js +256 -0
  51. package/dist/cases/membership-explain.d.ts +56 -0
  52. package/dist/cases/membership-explain.js +140 -0
  53. package/dist/cases/membership-matcher.d.ts +23 -0
  54. package/dist/cases/membership-matcher.js +193 -0
  55. package/dist/cases/merge-split.d.ts +61 -0
  56. package/dist/cases/merge-split.js +368 -0
  57. package/dist/cases/role-inference.d.ts +10 -0
  58. package/dist/cases/role-inference.js +85 -0
  59. package/dist/cases/search-rollup.d.ts +27 -0
  60. package/dist/cases/search-rollup.js +309 -0
  61. package/dist/cases/sqlite-transaction.d.ts +10 -0
  62. package/dist/cases/sqlite-transaction.js +62 -0
  63. package/dist/cases/store.d.ts +74 -0
  64. package/dist/cases/store.js +816 -0
  65. package/dist/cases/target-ref.d.ts +25 -0
  66. package/dist/cases/target-ref.js +22 -0
  67. package/dist/cases/timeline-range.d.ts +6 -0
  68. package/dist/cases/timeline-range.js +322 -0
  69. package/dist/cases/tombstone-sweeper.d.ts +13 -0
  70. package/dist/cases/tombstone-sweeper.js +128 -0
  71. package/dist/cases/types.d.ts +244 -0
  72. package/dist/cases/types.js +47 -0
  73. package/dist/cases/wiki-page-index.d.ts +50 -0
  74. package/dist/cases/wiki-page-index.js +350 -0
  75. package/dist/connectors/event-index.d.ts +45 -0
  76. package/dist/connectors/event-index.js +381 -0
  77. package/dist/connectors/raw-query.d.ts +18 -0
  78. package/dist/connectors/raw-query.js +305 -0
  79. package/dist/connectors/types.d.ts +109 -0
  80. package/dist/connectors/types.js +3 -0
  81. package/dist/db-adapter/node-sqlite-adapter.d.ts +6 -0
  82. package/dist/db-adapter/node-sqlite-adapter.js +131 -0
  83. package/dist/db-manager.d.ts +12 -1
  84. package/dist/db-manager.js +60 -5
  85. package/dist/decision-formatter.d.ts +2 -0
  86. package/dist/decision-formatter.js +11 -8
  87. package/dist/edges/ref-validation.d.ts +8 -0
  88. package/dist/edges/ref-validation.js +262 -0
  89. package/dist/edges/store.d.ts +10 -0
  90. package/dist/edges/store.js +343 -0
  91. package/dist/edges/types.d.ts +86 -0
  92. package/dist/edges/types.js +18 -0
  93. package/dist/embedding-server/mobile/daemon.d.ts +1 -0
  94. package/dist/embedding-server/mobile/daemon.js +11 -1
  95. package/dist/embeddings.js +13 -0
  96. package/dist/entities/audit-metrics.d.ts +73 -0
  97. package/dist/entities/audit-metrics.js +128 -0
  98. package/dist/entities/candidate-generator.d.ts +9 -0
  99. package/dist/entities/candidate-generator.js +436 -0
  100. package/dist/entities/entity-impact.d.ts +41 -0
  101. package/dist/entities/entity-impact.js +140 -0
  102. package/dist/entities/entity-linked-decision-counts.d.ts +2 -0
  103. package/dist/entities/entity-linked-decision-counts.js +29 -0
  104. package/dist/entities/entity-list.d.ts +23 -0
  105. package/dist/entities/entity-list.js +99 -0
  106. package/dist/entities/entity-orphan-list.d.ts +26 -0
  107. package/dist/entities/entity-orphan-list.js +91 -0
  108. package/dist/entities/entity-search.d.ts +21 -0
  109. package/dist/entities/entity-search.js +150 -0
  110. package/dist/entities/errors.d.ts +65 -0
  111. package/dist/entities/errors.js +124 -0
  112. package/dist/entities/exact-merge-backfill.d.ts +11 -0
  113. package/dist/entities/exact-merge-backfill.js +161 -0
  114. package/dist/entities/lineage-backfill.d.ts +12 -0
  115. package/dist/entities/lineage-backfill.js +101 -0
  116. package/dist/entities/lineage-store.d.ts +51 -0
  117. package/dist/entities/lineage-store.js +308 -0
  118. package/dist/entities/normalization.d.ts +14 -0
  119. package/dist/entities/normalization.js +63 -0
  120. package/dist/entities/policy-store.d.ts +17 -0
  121. package/dist/entities/policy-store.js +263 -0
  122. package/dist/entities/policy-types.d.ts +72 -0
  123. package/dist/entities/policy-types.js +59 -0
  124. package/dist/entities/projection.d.ts +8 -0
  125. package/dist/entities/projection.js +82 -0
  126. package/dist/entities/provenance-query.d.ts +33 -0
  127. package/dist/entities/provenance-query.js +167 -0
  128. package/dist/entities/read-identity.d.ts +31 -0
  129. package/dist/entities/read-identity.js +264 -0
  130. package/dist/entities/recall-bridge.d.ts +5 -0
  131. package/dist/entities/recall-bridge.js +171 -0
  132. package/dist/entities/resolution-engine.d.ts +15 -0
  133. package/dist/entities/resolution-engine.js +69 -0
  134. package/dist/entities/rollback-preview.d.ts +39 -0
  135. package/dist/entities/rollback-preview.js +235 -0
  136. package/dist/entities/source-locator.d.ts +3 -0
  137. package/dist/entities/source-locator.js +32 -0
  138. package/dist/entities/store.d.ts +75 -0
  139. package/dist/entities/store.js +465 -0
  140. package/dist/entities/types.d.ts +162 -0
  141. package/dist/entities/types.js +30 -0
  142. package/dist/index.d.ts +62 -3
  143. package/dist/index.js +116 -26
  144. package/dist/mama-api.d.ts +53 -35
  145. package/dist/mama-api.js +437 -78
  146. package/dist/memory/api.d.ts +26 -38
  147. package/dist/memory/api.js +772 -68
  148. package/dist/memory/event-store.d.ts +3 -0
  149. package/dist/memory/event-store.js +21 -2
  150. package/dist/memory/provenance-audit.d.ts +22 -0
  151. package/dist/memory/provenance-audit.js +112 -0
  152. package/dist/memory/provenance-query.d.ts +11 -0
  153. package/dist/memory/provenance-query.js +193 -0
  154. package/dist/memory/provenance.d.ts +29 -0
  155. package/dist/memory/provenance.js +136 -0
  156. package/dist/memory/scope-backfill.d.ts +17 -0
  157. package/dist/memory/scope-backfill.js +77 -0
  158. package/dist/memory/types.d.ts +97 -16
  159. package/dist/model-runs/store.d.ts +13 -0
  160. package/dist/model-runs/store.js +457 -0
  161. package/dist/model-runs/tool-trace-store.d.ts +4 -0
  162. package/dist/model-runs/tool-trace-store.js +114 -0
  163. package/dist/model-runs/types.d.ts +69 -0
  164. package/dist/model-runs/types.js +5 -0
  165. package/dist/runtime/trusted-provenance.d.ts +3 -0
  166. package/dist/runtime/trusted-provenance.js +6 -0
  167. package/dist/search/feedback-store.d.ts +59 -0
  168. package/dist/search/feedback-store.js +211 -0
  169. package/dist/search/question-type.d.ts +5 -0
  170. package/dist/search/question-type.js +41 -0
  171. package/dist/search/ranker-features.d.ts +23 -0
  172. package/dist/search/ranker-features.js +104 -0
  173. package/dist/search/ranker-rescore.d.ts +31 -0
  174. package/dist/search/ranker-rescore.js +226 -0
  175. package/dist/search/ranker-trainer.d.ts +94 -0
  176. package/dist/search/ranker-trainer.js +860 -0
  177. package/dist/search/search-quality.d.ts +36 -0
  178. package/dist/search/search-quality.js +29 -0
  179. package/dist/test-utils.d.ts +14 -0
  180. package/dist/test-utils.js +52 -0
  181. package/package.json +28 -3
@@ -0,0 +1,465 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EntityMergeError = void 0;
4
+ exports.parseObservationRow = parseObservationRow;
5
+ exports.createEntityNode = createEntityNode;
6
+ exports.getEntityNode = getEntityNode;
7
+ exports.listEntityNodes = listEntityNodes;
8
+ exports.attachEntityAlias = attachEntityAlias;
9
+ exports.listEntityAliases = listEntityAliases;
10
+ exports.upsertEntityObservation = upsertEntityObservation;
11
+ exports.upsertEntityObservations = upsertEntityObservations;
12
+ exports.appendEntityTimelineEvent = appendEntityTimelineEvent;
13
+ exports.resolveCanonicalEntityId = resolveCanonicalEntityId;
14
+ exports.mergeEntityNodes = mergeEntityNodes;
15
+ const node_crypto_1 = require("node:crypto");
16
+ const db_manager_js_1 = require("../db-manager.js");
17
+ const types_js_1 = require("./types.js");
18
+ class EntityMergeError extends Error {
19
+ code;
20
+ constructor(code, message) {
21
+ super(message);
22
+ this.code = code;
23
+ this.name = 'EntityMergeError';
24
+ }
25
+ }
26
+ exports.EntityMergeError = EntityMergeError;
27
+ const MAX_MERGE_CHAIN_DEPTH = 8;
28
+ function now() {
29
+ return Date.now();
30
+ }
31
+ function normalizeSourceLocator(value) {
32
+ // Write empty-string as the null sentinel so SQLite UNIQUE can collapse null-like locators; parseObservationRow maps it back to null on read.
33
+ return value ?? '';
34
+ }
35
+ function requireStringField(row, field) {
36
+ if (typeof row[field] !== 'string' || row[field].length === 0) {
37
+ throw new Error(`Invalid entity observation row: ${field} must be a non-empty string`);
38
+ }
39
+ return row[field];
40
+ }
41
+ function optionalStringField(row, field) {
42
+ if (!Object.prototype.hasOwnProperty.call(row, field)) {
43
+ throw new Error(`Invalid entity observation row: ${field} must be present`);
44
+ }
45
+ if (row[field] === null) {
46
+ return null;
47
+ }
48
+ if (typeof row[field] !== 'string') {
49
+ throw new Error(`Invalid entity observation row: ${field} must be a string or null`);
50
+ }
51
+ return row[field];
52
+ }
53
+ function optionalStringFieldIfPresent(row, field) {
54
+ if (!Object.prototype.hasOwnProperty.call(row, field)) {
55
+ return undefined;
56
+ }
57
+ if (row[field] === null) {
58
+ return null;
59
+ }
60
+ if (typeof row[field] !== 'string') {
61
+ throw new Error(`Invalid entity observation row: ${field} must be a string or null`);
62
+ }
63
+ return row[field];
64
+ }
65
+ function optionalNumberField(row, field) {
66
+ if (!Object.prototype.hasOwnProperty.call(row, field)) {
67
+ throw new Error(`Invalid entity observation row: ${field} must be present`);
68
+ }
69
+ if (row[field] === null) {
70
+ return null;
71
+ }
72
+ if (typeof row[field] !== 'number') {
73
+ throw new Error(`Invalid entity observation row: ${field} must be a number or null`);
74
+ }
75
+ return row[field];
76
+ }
77
+ function parseObservationRow(row) {
78
+ const id = requireStringField(row, 'id');
79
+ const observationType = requireStringField(row, 'observation_type');
80
+ if (!types_js_1.ENTITY_OBSERVATION_TYPES.includes(observationType)) {
81
+ throw new Error(`Invalid entity observation row: observation_type=${observationType}`);
82
+ }
83
+ const entityKindHint = optionalStringField(row, 'entity_kind_hint');
84
+ if (entityKindHint !== null &&
85
+ !types_js_1.ENTITY_KINDS.includes(entityKindHint)) {
86
+ throw new Error(`Invalid entity observation row: entity_kind_hint=${entityKindHint}`);
87
+ }
88
+ const scopeKind = optionalStringField(row, 'scope_kind');
89
+ if (scopeKind === null) {
90
+ throw new Error('Invalid entity observation row: scope_kind must not be null');
91
+ }
92
+ if (!types_js_1.ENTITY_SCOPE_KINDS.includes(scopeKind)) {
93
+ throw new Error(`Invalid entity observation row: scope_kind=${scopeKind}`);
94
+ }
95
+ const createdAt = row.created_at;
96
+ if (typeof createdAt !== 'number') {
97
+ throw new Error('Invalid entity observation row: created_at must be a number');
98
+ }
99
+ let relatedSurfaceForms = [];
100
+ if (typeof row.related_surface_forms === 'string') {
101
+ try {
102
+ const parsed = JSON.parse(row.related_surface_forms);
103
+ if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== 'string')) {
104
+ throw new Error('parsed value is not a string[]');
105
+ }
106
+ relatedSurfaceForms = parsed;
107
+ }
108
+ catch (error) {
109
+ throw new Error(`Invalid entity observation row: related_surface_forms must be valid JSON string[] (${error instanceof Error ? error.message : String(error)})`);
110
+ }
111
+ }
112
+ else if (row.related_surface_forms !== undefined && row.related_surface_forms !== null) {
113
+ throw new Error('Invalid entity observation row: related_surface_forms must be a JSON string or null');
114
+ }
115
+ const sourceLocator = optionalStringFieldIfPresent(row, 'source_locator');
116
+ if (sourceLocator === undefined) {
117
+ throw new Error('Invalid entity observation row: source_locator must be present');
118
+ }
119
+ return {
120
+ id,
121
+ observation_type: observationType,
122
+ entity_kind_hint: entityKindHint,
123
+ surface_form: requireStringField(row, 'surface_form'),
124
+ normalized_form: requireStringField(row, 'normalized_form'),
125
+ lang: optionalStringField(row, 'lang'),
126
+ script: optionalStringField(row, 'script'),
127
+ context_summary: optionalStringField(row, 'context_summary'),
128
+ related_surface_forms: relatedSurfaceForms,
129
+ timestamp_observed: optionalNumberField(row, 'timestamp_observed'),
130
+ scope_kind: scopeKind,
131
+ scope_id: optionalStringField(row, 'scope_id'),
132
+ extractor_version: requireStringField(row, 'extractor_version'),
133
+ embedding_model_version: optionalStringField(row, 'embedding_model_version'),
134
+ source_connector: requireStringField(row, 'source_connector'),
135
+ source_locator: sourceLocator && sourceLocator.length > 0 ? sourceLocator : null,
136
+ source_raw_record_id: requireStringField(row, 'source_raw_record_id'),
137
+ created_at: createdAt,
138
+ };
139
+ }
140
+ async function createEntityNode(input) {
141
+ await (0, db_manager_js_1.initDB)();
142
+ const adapter = (0, db_manager_js_1.getAdapter)();
143
+ const createdAt = now();
144
+ adapter
145
+ .prepare(`
146
+ INSERT INTO entity_nodes (
147
+ id, kind, preferred_label, status, scope_kind, scope_id, merged_into, created_at, updated_at
148
+ )
149
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
150
+ `)
151
+ .run(input.id, input.kind, input.preferred_label, input.status, input.scope_kind, input.scope_id, input.merged_into, createdAt, createdAt);
152
+ return {
153
+ ...input,
154
+ created_at: createdAt,
155
+ updated_at: createdAt,
156
+ };
157
+ }
158
+ function getEntityNode(id, adapter = (0, db_manager_js_1.getAdapter)()) {
159
+ const row = adapter.prepare('SELECT * FROM entity_nodes WHERE id = ?').get(id);
160
+ if (!row) {
161
+ return null;
162
+ }
163
+ return {
164
+ id: String(row.id),
165
+ kind: row.kind,
166
+ preferred_label: String(row.preferred_label),
167
+ status: row.status,
168
+ scope_kind: typeof row.scope_kind === 'string' ? row.scope_kind : null,
169
+ scope_id: typeof row.scope_id === 'string' ? row.scope_id : null,
170
+ merged_into: typeof row.merged_into === 'string' ? row.merged_into : null,
171
+ created_at: Number(row.created_at),
172
+ updated_at: Number(row.updated_at),
173
+ };
174
+ }
175
+ function listEntityNodes() {
176
+ const adapter = (0, db_manager_js_1.getAdapter)();
177
+ const rows = adapter
178
+ .prepare('SELECT * FROM entity_nodes ORDER BY created_at DESC')
179
+ .all();
180
+ return rows.map((row) => ({
181
+ id: String(row.id),
182
+ kind: row.kind,
183
+ preferred_label: String(row.preferred_label),
184
+ status: row.status,
185
+ scope_kind: typeof row.scope_kind === 'string' ? row.scope_kind : null,
186
+ scope_id: typeof row.scope_id === 'string' ? row.scope_id : null,
187
+ merged_into: typeof row.merged_into === 'string' ? row.merged_into : null,
188
+ created_at: Number(row.created_at),
189
+ updated_at: Number(row.updated_at),
190
+ }));
191
+ }
192
+ async function attachEntityAlias(input) {
193
+ await (0, db_manager_js_1.initDB)();
194
+ const adapter = (0, db_manager_js_1.getAdapter)();
195
+ const createdAt = now();
196
+ adapter
197
+ .prepare(`
198
+ INSERT INTO entity_aliases (
199
+ id, entity_id, label, normalized_label, lang, script, label_type,
200
+ source_type, source_ref, confidence, status, created_at
201
+ )
202
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
203
+ `)
204
+ .run(input.id, input.entity_id, input.label, input.normalized_label, input.lang, input.script, input.label_type, input.source_type, input.source_ref, input.confidence, input.status, createdAt);
205
+ return {
206
+ ...input,
207
+ created_at: createdAt,
208
+ };
209
+ }
210
+ function listEntityAliases(entityId, adapter = (0, db_manager_js_1.getAdapter)()) {
211
+ const rows = adapter
212
+ .prepare('SELECT * FROM entity_aliases WHERE entity_id = ? ORDER BY created_at ASC')
213
+ .all(entityId);
214
+ return rows.map((row) => ({
215
+ id: String(row.id),
216
+ entity_id: String(row.entity_id),
217
+ label: String(row.label),
218
+ normalized_label: String(row.normalized_label),
219
+ lang: typeof row.lang === 'string' ? row.lang : null,
220
+ script: typeof row.script === 'string' ? row.script : null,
221
+ label_type: row.label_type,
222
+ source_type: String(row.source_type),
223
+ source_ref: typeof row.source_ref === 'string' ? row.source_ref : null,
224
+ confidence: typeof row.confidence === 'number' ? row.confidence : null,
225
+ status: row.status,
226
+ created_at: Number(row.created_at),
227
+ }));
228
+ }
229
+ async function upsertEntityObservation(input) {
230
+ await (0, db_manager_js_1.initDB)();
231
+ const adapter = (0, db_manager_js_1.getAdapter)();
232
+ const createdAt = now();
233
+ const normalizedSourceLocator = normalizeSourceLocator(input.source_locator);
234
+ adapter
235
+ .prepare(`
236
+ INSERT INTO entity_observations (
237
+ id, observation_type, entity_kind_hint, surface_form, normalized_form, lang, script,
238
+ context_summary, related_surface_forms, timestamp_observed, scope_kind, scope_id,
239
+ extractor_version, embedding_model_version, source_connector, source_locator,
240
+ source_raw_record_id, created_at
241
+ )
242
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
243
+ ON CONFLICT(source_connector, source_locator, source_raw_record_id, observation_type)
244
+ DO UPDATE SET
245
+ entity_kind_hint = excluded.entity_kind_hint,
246
+ surface_form = excluded.surface_form,
247
+ normalized_form = excluded.normalized_form,
248
+ lang = excluded.lang,
249
+ script = excluded.script,
250
+ context_summary = excluded.context_summary,
251
+ related_surface_forms = excluded.related_surface_forms,
252
+ timestamp_observed = excluded.timestamp_observed,
253
+ scope_kind = excluded.scope_kind,
254
+ scope_id = excluded.scope_id,
255
+ extractor_version = excluded.extractor_version,
256
+ embedding_model_version = excluded.embedding_model_version,
257
+ source_locator = excluded.source_locator,
258
+ created_at = entity_observations.created_at
259
+ `)
260
+ .run(input.id, input.observation_type, input.entity_kind_hint, input.surface_form, input.normalized_form, input.lang, input.script, input.context_summary, JSON.stringify(input.related_surface_forms), input.timestamp_observed, input.scope_kind, input.scope_id, input.extractor_version, input.embedding_model_version, input.source_connector, normalizedSourceLocator, input.source_raw_record_id, createdAt);
261
+ const saved = adapter
262
+ .prepare(`
263
+ SELECT * FROM entity_observations
264
+ WHERE source_connector = ? AND source_locator = ? AND source_raw_record_id = ? AND observation_type = ?
265
+ `)
266
+ .get(input.source_connector, normalizedSourceLocator, input.source_raw_record_id, input.observation_type);
267
+ return parseObservationRow(saved);
268
+ }
269
+ async function upsertEntityObservations(inputs) {
270
+ await (0, db_manager_js_1.initDB)();
271
+ const adapter = (0, db_manager_js_1.getAdapter)();
272
+ const observations = [];
273
+ const upsertOne = (input) => {
274
+ const normalizedSourceLocator = normalizeSourceLocator(input.source_locator);
275
+ const existing = adapter
276
+ .prepare(`
277
+ SELECT id FROM entity_observations
278
+ WHERE source_connector = ? AND source_locator = ? AND source_raw_record_id = ? AND observation_type = ?
279
+ `)
280
+ .get(input.source_connector, normalizedSourceLocator, input.source_raw_record_id, input.observation_type);
281
+ const createdAt = now();
282
+ adapter
283
+ .prepare(`
284
+ INSERT INTO entity_observations (
285
+ id, observation_type, entity_kind_hint, surface_form, normalized_form, lang, script,
286
+ context_summary, related_surface_forms, timestamp_observed, scope_kind, scope_id,
287
+ extractor_version, embedding_model_version, source_connector, source_locator,
288
+ source_raw_record_id, created_at
289
+ )
290
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
291
+ ON CONFLICT(source_connector, source_locator, source_raw_record_id, observation_type)
292
+ DO UPDATE SET
293
+ entity_kind_hint = excluded.entity_kind_hint,
294
+ surface_form = excluded.surface_form,
295
+ normalized_form = excluded.normalized_form,
296
+ lang = excluded.lang,
297
+ script = excluded.script,
298
+ context_summary = excluded.context_summary,
299
+ related_surface_forms = excluded.related_surface_forms,
300
+ timestamp_observed = excluded.timestamp_observed,
301
+ scope_kind = excluded.scope_kind,
302
+ scope_id = excluded.scope_id,
303
+ extractor_version = excluded.extractor_version,
304
+ embedding_model_version = excluded.embedding_model_version,
305
+ source_locator = excluded.source_locator,
306
+ created_at = entity_observations.created_at
307
+ `)
308
+ .run(input.id, input.observation_type, input.entity_kind_hint, input.surface_form, input.normalized_form, input.lang, input.script, input.context_summary, JSON.stringify(input.related_surface_forms), input.timestamp_observed, input.scope_kind, input.scope_id, input.extractor_version, input.embedding_model_version, input.source_connector, normalizedSourceLocator, input.source_raw_record_id, createdAt);
309
+ const saved = adapter
310
+ .prepare(`
311
+ SELECT * FROM entity_observations
312
+ WHERE source_connector = ? AND source_locator = ? AND source_raw_record_id = ? AND observation_type = ?
313
+ `)
314
+ .get(input.source_connector, normalizedSourceLocator, input.source_raw_record_id, input.observation_type);
315
+ observations.push({
316
+ id: requireStringField(saved, 'id'),
317
+ created: !existing,
318
+ });
319
+ };
320
+ if ('transaction' in adapter && typeof adapter.transaction === 'function') {
321
+ const txResult = adapter.transaction(() => {
322
+ for (const input of inputs) {
323
+ upsertOne(input);
324
+ }
325
+ });
326
+ if (typeof txResult === 'function') {
327
+ txResult();
328
+ }
329
+ return observations;
330
+ }
331
+ for (const input of inputs) {
332
+ upsertOne(input);
333
+ }
334
+ return observations;
335
+ }
336
+ async function appendEntityTimelineEvent(input) {
337
+ if (!input.adapter) {
338
+ await (0, db_manager_js_1.initDB)();
339
+ }
340
+ const adapter = input.adapter ?? (0, db_manager_js_1.getAdapter)();
341
+ const eventId = input.event.id ?? `et_${(0, node_crypto_1.randomUUID)()}`;
342
+ const createdAt = input.event.created_at ?? now();
343
+ adapter
344
+ .prepare(`
345
+ INSERT INTO entity_timeline_events (
346
+ id, entity_id, event_type, role, valid_from, valid_to, observed_at,
347
+ source_ref, summary, details, created_at
348
+ )
349
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
350
+ `)
351
+ .run(eventId, input.event.entity_id, input.event.event_type, input.event.role ?? null, input.event.valid_from ?? null, input.event.valid_to ?? null, input.event.observed_at ?? null, input.event.source_ref ?? null, input.event.summary, input.event.details ?? null, createdAt);
352
+ return {
353
+ id: eventId,
354
+ entity_id: input.event.entity_id,
355
+ event_type: input.event.event_type,
356
+ role: input.event.role ?? null,
357
+ valid_from: input.event.valid_from ?? null,
358
+ valid_to: input.event.valid_to ?? null,
359
+ observed_at: input.event.observed_at ?? null,
360
+ source_ref: input.event.source_ref ?? null,
361
+ summary: input.event.summary,
362
+ details: input.event.details ?? null,
363
+ created_at: createdAt,
364
+ };
365
+ }
366
+ /**
367
+ * Walks the `entity_nodes.merged_into` chain from the given id and returns the
368
+ * terminal (unmerged) entity id. Detects cycles and caps depth. Read-only.
369
+ *
370
+ * Matches the read-time chain-walking approach described in the canonical
371
+ * entity ontology implementation plan.
372
+ */
373
+ function resolveCanonicalEntityId(adapter, id) {
374
+ const seen = new Set();
375
+ let current = id;
376
+ for (let depth = 0; depth < MAX_MERGE_CHAIN_DEPTH; depth++) {
377
+ if (seen.has(current)) {
378
+ throw new EntityMergeError('entity.merge_chain_cycle', `Cycle detected in merged_into chain starting at ${id}`);
379
+ }
380
+ seen.add(current);
381
+ const row = adapter
382
+ .prepare('SELECT merged_into FROM entity_nodes WHERE id = ?')
383
+ .get(current);
384
+ if (!row) {
385
+ throw new EntityMergeError('entity.node_not_found', `Entity node ${current} not found while resolving canonical id for ${id}`);
386
+ }
387
+ if (!row.merged_into) {
388
+ return current;
389
+ }
390
+ current = row.merged_into;
391
+ }
392
+ throw new EntityMergeError('entity.merge_chain_too_deep', `merged_into chain exceeds depth ${MAX_MERGE_CHAIN_DEPTH} starting at ${id}`);
393
+ }
394
+ /**
395
+ * Marks `source_id` as merged into `target_id`, emits a timeline event, and
396
+ * inserts the entity_merge_actions audit row with both entity IDs populated.
397
+ *
398
+ * Intentionally NOT wrapped in its own transaction — the caller is expected
399
+ * to wrap the call in `adapter.transaction(...)` so the candidate status
400
+ * update and this merge land atomically (Policy B1, hard transaction).
401
+ *
402
+ * Validates: source != target, both exist, both active/unmerged, same kind,
403
+ * same scope. Throws `EntityMergeError` with a stable `code` on any violation.
404
+ */
405
+ function mergeEntityNodes(input) {
406
+ const { adapter, source_id, target_id, actor_type, actor_id, reason, candidate_id, evidence_json, } = input;
407
+ if (!source_id || !target_id) {
408
+ throw new EntityMergeError('entity.merge_invalid_args', 'source_id and target_id are required');
409
+ }
410
+ if (source_id === target_id) {
411
+ throw new EntityMergeError('entity.merge_self', 'Cannot merge an entity into itself');
412
+ }
413
+ const source = adapter.prepare('SELECT * FROM entity_nodes WHERE id = ?').get(source_id);
414
+ if (!source) {
415
+ throw new EntityMergeError('entity.merge_source_not_found', `Source entity ${source_id} not found`);
416
+ }
417
+ const target = adapter.prepare('SELECT * FROM entity_nodes WHERE id = ?').get(target_id);
418
+ if (!target) {
419
+ throw new EntityMergeError('entity.merge_target_not_found', `Target entity ${target_id} not found`);
420
+ }
421
+ if (source.status === 'merged' || source.merged_into) {
422
+ throw new EntityMergeError('entity.merge_source_already_merged', `Source entity ${source_id} is already merged`);
423
+ }
424
+ if (target.status === 'merged' || target.merged_into) {
425
+ throw new EntityMergeError('entity.merge_target_already_merged', `Target entity ${target_id} is already merged`);
426
+ }
427
+ if (source.status === 'archived' || target.status === 'archived') {
428
+ throw new EntityMergeError('entity.merge_archived', 'Cannot merge archived entities');
429
+ }
430
+ if (source.kind !== target.kind) {
431
+ throw new EntityMergeError('entity.merge_kind_mismatch', `Cannot merge ${source.kind} into ${target.kind}`);
432
+ }
433
+ if (source.scope_kind !== target.scope_kind || source.scope_id !== target.scope_id) {
434
+ throw new EntityMergeError('entity.merge_scope_mismatch', `Cannot merge across scopes: ${source.scope_kind}/${source.scope_id ?? 'null'} vs ${target.scope_kind}/${target.scope_id ?? 'null'}`);
435
+ }
436
+ const mergedAt = now();
437
+ const mergeActionId = `mact_${(0, node_crypto_1.randomUUID)()}`;
438
+ const timelineEventId = `et_${(0, node_crypto_1.randomUUID)()}`;
439
+ adapter
440
+ .prepare(`UPDATE entity_nodes SET merged_into = ?, status = 'merged', updated_at = ? WHERE id = ?`)
441
+ .run(target_id, mergedAt, source_id);
442
+ adapter
443
+ .prepare(`
444
+ INSERT INTO entity_timeline_events (
445
+ id, entity_id, event_type, role, observed_at, source_ref, summary, details, created_at
446
+ )
447
+ VALUES (?, ?, 'merged', NULL, ?, ?, ?, ?, ?)
448
+ `)
449
+ .run(timelineEventId, source_id, mergedAt, candidate_id, `Merged into ${target_id}`, JSON.stringify({ target_entity_id: target_id, reason, actor_id, actor_type }), mergedAt);
450
+ adapter
451
+ .prepare(`
452
+ INSERT INTO entity_merge_actions (
453
+ id, action_type, source_entity_id, target_entity_id, candidate_id,
454
+ actor_type, actor_id, reason, evidence_json, created_at
455
+ )
456
+ VALUES (?, 'merge', ?, ?, ?, ?, ?, ?, ?, ?)
457
+ `)
458
+ .run(mergeActionId, source_id, target_id, candidate_id, actor_type, actor_id, reason, evidence_json, mergedAt);
459
+ return {
460
+ merge_action_id: mergeActionId,
461
+ timeline_event_id: timelineEventId,
462
+ merged_at: mergedAt,
463
+ };
464
+ }
465
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1,162 @@
1
+ export declare const ENTITY_KINDS: readonly ["project", "person", "organization", "work_item"];
2
+ export type EntityKind = (typeof ENTITY_KINDS)[number];
3
+ export declare const ENTITY_ALIAS_LABEL_TYPES: readonly ["pref", "alt", "hidden", "source_native"];
4
+ export type EntityAliasLabelType = (typeof ENTITY_ALIAS_LABEL_TYPES)[number];
5
+ export declare const ENTITY_CANDIDATE_STATUSES: readonly ["pending", "auto_merged", "approved", "rejected", "deferred"];
6
+ export type EntityCandidateStatus = (typeof ENTITY_CANDIDATE_STATUSES)[number];
7
+ export declare const ENTITY_MERGE_ACTION_TYPES: readonly ["merge", "reject", "defer", "split"];
8
+ export type EntityMergeActionType = (typeof ENTITY_MERGE_ACTION_TYPES)[number];
9
+ export declare const ENTITY_SCOPE_KINDS: readonly ["project", "channel", "user", "global"];
10
+ export type EntityScopeKind = (typeof ENTITY_SCOPE_KINDS)[number];
11
+ export declare const ENTITY_NODE_STATUSES: readonly ["active", "merged", "archived"];
12
+ export type EntityNodeStatus = (typeof ENTITY_NODE_STATUSES)[number];
13
+ export declare const ENTITY_ALIAS_STATUSES: readonly ["active", "suppressed"];
14
+ export type EntityAliasStatus = (typeof ENTITY_ALIAS_STATUSES)[number];
15
+ export declare const ENTITY_OBSERVATION_TYPES: readonly ["generic", "author", "channel"];
16
+ export type EntityObservationType = (typeof ENTITY_OBSERVATION_TYPES)[number];
17
+ export declare const ENTITY_CANDIDATE_KINDS: readonly ["alias_to_entity", "entity_to_entity", "cluster"];
18
+ export type EntityCandidateKind = (typeof ENTITY_CANDIDATE_KINDS)[number];
19
+ export declare const ENTITY_ACTOR_TYPES: readonly ["system", "user", "agent"];
20
+ export type EntityActorType = (typeof ENTITY_ACTOR_TYPES)[number];
21
+ export declare const ENTITY_INGEST_RUN_STATUSES: readonly ["running", "complete", "failed"];
22
+ export type EntityIngestRunStatus = (typeof ENTITY_INGEST_RUN_STATUSES)[number];
23
+ export declare const ENTITY_INGEST_RUN_KINDS: readonly ["live", "replay", "backfill"];
24
+ export type EntityIngestRunKind = (typeof ENTITY_INGEST_RUN_KINDS)[number];
25
+ export declare const ENTITY_LINEAGE_CONTRIBUTION_KINDS: readonly ["seed", "merge_adopt", "manual_attach", "rollback_restore"];
26
+ export type EntityLineageContributionKind = (typeof ENTITY_LINEAGE_CONTRIBUTION_KINDS)[number];
27
+ export declare const ENTITY_LINEAGE_STATUSES: readonly ["active", "superseded", "rolled_back"];
28
+ export type EntityLineageStatus = (typeof ENTITY_LINEAGE_STATUSES)[number];
29
+ export declare const ENTITY_LINEAGE_CAPTURE_MODES: readonly ["direct", "backfilled"];
30
+ export type EntityLineageCaptureMode = (typeof ENTITY_LINEAGE_CAPTURE_MODES)[number];
31
+ export interface EntityNode {
32
+ id: string;
33
+ kind: EntityKind;
34
+ preferred_label: string;
35
+ status: EntityNodeStatus;
36
+ scope_kind: EntityScopeKind | null;
37
+ scope_id: string | null;
38
+ merged_into: string | null;
39
+ created_at: number;
40
+ updated_at: number;
41
+ }
42
+ export interface EntityAlias {
43
+ id: string;
44
+ entity_id: string;
45
+ label: string;
46
+ normalized_label: string;
47
+ lang: string | null;
48
+ script: string | null;
49
+ label_type: EntityAliasLabelType;
50
+ source_type: string;
51
+ source_ref: string | null;
52
+ confidence: number | null;
53
+ status: EntityAliasStatus;
54
+ created_at: number;
55
+ }
56
+ export interface EntityObservation {
57
+ id: string;
58
+ observation_type: EntityObservationType;
59
+ entity_kind_hint: EntityKind | null;
60
+ surface_form: string;
61
+ normalized_form: string;
62
+ lang: string | null;
63
+ script: string | null;
64
+ context_summary: string | null;
65
+ related_surface_forms: string[];
66
+ timestamp_observed: number | null;
67
+ scope_kind: EntityScopeKind;
68
+ scope_id: string | null;
69
+ extractor_version: string;
70
+ embedding_model_version: string | null;
71
+ source_connector: string;
72
+ source_locator: string | null;
73
+ source_raw_record_id: string;
74
+ created_at: number;
75
+ }
76
+ export interface EntityResolutionCandidate {
77
+ id: string;
78
+ candidate_kind: EntityCandidateKind;
79
+ left_ref: string;
80
+ right_ref: string;
81
+ status: EntityCandidateStatus;
82
+ score_total: number;
83
+ score_structural: number;
84
+ score_string: number;
85
+ score_context: number;
86
+ score_graph: number;
87
+ score_embedding: number;
88
+ rule_trace: string | null;
89
+ extractor_version: string;
90
+ embedding_model_version: string | null;
91
+ created_at: number;
92
+ updated_at: number;
93
+ }
94
+ export interface EntityLink {
95
+ id: string;
96
+ from_entity_id: string;
97
+ to_entity_id: string;
98
+ relation_type: string;
99
+ confidence: number;
100
+ source_basis: string;
101
+ created_at: number;
102
+ updated_at: number;
103
+ }
104
+ export interface EntityTimelineEvent {
105
+ id: string;
106
+ entity_id: string;
107
+ event_type: string;
108
+ role?: string | null;
109
+ valid_from: number | null;
110
+ valid_to: number | null;
111
+ observed_at: number | null;
112
+ source_ref: string | null;
113
+ summary: string;
114
+ details: string | null;
115
+ created_at: number;
116
+ }
117
+ export interface EntityMergeAction {
118
+ id: string;
119
+ action_type: EntityMergeActionType;
120
+ source_entity_id: string | null;
121
+ target_entity_id: string | null;
122
+ candidate_id: string | null;
123
+ actor_type: EntityActorType;
124
+ actor_id: string | null;
125
+ reason: string;
126
+ evidence_json: string;
127
+ created_at: number;
128
+ }
129
+ export interface EntityIngestRun {
130
+ id: string;
131
+ connector: string;
132
+ run_kind: EntityIngestRunKind;
133
+ status: EntityIngestRunStatus;
134
+ scope_key: string;
135
+ source_window_start: number | null;
136
+ source_window_end: number | null;
137
+ raw_count: number;
138
+ observation_count: number;
139
+ candidate_count: number;
140
+ reviewable_count: number;
141
+ audit_run_id: string | null;
142
+ audit_classification: 'improved' | 'stable' | 'regressed' | 'inconclusive' | null;
143
+ error_reason: string | null;
144
+ created_at: number;
145
+ completed_at: number | null;
146
+ }
147
+ export interface EntityLineageLink {
148
+ id: string;
149
+ canonical_entity_id: string;
150
+ entity_observation_id: string;
151
+ source_entity_id: string | null;
152
+ contribution_kind: EntityLineageContributionKind;
153
+ run_id: string | null;
154
+ candidate_id: string | null;
155
+ review_action_id: string | null;
156
+ status: EntityLineageStatus;
157
+ capture_mode: EntityLineageCaptureMode;
158
+ confidence: number;
159
+ created_at: number;
160
+ superseded_at: number | null;
161
+ }
162
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ENTITY_LINEAGE_CAPTURE_MODES = exports.ENTITY_LINEAGE_STATUSES = exports.ENTITY_LINEAGE_CONTRIBUTION_KINDS = exports.ENTITY_INGEST_RUN_KINDS = exports.ENTITY_INGEST_RUN_STATUSES = exports.ENTITY_ACTOR_TYPES = exports.ENTITY_CANDIDATE_KINDS = exports.ENTITY_OBSERVATION_TYPES = exports.ENTITY_ALIAS_STATUSES = exports.ENTITY_NODE_STATUSES = exports.ENTITY_SCOPE_KINDS = exports.ENTITY_MERGE_ACTION_TYPES = exports.ENTITY_CANDIDATE_STATUSES = exports.ENTITY_ALIAS_LABEL_TYPES = exports.ENTITY_KINDS = void 0;
4
+ exports.ENTITY_KINDS = ['project', 'person', 'organization', 'work_item'];
5
+ exports.ENTITY_ALIAS_LABEL_TYPES = ['pref', 'alt', 'hidden', 'source_native'];
6
+ exports.ENTITY_CANDIDATE_STATUSES = [
7
+ 'pending',
8
+ 'auto_merged',
9
+ 'approved',
10
+ 'rejected',
11
+ 'deferred',
12
+ ];
13
+ exports.ENTITY_MERGE_ACTION_TYPES = ['merge', 'reject', 'defer', 'split'];
14
+ exports.ENTITY_SCOPE_KINDS = ['project', 'channel', 'user', 'global'];
15
+ exports.ENTITY_NODE_STATUSES = ['active', 'merged', 'archived'];
16
+ exports.ENTITY_ALIAS_STATUSES = ['active', 'suppressed'];
17
+ exports.ENTITY_OBSERVATION_TYPES = ['generic', 'author', 'channel'];
18
+ exports.ENTITY_CANDIDATE_KINDS = ['alias_to_entity', 'entity_to_entity', 'cluster'];
19
+ exports.ENTITY_ACTOR_TYPES = ['system', 'user', 'agent'];
20
+ exports.ENTITY_INGEST_RUN_STATUSES = ['running', 'complete', 'failed'];
21
+ exports.ENTITY_INGEST_RUN_KINDS = ['live', 'replay', 'backfill'];
22
+ exports.ENTITY_LINEAGE_CONTRIBUTION_KINDS = [
23
+ 'seed',
24
+ 'merge_adopt',
25
+ 'manual_attach',
26
+ 'rollback_restore',
27
+ ];
28
+ exports.ENTITY_LINEAGE_STATUSES = ['active', 'superseded', 'rolled_back'];
29
+ exports.ENTITY_LINEAGE_CAPTURE_MODES = ['direct', 'backfilled'];
30
+ //# sourceMappingURL=types.js.map