@jungjaehoon/mama-core 1.5.0 → 1.7.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 (103) 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/db/migrations/037-create-context-packets.sql +32 -0
  8. package/dist/agent-graph/alias-write.d.ts +3 -0
  9. package/dist/agent-graph/alias-write.js +231 -0
  10. package/dist/agent-graph/entity-resolve.d.ts +3 -0
  11. package/dist/agent-graph/entity-resolve.js +275 -0
  12. package/dist/agent-graph/errors.d.ts +4 -0
  13. package/dist/agent-graph/errors.js +11 -0
  14. package/dist/agent-graph/graph-query.d.ts +5 -0
  15. package/dist/agent-graph/graph-query.js +359 -0
  16. package/dist/agent-graph/index.d.ts +6 -0
  17. package/dist/agent-graph/index.js +22 -0
  18. package/dist/agent-graph/types.d.ts +162 -0
  19. package/dist/agent-graph/types.js +3 -0
  20. package/dist/agent-situation/builder.d.ts +8 -0
  21. package/dist/agent-situation/builder.js +239 -0
  22. package/dist/agent-situation/cache-key.d.ts +4 -0
  23. package/dist/agent-situation/cache-key.js +64 -0
  24. package/dist/agent-situation/index.d.ts +7 -0
  25. package/dist/agent-situation/index.js +28 -0
  26. package/dist/agent-situation/packet-store.d.ts +27 -0
  27. package/dist/agent-situation/packet-store.js +228 -0
  28. package/dist/agent-situation/ranking-policy.d.ts +18 -0
  29. package/dist/agent-situation/ranking-policy.js +75 -0
  30. package/dist/agent-situation/source-readers.d.ts +52 -0
  31. package/dist/agent-situation/source-readers.js +300 -0
  32. package/dist/agent-situation/types.d.ts +195 -0
  33. package/dist/agent-situation/types.js +3 -0
  34. package/dist/cases/search-rollup.d.ts +4 -0
  35. package/dist/cases/search-rollup.js +84 -0
  36. package/dist/cases/wiki-page-index.js +6 -2
  37. package/dist/connectors/event-index.d.ts +1 -0
  38. package/dist/connectors/event-index.js +19 -4
  39. package/dist/connectors/raw-query.d.ts +18 -0
  40. package/dist/connectors/raw-query.js +305 -0
  41. package/dist/connectors/types.d.ts +40 -0
  42. package/dist/context-compile/boundary-defaults.d.ts +12 -0
  43. package/dist/context-compile/boundary-defaults.js +105 -0
  44. package/dist/context-compile/compiler-policy.d.ts +39 -0
  45. package/dist/context-compile/compiler-policy.js +147 -0
  46. package/dist/context-compile/compiler.d.ts +18 -0
  47. package/dist/context-compile/compiler.js +401 -0
  48. package/dist/context-compile/index.d.ts +9 -0
  49. package/dist/context-compile/index.js +25 -0
  50. package/dist/context-compile/packet-store.d.ts +9 -0
  51. package/dist/context-compile/packet-store.js +255 -0
  52. package/dist/context-compile/ref.d.ts +7 -0
  53. package/dist/context-compile/ref.js +102 -0
  54. package/dist/context-compile/source-readers.d.ts +69 -0
  55. package/dist/context-compile/source-readers.js +707 -0
  56. package/dist/context-compile/types.d.ts +138 -0
  57. package/dist/context-compile/types.js +11 -0
  58. package/dist/context-compile/visibility.d.ts +21 -0
  59. package/dist/context-compile/visibility.js +200 -0
  60. package/dist/db-adapter/node-sqlite-adapter.d.ts +9 -0
  61. package/dist/db-adapter/node-sqlite-adapter.js +207 -0
  62. package/dist/db-manager.d.ts +8 -1
  63. package/dist/db-manager.js +53 -5
  64. package/dist/edges/ref-validation.d.ts +8 -0
  65. package/dist/edges/ref-validation.js +301 -0
  66. package/dist/edges/store.d.ts +10 -0
  67. package/dist/edges/store.js +343 -0
  68. package/dist/edges/types.d.ts +88 -0
  69. package/dist/edges/types.js +18 -0
  70. package/dist/embeddings.d.ts +3 -0
  71. package/dist/embeddings.js +49 -9
  72. package/dist/entities/store.d.ts +4 -1
  73. package/dist/entities/store.js +206 -78
  74. package/dist/entities/types.d.ts +5 -1
  75. package/dist/entities/types.js +1 -1
  76. package/dist/index.d.ts +18 -3
  77. package/dist/index.js +54 -4
  78. package/dist/mama-api.d.ts +32 -35
  79. package/dist/mama-api.js +173 -22
  80. package/dist/memory/api.d.ts +17 -56
  81. package/dist/memory/api.js +494 -52
  82. package/dist/memory/event-store.d.ts +3 -0
  83. package/dist/memory/event-store.js +21 -2
  84. package/dist/memory/provenance-audit.d.ts +22 -0
  85. package/dist/memory/provenance-audit.js +112 -0
  86. package/dist/memory/provenance-query.d.ts +11 -0
  87. package/dist/memory/provenance-query.js +193 -0
  88. package/dist/memory/provenance.d.ts +29 -0
  89. package/dist/memory/provenance.js +137 -0
  90. package/dist/memory/scope-backfill.d.ts +17 -0
  91. package/dist/memory/scope-backfill.js +77 -0
  92. package/dist/memory/types.d.ts +87 -14
  93. package/dist/model-runs/store.d.ts +13 -0
  94. package/dist/model-runs/store.js +457 -0
  95. package/dist/model-runs/tool-trace-store.d.ts +4 -0
  96. package/dist/model-runs/tool-trace-store.js +114 -0
  97. package/dist/model-runs/types.d.ts +69 -0
  98. package/dist/model-runs/types.js +5 -0
  99. package/dist/runtime/trusted-provenance.d.ts +3 -0
  100. package/dist/runtime/trusted-provenance.js +6 -0
  101. package/dist/search/search-quality.d.ts +36 -0
  102. package/dist/search/search-quality.js +29 -0
  103. package/package.json +11 -3
