@jungjaehoon/mama-core 1.9.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-graph/alias-write.js +3 -0
- package/dist/agent-graph/entity-resolve.js +3 -7
- package/dist/agent-graph/graph-query.js +5 -4
- package/dist/agent-graph/types.d.ts +30 -0
- package/dist/cases/store.d.ts +0 -4
- package/dist/cases/store.js +0 -66
- package/dist/cases/wiki-page-index.d.ts +0 -13
- package/dist/cases/wiki-page-index.js +0 -153
- package/dist/connectors/event-index.d.ts +1 -22
- package/dist/connectors/event-index.js +0 -157
- package/dist/context-compile/channel-grant.d.ts +46 -0
- package/dist/context-compile/channel-grant.js +66 -0
- package/dist/context-compile/compiler.js +5 -2
- package/dist/context-compile/index.d.ts +1 -0
- package/dist/context-compile/index.js +1 -0
- package/dist/context-compile/packet-store.d.ts +0 -1
- package/dist/context-compile/packet-store.js +0 -16
- package/dist/context-compile/source-readers.d.ts +1 -0
- package/dist/context-compile/source-readers.js +156 -23
- package/dist/context-compile/types.d.ts +17 -0
- package/dist/context-compile/visibility.js +28 -4
- package/dist/db-adapter/node-sqlite-adapter.js +4 -0
- package/dist/db-manager.d.ts +0 -13
- package/dist/db-manager.js +0 -66
- package/dist/decision-formatter.d.ts +0 -11
- package/dist/decision-formatter.js +0 -122
- package/dist/decision-tracker.d.ts +0 -12
- package/dist/decision-tracker.js +0 -50
- package/dist/edges/ref-validation.d.ts +1 -2
- package/dist/edges/ref-validation.js +33 -13
- package/dist/edges/store.d.ts +1 -3
- package/dist/edges/store.js +0 -214
- package/dist/edges/types.d.ts +10 -0
- package/dist/embedding-client.d.ts +0 -17
- package/dist/embedding-client.js +0 -80
- package/dist/embedding-server/mobile/auth.d.ts +1 -10
- package/dist/embedding-server/mobile/auth.js +0 -15
- package/dist/embeddings.d.ts +0 -12
- package/dist/embeddings.js +0 -68
- package/dist/entities/audit-metrics.d.ts +0 -5
- package/dist/entities/audit-metrics.js +0 -4
- package/dist/entities/candidate-generator.d.ts +1 -2
- package/dist/entities/candidate-generator.js +0 -412
- package/dist/entities/entity-list.d.ts +0 -1
- package/dist/entities/entity-list.js +0 -96
- package/dist/entities/entity-orphan-list.d.ts +0 -9
- package/dist/entities/entity-orphan-list.js +0 -88
- package/dist/entities/entity-search.d.ts +0 -1
- package/dist/entities/entity-search.js +0 -147
- package/dist/entities/errors.d.ts +0 -10
- package/dist/entities/errors.js +1 -25
- package/dist/entities/lineage-store.d.ts +0 -23
- package/dist/entities/lineage-store.js +0 -104
- package/dist/entities/policy-store.d.ts +0 -9
- package/dist/entities/policy-store.js +0 -188
- package/dist/entities/provenance-query.d.ts +0 -1
- package/dist/entities/provenance-query.js +0 -164
- package/dist/entities/resolution-engine.d.ts +1 -8
- package/dist/entities/resolution-engine.js +0 -66
- package/dist/entities/rollback-preview.d.ts +0 -4
- package/dist/entities/rollback-preview.js +0 -232
- package/dist/entities/store.d.ts +1 -10
- package/dist/entities/store.js +0 -49
- package/dist/index.d.ts +15 -31
- package/dist/index.js +4 -46
- package/dist/mama-api.d.ts +19 -1
- package/dist/mama-api.js +97 -2
- package/dist/memory/api.d.ts +8 -0
- package/dist/memory/api.js +55 -2
- package/dist/memory/event-store.d.ts +0 -1
- package/dist/memory/event-store.js +0 -15
- package/dist/memory/finding-store.d.ts +0 -1
- package/dist/memory/finding-store.js +0 -12
- package/dist/memory/scope-backfill.d.ts +0 -3
- package/dist/memory/scope-backfill.js +0 -74
- package/dist/memory/scope-store.d.ts +0 -1
- package/dist/memory/scope-store.js +0 -4
- package/dist/memory/truth-store.d.ts +0 -3
- package/dist/memory/truth-store.js +0 -22
- package/dist/memory/types.d.ts +0 -1
- package/dist/memory/types.js +0 -13
- package/dist/memory-store.d.ts +0 -16
- package/dist/memory-store.js +0 -51
- package/dist/ollama-client.d.ts +0 -9
- package/dist/ollama-client.js +0 -125
- package/dist/outcome-tracker.d.ts +0 -14
- package/dist/outcome-tracker.js +0 -44
- package/dist/progress-indicator.d.ts +0 -4
- package/dist/progress-indicator.js +0 -7
- package/dist/provenance/source-ref.d.ts +0 -9
- package/dist/provenance/source-ref.js +0 -145
- package/dist/query-intent.d.ts +0 -4
- package/dist/query-intent.js +0 -30
- package/dist/relevance-scorer.d.ts +0 -9
- package/dist/relevance-scorer.js +0 -81
- package/dist/search/feedback-store.d.ts +0 -1
- package/dist/search/feedback-store.js +0 -4
- package/dist/search/ranker-rescore.d.ts +0 -3
- package/dist/search/ranker-rescore.js +0 -27
- package/dist/search/ranker-trainer.d.ts +0 -8
- package/dist/search/ranker-trainer.js +0 -376
- package/dist/tier-validator.d.ts +0 -12
- package/dist/tier-validator.js +0 -56
- package/package.json +1 -10
- package/dist/cases/case-links.d.ts +0 -116
- package/dist/cases/case-links.js +0 -794
- package/dist/cases/composition-overrides.d.ts +0 -90
- package/dist/cases/composition-overrides.js +0 -724
- package/dist/cases/corrections.d.ts +0 -156
- package/dist/cases/corrections.js +0 -1045
- package/dist/cases/freshness.d.ts +0 -57
- package/dist/cases/freshness.js +0 -283
- package/dist/cases/live-state.d.ts +0 -48
- package/dist/cases/live-state.js +0 -256
- package/dist/cases/membership-explain.d.ts +0 -56
- package/dist/cases/membership-explain.js +0 -140
- package/dist/cases/membership-matcher.d.ts +0 -23
- package/dist/cases/membership-matcher.js +0 -193
- package/dist/cases/merge-split.d.ts +0 -61
- package/dist/cases/merge-split.js +0 -368
- package/dist/cases/role-inference.d.ts +0 -10
- package/dist/cases/role-inference.js +0 -85
- package/dist/cases/sqlite-transaction.d.ts +0 -10
- package/dist/cases/sqlite-transaction.js +0 -62
- package/dist/cases/target-ref.d.ts +0 -25
- package/dist/cases/target-ref.js +0 -22
- package/dist/cases/tombstone-sweeper.d.ts +0 -13
- package/dist/cases/tombstone-sweeper.js +0 -128
- package/dist/entities/entity-impact.d.ts +0 -41
- package/dist/entities/entity-impact.js +0 -140
- package/dist/entities/exact-merge-backfill.d.ts +0 -11
- package/dist/entities/exact-merge-backfill.js +0 -161
- package/dist/entities/lineage-backfill.d.ts +0 -12
- package/dist/entities/lineage-backfill.js +0 -101
- package/dist/memory-inject.d.ts +0 -24
- 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
|