@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,25 @@
1
+ import type { CaseMembershipSourceType } from './types.js';
2
+ export type CaseFieldName = 'case_id' | 'canonical_case_id' | 'title' | 'status' | 'status_reason' | 'primary_actors' | 'blockers' | 'confidence' | 'last_activity_at';
3
+ export interface CaseFieldTargetRef {
4
+ kind: 'case_field';
5
+ field: CaseFieldName;
6
+ }
7
+ export interface CaseMembershipTargetRef {
8
+ kind: 'membership';
9
+ source_type: CaseMembershipSourceType;
10
+ source_id: string;
11
+ }
12
+ export interface CaseWikiSectionTargetRef {
13
+ kind: 'wiki_section';
14
+ section_heading: string;
15
+ }
16
+ export type CaseTargetRef = CaseFieldTargetRef | CaseMembershipTargetRef | CaseWikiSectionTargetRef;
17
+ export interface CanonicalTargetRef {
18
+ json: string;
19
+ hash: Buffer;
20
+ }
21
+ export declare function buildCaseFieldTargetRef(fieldName: CaseFieldName): CaseFieldTargetRef;
22
+ export declare function buildMembershipTargetRef(sourceType: CaseMembershipSourceType, sourceId: string): CaseMembershipTargetRef;
23
+ export declare function buildWikiSectionTargetRef(sectionHeading: string): CaseWikiSectionTargetRef;
24
+ export declare function canonicalTargetRef(targetRef: CaseTargetRef): CanonicalTargetRef;
25
+ //# sourceMappingURL=target-ref.d.ts.map
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildCaseFieldTargetRef = buildCaseFieldTargetRef;
4
+ exports.buildMembershipTargetRef = buildMembershipTargetRef;
5
+ exports.buildWikiSectionTargetRef = buildWikiSectionTargetRef;
6
+ exports.canonicalTargetRef = canonicalTargetRef;
7
+ const canonicalize_js_1 = require("../canonicalize.js");
8
+ function buildCaseFieldTargetRef(fieldName) {
9
+ return { kind: 'case_field', field: fieldName };
10
+ }
11
+ function buildMembershipTargetRef(sourceType, sourceId) {
12
+ return { kind: 'membership', source_type: sourceType, source_id: sourceId };
13
+ }
14
+ function buildWikiSectionTargetRef(sectionHeading) {
15
+ return { kind: 'wiki_section', section_heading: sectionHeading };
16
+ }
17
+ function canonicalTargetRef(targetRef) {
18
+ const json = (0, canonicalize_js_1.canonicalizeJSON)(targetRef);
19
+ const hash = (0, canonicalize_js_1.targetRefHashCanonicalJSON)(json);
20
+ return { json, hash };
21
+ }
22
+ //# sourceMappingURL=target-ref.js.map
@@ -0,0 +1,6 @@
1
+ import type { DatabaseAdapter } from '../db-manager.js';
2
+ import type { CaseTimelineRangeInput, CaseTimelineRangeResult } from './types.js';
3
+ type CaseTimelineRangeAdapter = Pick<DatabaseAdapter, 'prepare' | 'transaction'>;
4
+ export declare function caseTimelineRange(adapter: CaseTimelineRangeAdapter, input: CaseTimelineRangeInput): CaseTimelineRangeResult;
5
+ export type { CaseTimelineRangeInput, CaseTimelineRangeItem, CaseTimelineRangeResult, } from './types.js';
6
+ //# sourceMappingURL=timeline-range.d.ts.map
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.caseTimelineRange = caseTimelineRange;
4
+ const event_index_js_1 = require("../connectors/event-index.js");
5
+ const store_js_1 = require("./store.js");
6
+ function codedError(code, message, context) {
7
+ const error = new Error(`${code}: ${message}`);
8
+ error.code = code;
9
+ error.context = context;
10
+ return error;
11
+ }
12
+ function placeholders(values) {
13
+ if (values.length === 0) {
14
+ throw new Error('Cannot build SQL placeholders for an empty list.');
15
+ }
16
+ return values.map(() => '?').join(', ');
17
+ }
18
+ function nullableString(value) {
19
+ if (value === null || value === undefined) {
20
+ return null;
21
+ }
22
+ return String(value);
23
+ }
24
+ function nullableNumber(value) {
25
+ if (value === null || value === undefined) {
26
+ return null;
27
+ }
28
+ const numberValue = Number(value);
29
+ return Number.isFinite(numberValue) ? numberValue : null;
30
+ }
31
+ function parseDateBound(value, field) {
32
+ if (value === undefined) {
33
+ return null;
34
+ }
35
+ if (typeof value === 'number') {
36
+ if (!Number.isFinite(value)) {
37
+ throw codedError('case.timeline_range_invalid_date', `${field} must be a finite number.`, {
38
+ field,
39
+ value,
40
+ });
41
+ }
42
+ return Math.floor(value);
43
+ }
44
+ const parsed = Date.parse(value);
45
+ if (!Number.isFinite(parsed)) {
46
+ throw codedError('case.timeline_range_invalid_date', `${field} is not a valid date.`, {
47
+ field,
48
+ value,
49
+ });
50
+ }
51
+ return parsed;
52
+ }
53
+ function parseBounds(input) {
54
+ const fromMs = parseDateBound(input.from, 'from');
55
+ const toMs = parseDateBound(input.to, 'to');
56
+ if (fromMs !== null && toMs !== null && fromMs > toMs) {
57
+ throw codedError('case.timeline_range_invalid_order', 'from must be before or equal to to.', {
58
+ from: input.from,
59
+ to: input.to,
60
+ });
61
+ }
62
+ return { fromMs, toMs };
63
+ }
64
+ function normalizeLimit(limit) {
65
+ if (limit === undefined) {
66
+ return 100;
67
+ }
68
+ return Math.min(500, Math.max(0, Math.floor(limit)));
69
+ }
70
+ function timestampMs(value) {
71
+ if (value === null || value === undefined) {
72
+ return null;
73
+ }
74
+ if (typeof value === 'number') {
75
+ return Number.isFinite(value) ? value : null;
76
+ }
77
+ const text = String(value);
78
+ if (text.length === 0) {
79
+ return null;
80
+ }
81
+ const numeric = Number(text);
82
+ if (Number.isFinite(numeric) && /^\d+$/u.test(text)) {
83
+ return numeric;
84
+ }
85
+ const parsed = Date.parse(text);
86
+ return Number.isFinite(parsed) ? parsed : null;
87
+ }
88
+ function eventDateFromMs(ms) {
89
+ if (ms === null) {
90
+ return null;
91
+ }
92
+ return new Date(ms).toISOString().slice(0, 10);
93
+ }
94
+ function withinBounds(ms, bounds) {
95
+ if (ms === null) {
96
+ return bounds.fromMs === null && bounds.toMs === null;
97
+ }
98
+ if (bounds.fromMs !== null && ms < bounds.fromMs) {
99
+ return false;
100
+ }
101
+ if (bounds.toMs !== null && ms > bounds.toMs) {
102
+ return false;
103
+ }
104
+ return true;
105
+ }
106
+ function compareItemsAsc(left, right) {
107
+ const leftMs = left.event_datetime ?? Number.NEGATIVE_INFINITY;
108
+ const rightMs = right.event_datetime ?? Number.NEGATIVE_INFINITY;
109
+ if (leftMs !== rightMs) {
110
+ return leftMs - rightMs;
111
+ }
112
+ const itemDelta = left.item_type.localeCompare(right.item_type);
113
+ if (itemDelta !== 0) {
114
+ return itemDelta;
115
+ }
116
+ const sourceTypeDelta = left.source_type.localeCompare(right.source_type);
117
+ if (sourceTypeDelta !== 0) {
118
+ return sourceTypeDelta;
119
+ }
120
+ return left.source_id.localeCompare(right.source_id);
121
+ }
122
+ function connectorEventSnapshot(row) {
123
+ if (!row) {
124
+ return null;
125
+ }
126
+ return {
127
+ event_index_id: row.event_index_id,
128
+ source_connector: row.source_connector,
129
+ source_id: row.source_id,
130
+ source_locator: row.source_locator,
131
+ artifact_locator: row.artifact_locator,
132
+ title: row.title,
133
+ content: row.content,
134
+ event_datetime: row.event_datetime,
135
+ event_date: row.event_date,
136
+ };
137
+ }
138
+ function membershipById(memberships, sourceType) {
139
+ return new Map(memberships
140
+ .filter((membership) => membership.source_type === sourceType)
141
+ .map((membership) => [membership.source_id, membership]));
142
+ }
143
+ function loadDecisionItems(adapter, memberships, bounds) {
144
+ const membershipMap = membershipById(memberships, 'decision');
145
+ const ids = Array.from(membershipMap.keys());
146
+ if (ids.length === 0) {
147
+ return [];
148
+ }
149
+ const rows = adapter
150
+ .prepare(`
151
+ SELECT id, topic, decision, summary, reasoning, confidence, event_datetime, event_date, created_at
152
+ FROM decisions
153
+ WHERE id IN (${placeholders(ids)})
154
+ `)
155
+ .all(...ids);
156
+ return rows
157
+ .map((row) => {
158
+ const sourceId = String(row.id);
159
+ const membership = membershipMap.get(sourceId);
160
+ const eventDatetime = timestampMs(row.event_datetime) ??
161
+ timestampMs(row.event_date) ??
162
+ timestampMs(row.created_at);
163
+ return {
164
+ item_type: 'decision',
165
+ source_type: 'decision',
166
+ source_id: sourceId,
167
+ source_locator: null,
168
+ event_datetime: eventDatetime,
169
+ event_date: nullableString(row.event_date) ?? eventDateFromMs(eventDatetime),
170
+ title: nullableString(row.topic),
171
+ summary: nullableString(row.summary ?? row.decision),
172
+ role: membership?.role ?? null,
173
+ confidence: membership?.confidence ?? nullableNumber(row.confidence),
174
+ membership_reason: membership?.reason ?? null,
175
+ };
176
+ })
177
+ .filter((item) => withinBounds(item.event_datetime, bounds));
178
+ }
179
+ function loadEventItems(adapter, memberships, bounds) {
180
+ const membershipMap = membershipById(memberships, 'event');
181
+ const ids = Array.from(membershipMap.keys());
182
+ if (ids.length === 0) {
183
+ return [];
184
+ }
185
+ const rows = adapter
186
+ .prepare(`
187
+ SELECT id, event_type, role, observed_at, source_ref, summary, details, created_at
188
+ FROM entity_timeline_events
189
+ WHERE id IN (${placeholders(ids)})
190
+ `)
191
+ .all(...ids);
192
+ return rows
193
+ .map((row) => {
194
+ const sourceId = String(row.id);
195
+ const membership = membershipMap.get(sourceId);
196
+ const eventDatetime = timestampMs(row.observed_at) ?? timestampMs(row.created_at);
197
+ return {
198
+ item_type: 'event',
199
+ source_type: 'event',
200
+ source_id: sourceId,
201
+ source_locator: nullableString(row.source_ref),
202
+ event_datetime: eventDatetime,
203
+ event_date: eventDateFromMs(eventDatetime),
204
+ title: nullableString(row.event_type),
205
+ summary: nullableString(row.summary),
206
+ role: membership?.role ?? nullableString(row.role),
207
+ confidence: membership?.confidence ?? null,
208
+ membership_reason: membership?.reason ?? null,
209
+ };
210
+ })
211
+ .filter((item) => withinBounds(item.event_datetime, bounds));
212
+ }
213
+ function loadObservationItems(adapter, memberships, bounds, includeConnectorEnrichments) {
214
+ const membershipMap = membershipById(memberships, 'observation');
215
+ const ids = Array.from(membershipMap.keys());
216
+ if (ids.length === 0) {
217
+ return [];
218
+ }
219
+ const rows = adapter
220
+ .prepare(`
221
+ SELECT id, surface_form, context_summary, source_connector, source_raw_record_id,
222
+ source_locator, timestamp_observed, created_at
223
+ FROM entity_observations
224
+ WHERE id IN (${placeholders(ids)})
225
+ `)
226
+ .all(...ids);
227
+ return rows
228
+ .map((row) => {
229
+ const sourceId = String(row.id);
230
+ const membership = membershipMap.get(sourceId);
231
+ const eventDatetime = timestampMs(row.timestamp_observed) ?? timestampMs(row.created_at);
232
+ const connectorEvent = includeConnectorEnrichments &&
233
+ typeof row.source_connector === 'string' &&
234
+ typeof row.source_raw_record_id === 'string'
235
+ ? (0, event_index_js_1.getConnectorEventIndexRecord)(adapter, String(row.source_connector), String(row.source_raw_record_id))
236
+ : null;
237
+ return {
238
+ item_type: 'observation',
239
+ source_type: 'observation',
240
+ source_id: sourceId,
241
+ source_locator: nullableString(row.source_locator),
242
+ event_datetime: eventDatetime,
243
+ event_date: eventDateFromMs(eventDatetime),
244
+ title: nullableString(row.surface_form),
245
+ summary: nullableString(row.context_summary ?? row.surface_form),
246
+ role: membership?.role ?? null,
247
+ confidence: membership?.confidence ?? null,
248
+ membership_reason: membership?.reason ?? null,
249
+ ...(includeConnectorEnrichments
250
+ ? { connector_event: connectorEventSnapshot(connectorEvent) }
251
+ : {}),
252
+ };
253
+ })
254
+ .filter((item) => withinBounds(item.event_datetime, bounds));
255
+ }
256
+ function loadArtifactConnectorEvent(adapter, sourceId) {
257
+ const row = adapter
258
+ .prepare(`
259
+ SELECT *
260
+ FROM connector_event_index
261
+ WHERE event_index_id = ?
262
+ OR artifact_locator = ?
263
+ ORDER BY event_datetime ASC, event_index_id ASC
264
+ LIMIT 1
265
+ `)
266
+ .get(sourceId, sourceId);
267
+ if (!row) {
268
+ return null;
269
+ }
270
+ const sourceConnector = String(row.source_connector);
271
+ const connectorSourceId = String(row.source_id);
272
+ return (0, event_index_js_1.getConnectorEventIndexRecord)(adapter, sourceConnector, connectorSourceId);
273
+ }
274
+ function loadArtifactItems(adapter, memberships, bounds, includeConnectorEnrichments) {
275
+ const artifactMemberships = memberships.filter((membership) => membership.source_type === 'artifact');
276
+ return artifactMemberships
277
+ .map((membership) => {
278
+ const connectorEvent = loadArtifactConnectorEvent(adapter, membership.source_id);
279
+ const eventDatetime = connectorEvent?.event_datetime ?? null;
280
+ return {
281
+ item_type: 'artifact',
282
+ source_type: 'artifact',
283
+ source_id: membership.source_id,
284
+ source_locator: connectorEvent?.artifact_locator ??
285
+ connectorEvent?.source_locator ??
286
+ membership.source_id,
287
+ event_datetime: eventDatetime,
288
+ event_date: connectorEvent?.event_date ?? eventDateFromMs(eventDatetime),
289
+ title: connectorEvent?.artifact_title ?? connectorEvent?.title ?? membership.source_id,
290
+ summary: connectorEvent?.content ?? null,
291
+ role: membership.role,
292
+ confidence: membership.confidence,
293
+ membership_reason: membership.reason,
294
+ ...(includeConnectorEnrichments
295
+ ? { connector_event: connectorEventSnapshot(connectorEvent) }
296
+ : {}),
297
+ };
298
+ })
299
+ .filter((item) => withinBounds(item.event_datetime, bounds));
300
+ }
301
+ function caseTimelineRange(adapter, input) {
302
+ const bounds = parseBounds(input);
303
+ const limit = normalizeLimit(input.limit);
304
+ const resolution = (0, store_js_1.resolveCanonicalCaseChain)(adapter, input.case_id);
305
+ const chainResult = (0, store_js_1.listActiveCorrectionsForCase)(adapter, input.case_id);
306
+ const chain = chainResult.chain;
307
+ const memberships = (0, store_js_1.listActiveMembershipsForCaseChain)(adapter, chain);
308
+ const items = [
309
+ ...loadDecisionItems(adapter, memberships, bounds),
310
+ ...loadEventItems(adapter, memberships, bounds),
311
+ ...loadObservationItems(adapter, memberships, bounds, input.include_connector_enrichments === true),
312
+ ...loadArtifactItems(adapter, memberships, bounds, input.include_connector_enrichments === true),
313
+ ].sort(compareItemsAsc);
314
+ const orderedItems = input.order === 'desc' ? [...items].reverse() : items;
315
+ return {
316
+ terminal_case_id: resolution.terminal_case_id,
317
+ resolved_via_case_id: resolution.resolved_via_case_id,
318
+ chain,
319
+ items: orderedItems.slice(0, limit),
320
+ };
321
+ }
322
+ //# sourceMappingURL=timeline-range.js.map
@@ -0,0 +1,13 @@
1
+ import type { DatabaseAdapter } from '../db-manager.js';
2
+ export interface SweepStaleCaseMembershipsOptions {
3
+ now?: string;
4
+ limit?: number;
5
+ }
6
+ export interface SweepStaleCaseMembershipsResult {
7
+ scanned_count: number;
8
+ stale_count: number;
9
+ locked_stale_count: number;
10
+ audit_event_ids: string[];
11
+ }
12
+ export declare function sweepStaleCaseMemberships(adapter: DatabaseAdapter, options?: SweepStaleCaseMembershipsOptions): SweepStaleCaseMembershipsResult;
13
+ //# sourceMappingURL=tombstone-sweeper.d.ts.map
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sweepStaleCaseMemberships = sweepStaleCaseMemberships;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const canonicalize_js_1 = require("../canonicalize.js");
6
+ const sqlite_transaction_js_1 = require("./sqlite-transaction.js");
7
+ const SWEEP_SOURCE_TYPES = ['decision', 'event', 'observation'];
8
+ const SOURCE_EXISTS_SQL = {
9
+ decision: 'SELECT 1 AS id FROM decisions WHERE id = ?',
10
+ event: 'SELECT 1 AS id FROM entity_timeline_events WHERE id = ?',
11
+ observation: 'SELECT 1 AS id FROM entity_observations WHERE id = ?',
12
+ };
13
+ function normalizeNow(value) {
14
+ return value ?? new Date().toISOString();
15
+ }
16
+ function createdAtMs(now) {
17
+ const parsed = Date.parse(now);
18
+ return Number.isFinite(parsed) ? parsed : Date.now();
19
+ }
20
+ function isSweptSourceType(value) {
21
+ return SWEEP_SOURCE_TYPES.includes(value);
22
+ }
23
+ function readCandidates(adapter, limit) {
24
+ const sql = `
25
+ SELECT source_type, source_id
26
+ FROM case_memberships
27
+ WHERE status IN ('active', 'candidate', 'removed', 'excluded')
28
+ AND source_type IN ('decision', 'event', 'observation')
29
+ GROUP BY source_type, source_id
30
+ ORDER BY source_type ASC, source_id ASC
31
+ ${limit === undefined ? '' : 'LIMIT ?'}
32
+ `;
33
+ const stmt = adapter.prepare(sql);
34
+ return (limit === undefined ? stmt.all() : stmt.all(limit));
35
+ }
36
+ function sourceExists(adapter, sourceType, sourceId) {
37
+ const row = adapter.prepare(SOURCE_EXISTS_SQL[sourceType]).get(sourceId);
38
+ return row !== undefined;
39
+ }
40
+ function countTransitionTargets(adapter, sourceType, sourceId) {
41
+ return adapter
42
+ .prepare(`
43
+ SELECT COUNT(*) AS count,
44
+ SUM(CASE WHEN user_locked = 1 THEN 1 ELSE 0 END) AS locked_count
45
+ FROM case_memberships
46
+ WHERE status <> 'stale'
47
+ AND source_type = ?
48
+ AND source_id = ?
49
+ `)
50
+ .get(sourceType, sourceId);
51
+ }
52
+ function markSourceStale(adapter, sourceType, sourceId, now) {
53
+ const result = adapter
54
+ .prepare(`
55
+ UPDATE case_memberships
56
+ SET status = 'stale',
57
+ updated_at = ?
58
+ WHERE status <> 'stale'
59
+ AND source_type = ?
60
+ AND source_id = ?
61
+ AND NOT EXISTS (${SOURCE_EXISTS_SQL[sourceType]})
62
+ `)
63
+ .run(now, sourceType, sourceId, sourceId);
64
+ return result.changes;
65
+ }
66
+ function insertTombstoneAuditEvent(input) {
67
+ const eventId = `me_${(0, node_crypto_1.randomUUID)()}`;
68
+ input.adapter
69
+ .prepare(`
70
+ INSERT INTO memory_events (
71
+ event_id, event_type, actor, source_turn_id, memory_id, topic,
72
+ scope_refs, evidence_refs, reason, created_at
73
+ )
74
+ VALUES (?, 'case.membership_tombstoned', 'system', NULL, NULL, ?, ?, ?, ?, ?)
75
+ `)
76
+ .run(eventId, 'case:tombstone-sweeper', (0, canonicalize_js_1.canonicalizeJSON)([{ kind: 'project', id: 'case-first' }]), (0, canonicalize_js_1.canonicalizeJSON)(input.sources), (0, canonicalize_js_1.canonicalizeJSON)({
77
+ stale_count: input.stale_count,
78
+ locked_stale_count: input.locked_stale_count,
79
+ }), createdAtMs(input.now));
80
+ return eventId;
81
+ }
82
+ function sweepStaleCaseMemberships(adapter, options = {}) {
83
+ const sweepAdapter = adapter;
84
+ return (0, sqlite_transaction_js_1.runImmediateTransaction)(sweepAdapter, () => {
85
+ const now = normalizeNow(options.now);
86
+ const candidates = readCandidates(sweepAdapter, options.limit);
87
+ let staleCount = 0;
88
+ let lockedStaleCount = 0;
89
+ const tombstonedSources = [];
90
+ for (const candidate of candidates) {
91
+ if (!isSweptSourceType(candidate.source_type)) {
92
+ continue;
93
+ }
94
+ if (sourceExists(sweepAdapter, candidate.source_type, candidate.source_id)) {
95
+ continue;
96
+ }
97
+ const before = countTransitionTargets(sweepAdapter, candidate.source_type, candidate.source_id);
98
+ if (before.count === 0) {
99
+ continue;
100
+ }
101
+ const changes = markSourceStale(sweepAdapter, candidate.source_type, candidate.source_id, now);
102
+ if (changes === 0) {
103
+ continue;
104
+ }
105
+ staleCount += changes;
106
+ lockedStaleCount += Math.min(changes, before.locked_count ?? 0);
107
+ tombstonedSources.push(candidate);
108
+ }
109
+ const auditEventIds = staleCount > 0
110
+ ? [
111
+ insertTombstoneAuditEvent({
112
+ adapter: sweepAdapter,
113
+ now,
114
+ stale_count: staleCount,
115
+ locked_stale_count: lockedStaleCount,
116
+ sources: tombstonedSources,
117
+ }),
118
+ ]
119
+ : [];
120
+ return {
121
+ scanned_count: candidates.length,
122
+ stale_count: staleCount,
123
+ locked_stale_count: lockedStaleCount,
124
+ audit_event_ids: auditEventIds,
125
+ };
126
+ });
127
+ }
128
+ //# sourceMappingURL=tombstone-sweeper.js.map