@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,57 +0,0 @@
1
- import type { DatabaseAdapter } from '../db-manager.js';
2
- export type CaseFreshnessState = 'fresh' | 'stale' | 'drifted' | 'unknown';
3
- export interface CaseFreshnessReason {
4
- code: 'missing_compile' | 'activity_after_compile' | 'state_update_after_compile' | 'wiki_index_missing' | 'timestamps_absent';
5
- penalty: number;
6
- detail?: string;
7
- }
8
- export interface CaseFreshnessCalculation {
9
- freshness_score: number;
10
- freshness_state: CaseFreshnessState;
11
- freshness_score_is_drifted: 0 | 1;
12
- freshness_drift_threshold: number;
13
- reasons: CaseFreshnessReason[];
14
- freshness_reason_json: string;
15
- }
16
- export interface CaseFreshnessResult extends CaseFreshnessCalculation {
17
- case_id: string;
18
- terminal_case_id: string;
19
- resolved_via_case_id: string | null;
20
- chain: string[];
21
- freshness_checked_at: string;
22
- changed: boolean;
23
- }
24
- export interface SweepCaseFreshnessInput {
25
- case_ids?: string[];
26
- now?: string;
27
- drift_threshold?: number;
28
- }
29
- export interface SweepCaseFreshnessResult {
30
- checked: number;
31
- fresh: number;
32
- stale: number;
33
- drifted: number;
34
- drifted_case_ids: string[];
35
- results: CaseFreshnessResult[];
36
- rejected?: Array<{
37
- case_id: string;
38
- code: 'case.terminal_status';
39
- message: string;
40
- }>;
41
- }
42
- export declare function calculateCaseFreshness(input: {
43
- last_activity_at?: string | null;
44
- state_updated_at?: string | null;
45
- compiled_at?: string | null;
46
- wiki_index_updated_at?: string | null;
47
- current_wiki_path?: string | null;
48
- drift_threshold?: number;
49
- now?: string;
50
- }): CaseFreshnessCalculation;
51
- export declare function sweepCaseFreshness(adapter: DatabaseAdapter, input?: SweepCaseFreshnessInput): SweepCaseFreshnessResult;
52
- export declare function listDriftedCases(adapter: DatabaseAdapter): Array<{
53
- case_id: string;
54
- freshness_score: number;
55
- freshness_checked_at: string | null;
56
- }>;
57
- //# sourceMappingURL=freshness.d.ts.map
@@ -1,283 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.calculateCaseFreshness = calculateCaseFreshness;
4
- exports.sweepCaseFreshness = sweepCaseFreshness;
5
- exports.listDriftedCases = listDriftedCases;
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 DEFAULT_DRIFT_THRESHOLD = 0.5;
11
- const TERMINAL_CASE_STATUSES = new Set(['merged', 'archived', 'split']);
12
- function parseRequiredTimestamp(value, errorMessage) {
13
- const parsed = Date.parse(value);
14
- if (!Number.isFinite(parsed)) {
15
- throw new Error(errorMessage);
16
- }
17
- return parsed;
18
- }
19
- function normalizeNow(value) {
20
- if (value === undefined) {
21
- return new Date().toISOString();
22
- }
23
- return new Date(parseRequiredTimestamp(value, 'invalid now timestamp')).toISOString();
24
- }
25
- function createdAtMs(value) {
26
- return parseRequiredTimestamp(value, 'invalid now timestamp');
27
- }
28
- function timestampMs(value) {
29
- if (!value) {
30
- return null;
31
- }
32
- const parsed = Date.parse(value);
33
- return Number.isFinite(parsed) ? parsed : null;
34
- }
35
- function normalizedThreshold(value) {
36
- if (value === undefined) {
37
- return DEFAULT_DRIFT_THRESHOLD;
38
- }
39
- if (!Number.isFinite(value) || value < 0 || value > 1) {
40
- throw new Error('freshness drift_threshold must be between 0 and 1.');
41
- }
42
- return Number(value.toFixed(4));
43
- }
44
- function score(value) {
45
- return Number(Math.max(0, Math.min(1, value)).toFixed(4));
46
- }
47
- function resolveChain(adapter, caseId) {
48
- try {
49
- return (0, store_js_1.resolveCanonicalCaseChain)(adapter, caseId);
50
- }
51
- catch (error) {
52
- if (error instanceof Error && /not found/i.test(error.message)) {
53
- return null;
54
- }
55
- throw error;
56
- }
57
- }
58
- function loadCase(adapter, caseId) {
59
- const row = adapter
60
- .prepare(`
61
- SELECT case_id, status, current_wiki_path, last_activity_at, state_updated_at,
62
- compiled_at, freshness_score, freshness_state, freshness_score_is_drifted,
63
- freshness_drift_threshold, freshness_checked_at, freshness_reason_json
64
- FROM case_truth
65
- WHERE case_id = ?
66
- `)
67
- .get(caseId);
68
- return row ?? null;
69
- }
70
- function wikiIndexUpdatedAt(adapter, caseId) {
71
- const row = adapter
72
- .prepare(`
73
- SELECT updated_at
74
- FROM wiki_page_index
75
- WHERE case_id = ?
76
- AND page_type = 'case'
77
- ORDER BY updated_at DESC, page_id ASC
78
- LIMIT 1
79
- `)
80
- .get(caseId);
81
- return row?.updated_at ?? null;
82
- }
83
- function allCandidateCaseIds(adapter) {
84
- const rows = adapter
85
- .prepare(`
86
- SELECT case_id
87
- FROM case_truth
88
- WHERE status NOT IN ('merged','archived','split')
89
- ORDER BY case_id ASC
90
- `)
91
- .all();
92
- return rows.map((row) => row.case_id);
93
- }
94
- function insertDriftEvent(input) {
95
- input.adapter
96
- .prepare(`
97
- INSERT INTO memory_events (
98
- event_id, event_type, actor, source_turn_id, memory_id, topic,
99
- scope_refs, evidence_refs, reason, created_at
100
- )
101
- VALUES (?, 'case.freshness_drifted', 'system', NULL, NULL, ?, ?, ?, ?, ?)
102
- `)
103
- .run(`me_${(0, node_crypto_1.randomUUID)()}`, `case:${input.case_id}`, (0, canonicalize_js_1.canonicalizeJSON)([{ type: 'case', id: input.case_id }]), (0, canonicalize_js_1.canonicalizeJSON)([input.case_id]), (0, canonicalize_js_1.canonicalizeJSON)({
104
- freshness_score: input.result.freshness_score,
105
- freshness_drift_threshold: input.result.freshness_drift_threshold,
106
- reasons: input.result.reasons,
107
- }), createdAtMs(input.now));
108
- }
109
- function hasFreshnessChanged(row, result) {
110
- return (row.freshness_score !== result.freshness_score ||
111
- row.freshness_state !== result.freshness_state ||
112
- Number(row.freshness_score_is_drifted) !== result.freshness_score_is_drifted ||
113
- row.freshness_drift_threshold !== result.freshness_drift_threshold ||
114
- row.freshness_reason_json !== result.freshness_reason_json);
115
- }
116
- function calculateCaseFreshness(input) {
117
- const threshold = normalizedThreshold(input.drift_threshold);
118
- const reasons = [];
119
- let value = 1.0;
120
- const compiledMs = timestampMs(input.compiled_at);
121
- const activityMs = timestampMs(input.last_activity_at);
122
- const stateUpdatedMs = timestampMs(input.state_updated_at);
123
- const wikiUpdatedMs = timestampMs(input.wiki_index_updated_at);
124
- const hasWikiPath = Boolean(input.current_wiki_path && input.current_wiki_path.trim().length > 0);
125
- if (compiledMs === null) {
126
- value -= 0.5;
127
- reasons.push({ code: 'missing_compile', penalty: 0.5 });
128
- }
129
- if (activityMs !== null && compiledMs !== null && activityMs > compiledMs) {
130
- value -= 0.35;
131
- reasons.push({ code: 'activity_after_compile', penalty: 0.35 });
132
- }
133
- if (stateUpdatedMs !== null && compiledMs !== null && stateUpdatedMs > compiledMs) {
134
- value -= 0.25;
135
- reasons.push({ code: 'state_update_after_compile', penalty: 0.25 });
136
- }
137
- if (hasWikiPath && wikiUpdatedMs === null) {
138
- value -= 0.25;
139
- reasons.push({
140
- code: 'wiki_index_missing',
141
- penalty: 0.25,
142
- detail: input.current_wiki_path ?? '',
143
- });
144
- }
145
- const timestampsAbsent = compiledMs === null && activityMs === null && stateUpdatedMs === null;
146
- if (timestampsAbsent) {
147
- reasons.push({ code: 'timestamps_absent', penalty: 0 });
148
- }
149
- const freshnessScore = score(value);
150
- let state;
151
- if (timestampsAbsent) {
152
- state = 'unknown';
153
- }
154
- else if (freshnessScore >= 0.8) {
155
- state = 'fresh';
156
- }
157
- else if (freshnessScore < threshold) {
158
- state = 'drifted';
159
- }
160
- else {
161
- state = 'stale';
162
- }
163
- const result = {
164
- freshness_score: freshnessScore,
165
- freshness_state: state,
166
- freshness_score_is_drifted: state === 'drifted' ? 1 : 0,
167
- freshness_drift_threshold: threshold,
168
- reasons,
169
- freshness_reason_json: (0, canonicalize_js_1.canonicalizeJSON)(reasons),
170
- };
171
- return result;
172
- }
173
- function sweepCaseFreshness(adapter, input = {}) {
174
- const freshnessAdapter = adapter;
175
- return (0, sqlite_transaction_js_1.runImmediateTransaction)(freshnessAdapter, () => {
176
- const now = normalizeNow(input.now);
177
- const requestedCaseIds = input.case_ids ?? allCandidateCaseIds(freshnessAdapter);
178
- const seenTerminalIds = new Set();
179
- const results = [];
180
- const rejected = [];
181
- for (const requestedCaseId of requestedCaseIds) {
182
- const resolution = resolveChain(freshnessAdapter, requestedCaseId);
183
- if (!resolution) {
184
- continue;
185
- }
186
- if (seenTerminalIds.has(resolution.terminal_case_id)) {
187
- continue;
188
- }
189
- const row = loadCase(freshnessAdapter, resolution.terminal_case_id);
190
- if (!row) {
191
- continue;
192
- }
193
- if (TERMINAL_CASE_STATUSES.has(row.status)) {
194
- if (input.case_ids) {
195
- rejected.push({
196
- case_id: requestedCaseId,
197
- code: 'case.terminal_status',
198
- message: `Freshness cannot be written to terminal case status ${row.status}.`,
199
- });
200
- }
201
- continue;
202
- }
203
- seenTerminalIds.add(resolution.terminal_case_id);
204
- const chain = (0, store_js_1.expandCaseChainForAssembly)(freshnessAdapter, resolution.terminal_case_id, resolution.chain);
205
- const calculation = calculateCaseFreshness({
206
- last_activity_at: row.last_activity_at,
207
- state_updated_at: row.state_updated_at,
208
- compiled_at: row.compiled_at,
209
- wiki_index_updated_at: wikiIndexUpdatedAt(freshnessAdapter, resolution.terminal_case_id),
210
- current_wiki_path: row.current_wiki_path,
211
- drift_threshold: input.drift_threshold,
212
- now,
213
- });
214
- const changed = hasFreshnessChanged(row, calculation);
215
- const driftedTransition = changed && row.freshness_state !== 'drifted' && calculation.freshness_state === 'drifted';
216
- if (changed) {
217
- freshnessAdapter
218
- .prepare(`
219
- UPDATE case_truth
220
- SET freshness_score = ?,
221
- freshness_state = ?,
222
- freshness_score_is_drifted = ?,
223
- freshness_drift_threshold = ?,
224
- freshness_checked_at = ?,
225
- freshness_reason_json = ?
226
- WHERE case_id = ?
227
- `)
228
- .run(calculation.freshness_score, calculation.freshness_state, calculation.freshness_score_is_drifted, calculation.freshness_drift_threshold, now, calculation.freshness_reason_json, resolution.terminal_case_id);
229
- if (driftedTransition) {
230
- insertDriftEvent({
231
- adapter: freshnessAdapter,
232
- case_id: resolution.terminal_case_id,
233
- result: calculation,
234
- now,
235
- });
236
- }
237
- }
238
- else {
239
- freshnessAdapter
240
- .prepare(`
241
- UPDATE case_truth
242
- SET freshness_checked_at = ?
243
- WHERE case_id = ?
244
- `)
245
- .run(now, resolution.terminal_case_id);
246
- }
247
- results.push({
248
- case_id: requestedCaseId,
249
- terminal_case_id: resolution.terminal_case_id,
250
- resolved_via_case_id: resolution.resolved_via_case_id,
251
- chain,
252
- freshness_checked_at: now,
253
- changed,
254
- ...calculation,
255
- });
256
- }
257
- return {
258
- checked: results.length,
259
- fresh: results.filter((result) => result.freshness_state === 'fresh').length,
260
- stale: results.filter((result) => result.freshness_state === 'stale').length,
261
- drifted: results.filter((result) => result.freshness_state === 'drifted').length,
262
- drifted_case_ids: results
263
- .filter((result) => result.freshness_state === 'drifted')
264
- .map((result) => result.terminal_case_id),
265
- results,
266
- ...(rejected.length > 0 ? { rejected } : {}),
267
- };
268
- });
269
- }
270
- function listDriftedCases(adapter) {
271
- const rows = adapter
272
- .prepare(`
273
- SELECT case_id, freshness_score, freshness_checked_at
274
- FROM case_truth
275
- WHERE freshness_state = 'drifted'
276
- AND freshness_score_is_drifted = 1
277
- AND status NOT IN ('merged','archived','split')
278
- ORDER BY freshness_score ASC, case_id ASC
279
- `)
280
- .all();
281
- return rows;
282
- }
283
- //# sourceMappingURL=freshness.js.map
@@ -1,48 +0,0 @@
1
- import type { DatabaseAdapter } from '../db-manager.js';
2
- import type { CaseFastWriteStatus as CaseFastWriteStatusFromTypes } from './types.js';
3
- export type CaseFastWriteErrorCode = 'case.precompile_gap' | 'case.lock_held' | 'case.terminal_status' | 'case.correction_active_conflict';
4
- export interface CaseMembershipWritePlan {
5
- source_type: 'decision' | 'event' | 'observation' | 'artifact';
6
- source_id: string;
7
- role?: 'requester' | 'implementer' | 'reviewer' | 'observer' | 'affected' | null;
8
- confidence: number;
9
- reason: string;
10
- status: 'active' | 'candidate';
11
- }
12
- type CaseFastWriteStatus = CaseFastWriteStatusFromTypes;
13
- export interface WriteCaseLiveStateInput {
14
- case_id: string;
15
- source_event_id: string;
16
- source_type: 'event';
17
- status?: CaseFastWriteStatus;
18
- last_activity_at?: string;
19
- membership?: CaseMembershipWritePlan;
20
- actor?: 'memory_agent';
21
- now?: string;
22
- }
23
- export type WriteCaseLiveStateResult = {
24
- kind: 'applied';
25
- case_id: string;
26
- applied_targets: Array<{
27
- target_kind: 'case_field' | 'membership';
28
- target_ref_json: string;
29
- }>;
30
- skipped_targets: Array<{
31
- target_kind: 'case_field' | 'membership';
32
- target_ref_json: string;
33
- correction_id: string;
34
- }>;
35
- audit_event_ids: string[];
36
- } | {
37
- kind: 'precompile_gap';
38
- code: 'case.precompile_gap';
39
- case_id: string;
40
- } | {
41
- kind: 'rejected';
42
- code: CaseFastWriteErrorCode;
43
- message: string;
44
- case_id: string;
45
- };
46
- export declare function writeCaseLiveStateFromEvent(adapter: DatabaseAdapter, input: WriteCaseLiveStateInput): WriteCaseLiveStateResult;
47
- export {};
48
- //# sourceMappingURL=live-state.d.ts.map
@@ -1,256 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.writeCaseLiveStateFromEvent = writeCaseLiveStateFromEvent;
4
- const node_crypto_1 = require("node:crypto");
5
- const canonicalize_js_1 = require("../canonicalize.js");
6
- const target_ref_js_1 = require("./target-ref.js");
7
- const sqlite_transaction_js_1 = require("./sqlite-transaction.js");
8
- const TERMINAL_CASE_STATUSES = new Set(['merged', 'archived', 'split']);
9
- const FAST_WRITE_STATUSES = new Set([
10
- 'active',
11
- 'blocked',
12
- 'resolved',
13
- 'stale',
14
- ]);
15
- function isCaseFastWriteStatus(value) {
16
- return typeof value === 'string' && FAST_WRITE_STATUSES.has(value);
17
- }
18
- function normalizeNow(value) {
19
- return value ?? new Date().toISOString();
20
- }
21
- function createdAtMs(now) {
22
- const parsed = Date.parse(now);
23
- return Number.isFinite(parsed) ? parsed : Date.now();
24
- }
25
- function placeholders(values) {
26
- if (values.length === 0) {
27
- throw new Error('Cannot build SQL IN clause for an empty value list.');
28
- }
29
- return values.map(() => '?').join(', ');
30
- }
31
- function toHashKey(value) {
32
- return Buffer.from(value).toString('hex');
33
- }
34
- function targetPlanForCanonical(target_kind, canonical) {
35
- return {
36
- target_kind,
37
- target_ref_json: canonical.json,
38
- target_ref_hash: canonical.hash,
39
- };
40
- }
41
- function buildTargetPlans(input) {
42
- const targets = [];
43
- if (input.status !== undefined) {
44
- targets.push(targetPlanForCanonical('case_field', (0, target_ref_js_1.canonicalTargetRef)((0, target_ref_js_1.buildCaseFieldTargetRef)('status'))));
45
- }
46
- if (input.last_activity_at !== undefined) {
47
- targets.push(targetPlanForCanonical('case_field', (0, target_ref_js_1.canonicalTargetRef)((0, target_ref_js_1.buildCaseFieldTargetRef)('last_activity_at'))));
48
- }
49
- if (input.membership) {
50
- targets.push(targetPlanForCanonical('membership', (0, target_ref_js_1.canonicalTargetRef)((0, target_ref_js_1.buildMembershipTargetRef)(input.membership.source_type, input.membership.source_id))));
51
- }
52
- return targets;
53
- }
54
- function readCaseChain(adapter, caseId) {
55
- const rows = adapter
56
- .prepare(`
57
- WITH RECURSIVE case_chain(case_id) AS (
58
- SELECT case_id
59
- FROM case_truth
60
- WHERE case_id = ?
61
-
62
- UNION
63
-
64
- SELECT ct.case_id
65
- FROM case_truth ct
66
- JOIN case_chain cc ON ct.canonical_case_id = cc.case_id
67
- )
68
- SELECT case_id
69
- FROM case_chain
70
- `)
71
- .all(caseId);
72
- return rows.map((row) => row.case_id);
73
- }
74
- function readActiveLocks(adapter, chainIds, targets) {
75
- if (chainIds.length === 0 || targets.length === 0) {
76
- return new Map();
77
- }
78
- const targetHashes = targets.map((target) => target.target_ref_hash);
79
- const rows = adapter
80
- .prepare(`
81
- SELECT correction_id, case_id, target_kind, field_name, target_ref_json, target_ref_hash
82
- FROM case_corrections
83
- WHERE case_id IN (${placeholders(chainIds)})
84
- AND is_lock_active = 1
85
- AND reverted_at IS NULL
86
- AND target_ref_hash IN (${placeholders(targetHashes)})
87
- `)
88
- .all(...chainIds, ...targetHashes);
89
- const locks = new Map();
90
- for (const row of rows) {
91
- locks.set(toHashKey(row.target_ref_hash), row);
92
- }
93
- return locks;
94
- }
95
- function findTarget(targets, target_kind, target_ref_json) {
96
- return (targets.find((target) => target.target_kind === target_kind && target.target_ref_json === target_ref_json) ?? null);
97
- }
98
- function hasLock(locks, target) {
99
- if (!target) {
100
- return null;
101
- }
102
- return locks.get(toHashKey(target.target_ref_hash)) ?? null;
103
- }
104
- function insertSkippedAuditEvent(input) {
105
- const eventId = `me_${(0, node_crypto_1.randomUUID)()}`;
106
- input.adapter
107
- .prepare(`
108
- INSERT INTO memory_events (
109
- event_id, event_type, actor, source_turn_id, memory_id, topic,
110
- scope_refs, evidence_refs, reason, created_at
111
- )
112
- VALUES (?, 'case.fast_write_lock_skipped', 'memory_agent', ?, NULL, ?, ?, ?, ?, ?)
113
- `)
114
- .run(eventId, input.source_event_id, `case:${input.case_id}`, (0, canonicalize_js_1.canonicalizeJSON)([{ type: 'case', id: input.case_id }]), (0, canonicalize_js_1.canonicalizeJSON)([input.source_event_id]), (0, canonicalize_js_1.canonicalizeJSON)({ skipped_targets: input.skipped_targets }), createdAtMs(input.now));
115
- return eventId;
116
- }
117
- function writeCaseLiveStateFromEvent(adapter, input) {
118
- const liveAdapter = adapter;
119
- return (0, sqlite_transaction_js_1.runImmediateTransaction)(liveAdapter, () => {
120
- const caseRow = liveAdapter
121
- .prepare(`
122
- SELECT case_id, status
123
- FROM case_truth
124
- WHERE case_id = ?
125
- `)
126
- .get(input.case_id);
127
- if (!caseRow) {
128
- return { kind: 'precompile_gap', code: 'case.precompile_gap', case_id: input.case_id };
129
- }
130
- if (TERMINAL_CASE_STATUSES.has(caseRow.status)) {
131
- return {
132
- kind: 'rejected',
133
- code: 'case.terminal_status',
134
- message: `Memory-agent cannot update terminal case status ${caseRow.status}.`,
135
- case_id: input.case_id,
136
- };
137
- }
138
- if (input.status !== undefined && !isCaseFastWriteStatus(input.status)) {
139
- return {
140
- kind: 'rejected',
141
- code: 'case.terminal_status',
142
- message: `Memory-agent cannot write terminal case status ${String(input.status)}.`,
143
- case_id: input.case_id,
144
- };
145
- }
146
- const now = normalizeNow(input.now);
147
- const chainIds = readCaseChain(liveAdapter, input.case_id);
148
- const targets = buildTargetPlans(input);
149
- const locks = readActiveLocks(liveAdapter, chainIds, targets);
150
- const applied_targets = [];
151
- const skipped_targets = [];
152
- const statusTarget = targets[0] ?? null;
153
- const lastActivityTarget = input.status !== undefined ? (targets[1] ?? null) : (targets[0] ?? null);
154
- const membershipTarget = input.membership
155
- ? findTarget(targets, 'membership', (0, target_ref_js_1.canonicalTargetRef)((0, target_ref_js_1.buildMembershipTargetRef)(input.membership.source_type, input.membership.source_id)).json)
156
- : null;
157
- const statusLock = input.status !== undefined ? hasLock(locks, statusTarget) : null;
158
- const lastActivityLock = input.last_activity_at !== undefined ? hasLock(locks, lastActivityTarget) : null;
159
- const membershipLock = input.membership ? hasLock(locks, membershipTarget) : null;
160
- if (statusLock && statusTarget) {
161
- skipped_targets.push({
162
- target_kind: statusTarget.target_kind,
163
- target_ref_json: statusTarget.target_ref_json,
164
- correction_id: statusLock.correction_id,
165
- });
166
- }
167
- if (lastActivityLock && lastActivityTarget) {
168
- skipped_targets.push({
169
- target_kind: lastActivityTarget.target_kind,
170
- target_ref_json: lastActivityTarget.target_ref_json,
171
- correction_id: lastActivityLock.correction_id,
172
- });
173
- }
174
- if (membershipLock && membershipTarget) {
175
- skipped_targets.push({
176
- target_kind: membershipTarget.target_kind,
177
- target_ref_json: membershipTarget.target_ref_json,
178
- correction_id: membershipLock.correction_id,
179
- });
180
- }
181
- const shouldWriteStatus = input.status !== undefined && !statusLock;
182
- const shouldWriteLastActivity = input.last_activity_at !== undefined && !lastActivityLock;
183
- if (shouldWriteStatus || shouldWriteLastActivity) {
184
- const updateResult = liveAdapter
185
- .prepare(`
186
- UPDATE case_truth
187
- SET status = CASE WHEN ? = 1 THEN ? ELSE status END,
188
- last_activity_at = CASE WHEN ? = 1 THEN ? ELSE last_activity_at END,
189
- state_updated_at = ?,
190
- updated_at = ?
191
- WHERE case_id = ?
192
- AND status NOT IN ('merged','archived','split')
193
- `)
194
- .run(shouldWriteStatus ? 1 : 0, input.status ?? null, shouldWriteLastActivity ? 1 : 0, input.last_activity_at ?? null, now, now, input.case_id);
195
- if (updateResult.changes > 0) {
196
- if (shouldWriteStatus && statusTarget) {
197
- applied_targets.push({
198
- target_kind: statusTarget.target_kind,
199
- target_ref_json: statusTarget.target_ref_json,
200
- });
201
- }
202
- if (shouldWriteLastActivity && lastActivityTarget) {
203
- applied_targets.push({
204
- target_kind: lastActivityTarget.target_kind,
205
- target_ref_json: lastActivityTarget.target_ref_json,
206
- });
207
- }
208
- }
209
- }
210
- if (input.membership && !membershipLock && membershipTarget) {
211
- const membershipResult = liveAdapter
212
- .prepare(`
213
- INSERT INTO case_memberships (
214
- case_id, source_type, source_id, role, confidence, reason, status,
215
- added_by, added_at, user_locked, updated_at
216
- )
217
- VALUES (?, ?, ?, ?, ?, ?, ?, 'memory-agent', ?, 0, ?)
218
- ON CONFLICT(case_id, source_type, source_id) DO UPDATE SET
219
- role = excluded.role,
220
- confidence = excluded.confidence,
221
- reason = excluded.reason,
222
- status = excluded.status,
223
- added_by = 'memory-agent',
224
- updated_at = excluded.updated_at
225
- WHERE case_memberships.user_locked = 0
226
- AND case_memberships.status <> 'stale'
227
- `)
228
- .run(input.case_id, input.membership.source_type, input.membership.source_id, input.membership.role ?? null, input.membership.confidence, input.membership.reason, input.membership.status, now, now);
229
- if (membershipResult.changes > 0) {
230
- applied_targets.push({
231
- target_kind: membershipTarget.target_kind,
232
- target_ref_json: membershipTarget.target_ref_json,
233
- });
234
- }
235
- }
236
- const audit_event_ids = skipped_targets.length > 0
237
- ? [
238
- insertSkippedAuditEvent({
239
- adapter: liveAdapter,
240
- case_id: input.case_id,
241
- source_event_id: input.source_event_id,
242
- skipped_targets,
243
- now,
244
- }),
245
- ]
246
- : [];
247
- return {
248
- kind: 'applied',
249
- case_id: input.case_id,
250
- applied_targets,
251
- skipped_targets,
252
- audit_event_ids,
253
- };
254
- });
255
- }
256
- //# sourceMappingURL=live-state.js.map