@@ -23,6 +23,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.initDB = initDB;
26
+ exports.assertTestProcessIsNotUsingRealDb = assertTestProcessIsNotUsingRealDb;
26
27
  exports.getDB = getDB;
27
28
  exports.getAdapter = getAdapter;
28
29
  exports.closeDB = closeDB;
@@ -43,6 +44,7 @@ exports.isTestMode = isTestMode;
43
44
  exports.fts5Search = fts5Search;
44
45
  exports.reindexEmbeddings = reindexEmbeddings;
45
46
  const path_1 = __importDefault(require("path"));
47
+ const os_1 = __importDefault(require("os"));
46
48
  const debug_logger_js_1 = require("./debug-logger.js");
47
49
  const progress_indicator_js_1 = require("./progress-indicator.js");
48
50
  const index_js_1 = require("./db-adapter/index.js");
@@ -53,6 +55,7 @@ let isInitialized = false;
53
55
  let initializingPromise = null; // Single-flight guard for concurrent callers
54
56
  // Migration directory (moved to src/db/migrations for M1.2)
55
57
  const MIGRATIONS_DIR = path_1.default.join(__dirname, '..', 'db', 'migrations');
58
+ const REAL_USER_DB_PATH = path_1.default.join(os_1.default.homedir(), '.claude', 'mama-memory.db');
56
59
  /**
57
60
  * Initialize SQLite database adapter and connect
58
61
  *
@@ -65,6 +68,7 @@ const MIGRATIONS_DIR = path_1.default.join(__dirname, '..', 'db', 'migrations');
65
68
  * @returns SQLite database connection
66
69
  */
