@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,264 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveReadIdentity = resolveReadIdentity;
4
+ exports.loadDecisionReadIdentityIndex = loadDecisionReadIdentityIndex;
5
+ const db_manager_js_1 = require("../db-manager.js");
6
+ function normalizeText(value) {
7
+ return value.normalize('NFKC').trim().replace(/\s+/gu, ' ').toLowerCase();
8
+ }
9
+ function normalizeLooseText(value) {
10
+ return normalizeText(value ?? '')
11
+ .replace(/[_-]+/gu, ' ')
12
+ .replace(/[^\p{Letter}\p{Number}\s/]+/gu, ' ')
13
+ .replace(/\s+/gu, ' ')
14
+ .trim();
15
+ }
16
+ function humanizeTopicSegment(segment) {
17
+ return segment.normalize('NFKC').replace(/[_]+/gu, ' ').replace(/\s+/gu, ' ').trim();
18
+ }
19
+ function humanizeTopicLabel(topic) {
20
+ if (typeof topic !== 'string') {
21
+ return null;
22
+ }
23
+ const segments = topic
24
+ .split('/')
25
+ .map((segment) => humanizeTopicSegment(segment))
26
+ .filter((segment) => segment.length > 0);
27
+ if (segments.length === 0) {
28
+ const fallback = humanizeTopicSegment(topic);
29
+ return fallback.length > 0 ? fallback : null;
30
+ }
31
+ return segments.join(' / ');
32
+ }
33
+ function hasProjectishSignal(label) {
34
+ return /(?:案件|相談|窓|民泊|project|room|board|launch|client|feedback|delivery)/iu.test(label ?? '');
35
+ }
36
+ function looksLikeMachineGeneratedLabel(label) {
37
+ return /^(slack|chatwork|telegram|line|kakao)(:|$)/iu.test(label ?? '');
38
+ }
39
+ function looksLikeParticipantLabel(label) {
40
+ if (!label) {
41
+ return false;
42
+ }
43
+ const trimmed = label.trim();
44
+ if (/^(kagemusha|system)$/iu.test(trimmed)) {
45
+ return true;
46
+ }
47
+ if (hasProjectishSignal(trimmed)) {
48
+ return false;
49
+ }
50
+ if (/[·,]/u.test(trimmed)) {
51
+ return true;
52
+ }
53
+ if (trimmed.length === 0 || trimmed.length > 20) {
54
+ return false;
55
+ }
56
+ if (/[A-Za-z0-9]/u.test(trimmed)) {
57
+ return false;
58
+ }
59
+ return /^[\p{Script=Hangul}\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\s()]+$/u.test(trimmed);
60
+ }
61
+ function hasNonEmptyTopic(record) {
62
+ return typeof record.topic === 'string' && record.topic.trim().length > 0;
63
+ }
64
+ function sortCanonicalReadEntities(entities) {
65
+ return [...entities].sort((left, right) => {
66
+ if (left.kind !== right.kind) {
67
+ return left.kind.localeCompare(right.kind);
68
+ }
69
+ if (left.label !== right.label) {
70
+ return left.label.localeCompare(right.label);
71
+ }
72
+ return left.id.localeCompare(right.id);
73
+ });
74
+ }
75
+ function hasShadowConflict(record, primaryEntity) {
76
+ if (!hasNonEmptyTopic(record)) {
77
+ return false;
78
+ }
79
+ const summary = normalizeText(record.summary ?? '');
80
+ const label = normalizeText(primaryEntity.label);
81
+ if (!summary || !label) {
82
+ return false;
83
+ }
84
+ if (summary === label) {
85
+ return false;
86
+ }
87
+ if (summary.includes(label) || label.includes(summary)) {
88
+ return false;
89
+ }
90
+ return true;
91
+ }
92
+ function getLegacyTopic(record) {
93
+ return hasNonEmptyTopic(record) ? record.topic.trim() : null;
94
+ }
95
+ function getLegacyNamespace(record) {
96
+ const legacyTopic = getLegacyTopic(record);
97
+ if (!legacyTopic) {
98
+ return null;
99
+ }
100
+ const humanized = humanizeTopicSegment(legacyTopic.split('/')[0] ?? '');
101
+ if (normalizeLooseText(humanized) === 'entity') {
102
+ return null;
103
+ }
104
+ return humanized || null;
105
+ }
106
+ function selectDisplayEntity(entities, primaryEntity) {
107
+ return (entities.find((entity) => ['project', 'work_item', 'organization'].includes(entity.kind)) ??
108
+ primaryEntity);
109
+ }
110
+ function buildDisplaySubject(record, entities, primaryEntity) {
111
+ const preferredEntity = selectDisplayEntity(entities, primaryEntity);
112
+ const preferredLabel = preferredEntity.label?.trim();
113
+ const legacyNamespace = getLegacyNamespace(record);
114
+ const summary = record.summary?.trim();
115
+ if (summary && typeof record.topic === 'string' && record.topic.startsWith('entity/')) {
116
+ return summary;
117
+ }
118
+ if (legacyNamespace &&
119
+ preferredLabel &&
120
+ normalizeLooseText(preferredLabel) !== normalizeLooseText(legacyNamespace) &&
121
+ (looksLikeMachineGeneratedLabel(preferredLabel) || looksLikeParticipantLabel(preferredLabel))) {
122
+ return legacyNamespace;
123
+ }
124
+ if (preferredLabel && !looksLikeMachineGeneratedLabel(preferredLabel)) {
125
+ return preferredLabel;
126
+ }
127
+ if (legacyNamespace) {
128
+ return legacyNamespace;
129
+ }
130
+ return summary || humanizeTopicLabel(getLegacyTopic(record)) || preferredLabel || null;
131
+ }
132
+ function buildDisplayTopic(record, entities, primaryEntity) {
133
+ const summary = record.summary?.trim();
134
+ if (summary && typeof record.topic === 'string' && record.topic.startsWith('entity/')) {
135
+ return summary;
136
+ }
137
+ const preferredEntity = selectDisplayEntity(entities, primaryEntity);
138
+ const subjectLabel = preferredEntity.kind !== 'person' && preferredEntity.label.trim().length > 0
139
+ ? preferredEntity.label.trim()
140
+ : '';
141
+ const humanizedTopic = humanizeTopicLabel(getLegacyTopic(record));
142
+ if (!subjectLabel) {
143
+ return humanizedTopic || summary || null;
144
+ }
145
+ if (!humanizedTopic) {
146
+ return subjectLabel;
147
+ }
148
+ const normalizedSubject = normalizeLooseText(subjectLabel);
149
+ const normalizedTopic = normalizeLooseText(humanizedTopic);
150
+ const tokens = normalizedTopic.split(' ').filter((token) => token.length > 0);
151
+ const genericTokens = new Set(['spine', 'fb', 'fix', 'pdf', 'sd', 'ex', 'ar', 'r18', 'sp']);
152
+ const informativeTokens = tokens.filter((token) => !genericTokens.has(token) && !/^\d+$/u.test(token) && token.length > 1);
153
+ const lowSignalTopic = normalizedTopic.length === 0 || informativeTokens.length === 0;
154
+ if (lowSignalTopic || normalizedTopic === normalizedSubject) {
155
+ return subjectLabel;
156
+ }
157
+ return humanizedTopic;
158
+ }
159
+ function buildDisplaySuffix(record, entities, primaryEntity) {
160
+ const displaySubject = buildDisplaySubject(record, entities, primaryEntity);
161
+ const displayTopic = buildDisplayTopic(record, entities, primaryEntity);
162
+ if (!displaySubject || !displayTopic) {
163
+ return null;
164
+ }
165
+ const normalizedSubject = normalizeLooseText(displaySubject);
166
+ const normalizedTopic = normalizeLooseText(displayTopic);
167
+ if (!normalizedTopic || normalizedTopic === normalizedSubject) {
168
+ return null;
169
+ }
170
+ const subjectParts = displaySubject
171
+ .split('/')
172
+ .map((part) => part.trim())
173
+ .filter(Boolean);
174
+ const topicParts = displayTopic
175
+ .split('/')
176
+ .map((part) => part.trim())
177
+ .filter(Boolean);
178
+ if (topicParts.length > 1 && subjectParts.length > 0) {
179
+ const firstTopicPart = topicParts[0] ?? '';
180
+ const firstSubjectPart = subjectParts[subjectParts.length - 1] ?? '';
181
+ if (normalizeLooseText(firstTopicPart) === normalizeLooseText(firstSubjectPart)) {
182
+ const suffix = topicParts.slice(1).join(' / ').trim();
183
+ return suffix.length > 0 ? suffix : null;
184
+ }
185
+ }
186
+ return displayTopic;
187
+ }
188
+ function resolveReadIdentity(record, entities) {
189
+ if (entities.length > 0) {
190
+ const sortedEntities = sortCanonicalReadEntities(entities);
191
+ const primaryEntity = sortedEntities[0];
192
+ return {
193
+ kind: 'canonical',
194
+ entities: sortedEntities,
195
+ primaryEntity,
196
+ legacyTopic: getLegacyTopic(record),
197
+ shadowConflict: hasShadowConflict(record, primaryEntity),
198
+ displaySubject: buildDisplaySubject(record, sortedEntities, primaryEntity),
199
+ displayTopic: buildDisplayTopic(record, sortedEntities, primaryEntity),
200
+ displaySuffix: buildDisplaySuffix(record, sortedEntities, primaryEntity),
201
+ };
202
+ }
203
+ if (hasNonEmptyTopic(record)) {
204
+ return {
205
+ kind: 'topic',
206
+ topic: record.topic.trim(),
207
+ displaySubject: null,
208
+ displayTopic: humanizeTopicLabel(record.topic),
209
+ displaySuffix: null,
210
+ };
211
+ }
212
+ return {
213
+ kind: 'raw',
214
+ label: record.summary.trim() || '<unknown>',
215
+ displaySubject: null,
216
+ displayTopic: record.summary.trim() || '<unknown>',
217
+ displaySuffix: null,
218
+ };
219
+ }
220
+ async function loadDecisionReadIdentityIndex(decisionIds) {
221
+ await (0, db_manager_js_1.initDB)();
222
+ const uniqueDecisionIds = Array.from(new Set(decisionIds
223
+ .filter((decisionId) => typeof decisionId === 'string')
224
+ .map((decisionId) => decisionId.trim())
225
+ .filter((decisionId) => decisionId.length > 0)));
226
+ const index = new Map();
227
+ if (uniqueDecisionIds.length === 0) {
228
+ return index;
229
+ }
230
+ const placeholders = uniqueDecisionIds.map(() => '?').join(', ');
231
+ const rows = (0, db_manager_js_1.getAdapter)()
232
+ .prepare(`
233
+ SELECT
234
+ des.decision_id,
235
+ n.id AS entity_id,
236
+ n.preferred_label AS entity_label,
237
+ n.kind AS entity_kind
238
+ FROM decision_entity_sources des
239
+ JOIN entity_lineage_links l
240
+ ON l.entity_observation_id = des.entity_observation_id
241
+ AND l.status = 'active'
242
+ JOIN entity_nodes n
243
+ ON n.id = l.canonical_entity_id
244
+ WHERE des.decision_id IN (${placeholders})
245
+ AND n.status = 'active'
246
+ AND n.merged_into IS NULL
247
+ GROUP BY des.decision_id, n.id, n.preferred_label, n.kind
248
+ `)
249
+ .all(...uniqueDecisionIds);
250
+ for (const row of rows) {
251
+ const existing = index.get(row.decision_id) ?? [];
252
+ existing.push({
253
+ id: row.entity_id,
254
+ label: row.entity_label,
255
+ kind: row.entity_kind,
256
+ });
257
+ index.set(row.decision_id, existing);
258
+ }
259
+ for (const [decisionId, entities] of index.entries()) {
260
+ index.set(decisionId, sortCanonicalReadEntities(entities));
261
+ }
262
+ return index;
263
+ }
264
+ //# sourceMappingURL=read-identity.js.map
@@ -0,0 +1,5 @@
1
+ import type { MemoryRecord, MemoryScopeRef } from '../memory/types.js';
2
+ export declare function queryCanonicalEntities(query: string, scopes: MemoryScopeRef[], options?: {
3
+ limit?: number;
4
+ }): Promise<MemoryRecord[]>;
5
+ //# sourceMappingURL=recall-bridge.d.ts.map
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.queryCanonicalEntities = queryCanonicalEntities;
4
+ const db_manager_js_1 = require("../db-manager.js");
5
+ const projection_js_1 = require("./projection.js");
6
+ const read_identity_js_1 = require("./read-identity.js");
7
+ const store_js_1 = require("./store.js");
8
+ function mapNode(row) {
9
+ return {
10
+ id: String(row.id),
11
+ kind: row.kind,
12
+ preferred_label: String(row.preferred_label),
13
+ status: row.status,
14
+ scope_kind: typeof row.scope_kind === 'string' ? row.scope_kind : null,
15
+ scope_id: typeof row.scope_id === 'string' ? row.scope_id : null,
16
+ merged_into: typeof row.merged_into === 'string' ? row.merged_into : null,
17
+ created_at: Number(row.created_at),
18
+ updated_at: Number(row.updated_at),
19
+ };
20
+ }
21
+ function mapAlias(row) {
22
+ return {
23
+ id: String(row.id),
24
+ entity_id: String(row.entity_id),
25
+ label: String(row.label),
26
+ normalized_label: String(row.normalized_label),
27
+ lang: typeof row.lang === 'string' ? row.lang : null,
28
+ script: typeof row.script === 'string' ? row.script : null,
29
+ label_type: row.label_type,
30
+ source_type: String(row.source_type),
31
+ source_ref: typeof row.source_ref === 'string' ? row.source_ref : null,
32
+ confidence: typeof row.confidence === 'number' ? row.confidence : null,
33
+ status: row.status,
34
+ created_at: Number(row.created_at),
35
+ };
36
+ }
37
+ function mapTimeline(row) {
38
+ return {
39
+ id: String(row.id),
40
+ entity_id: String(row.entity_id),
41
+ event_type: String(row.event_type),
42
+ valid_from: typeof row.valid_from === 'number' ? row.valid_from : null,
43
+ valid_to: typeof row.valid_to === 'number' ? row.valid_to : null,
44
+ observed_at: typeof row.observed_at === 'number' ? row.observed_at : null,
45
+ source_ref: typeof row.source_ref === 'string' ? row.source_ref : null,
46
+ summary: String(row.summary),
47
+ details: typeof row.details === 'string' ? row.details : null,
48
+ created_at: Number(row.created_at),
49
+ };
50
+ }
51
+ function escapeLikeQuery(query) {
52
+ return query.replace(/[\\%_]/g, '\\$&');
53
+ }
54
+ async function queryCanonicalEntities(query, scopes, options = {}) {
55
+ await (0, db_manager_js_1.initDB)();
56
+ const adapter = (0, db_manager_js_1.getAdapter)();
57
+ const escapedQuery = escapeLikeQuery(query);
58
+ // Match aliases and labels regardless of whether the owning node is merged,
59
+ // then chain-walk each match to its canonical terminal. Without this,
60
+ // searching by the old name of a merged entity returns nothing because the
61
+ // filter `n.status='active' AND n.merged_into IS NULL` would drop the
62
+ // source row. Matches the read-time chain-walking approach in the canonical
63
+ // entity ontology implementation plan.
64
+ const matchedRows = adapter
65
+ .prepare(`
66
+ SELECT DISTINCT n.id AS matched_id
67
+ FROM entity_nodes n
68
+ LEFT JOIN entity_aliases a ON a.entity_id = n.id
69
+ LEFT JOIN entity_lineage_links l ON l.canonical_entity_id = n.id AND l.status = 'active'
70
+ LEFT JOIN entity_observations o ON o.id = l.entity_observation_id
71
+ WHERE (
72
+ lower(n.preferred_label) LIKE '%' || lower(?) || '%' ESCAPE '\\'
73
+ OR lower(COALESCE(a.label, '')) LIKE '%' || lower(?) || '%' ESCAPE '\\'
74
+ OR lower(COALESCE(o.surface_form, '')) LIKE '%' || lower(?) || '%' ESCAPE '\\'
75
+ )
76
+ `)
77
+ .all(escapedQuery, escapedQuery, escapedQuery);
78
+ const canonicalIdSet = new Set();
79
+ for (const match of matchedRows) {
80
+ try {
81
+ canonicalIdSet.add((0, store_js_1.resolveCanonicalEntityId)(adapter, match.matched_id));
82
+ }
83
+ catch (err) {
84
+ // Cycle or missing node — skip this match rather than poison the whole
85
+ // recall. Merge integrity errors are surfaced elsewhere (the review
86
+ // handler 409 path) where the user can actually act on them.
87
+ if (!(err instanceof store_js_1.EntityMergeError)) {
88
+ throw err;
89
+ }
90
+ }
91
+ }
92
+ if (canonicalIdSet.size === 0) {
93
+ return [];
94
+ }
95
+ const canonicalIds = Array.from(canonicalIdSet);
96
+ const placeholders = canonicalIds.map(() => '?').join(', ');
97
+ const rows = adapter
98
+ .prepare(`
99
+ SELECT *
100
+ FROM entity_nodes
101
+ WHERE id IN (${placeholders})
102
+ AND status = 'active'
103
+ AND merged_into IS NULL
104
+ ORDER BY updated_at DESC
105
+ `)
106
+ .all(...canonicalIds);
107
+ const scopedRows = scopes.length === 0
108
+ ? rows
109
+ : rows.filter((row) =>
110
+ // Fix 2 part B (P0): globally-scoped entities are not bound to
111
+ // any narrower scope and must surface in every scoped query.
112
+ // Without this passthrough, switching person observations from
113
+ // channel-scope to global-scope (Fix 2 part A in
114
+ // history-extractor.ts) would make persons invisible to scoped
115
+ // recall callers, breaking the time-travel use case the fix is
116
+ // meant to enable. scope_id is intentionally not compared for
117
+ // globals — the global predicate is `scope_kind='global'` alone.
118
+ row.scope_kind === 'global' ||
119
+ scopes.some((scope) => scope.kind === row.scope_kind && row.scope_id === scope.id));
120
+ const limitedRows = scopedRows.slice(0, options.limit ?? 10);
121
+ const nodeLookup = Object.fromEntries(limitedRows.map((row) => [String(row.id), mapNode(row)]));
122
+ const entityIds = limitedRows.map((row) => String(row.id));
123
+ const aliasesByEntity = new Map();
124
+ const latestTimelineByEntity = new Map();
125
+ if (entityIds.length > 0) {
126
+ const placeholders = entityIds.map(() => '?').join(', ');
127
+ const aliasRows = adapter
128
+ .prepare(`
129
+ SELECT * FROM entity_aliases
130
+ WHERE entity_id IN (${placeholders})
131
+ ORDER BY entity_id ASC, created_at ASC
132
+ `)
133
+ .all(...entityIds);
134
+ for (const row of aliasRows) {
135
+ const alias = mapAlias(row);
136
+ const existing = aliasesByEntity.get(alias.entity_id) ?? [];
137
+ existing.push(alias);
138
+ aliasesByEntity.set(alias.entity_id, existing);
139
+ }
140
+ const timelineRows = adapter
141
+ .prepare(`
142
+ SELECT *
143
+ FROM (
144
+ SELECT *,
145
+ ROW_NUMBER() OVER (
146
+ PARTITION BY entity_id
147
+ ORDER BY COALESCE(observed_at, created_at) DESC
148
+ ) AS row_num
149
+ FROM entity_timeline_events
150
+ WHERE entity_id IN (${placeholders})
151
+ )
152
+ WHERE row_num = 1
153
+ `)
154
+ .all(...entityIds);
155
+ for (const row of timelineRows) {
156
+ const timeline = mapTimeline(row);
157
+ latestTimelineByEntity.set(timeline.entity_id, timeline);
158
+ }
159
+ }
160
+ const result = [];
161
+ for (const row of limitedRows) {
162
+ const node = mapNode(row);
163
+ const projected = (0, projection_js_1.projectEntityToRecallSummary)(node, aliasesByEntity.get(node.id) ?? [], latestTimelineByEntity.get(node.id) ?? null, { nodeLookup });
164
+ projected.read_identity = (0, read_identity_js_1.resolveReadIdentity)(projected, [
165
+ { id: node.id, label: node.preferred_label, kind: node.kind },
166
+ ]);
167
+ result.push(projected);
168
+ }
169
+ return result;
170
+ }
171
+ //# sourceMappingURL=recall-bridge.js.map
@@ -0,0 +1,15 @@
1
+ import type { EntityMergeAction, EntityObservation, EntityResolutionCandidate } from './types.js';
2
+ export interface ResolutionDecision {
3
+ candidate: EntityResolutionCandidate;
4
+ decision: 'auto_merge' | 'review';
5
+ reason: string;
6
+ actions: EntityMergeAction[];
7
+ }
8
+ export declare function shouldAutoMerge(result: ResolutionDecision): boolean;
9
+ export declare function buildReviewReason(result: ResolutionDecision): string;
10
+ export declare function evaluateResolutionCandidate(input: {
11
+ candidate: EntityResolutionCandidate;
12
+ left: EntityObservation;
13
+ right: EntityObservation;
14
+ }): ResolutionDecision;
15
+ //# sourceMappingURL=resolution-engine.d.ts.map
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.shouldAutoMerge = shouldAutoMerge;
4
+ exports.buildReviewReason = buildReviewReason;
5
+ exports.evaluateResolutionCandidate = evaluateResolutionCandidate;
6
+ const errors_js_1 = require("./errors.js");
7
+ function isCrossLanguage(left, right) {
8
+ return Boolean(left.lang && right.lang && left.lang !== right.lang);
9
+ }
10
+ function shouldAutoMerge(result) {
11
+ return result.decision === 'auto_merge';
12
+ }
13
+ function buildReviewReason(result) {
14
+ return result.reason;
15
+ }
16
+ function evaluateResolutionCandidate(input) {
17
+ const { candidate, left, right } = input;
18
+ if (left.entity_kind_hint &&
19
+ right.entity_kind_hint &&
20
+ left.entity_kind_hint !== right.entity_kind_hint) {
21
+ throw new errors_js_1.OntologyViolationError({
22
+ left_entity_kind: left.entity_kind_hint,
23
+ right_entity_kind: right.entity_kind_hint,
24
+ candidate_id: candidate.id,
25
+ });
26
+ }
27
+ const sameNormalized = left.normalized_form === right.normalized_form;
28
+ const exactStructural = candidate.score_structural >= 1 && sameNormalized;
29
+ const crossLanguage = isCrossLanguage(left, right);
30
+ if (exactStructural && !crossLanguage) {
31
+ return {
32
+ candidate,
33
+ decision: 'auto_merge',
34
+ reason: 'Exact structural match with no cross-language ambiguity.',
35
+ actions: [
36
+ {
37
+ id: `merge_${candidate.id}`,
38
+ action_type: 'merge',
39
+ source_entity_id: candidate.left_ref,
40
+ target_entity_id: candidate.right_ref,
41
+ candidate_id: candidate.id,
42
+ actor_type: 'system',
43
+ actor_id: 'resolution-engine',
44
+ reason: 'Exact structural match with no cross-language ambiguity.',
45
+ evidence_json: JSON.stringify({
46
+ score_structural: candidate.score_structural,
47
+ normalized_form: left.normalized_form,
48
+ }),
49
+ created_at: Date.now(),
50
+ },
51
+ ],
52
+ };
53
+ }
54
+ if (crossLanguage && candidate.score_embedding > 0) {
55
+ return {
56
+ candidate,
57
+ decision: 'review',
58
+ reason: 'High-similarity cross-language candidate requires human review.',
59
+ actions: [],
60
+ };
61
+ }
62
+ return {
63
+ candidate,
64
+ decision: 'review',
65
+ reason: 'Candidate requires manual review before merge.',
66
+ actions: [],
67
+ };
68
+ }
69
+ //# sourceMappingURL=resolution-engine.js.map
@@ -0,0 +1,39 @@
1
+ import { type EntityStoreAdapter } from './store.js';
2
+ export interface RollbackPreviewInput {
3
+ entityId: string;
4
+ mergeActionId?: string;
5
+ observationId?: string;
6
+ maxAffectedRows?: number;
7
+ }
8
+ export interface RollbackPreviewEntityChange {
9
+ entity_id: string;
10
+ label: string;
11
+ status_after: 'active' | 'merged';
12
+ active_lineage_after: number;
13
+ summary: string;
14
+ }
15
+ export interface RollbackPreviewMemoryChange {
16
+ id: string;
17
+ topic: string;
18
+ summary: string;
19
+ created_at: number;
20
+ }
21
+ export interface RollbackPreviewMetricMovement {
22
+ metric: 'false_merge_rate' | 'projection_fragmentation_rate';
23
+ direction: 'increase' | 'decrease';
24
+ reason: string;
25
+ }
26
+ export interface RollbackPreviewResult {
27
+ entity_id: string;
28
+ merge_action_id: string | null;
29
+ preview_unavailable: boolean;
30
+ history_incomplete: boolean;
31
+ truncated: boolean;
32
+ changed_entities: RollbackPreviewEntityChange[];
33
+ changed_memories: RollbackPreviewMemoryChange[];
34
+ metric_movement: RollbackPreviewMetricMovement[];
35
+ }
36
+ export declare function previewEntityRollback(input: RollbackPreviewInput, options?: {
37
+ adapter?: EntityStoreAdapter;
38
+ }): Promise<RollbackPreviewResult>;
39
+ //# sourceMappingURL=rollback-preview.d.ts.map