@jungjaehoon/mama-core 1.9.0 → 2.1.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 (139) hide show
  1. package/db/migrations/043-add-tool-trace-failure-code.sql +11 -0
  2. package/dist/agent-graph/alias-write.js +3 -0
  3. package/dist/agent-graph/entity-resolve.js +3 -7
  4. package/dist/agent-graph/graph-query.js +5 -4
  5. package/dist/agent-graph/types.d.ts +30 -0
  6. package/dist/cases/store.d.ts +0 -4
  7. package/dist/cases/store.js +0 -66
  8. package/dist/cases/wiki-page-index.d.ts +0 -13
  9. package/dist/cases/wiki-page-index.js +0 -153
  10. package/dist/connectors/event-index.d.ts +1 -22
  11. package/dist/connectors/event-index.js +0 -157
  12. package/dist/context-compile/channel-grant.d.ts +46 -0
  13. package/dist/context-compile/channel-grant.js +66 -0
  14. package/dist/context-compile/compiler.js +5 -2
  15. package/dist/context-compile/index.d.ts +1 -0
  16. package/dist/context-compile/index.js +1 -0
  17. package/dist/context-compile/packet-store.d.ts +0 -1
  18. package/dist/context-compile/packet-store.js +0 -16
  19. package/dist/context-compile/source-readers.d.ts +1 -0
  20. package/dist/context-compile/source-readers.js +156 -23
  21. package/dist/context-compile/types.d.ts +17 -0
  22. package/dist/context-compile/visibility.js +28 -4
  23. package/dist/db-adapter/node-sqlite-adapter.js +4 -0
  24. package/dist/db-manager.d.ts +0 -13
  25. package/dist/db-manager.js +0 -66
  26. package/dist/decision-formatter.d.ts +0 -11
  27. package/dist/decision-formatter.js +0 -122
  28. package/dist/decision-tracker.d.ts +0 -12
  29. package/dist/decision-tracker.js +0 -50
  30. package/dist/edges/ref-validation.d.ts +1 -2
  31. package/dist/edges/ref-validation.js +33 -13
  32. package/dist/edges/store.d.ts +1 -3
  33. package/dist/edges/store.js +0 -214
  34. package/dist/edges/types.d.ts +10 -0
  35. package/dist/embedding-client.d.ts +0 -17
  36. package/dist/embedding-client.js +0 -80
  37. package/dist/embedding-server/mobile/auth.d.ts +1 -10
  38. package/dist/embedding-server/mobile/auth.js +0 -15
  39. package/dist/embeddings.d.ts +0 -12
  40. package/dist/embeddings.js +0 -68
  41. package/dist/entities/audit-metrics.d.ts +0 -5
  42. package/dist/entities/audit-metrics.js +0 -4
  43. package/dist/entities/candidate-generator.d.ts +1 -2
  44. package/dist/entities/candidate-generator.js +0 -412
  45. package/dist/entities/entity-list.d.ts +0 -1
  46. package/dist/entities/entity-list.js +0 -96
  47. package/dist/entities/entity-orphan-list.d.ts +0 -9
  48. package/dist/entities/entity-orphan-list.js +0 -88
  49. package/dist/entities/entity-search.d.ts +0 -1
  50. package/dist/entities/entity-search.js +0 -147
  51. package/dist/entities/errors.d.ts +0 -10
  52. package/dist/entities/errors.js +1 -25
  53. package/dist/entities/lineage-store.d.ts +0 -23
  54. package/dist/entities/lineage-store.js +0 -104
  55. package/dist/entities/policy-store.d.ts +0 -9
  56. package/dist/entities/policy-store.js +0 -188
  57. package/dist/entities/provenance-query.d.ts +0 -1
  58. package/dist/entities/provenance-query.js +0 -164
  59. package/dist/entities/resolution-engine.d.ts +1 -8
  60. package/dist/entities/resolution-engine.js +0 -66
  61. package/dist/entities/rollback-preview.d.ts +0 -4
  62. package/dist/entities/rollback-preview.js +0 -232
  63. package/dist/entities/store.d.ts +1 -10
  64. package/dist/entities/store.js +0 -49
  65. package/dist/index.d.ts +15 -31
  66. package/dist/index.js +4 -46
  67. package/dist/mama-api.d.ts +19 -1
  68. package/dist/mama-api.js +97 -2
  69. package/dist/memory/api.d.ts +8 -0
  70. package/dist/memory/api.js +55 -2
  71. package/dist/memory/event-store.d.ts +0 -1
  72. package/dist/memory/event-store.js +0 -15
  73. package/dist/memory/finding-store.d.ts +0 -1
  74. package/dist/memory/finding-store.js +0 -12
  75. package/dist/memory/scope-backfill.d.ts +0 -3
  76. package/dist/memory/scope-backfill.js +0 -74
  77. package/dist/memory/scope-store.d.ts +0 -1
  78. package/dist/memory/scope-store.js +0 -4
  79. package/dist/memory/truth-store.d.ts +0 -3
  80. package/dist/memory/truth-store.js +0 -22
  81. package/dist/memory/types.d.ts +0 -1
  82. package/dist/memory/types.js +0 -13
  83. package/dist/memory-store.d.ts +0 -16
  84. package/dist/memory-store.js +0 -51
  85. package/dist/model-runs/tool-trace-store.js +9 -5
  86. package/dist/model-runs/types.d.ts +3 -0
  87. package/dist/ollama-client.d.ts +0 -9
  88. package/dist/ollama-client.js +0 -125
  89. package/dist/outcome-tracker.d.ts +0 -14
  90. package/dist/outcome-tracker.js +0 -44
  91. package/dist/progress-indicator.d.ts +0 -4
  92. package/dist/progress-indicator.js +0 -7
  93. package/dist/provenance/source-ref.d.ts +0 -9
  94. package/dist/provenance/source-ref.js +0 -145
  95. package/dist/query-intent.d.ts +0 -4
  96. package/dist/query-intent.js +0 -30
  97. package/dist/relevance-scorer.d.ts +0 -9
  98. package/dist/relevance-scorer.js +0 -81
  99. package/dist/search/feedback-store.d.ts +0 -1
  100. package/dist/search/feedback-store.js +0 -4
  101. package/dist/search/ranker-rescore.d.ts +0 -3
  102. package/dist/search/ranker-rescore.js +0 -27
  103. package/dist/search/ranker-trainer.d.ts +0 -8
  104. package/dist/search/ranker-trainer.js +0 -376
  105. package/dist/tier-validator.d.ts +0 -12
  106. package/dist/tier-validator.js +0 -56
  107. package/package.json +1 -10
  108. package/dist/cases/case-links.d.ts +0 -116
  109. package/dist/cases/case-links.js +0 -794
  110. package/dist/cases/composition-overrides.d.ts +0 -90
  111. package/dist/cases/composition-overrides.js +0 -724
  112. package/dist/cases/corrections.d.ts +0 -156
  113. package/dist/cases/corrections.js +0 -1045
  114. package/dist/cases/freshness.d.ts +0 -57
  115. package/dist/cases/freshness.js +0 -283
  116. package/dist/cases/live-state.d.ts +0 -48
  117. package/dist/cases/live-state.js +0 -256
  118. package/dist/cases/membership-explain.d.ts +0 -56
  119. package/dist/cases/membership-explain.js +0 -140
  120. package/dist/cases/membership-matcher.d.ts +0 -23
  121. package/dist/cases/membership-matcher.js +0 -193
  122. package/dist/cases/merge-split.d.ts +0 -61
  123. package/dist/cases/merge-split.js +0 -368
  124. package/dist/cases/role-inference.d.ts +0 -10
  125. package/dist/cases/role-inference.js +0 -85
  126. package/dist/cases/sqlite-transaction.d.ts +0 -10
  127. package/dist/cases/sqlite-transaction.js +0 -62
  128. package/dist/cases/target-ref.d.ts +0 -25
  129. package/dist/cases/target-ref.js +0 -22
  130. package/dist/cases/tombstone-sweeper.d.ts +0 -13
  131. package/dist/cases/tombstone-sweeper.js +0 -128
  132. package/dist/entities/entity-impact.d.ts +0 -41
  133. package/dist/entities/entity-impact.js +0 -140
  134. package/dist/entities/exact-merge-backfill.d.ts +0 -11
  135. package/dist/entities/exact-merge-backfill.js +0 -161
  136. package/dist/entities/lineage-backfill.d.ts +0 -12
  137. package/dist/entities/lineage-backfill.js +0 -101
  138. package/dist/memory-inject.d.ts +0 -24
  139. package/dist/memory-inject.js +0 -116
