@jungjaehoon/mama-core 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +48 -10
  2. package/db/migrations/032-add-memory-provenance-columns.sql +21 -0
  3. package/db/migrations/033-create-model-runs-and-tool-traces.sql +53 -0
  4. package/db/migrations/034-add-connector-event-scope-columns.sql +14 -0
  5. package/db/migrations/035-create-twin-edges.sql +63 -0
  6. package/db/migrations/036-create-agent-situation-packets.sql +61 -0
  7. package/dist/agent-graph/alias-write.d.ts +3 -0
  8. package/dist/agent-graph/alias-write.js +231 -0
  9. package/dist/agent-graph/entity-resolve.d.ts +3 -0
  10. package/dist/agent-graph/entity-resolve.js +275 -0
  11. package/dist/agent-graph/errors.d.ts +4 -0
  12. package/dist/agent-graph/errors.js +11 -0
  13. package/dist/agent-graph/graph-query.d.ts +5 -0
  14. package/dist/agent-graph/graph-query.js +359 -0
  15. package/dist/agent-graph/index.d.ts +6 -0
  16. package/dist/agent-graph/index.js +22 -0
  17. package/dist/agent-graph/types.d.ts +162 -0
  18. package/dist/agent-graph/types.js +3 -0
  19. package/dist/agent-situation/builder.d.ts +8 -0
  20. package/dist/agent-situation/builder.js +239 -0
  21. package/dist/agent-situation/cache-key.d.ts +4 -0
  22. package/dist/agent-situation/cache-key.js +64 -0
  23. package/dist/agent-situation/index.d.ts +7 -0
  24. package/dist/agent-situation/index.js +28 -0
  25. package/dist/agent-situation/packet-store.d.ts +27 -0
  26. package/dist/agent-situation/packet-store.js +228 -0
  27. package/dist/agent-situation/ranking-policy.d.ts +18 -0
  28. package/dist/agent-situation/ranking-policy.js +75 -0
  29. package/dist/agent-situation/source-readers.d.ts +52 -0
  30. package/dist/agent-situation/source-readers.js +300 -0
  31. package/dist/agent-situation/types.d.ts +195 -0
  32. package/dist/agent-situation/types.js +3 -0
  33. package/dist/cases/search-rollup.d.ts +4 -0
  34. package/dist/cases/search-rollup.js +84 -0
  35. package/dist/connectors/event-index.d.ts +1 -0
  36. package/dist/connectors/event-index.js +19 -4
  37. package/dist/connectors/raw-query.d.ts +18 -0
  38. package/dist/connectors/raw-query.js +305 -0
  39. package/dist/connectors/types.d.ts +40 -0
  40. package/dist/db-adapter/node-sqlite-adapter.d.ts +6 -0
  41. package/dist/db-adapter/node-sqlite-adapter.js +131 -0
  42. package/dist/db-manager.d.ts +8 -1
  43. package/dist/db-manager.js +53 -5
  44. package/dist/edges/ref-validation.d.ts +8 -0
  45. package/dist/edges/ref-validation.js +262 -0
  46. package/dist/edges/store.d.ts +10 -0
  47. package/dist/edges/store.js +343 -0
  48. package/dist/edges/types.d.ts +86 -0
  49. package/dist/edges/types.js +18 -0
  50. package/dist/embeddings.js +13 -0
  51. package/dist/index.d.ts +17 -3
  52. package/dist/index.js +53 -4
  53. package/dist/mama-api.d.ts +32 -35
  54. package/dist/mama-api.js +173 -22
  55. package/dist/memory/api.d.ts +17 -56
  56. package/dist/memory/api.js +494 -52
  57. package/dist/memory/event-store.d.ts +3 -0
  58. package/dist/memory/event-store.js +21 -2
  59. package/dist/memory/provenance-audit.d.ts +22 -0
  60. package/dist/memory/provenance-audit.js +112 -0
  61. package/dist/memory/provenance-query.d.ts +11 -0
  62. package/dist/memory/provenance-query.js +193 -0
  63. package/dist/memory/provenance.d.ts +29 -0
  64. package/dist/memory/provenance.js +136 -0
  65. package/dist/memory/scope-backfill.d.ts +17 -0
  66. package/dist/memory/scope-backfill.js +77 -0
  67. package/dist/memory/types.d.ts +86 -14
  68. package/dist/model-runs/store.d.ts +13 -0
  69. package/dist/model-runs/store.js +457 -0
  70. package/dist/model-runs/tool-trace-store.d.ts +4 -0
  71. package/dist/model-runs/tool-trace-store.js +114 -0
  72. package/dist/model-runs/types.d.ts +69 -0
  73. package/dist/model-runs/types.js +5 -0
  74. package/dist/runtime/trusted-provenance.d.ts +3 -0
  75. package/dist/runtime/trusted-provenance.js +6 -0
  76. package/dist/search/search-quality.d.ts +36 -0
  77. package/dist/search/search-quality.js +29 -0
  78. package/package.json +9 -2