67
70
  async function initDB() {
71
+ assertTestProcessIsNotUsingRealDb();
68
72
  // Already initialized - return immediately
69
73
  if (isInitialized) {
70
74
  return dbConnection;
@@ -79,6 +83,7 @@ async function initDB() {
79
83
  (0, progress_indicator_js_1.logSearching)('Initializing database...');
80
84
  // Create SQLite adapter
81
85
  dbAdapter = (0, index_js_1.createAdapter)();
86
+ assertTestProcessIsNotUsingRealDb(resolveAdapterDbPath(dbAdapter), 'adapter.getDbPath()');
82
87
  // Connect to database
83
88
  dbConnection = await dbAdapter.connect();
84
89
  // Run migrations (includes 012-create-checkpoints-table.sql)
@@ -104,6 +109,48 @@ async function initDB() {
104
109
  })();
105
110
  return initializingPromise;
106
111
  }
112
+ function assertTestProcessIsNotUsingRealDb(effectivePath, effectivePathSource = 'adapter') {
113
+ if (!isDatabaseBoundaryTestMode()) {
114
+ return;
115
+ }
116
+ const configuredPaths = [
117
+ { name: 'MAMA_DB_PATH', value: process.env.MAMA_DB_PATH },
118
+ { name: 'MAMA_DATABASE_PATH', value: process.env.MAMA_DATABASE_PATH },
119
+ ];
120
+ if (effectivePath) {
121
+ configuredPaths.push({ name: effectivePathSource, value: effectivePath });
122
+ }
123
+ for (const configuredPath of configuredPaths) {
124
+ if (!configuredPath.value) {
125
+ continue;
126
+ }
127
+ const resolvedPath = path_1.default.resolve(expandHomePath(configuredPath.value));
128
+ if (resolvedPath === path_1.default.resolve(REAL_USER_DB_PATH)) {
129
+ throw new Error(`[db-boundary] Refusing to initDB against real DB ${REAL_USER_DB_PATH} ` +
130
+ `from a test process (${configuredPath.name}=${configuredPath.value}). ` +
131
+ 'Set MAMA_DB_PATH to a temporary path before initDB.');
132
+ }
133
+ }
134
+ }
135
+ function resolveAdapterDbPath(adapter) {
136
+ if (typeof adapter.getDbPath === 'function') {
137
+ return adapter.getDbPath();
138
+ }
139
+ return adapter.dbPath;
140
+ }
141
+ function isDatabaseBoundaryTestMode() {
142
+ return isTestMode();
143
+ }
144
+ function expandHomePath(value) {
145
+ const home = os_1.default.homedir();
146
+ if (value === '~') {
147
+ return home;
148
+ }
149
+ if (value.startsWith('~/')) {
150
+ return path_1.default.join(home, value.slice(2));
151
+ }
152
+ return value.replaceAll('${HOME}', home);
153
+ }
107
154
  /**
108
155
  * Get database connection (singleton pattern)
109
156
  *
@@ -306,14 +353,15 @@ async function insertDecisionWithEmbedding(decision) {
306
353
  confidence, created_at, updated_at,
307
354
  needs_validation, validation_attempts, last_validated_at, usage_count,
308
355
  trust_context, usage_success, usage_failure, time_saved,
309
- evidence, alternatives, risks, event_date, event_datetime
310
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
356
+ evidence, alternatives, risks, event_date, event_datetime,
357
+ agent_id, model_run_id, envelope_hash, gateway_call_id, source_refs_json, provenance_json
358
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
311
359
  `);
312
360
  const insertResult = stmt.run(decision.id, decision.topic, decision.decision, decision.reasoning || null, decision.outcome || null, decision.failure_reason || null, decision.limitation || null, decision.user_involvement || null, decision.session_id || null, decision.supersedes || null, decision.superseded_by || null, decision.refined_from ? JSON.stringify(decision.refined_from) : null, decision.confidence !== undefined ? decision.confidence : 0.5,
313
361
  // IMPORTANT: All timestamps are stored in milliseconds (Date.now()).
314
362
  // The schema DEFAULT uses unixepoch() (seconds) but is never used
315
363
  // since all inserts go through this function which always provides ms.
316
- decision.created_at || Date.now(), decision.updated_at || Date.now(), decision.needs_validation !== undefined ? decision.needs_validation : 0, decision.validation_attempts || 0, decision.last_validated_at || null, decision.usage_count || 0, decision.trust_context || null, decision.usage_success || 0, decision.usage_failure || 0, decision.time_saved || 0, decision.evidence || null, decision.alternatives || null, decision.risks || null, decision.event_date || null, decision.event_datetime ?? null);
364
+ decision.created_at || Date.now(), decision.updated_at || Date.now(), decision.needs_validation !== undefined ? decision.needs_validation : 0, decision.validation_attempts || 0, decision.last_validated_at || null, decision.usage_count || 0, decision.trust_context || null, decision.usage_success || 0, decision.usage_failure || 0, decision.time_saved || 0, decision.evidence || null, decision.alternatives || null, decision.risks || null, decision.event_date || null, decision.event_datetime ?? null, decision.agent_id ?? null, decision.model_run_id ?? null, decision.envelope_hash ?? null, decision.gateway_call_id ?? null, decision.source_refs_json ?? null, decision.provenance_json ?? null);
317
365
  const rowid = Number(insertResult.lastInsertRowid);
318
366
  // Insert embedding in same transaction to ensure rowid matching
319
367
  (0, debug_logger_js_1.info)(`[db-manager] Vector search enabled: ${adapter.vectorSearchEnabled}`);
@@ -675,10 +723,10 @@ function resetDBState(options = {}) {
675
723
  /**
676
724
  * Check if running in test mode
677
725
  *
678
- * Returns true if MAMA_TEST_MODE or VITEST env vars are set
726
+ * Returns true if MAMA_TEST_MODE, VITEST, or NODE_ENV=test env vars are set
679
727
  */
680
728
  function isTestMode() {
681
- return !!(process.env.MAMA_TEST_MODE || process.env.VITEST);
729
+ return Boolean(process.env.MAMA_TEST_MODE || process.env.VITEST || process.env.NODE_ENV === 'test');
682
730
  }
683
731
  /**
684
732
  * FTS5 keyword search on decisions table.
@@ -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,301 @@
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
+ // Keep memory-truth quarantine semantics excluded for legacy/backcompat rows even though the
13
+ // current decisions.status CHECK only admits superseded/contradicted/stale terminal states.
14
+ const EXCLUDED_MEMORY_STATUSES = new Set(['superseded', 'quarantined', 'contradicted', 'stale']);
15
+ function scopeKey(scope) {
16
+ return `${scope.kind}\0${scope.id}`;
17
+ }
18
+ function hasScopes(scopes) {
19
+ return Array.isArray(scopes) && scopes.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 startMs(visibility) {
31
+ return typeof visibility.startMs === 'number' ? visibility.startMs : null;
32
+ }
33
+ function isWithinVisibilityTime(value, visibility) {
34
+ const start = startMs(visibility);
35
+ const asOf = asOfMs(visibility);
36
+ if (start === null && asOf === null) {
37
+ return true;
38
+ }
39
+ return (typeof value === 'number' &&
40
+ (start === null || value >= start) &&
41
+ (asOf === null || value <= asOf));
42
+ }
43
+ function parseTimestamp(value) {
44
+ if (typeof value === 'number' && Number.isFinite(value)) {
45
+ return Math.floor(value);
46
+ }
47
+ if (typeof value === 'string') {
48
+ const trimmed = value.trim();
49
+ if (trimmed.length === 0) {
50
+ return null;
51
+ }
52
+ const numeric = Number(trimmed);
53
+ if (Number.isFinite(numeric)) {
54
+ return Math.floor(numeric);
55
+ }
56
+ const parsed = Date.parse(trimmed);
57
+ return Number.isFinite(parsed) ? parsed : null;
58
+ }
59
+ return null;
60
+ }
61
+ function tableColumns(adapter, table) {
62
+ const pragma = TABLE_COLUMN_PRAGMAS[table];
63
+ if (!pragma) {
64
+ throw new Error(`Unsupported table for column introspection: ${table}`);
65
+ }
66
+ let adapterCache = tableColumnCache.get(adapter);
67
+ if (!adapterCache) {
68
+ adapterCache = new Map();
69
+ tableColumnCache.set(adapter, adapterCache);
70
+ }
71
+ const cached = adapterCache.get(table);
72
+ if (cached) {
73
+ return cached;
74
+ }
75
+ const rows = adapter.prepare(pragma).all();
76
+ const columns = new Set(rows.map((row) => row.name));
77
+ adapterCache.set(table, columns);
78
+ return columns;
79
+ }
80
+ function isMemoryVisible(adapter, id, visibility) {
81
+ const columns = tableColumns(adapter, 'decisions');
82
+ const statusSelect = columns.has('status') ? 'status' : 'NULL AS status';
83
+ const supersededBySelect = columns.has('superseded_by')
84
+ ? 'superseded_by'
85
+ : 'NULL AS superseded_by';
86
+ const row = adapter
87
+ .prepare(`
88
+ SELECT created_at, event_datetime, ${statusSelect}, ${supersededBySelect}
89
+ FROM decisions
90
+ WHERE id = ?
91
+ LIMIT 1
92
+ `)
93
+ .get(id);
94
+ if (!row) {
95
+ return false;
96
+ }
97
+ const status = typeof row.status === 'string' ? row.status : 'active';
98
+ if (EXCLUDED_MEMORY_STATUSES.has(status) ||
99
+ (typeof row.superseded_by === 'string' && row.superseded_by.length > 0)) {
100
+ return false;
101
+ }
102
+ if (!isWithinVisibilityTime(row.event_datetime ?? row.created_at, visibility)) {
103
+ return false;
104
+ }
105
+ const scopes = visibility.scopes;
106
+ if (!hasScopes(scopes)) {
107
+ return true;
108
+ }
109
+ const scopeClauses = scopes.map(() => '(ms.kind = ? AND ms.external_id = ?)').join(' OR ');
110
+ const params = scopes.flatMap((scope) => [scope.kind, scope.id]);
111
+ const bindingRow = adapter
112
+ .prepare(`
113
+ SELECT 1 AS ok
114
+ FROM memory_scope_bindings msb
115
+ JOIN memory_scopes ms ON ms.id = msb.scope_id
116
+ WHERE msb.memory_id = ?
117
+ AND (${scopeClauses})
118
+ LIMIT 1
119
+ `)
120
+ .get(id, ...params);
121
+ if (bindingRow?.ok) {
122
+ return true;
123
+ }
124
+ if (columns.has('memory_scope_kind') && columns.has('memory_scope_id')) {
125
+ const row = adapter
126
+ .prepare(`
127
+ SELECT 1 AS ok
128
+ FROM decisions
129
+ WHERE id = ?
130
+ AND (${scopes.map(() => '(memory_scope_kind = ? AND memory_scope_id = ?)').join(' OR ')})
131
+ LIMIT 1
132
+ `)
133
+ .get(id, ...params);
134
+ return Boolean(row?.ok);
135
+ }
136
+ return false;
137
+ }
138
+ function parseCaseScopeRefs(scopeRefs) {
139
+ if (typeof scopeRefs !== 'string' || scopeRefs.length === 0) {
140
+ return [];
141
+ }
142
+ try {
143
+ const parsed = JSON.parse(scopeRefs);
144
+ if (!Array.isArray(parsed)) {
145
+ return [];
146
+ }
147
+ return parsed.filter((item) => item !== null &&
148
+ typeof item === 'object' &&
149
+ typeof item.kind === 'string' &&
150
+ typeof item.id === 'string');
151
+ }
152
+ catch {
153
+ return [];
154
+ }
155
+ }
156
+ function isCaseVisible(adapter, id, visibility) {
157
+ const row = adapter.prepare('SELECT * FROM case_truth WHERE case_id = ? LIMIT 1').get(id);
158
+ if (!row) {
159
+ return false;
160
+ }
161
+ const timestamp = parseTimestamp(row.last_activity_at) ?? parseTimestamp(row.updated_at);
162
+ if (!isWithinVisibilityTime(timestamp, visibility)) {
163
+ return false;
164
+ }
165
+ const scopes = visibility.scopes;
166
+ if (!hasScopes(scopes)) {
167
+ return true;
168
+ }
169
+ const requested = new Set(scopes.map(scopeKey));
170
+ const scopeRefs = parseCaseScopeRefs(row.scope_refs);
171
+ if (scopeRefs.some((scope) => requested.has(scopeKey(scope)))) {
172
+ return true;
173
+ }
174
+ const columns = tableColumns(adapter, 'case_truth');
175
+ if (columns.has('memory_scope_kind') && columns.has('memory_scope_id')) {
176
+ return scopes.some((scope) => row.memory_scope_kind === scope.kind && row.memory_scope_id === scope.id);
177
+ }
178
+ return false;
179
+ }
180
+ function isRawVisible(adapter, id, visibility) {
181
+ const row = adapter
182
+ .prepare('SELECT * FROM connector_event_index WHERE event_index_id = ? LIMIT 1')
183
+ .get(id);
184
+ if (!row) {
185
+ return false;
186
+ }
187
+ if (Array.isArray(visibility.connectors) &&
188
+ !visibility.connectors.includes(String(row.source_connector))) {
189
+ return false;
190
+ }
191
+ if (hasProjectRefs(visibility.projectRefs) &&
192
+ !visibility.projectRefs.some((project) => project.id === row.project_id)) {
193
+ return false;
194
+ }
195
+ if (typeof visibility.tenantId === 'string' &&
196
+ visibility.tenantId.length > 0 &&
197
+ row.tenant_id !== visibility.tenantId) {
198
+ return false;
199
+ }
200
+ const rawTs = row.event_datetime ?? row.source_timestamp_ms;
201
+ if (rawTs === null || rawTs === undefined || (typeof rawTs === 'string' && rawTs.trim() === '')) {
202
+ return false;
203
+ }
204
+ const eventTsMs = parseTimestamp(rawTs);
205
+ if (eventTsMs === null || eventTsMs < 0 || !isWithinVisibilityTime(eventTsMs, visibility)) {
206
+ return false;
207
+ }
208
+ if (!hasScopes(visibility.scopes)) {
209
+ return true;
210
+ }
211
+ return visibility.scopes.some((scope) => row.memory_scope_kind === scope.kind && row.memory_scope_id === scope.id);
212
+ }
213
+ function isEntityVisible(adapter, id, visibility) {
214
+ const row = adapter
215
+ .prepare('SELECT scope_kind, scope_id, created_at FROM entity_nodes WHERE id = ? AND status = ? LIMIT 1')
216
+ .get(id, 'active');
217
+ if (!row) {
218
+ return false;
219
+ }
220
+ if (!isWithinVisibilityTime(row.created_at, visibility)) {
221
+ return false;
222
+ }
223
+ const scopes = visibility.scopes;
224
+ if (!hasScopes(scopes)) {
225
+ return true;
226
+ }
227
+ return scopes.some((scope) => row.scope_kind === scope.kind && row.scope_id === scope.id);
228
+ }
229
+ function isTwinRefVisible(adapter, ref, visibility, visitedEdges, edgeCache) {
230
+ if (ref.kind === 'entity') {
231
+ return isEntityVisible(adapter, ref.id, visibility);
232
+ }
233
+ if (ref.kind === 'report') {
234
+ return !hasScopes(visibility.scopes);
235
+ }
236
+ if (ref.kind === 'memory') {
237
+ return isMemoryVisible(adapter, ref.id, visibility);
238
+ }
239
+ if (ref.kind === 'case') {
240
+ return isCaseVisible(adapter, ref.id, visibility);
241
+ }
242
+ if (ref.kind === 'raw') {
243
+ return isRawVisible(adapter, ref.id, visibility);
244
+ }
245
+ if (visitedEdges.has(ref.id)) {
246
+ return false;
247
+ }
248
+ let edge = edgeCache.get(ref.id);
249
+ if (edge === undefined) {
250
+ edge = (0, store_js_1.getTwinEdge)(adapter, ref.id);
251
+ edgeCache.set(ref.id, edge);
252
+ }
253
+ if (!edge) {
254
+ return false;
255
+ }
256
+ if (!isWithinVisibilityTime(edge.created_at, visibility)) {
257
+ return false;
258
+ }
259
+ const pathWithCurrent = new Set(visitedEdges);
260
+ pathWithCurrent.add(ref.id);
261
+ return (isTwinRefVisible(adapter, edge.subject_ref, visibility, new Set(pathWithCurrent), edgeCache) &&
262
+ isTwinRefVisible(adapter, edge.object_ref, visibility, new Set(pathWithCurrent), edgeCache));
263
+ }
264
+ function assertTwinRefsVisible(adapter, refs, visibility = {}) {
265
+ const edgeCache = new Map();
266
+ for (const ref of refs) {
267
+ if (!isTwinRefVisible(adapter, ref, visibility, new Set(), edgeCache)) {
268
+ throw new Error(`Twin ref is not visible to requested visibility: ${ref.kind}:${ref.id}`);
269
+ }
270
+ }
271
+ }
272
+ function assertTwinRefsVisibleToScopes(adapter, refs, scopes) {
273
+ assertTwinRefsVisible(adapter, refs, visibilityFromScopes(scopes));
274
+ }
275
+ function listVisibleTwinEdgesForRefs(adapter, refs, options = {}) {
276
+ const edgeCache = new Map();
277
+ const edgeTypes = normalizeEdgeTypes(options.edgeTypes);
278
+ const edges = (0, store_js_1.listTwinEdgesForRefs)(adapter, refs).filter((edge) => (edgeTypes.size === 0 || edgeTypes.has(edge.edge_type)) &&
279
+ (typeof options.startMs !== 'number' || edge.created_at >= options.startMs) &&
280
+ (typeof options.asOfMs !== 'number' || edge.created_at <= options.asOfMs) &&
281
+ isTwinRefVisible(adapter, edge.subject_ref, options, new Set(), edgeCache) &&
282
+ isTwinRefVisible(adapter, edge.object_ref, options, new Set(), edgeCache));
283
+ const limit = typeof options.limit === 'number' && Number.isFinite(options.limit)
284
+ ? Math.max(0, Math.floor(options.limit))
285
+ : null;
286
+ const sortedEdges = [...edges].sort((left, right) => {
287
+ const createdDiff = right.created_at - left.created_at;
288
+ return createdDiff !== 0 ? createdDiff : left.edge_id.localeCompare(right.edge_id);
289
+ });
290
+ if (limit === null) {
291
+ return sortedEdges;
292
+ }
293
+ return sortedEdges.slice(0, limit);
294
+ }
295
+ function normalizeEdgeTypes(edgeTypes) {
296
+ if (!Array.isArray(edgeTypes) || edgeTypes.length === 0) {
297
+ return new Set();
298
+ }
299
+ return new Set(edgeTypes);
300
+ }
301
+ //# 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