@@ -1,724 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.pinCaseMembership = pinCaseMembership;
4
- exports.unpinCaseMembership = unpinCaseMembership;
5
- exports.promoteCaseSource = promoteCaseSource;
6
- const node_crypto_1 = require("node:crypto");
7
- const canonicalize_js_1 = require("../canonicalize.js");
8
- const sqlite_transaction_js_1 = require("./sqlite-transaction.js");
9
- const store_js_1 = require("./store.js");
10
- const TERMINAL_CASE_STATUSES = new Set(['merged', 'archived', 'split']);
11
- const RECONFIRM_TOKEN_TTL_MS = 10 * 60_000;
12
- function normalizeNow(value) {
13
- return value ?? new Date().toISOString();
14
- }
15
- function timestampMs(value) {
16
- const parsed = Date.parse(value);
17
- return Number.isFinite(parsed) ? parsed : Date.now();
18
- }
19
- function createdAtMs(value) {
20
- return timestampMs(value);
21
- }
22
- function sha256Hex(value) {
23
- return (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
24
- }
25
- function hmacHex(secret, value) {
26
- return (0, node_crypto_1.createHmac)('sha256', secret).update(value, 'utf8').digest('hex');
27
- }
28
- function deterministicEventId(nonce) {
29
- return `me_${sha256Hex(`case-composition-reconfirm:v1:${nonce}`).slice(0, 32)}`;
30
- }
31
- function decodeSecret(raw) {
32
- const trimmed = raw.trim();
33
- if (trimmed.length === 0) {
34
- return null;
35
- }
36
- if (/^[0-9a-f]+$/i.test(trimmed) && trimmed.length % 2 === 0) {
37
- const hex = Buffer.from(trimmed, 'hex');
38
- if (hex.length >= 32) {
39
- return hex;
40
- }
41
- }
42
- const base64 = Buffer.from(trimmed, 'base64');
43
- if (base64.length >= 32) {
44
- return base64;
45
- }
46
- const utf8 = Buffer.from(trimmed, 'utf8');
47
- if (utf8.length >= 32) {
48
- return utf8;
49
- }
50
- return null;
51
- }
52
- function secretKid(secret) {
53
- return (0, node_crypto_1.createHash)('sha256').update(secret).digest('hex').slice(0, 16);
54
- }
55
- function currentSecret() {
56
- const raw = process.env.MAMA_RECONFIRM_TOKEN_SECRET;
57
- const secret = raw ? decodeSecret(raw) : null;
58
- if (!secret) {
59
- throw new Error('MAMA_RECONFIRM_TOKEN_SECRET must be a 32+ byte base64 or hex secret.');
60
- }
61
- return { kid: secretKid(secret), secret };
62
- }
63
- function verificationSecrets() {
64
- const secrets = new Map();
65
- const current = currentSecret();
66
- secrets.set(current.kid, current.secret);
67
- for (const raw of (process.env.MAMA_RECONFIRM_TOKEN_OLD_SECRETS ?? '').split(',')) {
68
- const secret = decodeSecret(raw);
69
- if (secret) {
70
- secrets.set(secretKid(secret), secret);
71
- }
72
- }
73
- return secrets;
74
- }
75
- function encodeReconfirmToken(payload, secret) {
76
- const payloadJson = (0, canonicalize_js_1.canonicalizeJSON)(payload);
77
- const envelope = {
78
- kid: payload.kid,
79
- payload,
80
- signature_hex: hmacHex(secret, payloadJson),
81
- };
82
- return Buffer.from((0, canonicalize_js_1.canonicalizeJSON)(envelope), 'utf8').toString('base64url');
83
- }
84
- function isRecord(value) {
85
- return value !== null && typeof value === 'object' && !Array.isArray(value);
86
- }
87
- function readReconfirmEnvelope(token) {
88
- try {
89
- const parsed = JSON.parse(Buffer.from(token, 'base64url').toString('utf8'));
90
- if (!isRecord(parsed) || !isRecord(parsed.payload)) {
91
- return null;
92
- }
93
- const payload = parsed.payload;
94
- if (parsed.kid !== payload.kid ||
95
- payload.v !== 1 ||
96
- typeof parsed.kid !== 'string' ||
97
- typeof parsed.signature_hex !== 'string' ||
98
- typeof payload.kid !== 'string' ||
99
- typeof payload.nonce !== 'string' ||
100
- typeof payload.case_id !== 'string' ||
101
- payload.target_kind !== 'membership' ||
102
- typeof payload.target_ref_hash_hex !== 'string' ||
103
- typeof payload.current_value_hash_hex !== 'string' ||
104
- typeof payload.proposed_value_hash_hex !== 'string' ||
105
- typeof payload.confirmed_by !== 'string' ||
106
- !(payload.session_id === null || typeof payload.session_id === 'string') ||
107
- typeof payload.expires_at !== 'string') {
108
- return null;
109
- }
110
- return {
111
- kid: parsed.kid,
112
- payload: payload,
113
- signature_hex: parsed.signature_hex,
114
- };
115
- }
116
- catch {
117
- return null;
118
- }
119
- }
120
- function verifyReconfirmToken(input) {
121
- const envelope = readReconfirmEnvelope(input.token);
122
- if (!envelope) {
123
- return null;
124
- }
125
- const secret = verificationSecrets().get(envelope.kid);
126
- if (!secret) {
127
- return null;
128
- }
129
- const expected = Buffer.from(hmacHex(secret, (0, canonicalize_js_1.canonicalizeJSON)(envelope.payload)), 'hex');
130
- const actual = Buffer.from(envelope.signature_hex, 'hex');
131
- if (actual.length !== expected.length || !(0, node_crypto_1.timingSafeEqual)(actual, expected)) {
132
- return null;
133
- }
134
- const expiresAtMs = Date.parse(envelope.payload.expires_at);
135
- if (!Number.isFinite(expiresAtMs) || expiresAtMs <= timestampMs(input.now)) {
136
- return null;
137
- }
138
- if (envelope.payload.case_id !== input.case_id ||
139
- envelope.payload.target_ref_hash_hex !== input.target_ref_hash_hex ||
140
- envelope.payload.proposed_value_hash_hex !== input.proposed_value_hash_hex ||
141
- envelope.payload.confirmed_by !== input.confirmed_by ||
142
- envelope.payload.session_id !== input.session_id) {
143
- return null;
144
- }
145
- return envelope.payload;
146
- }
147
- function buildRequiresReconfirm(input) {
148
- const secret = currentSecret();
149
- const expiresAt = new Date(timestampMs(input.now) + RECONFIRM_TOKEN_TTL_MS).toISOString();
150
- const payload = {
151
- v: 1,
152
- kid: secret.kid,
153
- nonce: (0, node_crypto_1.randomUUID)(),
154
- case_id: input.case_id,
155
- target_kind: 'membership',
156
- target_ref_hash_hex: input.target_ref_hash_hex,
157
- current_value_hash_hex: sha256Hex(input.current_value_json),
158
- proposed_value_hash_hex: sha256Hex(input.proposed_new_value_json),
159
- confirmed_by: input.confirmed_by,
160
- session_id: input.session_id,
161
- expires_at: expiresAt,
162
- };
163
- return {
164
- kind: 'requires_reconfirm',
165
- code: 'case.correction_requires_reconfirm',
166
- case_id: input.case_id,
167
- target_kind: 'membership',
168
- target_ref_json: input.target_ref_json,
169
- current_value_json: input.current_value_json,
170
- old_value_json: input.old_value_json,
171
- proposed_new_value_json: input.proposed_new_value_json,
172
- reconfirm_token: encodeReconfirmToken(payload, secret.secret),
173
- reconfirm_token_expires_at: expiresAt,
174
- message: 'The target changed since the viewed snapshot. Reconfirm against the current value.',
175
- };
176
- }
177
- function parseJsonValue(value) {
178
- return JSON.parse(value);
179
- }
180
- function canonicalExpectedJson(value) {
181
- if (typeof value !== 'string') {
182
- return null;
183
- }
184
- return (0, canonicalize_js_1.canonicalizeJSON)(parseJsonValue(value));
185
- }
186
- function placeholders(values) {
187
- if (values.length === 0) {
188
- throw new Error('Cannot build SQL IN clause for an empty value list.');
189
- }
190
- return values.map(() => '?').join(', ');
191
- }
192
- function resolveChain(adapter, caseId) {
193
- try {
194
- return (0, store_js_1.resolveCanonicalCaseChain)(adapter, caseId);
195
- }
196
- catch (error) {
197
- if (error instanceof Error && /not found/i.test(error.message)) {
198
- return null;
199
- }
200
- throw error;
201
- }
202
- }
203
- function loadCaseGate(adapter, caseId) {
204
- const row = adapter
205
- .prepare(`
206
- SELECT case_id, status, canonical_decision_id, canonical_event_id
207
- FROM case_truth
208
- WHERE case_id = ?
209
- `)
210
- .get(caseId);
211
- return row ?? null;
212
- }
213
- function findMembership(adapter, chain, sourceType, sourceId) {
214
- const row = adapter
215
- .prepare(`
216
- SELECT case_id, source_type, source_id, status, role, confidence, reason,
217
- user_locked, assignment_strategy, added_at AS assigned_at, updated_at
218
- FROM case_memberships
219
- WHERE case_id IN (${placeholders(chain)})
220
- AND source_type = ?
221
- AND source_id = ?
222
- ORDER BY
223
- CASE status
224
- WHEN 'active' THEN 0
225
- WHEN 'candidate' THEN 1
226
- WHEN 'excluded' THEN 2
227
- WHEN 'removed' THEN 3
228
- ELSE 4
229
- END ASC,
230
- user_locked DESC,
231
- updated_at DESC,
232
- case_id ASC
233
- LIMIT 1
234
- `)
235
- .get(...chain, sourceType, sourceId);
236
- return row ?? null;
237
- }
238
- function membershipTargetRefJson(input) {
239
- return (0, canonicalize_js_1.canonicalizeJSON)({
240
- kind: 'membership',
241
- source_type: input.source_type,
242
- source_id: input.source_id,
243
- });
244
- }
245
- function membershipSnapshot(row) {
246
- return {
247
- case_id: row.case_id,
248
- source_type: row.source_type,
249
- source_id: row.source_id,
250
- status: row.status,
251
- role: row.role,
252
- confidence: row.confidence,
253
- reason: row.reason,
254
- user_locked: Number(row.user_locked) === 1 ? 1 : 0,
255
- assignment_strategy: row.assignment_strategy,
256
- assigned_at: row.assigned_at,
257
- updated_at: row.updated_at,
258
- };
259
- }
260
- function appendReason(existing, reason) {
261
- if (!existing || existing.trim().length === 0) {
262
- return reason;
263
- }
264
- const lines = existing.split('\n');
265
- const lastLine = lines.at(-1)?.trim() ?? '';
266
- const nextLine = `manual-pin: ${reason}`;
267
- if (lastLine === nextLine) {
268
- return existing;
269
- }
270
- if (lastLine.startsWith('manual-pin:')) {
271
- lines[lines.length - 1] = nextLine;
272
- return lines.join('\n');
273
- }
274
- return `${existing}\n${nextLine}`;
275
- }
276
- function insertMemoryEvent(input) {
277
- input.adapter
278
- .prepare(`
279
- INSERT INTO memory_events (
280
- event_id, event_type, actor, source_turn_id, memory_id, topic,
281
- scope_refs, evidence_refs, reason, created_at
282
- )
283
- VALUES (?, ?, ?, NULL, NULL, ?, ?, ?, ?, ?)
284
- `)
285
- .run(input.event_id ?? `me_${(0, node_crypto_1.randomUUID)()}`, input.event_type, input.actor, `case:${input.case_id}`, (0, canonicalize_js_1.canonicalizeJSON)([{ type: 'case', id: input.case_id }]), (0, canonicalize_js_1.canonicalizeJSON)(input.evidence_refs), (0, canonicalize_js_1.canonicalizeJSON)(input.reason), createdAtMs(input.now));
286
- }
287
- function memoryEventExists(adapter, eventId) {
288
- const row = adapter
289
- .prepare('SELECT event_id FROM memory_events WHERE event_id = ?')
290
- .get(eventId);
291
- return Boolean(row);
292
- }
293
- function casCheck(input) {
294
- const canonicalExpectedCurrent = canonicalExpectedJson(input.expected_current_value_json);
295
- if (canonicalExpectedCurrent === null || canonicalExpectedCurrent === input.current_value_json) {
296
- return { kind: 'ok' };
297
- }
298
- const targetRefHashHex = sha256Hex(input.target_ref_json);
299
- const token = input.reconfirm_token ?? null;
300
- if (!token) {
301
- return buildRequiresReconfirm({
302
- case_id: input.case_id,
303
- target_ref_json: input.target_ref_json,
304
- target_ref_hash_hex: targetRefHashHex,
305
- current_value_json: input.current_value_json,
306
- old_value_json: input.expected_current_value_json ?? null,
307
- proposed_new_value_json: input.proposed_new_value_json,
308
- confirmed_by: input.actor,
309
- session_id: input.session_id ?? null,
310
- now: input.now,
311
- });
312
- }
313
- const payload = verifyReconfirmToken({
314
- token,
315
- case_id: input.case_id,
316
- target_ref_hash_hex: targetRefHashHex,
317
- proposed_value_hash_hex: sha256Hex(input.proposed_new_value_json),
318
- confirmed_by: input.actor,
319
- session_id: input.session_id ?? null,
320
- now: input.now,
321
- });
322
- if (!payload || payload.current_value_hash_hex !== sha256Hex(input.current_value_json)) {
323
- if (payload) {
324
- const replayEventId = deterministicEventId(payload.nonce);
325
- if (memoryEventExists(input.adapter, replayEventId)) {
326
- return {
327
- kind: 'rejected',
328
- code: 'case.reconfirm_token_replayed',
329
- message: 'Reconfirm token has already been consumed.',
330
- case_id: input.case_id,
331
- };
332
- }
333
- }
334
- return buildRequiresReconfirm({
335
- case_id: input.case_id,
336
- target_ref_json: input.target_ref_json,
337
- target_ref_hash_hex: targetRefHashHex,
338
- current_value_json: input.current_value_json,
339
- old_value_json: input.expected_current_value_json ?? null,
340
- proposed_new_value_json: input.proposed_new_value_json,
341
- confirmed_by: input.actor,
342
- session_id: input.session_id ?? null,
343
- now: input.now,
344
- });
345
- }
346
- const eventId = deterministicEventId(payload.nonce);
347
- if (memoryEventExists(input.adapter, eventId)) {
348
- return {
349
- kind: 'rejected',
350
- code: 'case.reconfirm_token_replayed',
351
- message: 'Reconfirm token has already been consumed.',
352
- case_id: input.case_id,
353
- };
354
- }
355
- return { kind: 'ok', event_id: eventId };
356
- }
357
- function pinCaseMembership(adapter, input) {
358
- const compositionAdapter = adapter;
359
- return (0, sqlite_transaction_js_1.runImmediateTransaction)(compositionAdapter, () => {
360
- const now = normalizeNow(input.now);
361
- const resolution = resolveChain(compositionAdapter, input.case_id);
362
- if (!resolution) {
363
- return {
364
- kind: 'rejected',
365
- code: 'case.precompile_gap',
366
- message: `Case ${input.case_id} does not exist.`,
367
- case_id: input.case_id,
368
- };
369
- }
370
- const terminalCase = loadCaseGate(compositionAdapter, resolution.terminal_case_id);
371
- if (!terminalCase) {
372
- return {
373
- kind: 'rejected',
374
- code: 'case.precompile_gap',
375
- message: `Case ${resolution.terminal_case_id} does not exist.`,
376
- case_id: resolution.terminal_case_id,
377
- };
378
- }
379
- if (TERMINAL_CASE_STATUSES.has(terminalCase.status)) {
380
- return {
381
- kind: 'rejected',
382
- code: 'case.terminal_status',
383
- message: `Cannot pin membership on terminal case status ${terminalCase.status}.`,
384
- case_id: terminalCase.case_id,
385
- };
386
- }
387
- const chain = (0, store_js_1.expandCaseChainForAssembly)(compositionAdapter, resolution.terminal_case_id, resolution.chain);
388
- const membership = findMembership(compositionAdapter, chain, input.source_type, input.source_id);
389
- if (!membership) {
390
- return {
391
- kind: 'rejected',
392
- code: 'case.membership_not_found',
393
- message: 'Membership row was not found in the canonical case chain.',
394
- case_id: resolution.terminal_case_id,
395
- };
396
- }
397
- if (membership.status === 'stale') {
398
- return {
399
- kind: 'rejected',
400
- code: 'case.membership_stale',
401
- message: 'Stale memberships cannot be pinned.',
402
- case_id: membership.case_id,
403
- };
404
- }
405
- const targetRefJson = membershipTargetRefJson(input);
406
- const newReason = appendReason(membership.reason, input.reason);
407
- const currentValueJson = (0, canonicalize_js_1.canonicalizeJSON)(membershipSnapshot(membership));
408
- const proposedNewValueJson = (0, canonicalize_js_1.canonicalizeJSON)({
409
- ...membershipSnapshot(membership),
410
- user_locked: 1,
411
- assignment_strategy: 'manual-pin',
412
- reason: newReason,
413
- });
414
- const cas = casCheck({
415
- adapter: compositionAdapter,
416
- case_id: resolution.terminal_case_id,
417
- target_ref_json: targetRefJson,
418
- current_value_json: currentValueJson,
419
- expected_current_value_json: input.expected_current_value_json,
420
- proposed_new_value_json: proposedNewValueJson,
421
- actor: input.pinned_by,
422
- session_id: input.session_id,
423
- reconfirm_token: input.reconfirm_token,
424
- now,
425
- });
426
- if (cas.kind !== 'ok') {
427
- return cas;
428
- }
429
- compositionAdapter
430
- .prepare(`
431
- UPDATE case_memberships
432
- SET user_locked = 1,
433
- assignment_strategy = 'manual-pin',
434
- reason = ?,
435
- updated_at = ?,
436
- explanation_updated_at = ?
437
- WHERE case_id = ?
438
- AND source_type = ?
439
- AND source_id = ?
440
- AND status <> 'stale'
441
- `)
442
- .run(newReason, now, now, membership.case_id, membership.source_type, membership.source_id);
443
- insertMemoryEvent({
444
- adapter: compositionAdapter,
445
- event_type: 'case.membership_pinned',
446
- event_id: cas.event_id,
447
- actor: input.pinned_by,
448
- case_id: resolution.terminal_case_id,
449
- evidence_refs: [membership.source_id],
450
- reason: {
451
- source_type: membership.source_type,
452
- source_id: membership.source_id,
453
- membership_case_id: membership.case_id,
454
- reason: input.reason,
455
- },
456
- now,
457
- });
458
- return {
459
- kind: 'pinned',
460
- case_id: input.case_id,
461
- terminal_case_id: resolution.terminal_case_id,
462
- resolved_via_case_id: resolution.resolved_via_case_id,
463
- chain,
464
- membership_case_id: membership.case_id,
465
- };
466
- });
467
- }
468
- function unpinCaseMembership(adapter, input) {
469
- const compositionAdapter = adapter;
470
- return (0, sqlite_transaction_js_1.runImmediateTransaction)(compositionAdapter, () => {
471
- const now = normalizeNow(input.now);
472
- const resolution = resolveChain(compositionAdapter, input.case_id);
473
- if (!resolution) {
474
- return {
475
- kind: 'rejected',
476
- code: 'case.precompile_gap',
477
- message: `Case ${input.case_id} does not exist.`,
478
- case_id: input.case_id,
479
- };
480
- }
481
- const terminalCase = loadCaseGate(compositionAdapter, resolution.terminal_case_id);
482
- if (!terminalCase) {
483
- return {
484
- kind: 'rejected',
485
- code: 'case.precompile_gap',
486
- message: `Case ${resolution.terminal_case_id} does not exist.`,
487
- case_id: resolution.terminal_case_id,
488
- };
489
- }
490
- if (TERMINAL_CASE_STATUSES.has(terminalCase.status)) {
491
- return {
492
- kind: 'rejected',
493
- code: 'case.terminal_status',
494
- message: `Cannot unpin membership on terminal case status ${terminalCase.status}.`,
495
- case_id: terminalCase.case_id,
496
- };
497
- }
498
- const chain = (0, store_js_1.expandCaseChainForAssembly)(compositionAdapter, resolution.terminal_case_id, resolution.chain);
499
- const membership = findMembership(compositionAdapter, chain, input.source_type, input.source_id);
500
- if (!membership) {
501
- return {
502
- kind: 'rejected',
503
- code: 'case.membership_not_found',
504
- message: 'Membership row was not found in the canonical case chain.',
505
- case_id: resolution.terminal_case_id,
506
- };
507
- }
508
- if (membership.status === 'stale') {
509
- return {
510
- kind: 'rejected',
511
- code: 'case.membership_stale',
512
- message: 'Stale memberships cannot be unpinned.',
513
- case_id: membership.case_id,
514
- };
515
- }
516
- const targetRefJson = membershipTargetRefJson(input);
517
- const currentValueJson = (0, canonicalize_js_1.canonicalizeJSON)(membershipSnapshot(membership));
518
- const proposedNewValueJson = (0, canonicalize_js_1.canonicalizeJSON)({
519
- ...membershipSnapshot(membership),
520
- user_locked: 0,
521
- assignment_strategy: null,
522
- });
523
- const cas = casCheck({
524
- adapter: compositionAdapter,
525
- case_id: resolution.terminal_case_id,
526
- target_ref_json: targetRefJson,
527
- current_value_json: currentValueJson,
528
- expected_current_value_json: input.expected_current_value_json,
529
- proposed_new_value_json: proposedNewValueJson,
530
- actor: input.unpinned_by,
531
- session_id: input.session_id,
532
- reconfirm_token: input.reconfirm_token,
533
- now,
534
- });
535
- if (cas.kind !== 'ok') {
536
- return cas;
537
- }
538
- compositionAdapter
539
- .prepare(`
540
- UPDATE case_memberships
541
- SET user_locked = 0,
542
- assignment_strategy = NULL,
543
- updated_at = ?
544
- WHERE case_id = ?
545
- AND source_type = ?
546
- AND source_id = ?
547
- AND status <> 'stale'
548
- `)
549
- .run(now, membership.case_id, membership.source_type, membership.source_id);
550
- insertMemoryEvent({
551
- adapter: compositionAdapter,
552
- event_type: 'case.membership_unpinned',
553
- event_id: cas.event_id,
554
- actor: input.unpinned_by,
555
- case_id: resolution.terminal_case_id,
556
- evidence_refs: [membership.source_id],
557
- reason: {
558
- source_type: membership.source_type,
559
- source_id: membership.source_id,
560
- membership_case_id: membership.case_id,
561
- reason: input.reason ?? null,
562
- },
563
- now,
564
- });
565
- return {
566
- kind: 'unpinned',
567
- case_id: input.case_id,
568
- terminal_case_id: resolution.terminal_case_id,
569
- resolved_via_case_id: resolution.resolved_via_case_id,
570
- chain,
571
- membership_case_id: membership.case_id,
572
- };
573
- });
574
- }
575
- function promoteCaseSource(adapter, input) {
576
- const compositionAdapter = adapter;
577
- return (0, sqlite_transaction_js_1.runImmediateTransaction)(compositionAdapter, () => {
578
- const now = normalizeNow(input.now);
579
- if (input.source_type !== 'decision' && input.source_type !== 'event') {
580
- return {
581
- kind: 'rejected',
582
- code: 'case.promote_invalid_source_type',
583
- message: 'Only decision and event memberships can be promoted.',
584
- case_id: input.case_id,
585
- };
586
- }
587
- const resolution = resolveChain(compositionAdapter, input.case_id);
588
- if (!resolution) {
589
- return {
590
- kind: 'rejected',
591
- code: 'case.precompile_gap',
592
- message: `Case ${input.case_id} does not exist.`,
593
- case_id: input.case_id,
594
- };
595
- }
596
- const terminalCase = loadCaseGate(compositionAdapter, resolution.terminal_case_id);
597
- if (!terminalCase) {
598
- return {
599
- kind: 'rejected',
600
- code: 'case.precompile_gap',
601
- message: `Case ${resolution.terminal_case_id} does not exist.`,
602
- case_id: resolution.terminal_case_id,
603
- };
604
- }
605
- if (TERMINAL_CASE_STATUSES.has(terminalCase.status)) {
606
- return {
607
- kind: 'rejected',
608
- code: 'case.terminal_status',
609
- message: `Cannot promote source on terminal case status ${terminalCase.status}.`,
610
- case_id: terminalCase.case_id,
611
- };
612
- }
613
- const chain = (0, store_js_1.expandCaseChainForAssembly)(compositionAdapter, resolution.terminal_case_id, resolution.chain);
614
- const membership = findMembership(compositionAdapter, chain, input.source_type, input.source_id);
615
- if (!membership || membership.status !== 'active') {
616
- return {
617
- kind: 'rejected',
618
- code: 'case.promote_source_not_active',
619
- message: 'Promoted source must be an active membership in the canonical case chain.',
620
- case_id: resolution.terminal_case_id,
621
- };
622
- }
623
- const targetRefJson = membershipTargetRefJson(input);
624
- const currentValueJson = (0, canonicalize_js_1.canonicalizeJSON)({
625
- promotion: {
626
- canonical_decision_id: terminalCase.canonical_decision_id,
627
- canonical_event_id: terminalCase.canonical_event_id,
628
- },
629
- membership: membershipSnapshot(membership),
630
- });
631
- const proposedCanonicalDecisionId = input.source_type === 'decision' ? input.source_id : terminalCase.canonical_decision_id;
632
- const proposedCanonicalEventId = input.source_type === 'event' ? input.source_id : terminalCase.canonical_event_id;
633
- const proposedNewValueJson = (0, canonicalize_js_1.canonicalizeJSON)({
634
- promotion: {
635
- canonical_decision_id: proposedCanonicalDecisionId,
636
- canonical_event_id: proposedCanonicalEventId,
637
- promoted_by: input.promoted_by,
638
- promotion_reason: input.reason,
639
- },
640
- membership: {
641
- ...membershipSnapshot(membership),
642
- user_locked: 1,
643
- },
644
- });
645
- const cas = casCheck({
646
- adapter: compositionAdapter,
647
- case_id: resolution.terminal_case_id,
648
- target_ref_json: targetRefJson,
649
- current_value_json: currentValueJson,
650
- expected_current_value_json: input.expected_current_value_json,
651
- proposed_new_value_json: proposedNewValueJson,
652
- actor: input.promoted_by,
653
- session_id: input.session_id,
654
- reconfirm_token: input.reconfirm_token,
655
- now,
656
- });
657
- if (cas.kind !== 'ok') {
658
- return cas;
659
- }
660
- if (input.source_type === 'decision') {
661
- compositionAdapter
662
- .prepare(`
663
- UPDATE case_truth
664
- SET canonical_decision_id = ?,
665
- promoted_at = ?,
666
- promoted_by = ?,
667
- promotion_reason = ?,
668
- updated_at = ?
669
- WHERE case_id = ?
670
- `)
671
- .run(input.source_id, now, input.promoted_by, input.reason, now, resolution.terminal_case_id);
672
- }
673
- else {
674
- compositionAdapter
675
- .prepare(`
676
- UPDATE case_truth
677
- SET canonical_event_id = ?,
678
- promoted_at = ?,
679
- promoted_by = ?,
680
- promotion_reason = ?,
681
- updated_at = ?
682
- WHERE case_id = ?
683
- `)
684
- .run(input.source_id, now, input.promoted_by, input.reason, now, resolution.terminal_case_id);
685
- }
686
- compositionAdapter
687
- .prepare(`
688
- UPDATE case_memberships
689
- SET user_locked = 1,
690
- updated_at = ?
691
- WHERE case_id = ?
692
- AND source_type = ?
693
- AND source_id = ?
694
- AND status = 'active'
695
- `)
696
- .run(now, membership.case_id, membership.source_type, membership.source_id);
697
- insertMemoryEvent({
698
- adapter: compositionAdapter,
699
- event_type: 'case.source_promoted',
700
- event_id: cas.event_id,
701
- actor: input.promoted_by,
702
- case_id: resolution.terminal_case_id,
703
- evidence_refs: [membership.source_id],
704
- reason: {
705
- source_type: membership.source_type,
706
- source_id: membership.source_id,
707
- membership_case_id: membership.case_id,
708
- reason: input.reason,
709
- },
710
- now,
711
- });
712
- return {
713
- kind: 'promoted',
714
- case_id: input.case_id,
715
- terminal_case_id: resolution.terminal_case_id,
716
- resolved_via_case_id: resolution.resolved_via_case_id,
717
- chain,
718
- membership_case_id: membership.case_id,
719
- canonical_decision_id: proposedCanonicalDecisionId,
720
- canonical_event_id: proposedCanonicalEventId,
721
- };
722
- });
723
- }
724
- //# sourceMappingURL=composition-overrides.js.map