@@ -0,0 +1,8 @@
1
+ import type { DatabaseAdapter } from '../db-manager.js';
2
+ import type { ListVisibleTwinEdgesOptions, TwinEdgeRecord, TwinRef, TwinScopeRef, TwinVisibility } from './types.js';
3
+ type TwinRefVisibilityAdapter = Pick<DatabaseAdapter, 'prepare'>;
4
+ export declare function assertTwinRefsVisible(adapter: TwinRefVisibilityAdapter, refs: readonly TwinRef[], visibility?: TwinVisibility): void;
5
+ export declare function assertTwinRefsVisibleToScopes(adapter: TwinRefVisibilityAdapter, refs: readonly TwinRef[], scopes?: TwinScopeRef[]): void;
6
+ export declare function listVisibleTwinEdgesForRefs(adapter: TwinRefVisibilityAdapter, refs: readonly TwinRef[], options?: ListVisibleTwinEdgesOptions): TwinEdgeRecord[];
7
+ export {};
8
+ //# sourceMappingURL=ref-validation.d.ts.map
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertTwinRefsVisible = assertTwinRefsVisible;
4
+ exports.assertTwinRefsVisibleToScopes = assertTwinRefsVisibleToScopes;
5
+ exports.listVisibleTwinEdgesForRefs = listVisibleTwinEdgesForRefs;
6
+ const store_js_1 = require("./store.js");
7
+ const TABLE_COLUMN_PRAGMAS = {
8
+ decisions: 'PRAGMA table_info(decisions)',
9
+ case_truth: 'PRAGMA table_info(case_truth)',
10
+ };
11
+ const tableColumnCache = new WeakMap();
12
+ function scopeKey(scope) {
13
+ return `${scope.kind}\0${scope.id}`;
14
+ }
15
+ function hasScopes(scopes) {
16
+ return Array.isArray(scopes) && scopes.length > 0;
17
+ }
18
+ function hasConnectors(connectors) {
19
+ return Array.isArray(connectors) && connectors.length > 0;
20
+ }
21
+ function hasProjectRefs(projectRefs) {
22
+ return Array.isArray(projectRefs) && projectRefs.length > 0;
23
+ }
24
+ function visibilityFromScopes(scopes) {
25
+ return { scopes: scopes ? [...scopes] : undefined };
26
+ }
27
+ function asOfMs(visibility) {
28
+ return typeof visibility.asOfMs === 'number' ? visibility.asOfMs : null;
29
+ }
30
+ function isAtOrBeforeAsOf(value, visibility) {
31
+ const asOf = asOfMs(visibility);
32
+ return asOf === null || (typeof value === 'number' && value <= asOf);
33
+ }
34
+ function parseTimestamp(value) {
35
+ if (typeof value === 'number' && Number.isFinite(value)) {
36
+ return Math.floor(value);
37
+ }
38
+ if (typeof value === 'string') {
39
+ const parsed = Date.parse(value);
40
+ return Number.isFinite(parsed) ? parsed : null;
41
+ }
42
+ return null;
43
+ }
44
+ function tableColumns(adapter, table) {
45
+ const pragma = TABLE_COLUMN_PRAGMAS[table];
46
+ if (!pragma) {
47
+ throw new Error(`Unsupported table for column introspection: ${table}`);
48
+ }
49
+ let adapterCache = tableColumnCache.get(adapter);
50
+ if (!adapterCache) {
51
+ adapterCache = new Map();
52
+ tableColumnCache.set(adapter, adapterCache);
53
+ }
54
+ const cached = adapterCache.get(table);
55
+ if (cached) {
56
+ return cached;
57
+ }
58
+ const rows = adapter.prepare(pragma).all();
59
+ const columns = new Set(rows.map((row) => row.name));
60
+ adapterCache.set(table, columns);
61
+ return columns;
62
+ }
63
+ function isMemoryVisible(adapter, id, visibility) {
64
+ const row = adapter
65
+ .prepare(`
66
+ SELECT created_at, event_datetime
67
+ FROM decisions
68
+ WHERE id = ?
69
+ LIMIT 1
70
+ `)
71
+ .get(id);
72
+ if (!row) {
73
+ return false;
74
+ }
75
+ if (!isAtOrBeforeAsOf(row.event_datetime ?? row.created_at, visibility)) {
76
+ return false;
77
+ }
78
+ const scopes = visibility.scopes;
79
+ if (!hasScopes(scopes)) {
80
+ return true;
81
+ }
82
+ const scopeClauses = scopes.map(() => '(ms.kind = ? AND ms.external_id = ?)').join(' OR ');
83
+ const params = scopes.flatMap((scope) => [scope.kind, scope.id]);
84
+ const bindingRow = adapter
85
+ .prepare(`
86
+ SELECT 1 AS ok
87
+ FROM memory_scope_bindings msb
88
+ JOIN memory_scopes ms ON ms.id = msb.scope_id
89
+ WHERE msb.memory_id = ?
90
+ AND (${scopeClauses})
91
+ LIMIT 1
92
+ `)
93
+ .get(id, ...params);
94
+ if (bindingRow?.ok) {
95
+ return true;
96
+ }
97
+ const columns = tableColumns(adapter, 'decisions');
98
+ if (columns.has('memory_scope_kind') && columns.has('memory_scope_id')) {
99
+ const row = adapter
100
+ .prepare(`
101
+ SELECT 1 AS ok
102
+ FROM decisions
103
+ WHERE id = ?
104
+ AND (${scopes.map(() => '(memory_scope_kind = ? AND memory_scope_id = ?)').join(' OR ')})
105
+ LIMIT 1
106
+ `)
107
+ .get(id, ...params);
108
+ return Boolean(row?.ok);
109
+ }
110
+ return false;
111
+ }
112
+ function parseCaseScopeRefs(scopeRefs) {
113
+ if (typeof scopeRefs !== 'string' || scopeRefs.length === 0) {
114
+ return [];
115
+ }
116
+ try {
117
+ const parsed = JSON.parse(scopeRefs);
118
+ if (!Array.isArray(parsed)) {
119
+ return [];
120
+ }
121
+ return parsed.filter((item) => item !== null &&
122
+ typeof item === 'object' &&
123
+ typeof item.kind === 'string' &&
124
+ typeof item.id === 'string');
125
+ }
126
+ catch {
127
+ return [];
128
+ }
129
+ }
130
+ function isCaseVisible(adapter, id, visibility) {
131
+ const row = adapter.prepare('SELECT * FROM case_truth WHERE case_id = ? LIMIT 1').get(id);
132
+ if (!row) {
133
+ return false;
134
+ }
135
+ const timestamp = parseTimestamp(row.last_activity_at) ?? parseTimestamp(row.updated_at);
136
+ if (!isAtOrBeforeAsOf(timestamp, visibility)) {
137
+ return false;
138
+ }
139
+ const scopes = visibility.scopes;
140
+ if (!hasScopes(scopes)) {
141
+ return true;
142
+ }
143
+ const requested = new Set(scopes.map(scopeKey));
144
+ const scopeRefs = parseCaseScopeRefs(row.scope_refs);
145
+ if (scopeRefs.some((scope) => requested.has(scopeKey(scope)))) {
146
+ return true;
147
+ }
148
+ const columns = tableColumns(adapter, 'case_truth');
149
+ if (columns.has('memory_scope_kind') && columns.has('memory_scope_id')) {
150
+ return scopes.some((scope) => row.memory_scope_kind === scope.kind && row.memory_scope_id === scope.id);
151
+ }
152
+ return false;
153
+ }
154
+ function isRawVisible(adapter, id, visibility) {
155
+ const row = adapter
156
+ .prepare('SELECT * FROM connector_event_index WHERE event_index_id = ? LIMIT 1')
157
+ .get(id);
158
+ if (!row) {
159
+ return false;
160
+ }
161
+ if (hasConnectors(visibility.connectors) &&
162
+ !visibility.connectors.includes(String(row.source_connector))) {
163
+ return false;
164
+ }
165
+ if (hasProjectRefs(visibility.projectRefs) &&
166
+ !visibility.projectRefs.some((project) => project.id === row.project_id)) {
167
+ return false;
168
+ }
169
+ if (typeof visibility.tenantId === 'string' &&
170
+ visibility.tenantId.length > 0 &&
171
+ row.tenant_id !== visibility.tenantId) {
172
+ return false;
173
+ }
174
+ if (!isAtOrBeforeAsOf(Number(row.event_datetime ?? row.source_timestamp_ms), visibility)) {
175
+ return false;
176
+ }
177
+ if (!hasScopes(visibility.scopes)) {
178
+ return true;
179
+ }
180
+ return visibility.scopes.some((scope) => row.memory_scope_kind === scope.kind && row.memory_scope_id === scope.id);
181
+ }
182
+ function isEntityVisible(adapter, id, visibility) {
183
+ const row = adapter
184
+ .prepare('SELECT scope_kind, scope_id, created_at FROM entity_nodes WHERE id = ? AND status = ? LIMIT 1')
185
+ .get(id, 'active');
186
+ if (!row) {
187
+ return false;
188
+ }
189
+ if (!isAtOrBeforeAsOf(row.created_at, visibility)) {
190
+ return false;
191
+ }
192
+ const scopes = visibility.scopes;
193
+ if (!hasScopes(scopes)) {
194
+ return true;
195
+ }
196
+ return scopes.some((scope) => row.scope_kind === scope.kind && row.scope_id === scope.id);
197
+ }
198
+ function isTwinRefVisible(adapter, ref, visibility, visitedEdges, edgeCache) {
199
+ if (ref.kind === 'entity') {
200
+ return isEntityVisible(adapter, ref.id, visibility);
201
+ }
202
+ if (ref.kind === 'report') {
203
+ return !hasScopes(visibility.scopes);
204
+ }
205
+ if (ref.kind === 'memory') {
206
+ return isMemoryVisible(adapter, ref.id, visibility);
207
+ }
208
+ if (ref.kind === 'case') {
209
+ return isCaseVisible(adapter, ref.id, visibility);
210
+ }
211
+ if (ref.kind === 'raw') {
212
+ return isRawVisible(adapter, ref.id, visibility);
213
+ }
214
+ if (visitedEdges.has(ref.id)) {
215
+ return false;
216
+ }
217
+ let edge = edgeCache.get(ref.id);
218
+ if (edge === undefined) {
219
+ edge = (0, store_js_1.getTwinEdge)(adapter, ref.id);
220
+ edgeCache.set(ref.id, edge);
221
+ }
222
+ if (!edge) {
223
+ return false;
224
+ }
225
+ if (!isAtOrBeforeAsOf(edge.created_at, visibility)) {
226
+ return false;
227
+ }
228
+ const pathWithCurrent = new Set(visitedEdges);
229
+ pathWithCurrent.add(ref.id);
230
+ return (isTwinRefVisible(adapter, edge.subject_ref, visibility, new Set(pathWithCurrent), edgeCache) &&
231
+ isTwinRefVisible(adapter, edge.object_ref, visibility, new Set(pathWithCurrent), edgeCache));
232
+ }
233
+ function assertTwinRefsVisible(adapter, refs, visibility = {}) {
234
+ const edgeCache = new Map();
235
+ for (const ref of refs) {
236
+ if (!isTwinRefVisible(adapter, ref, visibility, new Set(), edgeCache)) {
237
+ throw new Error(`Twin ref is not visible to requested visibility: ${ref.kind}:${ref.id}`);
238
+ }
239
+ }
240
+ }
241
+ function assertTwinRefsVisibleToScopes(adapter, refs, scopes) {
242
+ assertTwinRefsVisible(adapter, refs, visibilityFromScopes(scopes));
243
+ }
244
+ function listVisibleTwinEdgesForRefs(adapter, refs, options = {}) {
245
+ const edgeCache = new Map();
246
+ const edgeTypes = normalizeEdgeTypes(options.edgeTypes);
247
+ const edges = (0, store_js_1.listTwinEdgesForRefs)(adapter, refs).filter((edge) => (edgeTypes.size === 0 || edgeTypes.has(edge.edge_type)) &&
248
+ (typeof options.asOfMs !== 'number' || edge.created_at <= options.asOfMs) &&
249
+ isTwinRefVisible(adapter, edge.subject_ref, options, new Set(), edgeCache) &&
250
+ isTwinRefVisible(adapter, edge.object_ref, options, new Set(), edgeCache));
251
+ const limit = typeof options.limit === 'number' && Number.isFinite(options.limit)
252
+ ? Math.max(0, Math.floor(options.limit))
253
+ : edges.length;
254
+ return edges.slice(0, limit);
255
+ }
256
+ function normalizeEdgeTypes(edgeTypes) {
257
+ if (!Array.isArray(edgeTypes) || edgeTypes.length === 0) {
258
+ return new Set();
259
+ }
260
+ return new Set(edgeTypes);
261
+ }
262
+ //# sourceMappingURL=ref-validation.js.map
@@ -0,0 +1,10 @@
1
+ import type { DatabaseAdapter } from '../db-manager.js';
2
+ import { type InsertTwinEdgeInput, type TwinEdgeRecord, type TwinRef } from './types.js';
3
+ type TwinEdgeReadAdapter = Pick<DatabaseAdapter, 'prepare'>;
4
+ type TwinEdgeAdapter = Pick<DatabaseAdapter, 'prepare' | 'transaction'>;
5
+ export declare function mapTwinEdgeRow(row: Record<string, unknown>): TwinEdgeRecord;
6
+ export declare function getTwinEdge(adapter: TwinEdgeReadAdapter, edgeId: string): TwinEdgeRecord | null;
7
+ export declare function listTwinEdgesForRefs(adapter: TwinEdgeReadAdapter, refs: readonly TwinRef[]): TwinEdgeRecord[];
8
+ export declare function insertTwinEdge(adapter: TwinEdgeAdapter, input: InsertTwinEdgeInput): TwinEdgeRecord;
9
+ export {};
10
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1,343 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mapTwinEdgeRow = mapTwinEdgeRow;
4
+ exports.getTwinEdge = getTwinEdge;
5
+ exports.listTwinEdgesForRefs = listTwinEdgesForRefs;
6
+ exports.insertTwinEdge = insertTwinEdge;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const canonicalize_js_1 = require("../canonicalize.js");
9
+ const types_js_1 = require("./types.js");
10
+ const EDGE_TYPE_SET = new Set(types_js_1.TWIN_EDGE_TYPES);
11
+ const REF_KIND_SET = new Set(types_js_1.TWIN_REF_KINDS);
12
+ const SOURCE_SET = new Set(types_js_1.TWIN_EDGE_SOURCES);
13
+ const HUMAN_ACTOR_ROLES = new Set(['commander', 'configurator_elevated']);
14
+ const HUMAN_REASON_CLASSIFICATIONS = new Set([
15
+ 'factual_correction',
16
+ 'agent_inference_wrong',
17
+ 'privacy_redaction',
18
+ 'duplicate_merge',
19
+ 'state_override',
20
+ 'other',
21
+ ]);
22
+ function sha256Hex(input) {
23
+ return (0, node_crypto_1.createHash)('sha256').update(input, 'utf8').digest('hex');
24
+ }
25
+ function sha256Buffer(input) {
26
+ return (0, node_crypto_1.createHash)('sha256').update(input, 'utf8').digest();
27
+ }
28
+ function generatedEdgeId() {
29
+ return `edge_${(0, node_crypto_1.randomUUID)().replace(/-/g, '')}`;
30
+ }
31
+ function nullableString(value) {
32
+ return typeof value === 'string' && value.length > 0 ? value : null;
33
+ }
34
+ function requireNonEmptyString(value, field) {
35
+ if (typeof value !== 'string' || value.length === 0) {
36
+ throw new Error(`twin_edges.${field} must be a non-empty string`);
37
+ }
38
+ return value;
39
+ }
40
+ function normalizeEdgeType(value) {
41
+ if (typeof value === 'string' && EDGE_TYPE_SET.has(value)) {
42
+ return value;
43
+ }
44
+ throw new Error(`Unsupported twin edge type: ${String(value)}`);
45
+ }
46
+ function normalizeSource(value) {
47
+ if (typeof value === 'string' && SOURCE_SET.has(value)) {
48
+ return value;
49
+ }
50
+ throw new Error(`Unsupported twin edge source: ${String(value)}`);
51
+ }
52
+ function normalizeRef(ref, field) {
53
+ if (!ref || typeof ref !== 'object') {
54
+ throw new Error(`${field} must be a TwinRef`);
55
+ }
56
+ if (!REF_KIND_SET.has(ref.kind)) {
57
+ throw new Error(`${field}.kind is unsupported: ${String(ref.kind)}`);
58
+ }
59
+ return {
60
+ kind: ref.kind,
61
+ id: requireNonEmptyString(ref.id, `${field}.id`),
62
+ };
63
+ }
64
+ function assertSubjectKindAllowed(ref) {
65
+ if (ref.kind === 'raw') {
66
+ throw new Error('twin_edges.subject_ref.kind cannot be raw');
67
+ }
68
+ }
69
+ function normalizeConfidence(value) {
70
+ if (value === undefined || value === null) {
71
+ return 1;
72
+ }
73
+ if (typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1) {
74
+ return value;
75
+ }
76
+ throw new Error(`twin_edges.confidence must be between 0 and 1: ${String(value)}`);
77
+ }
78
+ function hasDeterministicRule(value) {
79
+ return (value !== null &&
80
+ typeof value === 'object' &&
81
+ !Array.isArray(value) &&
82
+ typeof value.deterministic_rule === 'string' &&
83
+ value.deterministic_rule.length > 0);
84
+ }
85
+ function validateSourceMetadata(input, source) {
86
+ if (source === 'agent') {
87
+ requireNonEmptyString(input.agent_id, 'agent_id');
88
+ requireNonEmptyString(input.model_run_id, 'model_run_id');
89
+ requireNonEmptyString(input.envelope_hash, 'envelope_hash');
90
+ return;
91
+ }
92
+ if (source === 'human') {
93
+ requireNonEmptyString(input.human_actor_id, 'human_actor_id');
94
+ const role = requireNonEmptyString(input.human_actor_role, 'human_actor_role');
95
+ if (!HUMAN_ACTOR_ROLES.has(role)) {
96
+ throw new Error(`Unsupported twin_edges.human_actor_role: ${role}`);
97
+ }
98
+ if (input.authority_scope_json === undefined || input.authority_scope_json === null) {
99
+ throw new Error('twin_edges.authority_scope_json is required for human edges');
100
+ }
101
+ const classification = requireNonEmptyString(input.reason_classification, 'reason_classification');
102
+ if (!HUMAN_REASON_CLASSIFICATIONS.has(classification)) {
103
+ throw new Error(`Unsupported twin_edges.reason_classification: ${classification}`);
104
+ }
105
+ return;
106
+ }
107
+ if (!nullableString(input.reason_text) && !hasDeterministicRule(input.relation_attrs)) {
108
+ throw new Error('twin_edges.reason_text or relation_attrs.deterministic_rule is required for code edges');
109
+ }
110
+ }
111
+ function jsonField(value, field) {
112
+ if (value === undefined || value === null) {
113
+ return null;
114
+ }
115
+ try {
116
+ if (typeof value === 'string') {
117
+ return (0, canonicalize_js_1.canonicalizeJSON)(JSON.parse(value));
118
+ }
119
+ return (0, canonicalize_js_1.canonicalizeJSON)(value);
120
+ }
121
+ catch (error) {
122
+ const message = error instanceof Error ? error.message : String(error);
123
+ throw new Error(`Invalid twin_edges.${field}: ${message}`);
124
+ }
125
+ }
126
+ function parseJsonField(value, field, edgeId) {
127
+ if (value === null || value === undefined) {
128
+ return null;
129
+ }
130
+ const text = String(value);
131
+ if (text.length === 0) {
132
+ return null;
133
+ }
134
+ try {
135
+ return JSON.parse(text);
136
+ }
137
+ catch (error) {
138
+ const message = error instanceof Error ? error.message : String(error);
139
+ throw new Error(`Invalid twin_edges.${field} for ${edgeId}: ${message}`);
140
+ }
141
+ }
142
+ function toBuffer(value) {
143
+ if (Buffer.isBuffer(value)) {
144
+ return value;
145
+ }
146
+ if (value instanceof Uint8Array) {
147
+ return Buffer.from(value);
148
+ }
149
+ throw new Error('twin_edges.content_hash must be a 32-byte Buffer');
150
+ }
151
+ function exists(adapter, table, column, id) {
152
+ const row = adapter
153
+ .prepare(`SELECT 1 AS ok FROM ${table} WHERE ${column} = ? LIMIT 1`)
154
+ .get(id);
155
+ return Boolean(row?.ok);
156
+ }
157
+ function validateRefExists(adapter, ref, field) {
158
+ if (ref.kind === 'report') {
159
+ throw new Error(`${field} ${ref.kind}:${ref.id} is not supported by twin_edges yet`);
160
+ }
161
+ const found = (ref.kind === 'memory' && exists(adapter, 'decisions', 'id', ref.id)) ||
162
+ (ref.kind === 'case' && exists(adapter, 'case_truth', 'case_id', ref.id)) ||
163
+ (ref.kind === 'entity' && exists(adapter, 'entity_nodes', 'id', ref.id)) ||
164
+ (ref.kind === 'edge' && exists(adapter, 'twin_edges', 'edge_id', ref.id)) ||
165
+ (ref.kind === 'raw' && exists(adapter, 'connector_event_index', 'event_index_id', ref.id));
166
+ if (!found) {
167
+ throw new Error(`${field} not found: ${ref.kind}:${ref.id}`);
168
+ }
169
+ }
170
+ function contentHashInput(input) {
171
+ return (0, canonicalize_js_1.canonicalizeJSON)(input);
172
+ }
173
+ function deriveEdgeIdempotencyKey(requestIdempotencyKey, contentHash) {
174
+ return `reqedge_${sha256Hex((0, canonicalize_js_1.canonicalizeJSON)({
175
+ request_idempotency_key: requestIdempotencyKey,
176
+ content_hash: contentHash.toString('hex'),
177
+ })).slice(0, 40)}`;
178
+ }
179
+ function selectByIdempotency(adapter, modelRunId, edgeIdempotencyKey) {
180
+ if (!edgeIdempotencyKey) {
181
+ return null;
182
+ }
183
+ if (!modelRunId) {
184
+ const row = adapter
185
+ .prepare(`
186
+ SELECT *
187
+ FROM twin_edges
188
+ WHERE model_run_id IS NULL
189
+ AND edge_idempotency_key = ?
190
+ ORDER BY created_at ASC, edge_id ASC
191
+ LIMIT 1
192
+ `)
193
+ .get(edgeIdempotencyKey);
194
+ return row ? mapTwinEdgeRow(row) : null;
195
+ }
196
+ const row = adapter
197
+ .prepare(`
198
+ SELECT *
199
+ FROM twin_edges
200
+ WHERE model_run_id = ?
201
+ AND edge_idempotency_key = ?
202
+ LIMIT 1
203
+ `)
204
+ .get(modelRunId, edgeIdempotencyKey);
205
+ return row ? mapTwinEdgeRow(row) : null;
206
+ }
207
+ function mapTwinEdgeRow(row) {
208
+ const edgeId = String(row.edge_id);
209
+ return {
210
+ edge_id: edgeId,
211
+ edge_type: normalizeEdgeType(row.edge_type),
212
+ subject_ref: {
213
+ kind: String(row.subject_kind),
214
+ id: String(row.subject_id),
215
+ },
216
+ object_ref: {
217
+ kind: String(row.object_kind),
218
+ id: String(row.object_id),
219
+ },
220
+ relation_attrs_json: nullableString(row.relation_attrs_json),
221
+ relation_attrs: parseJsonField(row.relation_attrs_json, 'relation_attrs_json', edgeId),
222
+ confidence: Number(row.confidence),
223
+ source: normalizeSource(row.source),
224
+ agent_id: nullableString(row.agent_id),
225
+ model_run_id: nullableString(row.model_run_id),
226
+ envelope_hash: nullableString(row.envelope_hash),
227
+ human_actor_id: nullableString(row.human_actor_id),
228
+ human_actor_role: nullableString(row.human_actor_role),
229
+ authority_scope_json: nullableString(row.authority_scope_json),
230
+ authority_scope: parseJsonField(row.authority_scope_json, 'authority_scope_json', edgeId),
231
+ reason_classification: nullableString(row.reason_classification),
232
+ reason_text: nullableString(row.reason_text),
233
+ evidence_refs_json: nullableString(row.evidence_refs_json),
234
+ evidence_refs: parseJsonField(row.evidence_refs_json, 'evidence_refs_json', edgeId),
235
+ request_idempotency_key: nullableString(row.request_idempotency_key),
236
+ edge_idempotency_key: nullableString(row.edge_idempotency_key),
237
+ content_hash: toBuffer(row.content_hash),
238
+ created_at: Number(row.created_at),
239
+ };
240
+ }
241
+ function getTwinEdge(adapter, edgeId) {
242
+ const row = adapter.prepare('SELECT * FROM twin_edges WHERE edge_id = ?').get(edgeId);
243
+ return row ? mapTwinEdgeRow(row) : null;
244
+ }
245
+ function listTwinEdgesForRefs(adapter, refs) {
246
+ const normalizedRefs = refs.map((ref, index) => normalizeRef(ref, `refs[${index}]`));
247
+ if (normalizedRefs.length === 0) {
248
+ return [];
249
+ }
250
+ const clauses = [];
251
+ const params = [];
252
+ for (const ref of normalizedRefs) {
253
+ clauses.push('(subject_kind = ? AND subject_id = ?)');
254
+ params.push(ref.kind, ref.id);
255
+ clauses.push('(object_kind = ? AND object_id = ?)');
256
+ params.push(ref.kind, ref.id);
257
+ }
258
+ const rows = adapter
259
+ .prepare(`
260
+ SELECT *
261
+ FROM twin_edges
262
+ WHERE ${clauses.join(' OR ')}
263
+ ORDER BY created_at ASC, edge_id ASC
264
+ `)
265
+ .all(...params);
266
+ return rows.map(mapTwinEdgeRow);
267
+ }
268
+ function insertTwinEdge(adapter, input) {
269
+ const edgeType = normalizeEdgeType(input.edge_type);
270
+ const subjectRef = normalizeRef(input.subject_ref, 'subject_ref');
271
+ const objectRef = normalizeRef(input.object_ref, 'object_ref');
272
+ assertSubjectKindAllowed(subjectRef);
273
+ const source = normalizeSource(input.source);
274
+ const confidence = normalizeConfidence(input.confidence);
275
+ validateSourceMetadata(input, source);
276
+ validateRefExists(adapter, subjectRef, 'subject_ref');
277
+ validateRefExists(adapter, objectRef, 'object_ref');
278
+ const relationAttrsJson = jsonField(input.relation_attrs, 'relation_attrs_json');
279
+ const evidenceRefsJson = jsonField(input.evidence_refs, 'evidence_refs_json');
280
+ const authorityScopeJson = jsonField(input.authority_scope_json, 'authority_scope_json');
281
+ const agentId = nullableString(input.agent_id);
282
+ const reasonText = nullableString(input.reason_text);
283
+ const envelopeHash = nullableString(input.envelope_hash);
284
+ const humanActorId = nullableString(input.human_actor_id);
285
+ const humanActorRole = nullableString(input.human_actor_role);
286
+ const reasonClassification = nullableString(input.reason_classification);
287
+ const requestIdempotencyKey = nullableString(input.request_idempotency_key);
288
+ const modelRunId = nullableString(input.model_run_id);
289
+ const contentHash = sha256Buffer(contentHashInput({
290
+ edge_type: edgeType,
291
+ subject_ref: subjectRef,
292
+ object_ref: objectRef,
293
+ relation_attrs_json: relationAttrsJson,
294
+ confidence,
295
+ source,
296
+ agent_id: agentId,
297
+ model_run_id: modelRunId,
298
+ envelope_hash: envelopeHash,
299
+ human_actor_id: humanActorId,
300
+ human_actor_role: humanActorRole,
301
+ authority_scope_json: authorityScopeJson,
302
+ reason_classification: reasonClassification,
303
+ reason_text: reasonText,
304
+ evidence_refs_json: evidenceRefsJson,
305
+ }));
306
+ const edgeIdempotencyKey = nullableString(input.edge_idempotency_key) ??
307
+ (requestIdempotencyKey ? deriveEdgeIdempotencyKey(requestIdempotencyKey, contentHash) : null);
308
+ const existing = selectByIdempotency(adapter, modelRunId, edgeIdempotencyKey);
309
+ if (existing) {
310
+ return existing;
311
+ }
312
+ const edgeId = nullableString(input.edge_id) ?? generatedEdgeId();
313
+ const createdAt = Date.now();
314
+ try {
315
+ return adapter.transaction(() => {
316
+ adapter
317
+ .prepare(`
318
+ INSERT INTO twin_edges (
319
+ edge_id, edge_type, subject_kind, subject_id, object_kind, object_id,
320
+ relation_attrs_json, confidence, source, agent_id, model_run_id, envelope_hash,
321
+ human_actor_id, human_actor_role, authority_scope_json, reason_classification,
322
+ reason_text, evidence_refs_json, request_idempotency_key, edge_idempotency_key,
323
+ content_hash, created_at
324
+ )
325
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
326
+ `)
327
+ .run(edgeId, edgeType, subjectRef.kind, subjectRef.id, objectRef.kind, objectRef.id, relationAttrsJson, confidence, source, agentId, modelRunId, envelopeHash, humanActorId, humanActorRole, authorityScopeJson, reasonClassification, reasonText, evidenceRefsJson, requestIdempotencyKey, edgeIdempotencyKey, contentHash, createdAt);
328
+ const inserted = getTwinEdge(adapter, edgeId);
329
+ if (!inserted) {
330
+ throw new Error(`Failed to load inserted twin edge: ${edgeId}`);
331
+ }
332
+ return inserted;
333
+ });
334
+ }
335
+ catch (error) {
336
+ const duplicate = selectByIdempotency(adapter, modelRunId, edgeIdempotencyKey);
337
+ if (duplicate) {
338
+ return duplicate;
339
+ }
340
+ throw error;
341
+ }
342
+ }
343
+ //# sourceMappingURL=store.js.map