@hasna/mementos 0.14.83 → 0.14.85

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 (72) hide show
  1. package/Dockerfile.package +0 -1
  2. package/README.md +13 -10
  3. package/dashboard/bun.lock +519 -0
  4. package/dist/cli/commands/info-history.d.ts.map +1 -1
  5. package/dist/cli/commands/info-stale.d.ts.map +1 -1
  6. package/dist/cli/commands/io-export.d.ts.map +1 -1
  7. package/dist/cli/commands/memory-cmd-list.d.ts.map +1 -1
  8. package/dist/cli/commands/storage.d.ts.map +1 -1
  9. package/dist/cli/helpers.d.ts +31 -0
  10. package/dist/cli/helpers.d.ts.map +1 -1
  11. package/dist/cli/index.js +3781 -1512
  12. package/dist/db/analytics.d.ts +13 -0
  13. package/dist/db/analytics.d.ts.map +1 -1
  14. package/dist/db/api-mode.d.ts +5 -1
  15. package/dist/db/api-mode.d.ts.map +1 -1
  16. package/dist/db/database.d.ts.map +1 -1
  17. package/dist/db/memories.d.ts +59 -3
  18. package/dist/db/memories.d.ts.map +1 -1
  19. package/dist/db/store-backend.d.ts +3 -3
  20. package/dist/db/store-backend.d.ts.map +1 -1
  21. package/dist/diagnostics/historical-project-registration-receipt.d.ts +39 -0
  22. package/dist/diagnostics/historical-project-registration-receipt.d.ts.map +1 -0
  23. package/dist/diagnostics/historical-project-registration-receipt.js +1656 -0
  24. package/dist/generated/storage-kit/backend.d.ts +20 -0
  25. package/dist/generated/storage-kit/backend.d.ts.map +1 -0
  26. package/dist/generated/storage-kit/index.d.ts +2 -2
  27. package/dist/generated/storage-kit/index.d.ts.map +1 -1
  28. package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
  29. package/dist/generated/storage-kit/own.d.ts +11 -0
  30. package/dist/generated/storage-kit/own.d.ts.map +1 -0
  31. package/dist/generated/storage-kit/pool.d.ts +7 -6
  32. package/dist/generated/storage-kit/pool.d.ts.map +1 -1
  33. package/dist/generated/storage-kit/tls.d.ts +30 -3
  34. package/dist/generated/storage-kit/tls.d.ts.map +1 -1
  35. package/dist/index.d.ts +2 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3628 -1453
  38. package/dist/lib/gatherer.d.ts.map +1 -1
  39. package/dist/lib/retired-storage-mode.d.ts +11 -0
  40. package/dist/lib/retired-storage-mode.d.ts.map +1 -0
  41. package/dist/lib/storage-sync.d.ts +2 -2
  42. package/dist/lib/storage-sync.d.ts.map +1 -1
  43. package/dist/mcp/index.js +3684 -1492
  44. package/dist/mcp/tools/memory-io.d.ts.map +1 -1
  45. package/dist/mcp/tools/project-tools.d.ts.map +1 -1
  46. package/dist/project-registration/authority.d.ts.map +1 -1
  47. package/dist/project-registration/historical-receipt.d.ts +38 -0
  48. package/dist/project-registration/historical-receipt.d.ts.map +1 -0
  49. package/dist/project-registration/identity.d.ts.map +1 -1
  50. package/dist/project-registration/index.d.ts +1 -1
  51. package/dist/project-registration/index.d.ts.map +1 -1
  52. package/dist/project-registration/types.d.ts +26 -0
  53. package/dist/project-registration/types.d.ts.map +1 -1
  54. package/dist/project-registration.js +361 -162
  55. package/dist/sdk/index.d.ts +7 -0
  56. package/dist/sdk/index.d.ts.map +1 -1
  57. package/dist/sdk/index.js +122 -30
  58. package/dist/server/index.js +3698 -1431
  59. package/dist/storage.d.ts +18 -50
  60. package/dist/storage.d.ts.map +1 -1
  61. package/dist/storage.js +151 -122
  62. package/dist/test-support/memories-page-stub-server.d.ts +2 -0
  63. package/dist/test-support/memories-page-stub-server.d.ts.map +1 -0
  64. package/dist/test-support/memories-page-stub.d.ts +28 -0
  65. package/dist/test-support/memories-page-stub.d.ts.map +1 -0
  66. package/dist/test-support/store-isolation.d.ts +5 -0
  67. package/dist/test-support/store-isolation.d.ts.map +1 -1
  68. package/docker-entrypoint.sh +4 -5
  69. package/hasna.contract.json +2 -2
  70. package/package.json +7 -6
  71. package/dist/generated/storage-kit/mode.d.ts +0 -48
  72. package/dist/generated/storage-kit/mode.d.ts.map +0 -1
@@ -0,0 +1,1656 @@
1
+ // @bun
2
+ // src/diagnostics/historical-project-registration-receipt.ts
3
+ import { pathToFileURL } from "url";
4
+ import pg2 from "pg";
5
+
6
+ // src/project-registration/authority.ts
7
+ import { createHash } from "crypto";
8
+
9
+ // src/storage.ts
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
11
+ import { homedir } from "os";
12
+ import { join } from "path";
13
+ import { fileURLToPath } from "url";
14
+ import { Worker } from "worker_threads";
15
+ import pg from "pg";
16
+ function shouldUsePgSsl(connectionString) {
17
+ let params;
18
+ try {
19
+ params = new URL(connectionString).searchParams;
20
+ } catch {
21
+ params = new URLSearchParams(connectionString.split("?", 2)[1] ?? "");
22
+ }
23
+ const ssl = params.get("ssl")?.trim().toLowerCase();
24
+ const sslMode = params.get("sslmode")?.trim().toLowerCase();
25
+ return ["1", "true", "yes", "on", "require"].includes(ssl ?? "") || ["require", "verify-ca", "verify-full"].includes(sslMode ?? "");
26
+ }
27
+ function sslConfigFor(connectionString) {
28
+ if (!shouldUsePgSsl(connectionString))
29
+ return;
30
+ let sslMode;
31
+ try {
32
+ sslMode = new URL(connectionString).searchParams.get("sslmode")?.trim().toLowerCase() ?? undefined;
33
+ } catch {
34
+ sslMode = new URLSearchParams(connectionString.split("?", 2)[1] ?? "").get("sslmode")?.trim().toLowerCase() ?? undefined;
35
+ }
36
+ if (sslMode === "verify-ca" || sslMode === "verify-full") {
37
+ return { rejectUnauthorized: true };
38
+ }
39
+ return { rejectUnauthorized: false };
40
+ }
41
+ function stripSslParams(connectionString) {
42
+ try {
43
+ const url = new URL(connectionString);
44
+ url.searchParams.delete("ssl");
45
+ url.searchParams.delete("sslmode");
46
+ return url.toString();
47
+ } catch {
48
+ return connectionString;
49
+ }
50
+ }
51
+ class PgSyncPool {
52
+ worker;
53
+ status;
54
+ data;
55
+ closed = false;
56
+ lastError = null;
57
+ static DATA_BYTES = 128 * 1024 * 1024;
58
+ static QUERY_TIMEOUT_MS = 60000;
59
+ static resolveWorkerPath() {
60
+ const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
61
+ const here = fileURLToPath(new URL(".", import.meta.url));
62
+ const candidates = [
63
+ join(here, `pg-sync-worker${ext}`),
64
+ join(here, "..", `pg-sync-worker${ext}`),
65
+ join(here, "..", "..", `pg-sync-worker${ext}`)
66
+ ];
67
+ for (const candidate of candidates) {
68
+ if (existsSync(candidate))
69
+ return candidate;
70
+ }
71
+ return candidates[0];
72
+ }
73
+ constructor(connectionString) {
74
+ const control = new SharedArrayBuffer(8);
75
+ const dataSab = new SharedArrayBuffer(PgSyncPool.DATA_BYTES);
76
+ this.status = new Int32Array(control);
77
+ this.data = new Uint8Array(dataSab);
78
+ this.worker = new Worker(PgSyncPool.resolveWorkerPath(), {
79
+ workerData: {
80
+ dsn: stripSslParams(connectionString),
81
+ ssl: sslConfigFor(connectionString),
82
+ control,
83
+ data: dataSab
84
+ }
85
+ });
86
+ this.worker.unref();
87
+ this.worker.on("error", (err) => {
88
+ this.lastError = err;
89
+ });
90
+ }
91
+ query(sql, params) {
92
+ if (this.closed)
93
+ throw new Error("PgSyncPool is closed");
94
+ if (this.lastError)
95
+ throw this.lastError;
96
+ Atomics.store(this.status, 0, 0);
97
+ this.worker.postMessage({ sql, params });
98
+ const waitResult = Atomics.wait(this.status, 0, 0, PgSyncPool.QUERY_TIMEOUT_MS);
99
+ const code = Atomics.load(this.status, 0);
100
+ if (code === 0 || waitResult === "timed-out") {
101
+ if (this.lastError)
102
+ throw this.lastError;
103
+ throw new Error("PostgreSQL query timed out after 60s");
104
+ }
105
+ const len = Atomics.load(this.status, 1);
106
+ const payload = JSON.parse(new TextDecoder().decode(this.data.subarray(0, len)));
107
+ if (code === 2) {
108
+ throw new Error(payload.message ?? "PostgreSQL error");
109
+ }
110
+ return payload;
111
+ }
112
+ end() {
113
+ if (this.closed)
114
+ return;
115
+ this.closed = true;
116
+ this.worker.terminate();
117
+ }
118
+ }
119
+ var MEMENTOS_STORAGE_ENV = {
120
+ databaseUrl: "HASNA_MEMENTOS_DATABASE_URL"
121
+ };
122
+ var MEMENTOS_STORAGE_FALLBACK_ENV = {
123
+ databaseUrl: "MEMENTOS_DATABASE_URL"
124
+ };
125
+ var LOCAL_DATA_DIR = join(homedir(), ".hasna", "mementos");
126
+ var STORAGE_CONFIG_DIR = join(LOCAL_DATA_DIR, "storage");
127
+ var STORAGE_CONFIG_PATH = join(STORAGE_CONFIG_DIR, "config.json");
128
+ var DATABASE_ENV_NAMES = [
129
+ { name: MEMENTOS_STORAGE_ENV.databaseUrl, deprecated: false },
130
+ { name: MEMENTOS_STORAGE_FALLBACK_ENV.databaseUrl, deprecated: false }
131
+ ];
132
+ var SECRET_QUERY_PARAMS = new Set([
133
+ "password",
134
+ "pass",
135
+ "pwd",
136
+ "token",
137
+ "secret",
138
+ "api_key",
139
+ "apikey"
140
+ ]);
141
+
142
+ // src/project-registration/schema.ts
143
+ function sqliteMementosProjectRegistrationSchemaSql() {
144
+ return `
145
+ CREATE TABLE IF NOT EXISTS mementos_project_registration_receipts (
146
+ receipt_id TEXT PRIMARY KEY,
147
+ authority TEXT NOT NULL CHECK(authority = 'mementos'),
148
+ route TEXT NOT NULL,
149
+ package_version TEXT NOT NULL,
150
+ authority_id TEXT NOT NULL,
151
+ tenant_id TEXT NOT NULL,
152
+ corpus_id TEXT NOT NULL,
153
+ operation_id TEXT NOT NULL,
154
+ step_id TEXT NOT NULL,
155
+ resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
156
+ direction TEXT NOT NULL CHECK(direction IN ('forward', 'inverse')),
157
+ target_selector TEXT NOT NULL,
158
+ idempotency_key TEXT NOT NULL,
159
+ request_digest TEXT NOT NULL,
160
+ precondition_digest TEXT NOT NULL,
161
+ normalized_call_digest TEXT NOT NULL,
162
+ outcome TEXT NOT NULL CHECK(outcome IN (
163
+ 'accepted', 'duplicate_of_accepted', 'terminal_nonacceptance'
164
+ )),
165
+ reason TEXT,
166
+ target_id TEXT,
167
+ result_revision TEXT,
168
+ result_digest TEXT,
169
+ duplicate_of_receipt_id TEXT,
170
+ accepted_receipt_id TEXT,
171
+ created_by_operation INTEGER NOT NULL CHECK(created_by_operation IN (0, 1)),
172
+ created_at TEXT NOT NULL
173
+ );
174
+
175
+ CREATE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_lookup
176
+ ON mementos_project_registration_receipts (
177
+ authority_id, tenant_id, corpus_id, operation_id, step_id,
178
+ resource_kind, direction, idempotency_key, target_selector
179
+ );
180
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_receipts_accepted_step
181
+ ON mementos_project_registration_receipts (
182
+ authority_id, tenant_id, corpus_id, operation_id, step_id,
183
+ resource_kind, direction
184
+ )
185
+ WHERE outcome = 'accepted';
186
+
187
+ CREATE TABLE IF NOT EXISTS mementos_project_registration_bindings (
188
+ authority_id TEXT NOT NULL,
189
+ tenant_id TEXT NOT NULL,
190
+ corpus_id TEXT NOT NULL,
191
+ resource_kind TEXT NOT NULL CHECK(resource_kind = 'project'),
192
+ target_selector TEXT NOT NULL,
193
+ operation_id TEXT NOT NULL,
194
+ step_id TEXT NOT NULL,
195
+ direction TEXT NOT NULL CHECK(direction = 'forward'),
196
+ idempotency_key TEXT NOT NULL,
197
+ request_digest TEXT NOT NULL,
198
+ precondition_digest TEXT NOT NULL,
199
+ normalized_call_digest TEXT NOT NULL,
200
+ state TEXT NOT NULL CHECK(state IN (
201
+ 'pending', 'accepted', 'terminal_nonacceptance', 'removed'
202
+ )),
203
+ target_id TEXT,
204
+ accepted_receipt_id TEXT,
205
+ result_revision TEXT,
206
+ result_digest TEXT,
207
+ removed_receipt_id TEXT,
208
+ created_at TEXT NOT NULL,
209
+ updated_at TEXT NOT NULL,
210
+ PRIMARY KEY(authority_id, tenant_id, corpus_id, resource_kind, target_selector),
211
+ UNIQUE(accepted_receipt_id)
212
+ );
213
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_mementos_project_registration_binding_target
214
+ ON mementos_project_registration_bindings(
215
+ authority_id, tenant_id, corpus_id, resource_kind, target_id
216
+ )
217
+ WHERE target_id IS NOT NULL;
218
+
219
+ CREATE TRIGGER IF NOT EXISTS mementos_project_registration_receipts_immutable_update
220
+ BEFORE UPDATE ON mementos_project_registration_receipts
221
+ BEGIN
222
+ SELECT RAISE(ABORT, 'mementos project registration receipts are immutable');
223
+ END;
224
+
225
+ CREATE TRIGGER IF NOT EXISTS mementos_project_registration_receipts_immutable_delete
226
+ BEFORE DELETE ON mementos_project_registration_receipts
227
+ BEGIN
228
+ SELECT RAISE(ABORT, 'mementos project registration receipts are immutable');
229
+ END;
230
+ `;
231
+ }
232
+ function sqliteMementosProjectGuardedUpdateSchemaSql() {
233
+ return `
234
+ CREATE TABLE IF NOT EXISTS mementos_project_update_receipts (
235
+ receipt_id TEXT PRIMARY KEY,
236
+ authority TEXT NOT NULL CHECK(authority = 'mementos'),
237
+ route TEXT NOT NULL CHECK(route = 'mementos.project-guarded-update.v1'),
238
+ package_version TEXT NOT NULL,
239
+ authority_id TEXT NOT NULL,
240
+ tenant_id TEXT NOT NULL,
241
+ corpus_id TEXT NOT NULL,
242
+ operation_id TEXT NOT NULL,
243
+ step_id TEXT NOT NULL,
244
+ direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
245
+ idempotency_key TEXT NOT NULL,
246
+ request_digest TEXT NOT NULL,
247
+ outcome TEXT NOT NULL CHECK(outcome = 'accepted'),
248
+ target_id TEXT NOT NULL,
249
+ expected_revision TEXT NOT NULL,
250
+ result_revision TEXT NOT NULL,
251
+ result_digest TEXT NOT NULL,
252
+ accepted_receipt_id TEXT,
253
+ before_project_json TEXT NOT NULL,
254
+ after_project_json TEXT NOT NULL,
255
+ created_at TEXT NOT NULL,
256
+ UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
257
+ );
258
+
259
+ CREATE INDEX IF NOT EXISTS idx_mementos_project_update_receipts_target
260
+ ON mementos_project_update_receipts(
261
+ authority_id, tenant_id, corpus_id, target_id, created_at
262
+ );
263
+
264
+ CREATE TRIGGER IF NOT EXISTS mementos_project_update_receipts_immutable_update
265
+ BEFORE UPDATE ON mementos_project_update_receipts
266
+ BEGIN
267
+ SELECT RAISE(ABORT, 'mementos project update receipts are immutable');
268
+ END;
269
+
270
+ CREATE TRIGGER IF NOT EXISTS mementos_project_update_receipts_immutable_delete
271
+ BEFORE DELETE ON mementos_project_update_receipts
272
+ BEGIN
273
+ SELECT RAISE(ABORT, 'mementos project update receipts are immutable');
274
+ END;
275
+ `;
276
+ }
277
+
278
+ // src/memory-project-link/schema.ts
279
+ function sqliteMementosMemoryProjectLinkSchemaSql() {
280
+ return `
281
+ CREATE TABLE IF NOT EXISTS mementos_memory_project_link_receipts (
282
+ receipt_id TEXT PRIMARY KEY,
283
+ authority TEXT NOT NULL CHECK(authority = 'mementos'),
284
+ route TEXT NOT NULL CHECK(route = 'mementos.memory-project-link.v1'),
285
+ package_version TEXT NOT NULL,
286
+ authority_id TEXT NOT NULL,
287
+ tenant_id TEXT NOT NULL,
288
+ corpus_id TEXT NOT NULL,
289
+ operation_id TEXT NOT NULL,
290
+ step_id TEXT NOT NULL,
291
+ direction TEXT NOT NULL CHECK(direction IN ('forward', 'rollback')),
292
+ idempotency_key TEXT NOT NULL,
293
+ request_digest TEXT NOT NULL,
294
+ outcome TEXT NOT NULL CHECK(outcome IN ('accepted', 'no_change')),
295
+ target_memory_id TEXT NOT NULL,
296
+ requested_project_id TEXT NOT NULL,
297
+ expected_memory_version INTEGER NOT NULL,
298
+ expected_memory_revision TEXT NOT NULL,
299
+ expected_project_revision TEXT,
300
+ result_memory_version INTEGER NOT NULL,
301
+ result_memory_revision TEXT NOT NULL,
302
+ result_memory_digest TEXT NOT NULL,
303
+ result_project_revision TEXT,
304
+ result_project_digest TEXT,
305
+ accepted_receipt_id TEXT,
306
+ before_link_json TEXT NOT NULL,
307
+ after_link_json TEXT NOT NULL,
308
+ before_project_revision TEXT,
309
+ before_project_digest TEXT,
310
+ after_project_revision TEXT,
311
+ after_project_digest TEXT,
312
+ created_at TEXT NOT NULL,
313
+ UNIQUE(authority_id, tenant_id, corpus_id, direction, idempotency_key)
314
+ );
315
+
316
+ CREATE INDEX IF NOT EXISTS idx_mementos_memory_project_link_receipts_target
317
+ ON mementos_memory_project_link_receipts(
318
+ authority_id, tenant_id, corpus_id, target_memory_id, created_at
319
+ );
320
+
321
+ CREATE TRIGGER IF NOT EXISTS mementos_memory_project_link_receipts_immutable_update
322
+ BEFORE UPDATE ON mementos_memory_project_link_receipts
323
+ BEGIN
324
+ SELECT RAISE(ABORT, 'mementos memory project link receipts are immutable');
325
+ END;
326
+
327
+ CREATE TRIGGER IF NOT EXISTS mementos_memory_project_link_receipts_immutable_delete
328
+ BEFORE DELETE ON mementos_memory_project_link_receipts
329
+ BEGIN
330
+ SELECT RAISE(ABORT, 'mementos memory project link receipts are immutable');
331
+ END;
332
+ `;
333
+ }
334
+
335
+ // src/db/migrations.ts
336
+ var MEMORY_VERSION_SNAPSHOT_TRIGGER = `
337
+ CREATE TRIGGER IF NOT EXISTS memories_version_snapshot
338
+ BEFORE UPDATE ON memories
339
+ WHEN NEW.version > OLD.version
340
+ BEGIN
341
+ INSERT OR IGNORE INTO memory_versions (
342
+ id, memory_id, version, value, importance, scope, category, tags,
343
+ summary, pinned, status, when_to_use, created_at
344
+ ) VALUES (
345
+ lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-' ||
346
+ lower(hex(randomblob(2))) || '-' || lower(hex(randomblob(2))) || '-' ||
347
+ lower(hex(randomblob(6))),
348
+ OLD.id, OLD.version, OLD.value, OLD.importance, OLD.scope, OLD.category,
349
+ OLD.tags, OLD.summary, OLD.pinned, OLD.status, OLD.when_to_use,
350
+ OLD.updated_at
351
+ );
352
+ END;
353
+ `;
354
+ var MIGRATIONS = [
355
+ `
356
+ CREATE TABLE IF NOT EXISTS projects (
357
+ id TEXT PRIMARY KEY,
358
+ name TEXT NOT NULL,
359
+ path TEXT UNIQUE NOT NULL,
360
+ description TEXT,
361
+ memory_prefix TEXT,
362
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
363
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
364
+ );
365
+
366
+ CREATE TABLE IF NOT EXISTS agents (
367
+ id TEXT PRIMARY KEY,
368
+ name TEXT NOT NULL UNIQUE,
369
+ description TEXT,
370
+ role TEXT DEFAULT 'agent',
371
+ metadata TEXT DEFAULT '{}',
372
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
373
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
374
+ );
375
+
376
+ CREATE TABLE IF NOT EXISTS memories (
377
+ id TEXT PRIMARY KEY,
378
+ key TEXT NOT NULL,
379
+ value TEXT NOT NULL,
380
+ category TEXT NOT NULL DEFAULT 'knowledge' CHECK(category IN ('preference', 'fact', 'knowledge', 'history')),
381
+ scope TEXT NOT NULL DEFAULT 'private' CHECK(scope IN ('global', 'shared', 'private')),
382
+ summary TEXT,
383
+ tags TEXT DEFAULT '[]',
384
+ importance INTEGER NOT NULL DEFAULT 5 CHECK(importance >= 1 AND importance <= 10),
385
+ source TEXT NOT NULL DEFAULT 'agent' CHECK(source IN ('user', 'agent', 'system', 'auto', 'imported')),
386
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived', 'expired')),
387
+ pinned INTEGER NOT NULL DEFAULT 0,
388
+ agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
389
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
390
+ session_id TEXT,
391
+ metadata TEXT DEFAULT '{}',
392
+ access_count INTEGER NOT NULL DEFAULT 0,
393
+ version INTEGER NOT NULL DEFAULT 1,
394
+ expires_at TEXT,
395
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
396
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
397
+ accessed_at TEXT
398
+ );
399
+
400
+ CREATE TABLE IF NOT EXISTS memory_tags (
401
+ memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
402
+ tag TEXT NOT NULL,
403
+ PRIMARY KEY (memory_id, tag)
404
+ );
405
+
406
+ CREATE TABLE IF NOT EXISTS sessions (
407
+ id TEXT PRIMARY KEY,
408
+ agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
409
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
410
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
411
+ last_activity TEXT NOT NULL DEFAULT (datetime('now')),
412
+ metadata TEXT DEFAULT '{}'
413
+ );
414
+
415
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_unique_key
416
+ ON memories(key, scope, COALESCE(agent_id, ''), COALESCE(project_id, ''), COALESCE(session_id, ''));
417
+
418
+ CREATE INDEX IF NOT EXISTS idx_memories_key ON memories(key);
419
+ CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope);
420
+ CREATE INDEX IF NOT EXISTS idx_memories_category ON memories(category);
421
+ CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status);
422
+ CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance);
423
+ CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
424
+ CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_id);
425
+ CREATE INDEX IF NOT EXISTS idx_memories_session ON memories(session_id);
426
+ CREATE INDEX IF NOT EXISTS idx_memories_pinned ON memories(pinned);
427
+ CREATE INDEX IF NOT EXISTS idx_memories_expires ON memories(expires_at);
428
+ CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at);
429
+ CREATE INDEX IF NOT EXISTS idx_memory_tags_tag ON memory_tags(tag);
430
+ CREATE INDEX IF NOT EXISTS idx_memory_tags_memory ON memory_tags(memory_id);
431
+ CREATE INDEX IF NOT EXISTS idx_agents_name ON agents(name);
432
+ CREATE INDEX IF NOT EXISTS idx_sessions_agent ON sessions(agent_id);
433
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
434
+
435
+ CREATE TABLE IF NOT EXISTS _migrations (
436
+ id INTEGER PRIMARY KEY,
437
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
438
+ );
439
+
440
+ INSERT OR IGNORE INTO _migrations (id) VALUES (1);
441
+ `,
442
+ `
443
+ CREATE TABLE IF NOT EXISTS memory_versions (
444
+ id TEXT PRIMARY KEY,
445
+ memory_id TEXT NOT NULL,
446
+ version INTEGER NOT NULL,
447
+ value TEXT NOT NULL,
448
+ importance INTEGER NOT NULL,
449
+ scope TEXT NOT NULL,
450
+ category TEXT NOT NULL,
451
+ tags TEXT NOT NULL DEFAULT '[]',
452
+ summary TEXT,
453
+ pinned INTEGER NOT NULL DEFAULT 0,
454
+ status TEXT NOT NULL DEFAULT 'active',
455
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
456
+ UNIQUE(memory_id, version)
457
+ );
458
+
459
+ CREATE INDEX IF NOT EXISTS idx_memory_versions_memory ON memory_versions(memory_id);
460
+ CREATE INDEX IF NOT EXISTS idx_memory_versions_version ON memory_versions(memory_id, version);
461
+
462
+ INSERT OR IGNORE INTO _migrations (id) VALUES (2);
463
+ `,
464
+ `
465
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
466
+ key, value, summary,
467
+ content='memories',
468
+ content_rowid='rowid'
469
+ );
470
+
471
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
472
+ INSERT INTO memories_fts(rowid, key, value, summary) VALUES (new.rowid, new.key, new.value, new.summary);
473
+ END;
474
+
475
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
476
+ INSERT INTO memories_fts(memories_fts, rowid, key, value, summary) VALUES('delete', old.rowid, old.key, old.value, old.summary);
477
+ END;
478
+
479
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
480
+ INSERT INTO memories_fts(memories_fts, rowid, key, value, summary) VALUES('delete', old.rowid, old.key, old.value, old.summary);
481
+ INSERT INTO memories_fts(rowid, key, value, summary) VALUES (new.rowid, new.key, new.value, new.summary);
482
+ END;
483
+
484
+ INSERT INTO memories_fts(memories_fts) VALUES('rebuild');
485
+
486
+ INSERT OR IGNORE INTO _migrations (id) VALUES (3);
487
+ `,
488
+ `
489
+ CREATE TABLE IF NOT EXISTS search_history (
490
+ id TEXT PRIMARY KEY,
491
+ query TEXT NOT NULL,
492
+ result_count INTEGER NOT NULL DEFAULT 0,
493
+ agent_id TEXT,
494
+ project_id TEXT,
495
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
496
+ );
497
+ CREATE INDEX IF NOT EXISTS idx_search_history_query ON search_history(query);
498
+ CREATE INDEX IF NOT EXISTS idx_search_history_created ON search_history(created_at);
499
+
500
+ INSERT OR IGNORE INTO _migrations (id) VALUES (4);
501
+ `,
502
+ `
503
+ CREATE TABLE IF NOT EXISTS entities (
504
+ id TEXT PRIMARY KEY,
505
+ name TEXT NOT NULL,
506
+ type TEXT NOT NULL CHECK (type IN ('person','project','tool','concept','file','api','pattern','organization')),
507
+ description TEXT,
508
+ metadata TEXT NOT NULL DEFAULT '{}',
509
+ project_id TEXT,
510
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
511
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
512
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL
513
+ );
514
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_entities_unique_name_type_project
515
+ ON entities(name, type, COALESCE(project_id, ''));
516
+ CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
517
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
518
+ CREATE INDEX IF NOT EXISTS idx_entities_project ON entities(project_id);
519
+
520
+ CREATE TABLE IF NOT EXISTS relations (
521
+ id TEXT PRIMARY KEY,
522
+ source_entity_id TEXT NOT NULL,
523
+ target_entity_id TEXT NOT NULL,
524
+ relation_type TEXT NOT NULL CHECK (relation_type IN ('uses','knows','depends_on','created_by','related_to','contradicts','part_of','implements')),
525
+ weight REAL NOT NULL DEFAULT 1.0,
526
+ metadata TEXT NOT NULL DEFAULT '{}',
527
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
528
+ UNIQUE(source_entity_id, target_entity_id, relation_type),
529
+ FOREIGN KEY (source_entity_id) REFERENCES entities(id) ON DELETE CASCADE,
530
+ FOREIGN KEY (target_entity_id) REFERENCES entities(id) ON DELETE CASCADE
531
+ );
532
+ CREATE INDEX IF NOT EXISTS idx_relations_source ON relations(source_entity_id);
533
+ CREATE INDEX IF NOT EXISTS idx_relations_target ON relations(target_entity_id);
534
+ CREATE INDEX IF NOT EXISTS idx_relations_type ON relations(relation_type);
535
+
536
+ CREATE TABLE IF NOT EXISTS entity_memories (
537
+ entity_id TEXT NOT NULL,
538
+ memory_id TEXT NOT NULL,
539
+ role TEXT NOT NULL DEFAULT 'context' CHECK (role IN ('subject','object','context')),
540
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
541
+ PRIMARY KEY (entity_id, memory_id),
542
+ FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE,
543
+ FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
544
+ );
545
+ CREATE INDEX IF NOT EXISTS idx_entity_memories_memory ON entity_memories(memory_id);
546
+
547
+ INSERT OR IGNORE INTO _migrations (id) VALUES (5);
548
+ `,
549
+ `
550
+ ALTER TABLE agents ADD COLUMN active_project_id TEXT REFERENCES projects(id) ON DELETE SET NULL;
551
+ CREATE INDEX IF NOT EXISTS idx_agents_active_project ON agents(active_project_id);
552
+ INSERT OR IGNORE INTO _migrations (id) VALUES (6);
553
+ `,
554
+ `
555
+ ALTER TABLE agents ADD COLUMN session_id TEXT;
556
+ CREATE INDEX IF NOT EXISTS idx_agents_session ON agents(session_id);
557
+ INSERT OR IGNORE INTO _migrations (id) VALUES (7);
558
+ `,
559
+ `
560
+ CREATE TABLE IF NOT EXISTS resource_locks (
561
+ id TEXT PRIMARY KEY,
562
+ resource_type TEXT NOT NULL CHECK(resource_type IN ('project', 'memory', 'entity', 'agent', 'connector')),
563
+ resource_id TEXT NOT NULL,
564
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
565
+ lock_type TEXT NOT NULL DEFAULT 'exclusive' CHECK(lock_type IN ('advisory', 'exclusive')),
566
+ locked_at TEXT NOT NULL DEFAULT (datetime('now')),
567
+ expires_at TEXT NOT NULL
568
+ );
569
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
570
+ ON resource_locks(resource_type, resource_id)
571
+ WHERE lock_type = 'exclusive';
572
+ CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id);
573
+ CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at);
574
+ INSERT OR IGNORE INTO _migrations (id) VALUES (8);
575
+ `,
576
+ `
577
+ ALTER TABLE memories ADD COLUMN recall_count INTEGER NOT NULL DEFAULT 0;
578
+ CREATE INDEX IF NOT EXISTS idx_memories_recall_count ON memories(recall_count DESC);
579
+ INSERT OR IGNORE INTO _migrations (id) VALUES (9);
580
+ `,
581
+ `
582
+ CREATE TABLE IF NOT EXISTS synthesis_events (
583
+ id TEXT PRIMARY KEY,
584
+ event_type TEXT NOT NULL CHECK(event_type IN ('recalled','searched','saved','updated','deleted','injected')),
585
+ memory_id TEXT,
586
+ agent_id TEXT,
587
+ project_id TEXT,
588
+ session_id TEXT,
589
+ query TEXT,
590
+ importance_at_time INTEGER,
591
+ metadata TEXT NOT NULL DEFAULT '{}',
592
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
593
+ );
594
+ CREATE INDEX IF NOT EXISTS idx_synthesis_events_memory ON synthesis_events(memory_id);
595
+ CREATE INDEX IF NOT EXISTS idx_synthesis_events_project ON synthesis_events(project_id);
596
+ CREATE INDEX IF NOT EXISTS idx_synthesis_events_type ON synthesis_events(event_type);
597
+ CREATE INDEX IF NOT EXISTS idx_synthesis_events_created ON synthesis_events(created_at);
598
+ INSERT OR IGNORE INTO _migrations (id) VALUES (11);
599
+ `,
600
+ `
601
+ CREATE TABLE IF NOT EXISTS synthesis_runs (
602
+ id TEXT PRIMARY KEY,
603
+ triggered_by TEXT NOT NULL DEFAULT 'manual' CHECK(triggered_by IN ('scheduler','manual','threshold','hook')),
604
+ project_id TEXT,
605
+ agent_id TEXT,
606
+ corpus_size INTEGER NOT NULL DEFAULT 0,
607
+ proposals_generated INTEGER NOT NULL DEFAULT 0,
608
+ proposals_accepted INTEGER NOT NULL DEFAULT 0,
609
+ proposals_rejected INTEGER NOT NULL DEFAULT 0,
610
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','running','completed','failed','rolled_back')),
611
+ error TEXT,
612
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
613
+ completed_at TEXT
614
+ );
615
+ CREATE INDEX IF NOT EXISTS idx_synthesis_runs_project ON synthesis_runs(project_id);
616
+ CREATE INDEX IF NOT EXISTS idx_synthesis_runs_status ON synthesis_runs(status);
617
+ CREATE INDEX IF NOT EXISTS idx_synthesis_runs_started ON synthesis_runs(started_at);
618
+
619
+ CREATE TABLE IF NOT EXISTS synthesis_proposals (
620
+ id TEXT PRIMARY KEY,
621
+ run_id TEXT NOT NULL REFERENCES synthesis_runs(id) ON DELETE CASCADE,
622
+ proposal_type TEXT NOT NULL CHECK(proposal_type IN ('merge','archive','promote','update_value','add_tag','remove_duplicate')),
623
+ memory_ids TEXT NOT NULL DEFAULT '[]',
624
+ target_memory_id TEXT,
625
+ proposed_changes TEXT NOT NULL DEFAULT '{}',
626
+ reasoning TEXT,
627
+ confidence REAL NOT NULL DEFAULT 0.5,
628
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','accepted','rejected','rolled_back')),
629
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
630
+ executed_at TEXT,
631
+ rollback_data TEXT
632
+ );
633
+ CREATE INDEX IF NOT EXISTS idx_synthesis_proposals_run ON synthesis_proposals(run_id);
634
+ CREATE INDEX IF NOT EXISTS idx_synthesis_proposals_status ON synthesis_proposals(status);
635
+ CREATE INDEX IF NOT EXISTS idx_synthesis_proposals_type ON synthesis_proposals(proposal_type);
636
+
637
+ CREATE TABLE IF NOT EXISTS synthesis_metrics (
638
+ id TEXT PRIMARY KEY,
639
+ run_id TEXT NOT NULL REFERENCES synthesis_runs(id) ON DELETE CASCADE,
640
+ metric_type TEXT NOT NULL,
641
+ value REAL NOT NULL,
642
+ baseline REAL,
643
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
644
+ );
645
+ CREATE INDEX IF NOT EXISTS idx_synthesis_metrics_run ON synthesis_metrics(run_id);
646
+ CREATE INDEX IF NOT EXISTS idx_synthesis_metrics_type ON synthesis_metrics(metric_type);
647
+
648
+ INSERT OR IGNORE INTO _migrations (id) VALUES (12);
649
+ `,
650
+ `
651
+ CREATE TABLE IF NOT EXISTS webhook_hooks (
652
+ id TEXT PRIMARY KEY,
653
+ type TEXT NOT NULL,
654
+ handler_url TEXT NOT NULL,
655
+ priority INTEGER NOT NULL DEFAULT 50,
656
+ blocking INTEGER NOT NULL DEFAULT 0,
657
+ agent_id TEXT,
658
+ project_id TEXT,
659
+ description TEXT,
660
+ enabled INTEGER NOT NULL DEFAULT 1,
661
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
662
+ invocation_count INTEGER NOT NULL DEFAULT 0,
663
+ failure_count INTEGER NOT NULL DEFAULT 0
664
+ );
665
+ CREATE INDEX IF NOT EXISTS idx_webhook_hooks_type ON webhook_hooks(type);
666
+ CREATE INDEX IF NOT EXISTS idx_webhook_hooks_enabled ON webhook_hooks(enabled);
667
+ INSERT OR IGNORE INTO _migrations (id) VALUES (10);
668
+ `,
669
+ `
670
+ CREATE TABLE IF NOT EXISTS session_memory_jobs (
671
+ id TEXT PRIMARY KEY,
672
+ session_id TEXT NOT NULL,
673
+ agent_id TEXT,
674
+ project_id TEXT,
675
+ source TEXT NOT NULL DEFAULT 'manual' CHECK(source IN ('claude-code','codex','manual','open-sessions')),
676
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','processing','completed','failed')),
677
+ transcript TEXT NOT NULL,
678
+ chunk_count INTEGER NOT NULL DEFAULT 0,
679
+ memories_extracted INTEGER NOT NULL DEFAULT 0,
680
+ error TEXT,
681
+ metadata TEXT NOT NULL DEFAULT '{}',
682
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
683
+ started_at TEXT,
684
+ completed_at TEXT
685
+ );
686
+ CREATE INDEX IF NOT EXISTS idx_session_memory_jobs_status ON session_memory_jobs(status);
687
+ CREATE INDEX IF NOT EXISTS idx_session_memory_jobs_agent ON session_memory_jobs(agent_id);
688
+ CREATE INDEX IF NOT EXISTS idx_session_memory_jobs_project ON session_memory_jobs(project_id);
689
+ CREATE INDEX IF NOT EXISTS idx_session_memory_jobs_session ON session_memory_jobs(session_id);
690
+ INSERT OR IGNORE INTO _migrations (id) VALUES (13);
691
+ `,
692
+ `
693
+ ALTER TABLE resource_locks RENAME TO resource_locks_old;
694
+ CREATE TABLE resource_locks (
695
+ id TEXT PRIMARY KEY,
696
+ resource_type TEXT NOT NULL CHECK(resource_type IN ('project', 'memory', 'entity', 'agent', 'connector', 'file')),
697
+ resource_id TEXT NOT NULL,
698
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
699
+ lock_type TEXT NOT NULL DEFAULT 'exclusive' CHECK(lock_type IN ('advisory', 'exclusive')),
700
+ locked_at TEXT NOT NULL DEFAULT (datetime('now')),
701
+ expires_at TEXT NOT NULL
702
+ );
703
+ INSERT INTO resource_locks SELECT * FROM resource_locks_old;
704
+ DROP TABLE resource_locks_old;
705
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_resource_locks_exclusive
706
+ ON resource_locks(resource_type, resource_id)
707
+ WHERE lock_type = 'exclusive';
708
+ CREATE INDEX IF NOT EXISTS idx_resource_locks_agent ON resource_locks(agent_id);
709
+ CREATE INDEX IF NOT EXISTS idx_resource_locks_expires ON resource_locks(expires_at);
710
+ INSERT OR IGNORE INTO _migrations (id) VALUES (14);
711
+ `,
712
+ `
713
+ CREATE TABLE IF NOT EXISTS machines (
714
+ id TEXT PRIMARY KEY,
715
+ name TEXT NOT NULL UNIQUE,
716
+ hostname TEXT NOT NULL,
717
+ platform TEXT NOT NULL DEFAULT 'unknown',
718
+ is_primary INTEGER NOT NULL DEFAULT 0,
719
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
720
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
721
+ );
722
+ CREATE INDEX IF NOT EXISTS idx_machines_hostname ON machines(hostname);
723
+ ALTER TABLE memories ADD COLUMN machine_id TEXT REFERENCES machines(id) ON DELETE SET NULL;
724
+ CREATE INDEX IF NOT EXISTS idx_memories_machine ON memories(machine_id);
725
+ INSERT OR IGNORE INTO _migrations (id) VALUES (15);
726
+ `,
727
+ `
728
+ ALTER TABLE memories ADD COLUMN flag TEXT;
729
+ CREATE INDEX IF NOT EXISTS idx_memories_flag ON memories(flag);
730
+ INSERT OR IGNORE INTO _migrations (id) VALUES (16);
731
+ `,
732
+ `
733
+ CREATE TABLE IF NOT EXISTS memory_embeddings (
734
+ memory_id TEXT PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
735
+ embedding TEXT NOT NULL,
736
+ model TEXT NOT NULL DEFAULT 'tfidf-512',
737
+ dimensions INTEGER NOT NULL DEFAULT 512,
738
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
739
+ );
740
+ CREATE INDEX IF NOT EXISTS idx_memory_embeddings_model ON memory_embeddings(model);
741
+ INSERT OR IGNORE INTO _migrations (id) VALUES (17);
742
+ `,
743
+ `
744
+ ALTER TABLE memories ADD COLUMN valid_from TEXT DEFAULT NULL;
745
+ ALTER TABLE memories ADD COLUMN valid_until TEXT DEFAULT NULL;
746
+ ALTER TABLE memories ADD COLUMN ingested_at TEXT DEFAULT NULL;
747
+ CREATE INDEX IF NOT EXISTS idx_memories_valid_from ON memories(valid_from);
748
+ CREATE INDEX IF NOT EXISTS idx_memories_valid_until ON memories(valid_until);
749
+ UPDATE memories SET valid_from = created_at, ingested_at = created_at WHERE valid_from IS NULL;
750
+ INSERT OR IGNORE INTO _migrations (id) VALUES (18);
751
+ `,
752
+ `
753
+ PRAGMA foreign_keys = OFF;
754
+ ALTER TABLE memories RENAME TO memories_old;
755
+ CREATE TABLE memories (
756
+ id TEXT PRIMARY KEY,
757
+ key TEXT NOT NULL,
758
+ value TEXT NOT NULL,
759
+ category TEXT NOT NULL DEFAULT 'knowledge' CHECK(category IN ('preference', 'fact', 'knowledge', 'history')),
760
+ scope TEXT NOT NULL DEFAULT 'private' CHECK(scope IN ('global', 'shared', 'private', 'working')),
761
+ summary TEXT,
762
+ tags TEXT DEFAULT '[]',
763
+ importance INTEGER NOT NULL DEFAULT 5 CHECK(importance >= 1 AND importance <= 10),
764
+ source TEXT NOT NULL DEFAULT 'agent' CHECK(source IN ('user', 'agent', 'system', 'auto', 'imported')),
765
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived', 'expired')),
766
+ pinned INTEGER NOT NULL DEFAULT 0,
767
+ agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
768
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
769
+ session_id TEXT,
770
+ machine_id TEXT REFERENCES machines(id) ON DELETE SET NULL,
771
+ flag TEXT,
772
+ metadata TEXT DEFAULT '{}',
773
+ access_count INTEGER NOT NULL DEFAULT 0,
774
+ recall_count INTEGER NOT NULL DEFAULT 0,
775
+ version INTEGER NOT NULL DEFAULT 1,
776
+ expires_at TEXT,
777
+ valid_from TEXT DEFAULT NULL,
778
+ valid_until TEXT DEFAULT NULL,
779
+ ingested_at TEXT DEFAULT NULL,
780
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
781
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
782
+ accessed_at TEXT
783
+ );
784
+ INSERT INTO memories SELECT * FROM memories_old;
785
+ DROP TABLE memories_old;
786
+
787
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_unique_key
788
+ ON memories(key, scope, COALESCE(agent_id, ''), COALESCE(project_id, ''), COALESCE(session_id, ''));
789
+ CREATE INDEX IF NOT EXISTS idx_memories_key ON memories(key);
790
+ CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope);
791
+ CREATE INDEX IF NOT EXISTS idx_memories_category ON memories(category);
792
+ CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status);
793
+ CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance);
794
+ CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
795
+ CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_id);
796
+ CREATE INDEX IF NOT EXISTS idx_memories_session ON memories(session_id);
797
+ CREATE INDEX IF NOT EXISTS idx_memories_pinned ON memories(pinned);
798
+ CREATE INDEX IF NOT EXISTS idx_memories_expires ON memories(expires_at);
799
+ CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at);
800
+ CREATE INDEX IF NOT EXISTS idx_memories_machine ON memories(machine_id);
801
+ CREATE INDEX IF NOT EXISTS idx_memories_flag ON memories(flag);
802
+ CREATE INDEX IF NOT EXISTS idx_memories_recall_count ON memories(recall_count DESC);
803
+ CREATE INDEX IF NOT EXISTS idx_memories_valid_from ON memories(valid_from);
804
+ CREATE INDEX IF NOT EXISTS idx_memories_valid_until ON memories(valid_until);
805
+
806
+ INSERT INTO memories_fts(memories_fts) VALUES('rebuild');
807
+
808
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
809
+ INSERT INTO memories_fts(rowid, key, value, summary) VALUES (new.rowid, new.key, new.value, new.summary);
810
+ END;
811
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
812
+ INSERT INTO memories_fts(memories_fts, rowid, key, value, summary) VALUES('delete', old.rowid, old.key, old.value, old.summary);
813
+ END;
814
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
815
+ INSERT INTO memories_fts(memories_fts, rowid, key, value, summary) VALUES('delete', old.rowid, old.key, old.value, old.summary);
816
+ INSERT INTO memories_fts(rowid, key, value, summary) VALUES (new.rowid, new.key, new.value, new.summary);
817
+ END;
818
+
819
+ PRAGMA foreign_keys = ON;
820
+ INSERT OR IGNORE INTO _migrations (id) VALUES (19);
821
+ `,
822
+ `
823
+ PRAGMA foreign_keys = OFF;
824
+ ALTER TABLE memories RENAME TO memories_old;
825
+ CREATE TABLE memories (
826
+ id TEXT PRIMARY KEY,
827
+ key TEXT NOT NULL,
828
+ value TEXT NOT NULL,
829
+ category TEXT NOT NULL DEFAULT 'knowledge' CHECK(category IN ('preference', 'fact', 'knowledge', 'history', 'procedural', 'resource')),
830
+ scope TEXT NOT NULL DEFAULT 'private' CHECK(scope IN ('global', 'shared', 'private', 'working')),
831
+ summary TEXT,
832
+ tags TEXT DEFAULT '[]',
833
+ importance INTEGER NOT NULL DEFAULT 5 CHECK(importance >= 1 AND importance <= 10),
834
+ source TEXT NOT NULL DEFAULT 'agent' CHECK(source IN ('user', 'agent', 'system', 'auto', 'imported')),
835
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived', 'expired')),
836
+ pinned INTEGER NOT NULL DEFAULT 0,
837
+ agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
838
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
839
+ session_id TEXT,
840
+ machine_id TEXT REFERENCES machines(id) ON DELETE SET NULL,
841
+ flag TEXT,
842
+ content_type TEXT NOT NULL DEFAULT 'text' CHECK(content_type IN ('text', 'code', 'image', 'resource')),
843
+ metadata TEXT DEFAULT '{}',
844
+ access_count INTEGER NOT NULL DEFAULT 0,
845
+ recall_count INTEGER NOT NULL DEFAULT 0,
846
+ version INTEGER NOT NULL DEFAULT 1,
847
+ expires_at TEXT,
848
+ valid_from TEXT DEFAULT NULL,
849
+ valid_until TEXT DEFAULT NULL,
850
+ ingested_at TEXT DEFAULT NULL,
851
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
852
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
853
+ accessed_at TEXT
854
+ );
855
+ INSERT INTO memories (id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, machine_id, flag, metadata, access_count, recall_count, version, expires_at, valid_from, valid_until, ingested_at, created_at, updated_at, accessed_at)
856
+ SELECT id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, machine_id, flag, metadata, access_count, recall_count, version, expires_at, valid_from, valid_until, ingested_at, created_at, updated_at, accessed_at FROM memories_old;
857
+ DROP TABLE memories_old;
858
+
859
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_unique_key
860
+ ON memories(key, scope, COALESCE(agent_id, ''), COALESCE(project_id, ''), COALESCE(session_id, ''));
861
+ CREATE INDEX IF NOT EXISTS idx_memories_key ON memories(key);
862
+ CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope);
863
+ CREATE INDEX IF NOT EXISTS idx_memories_category ON memories(category);
864
+ CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status);
865
+ CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance);
866
+ CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
867
+ CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_id);
868
+ CREATE INDEX IF NOT EXISTS idx_memories_session ON memories(session_id);
869
+ CREATE INDEX IF NOT EXISTS idx_memories_pinned ON memories(pinned);
870
+ CREATE INDEX IF NOT EXISTS idx_memories_expires ON memories(expires_at);
871
+ CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at);
872
+ CREATE INDEX IF NOT EXISTS idx_memories_machine ON memories(machine_id);
873
+ CREATE INDEX IF NOT EXISTS idx_memories_flag ON memories(flag);
874
+ CREATE INDEX IF NOT EXISTS idx_memories_recall_count ON memories(recall_count DESC);
875
+ CREATE INDEX IF NOT EXISTS idx_memories_valid_from ON memories(valid_from);
876
+ CREATE INDEX IF NOT EXISTS idx_memories_valid_until ON memories(valid_until);
877
+ CREATE INDEX IF NOT EXISTS idx_memories_content_type ON memories(content_type);
878
+
879
+ INSERT INTO memories_fts(memories_fts) VALUES('rebuild');
880
+
881
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
882
+ INSERT INTO memories_fts(rowid, key, value, summary) VALUES (new.rowid, new.key, new.value, new.summary);
883
+ END;
884
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
885
+ INSERT INTO memories_fts(memories_fts, rowid, key, value, summary) VALUES('delete', old.rowid, old.key, old.value, old.summary);
886
+ END;
887
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
888
+ INSERT INTO memories_fts(memories_fts, rowid, key, value, summary) VALUES('delete', old.rowid, old.key, old.value, old.summary);
889
+ INSERT INTO memories_fts(rowid, key, value, summary) VALUES (new.rowid, new.key, new.value, new.summary);
890
+ END;
891
+
892
+ PRAGMA foreign_keys = ON;
893
+ INSERT OR IGNORE INTO _migrations (id) VALUES (20);
894
+ `,
895
+ `
896
+ PRAGMA foreign_keys = OFF;
897
+ ALTER TABLE relations RENAME TO relations_old;
898
+ CREATE TABLE relations (
899
+ id TEXT PRIMARY KEY,
900
+ source_entity_id TEXT NOT NULL,
901
+ target_entity_id TEXT NOT NULL,
902
+ relation_type TEXT NOT NULL CHECK (relation_type IN ('uses','knows','depends_on','created_by','related_to','contradicts','part_of','implements','happened_before','happened_after','caused_by','resulted_in','supersedes','version_of')),
903
+ weight REAL NOT NULL DEFAULT 1.0,
904
+ metadata TEXT NOT NULL DEFAULT '{}',
905
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
906
+ UNIQUE(source_entity_id, target_entity_id, relation_type),
907
+ FOREIGN KEY (source_entity_id) REFERENCES entities(id) ON DELETE CASCADE,
908
+ FOREIGN KEY (target_entity_id) REFERENCES entities(id) ON DELETE CASCADE
909
+ );
910
+ INSERT INTO relations SELECT * FROM relations_old;
911
+ DROP TABLE relations_old;
912
+ CREATE INDEX IF NOT EXISTS idx_relations_source ON relations(source_entity_id);
913
+ CREATE INDEX IF NOT EXISTS idx_relations_target ON relations(target_entity_id);
914
+ CREATE INDEX IF NOT EXISTS idx_relations_type ON relations(relation_type);
915
+ PRAGMA foreign_keys = ON;
916
+ INSERT OR IGNORE INTO _migrations (id) VALUES (21);
917
+ `,
918
+ `
919
+ CREATE TABLE IF NOT EXISTS memory_audit_log (
920
+ id TEXT PRIMARY KEY,
921
+ memory_id TEXT NOT NULL,
922
+ memory_key TEXT,
923
+ operation TEXT NOT NULL CHECK(operation IN ('create','update','delete','archive','restore','read')),
924
+ agent_id TEXT,
925
+ old_value_hash TEXT,
926
+ new_value_hash TEXT,
927
+ changes TEXT DEFAULT '{}',
928
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
929
+ );
930
+ CREATE INDEX IF NOT EXISTS idx_audit_log_memory ON memory_audit_log(memory_id);
931
+ CREATE INDEX IF NOT EXISTS idx_audit_log_operation ON memory_audit_log(operation);
932
+ CREATE INDEX IF NOT EXISTS idx_audit_log_agent ON memory_audit_log(agent_id);
933
+ CREATE INDEX IF NOT EXISTS idx_audit_log_created ON memory_audit_log(created_at);
934
+
935
+ CREATE TRIGGER IF NOT EXISTS audit_memory_insert AFTER INSERT ON memories BEGIN
936
+ INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, new_value_hash, created_at)
937
+ VALUES (hex(randomblob(4)), new.id, new.key, 'create', new.agent_id, hex(randomblob(16)), datetime('now'));
938
+ END;
939
+
940
+ CREATE TRIGGER IF NOT EXISTS audit_memory_update AFTER UPDATE ON memories BEGIN
941
+ INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, old_value_hash, new_value_hash, changes, created_at)
942
+ VALUES (hex(randomblob(4)), new.id, new.key, 'update', new.agent_id, hex(randomblob(16)), hex(randomblob(16)),
943
+ json_object('version_from', old.version, 'version_to', new.version, 'importance_from', old.importance, 'importance_to', new.importance),
944
+ datetime('now'));
945
+ END;
946
+
947
+ CREATE TRIGGER IF NOT EXISTS audit_memory_delete AFTER DELETE ON memories BEGIN
948
+ INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, old_value_hash, created_at)
949
+ VALUES (hex(randomblob(4)), old.id, old.key, 'delete', old.agent_id, hex(randomblob(16)), datetime('now'));
950
+ END;
951
+
952
+ INSERT OR IGNORE INTO _migrations (id) VALUES (22);
953
+ `,
954
+ `
955
+ ALTER TABLE memories ADD COLUMN namespace TEXT DEFAULT NULL;
956
+ ALTER TABLE memories ADD COLUMN created_by_agent TEXT DEFAULT NULL;
957
+ ALTER TABLE memories ADD COLUMN updated_by_agent TEXT DEFAULT NULL;
958
+ CREATE INDEX IF NOT EXISTS idx_memories_namespace ON memories(namespace);
959
+ CREATE INDEX IF NOT EXISTS idx_memories_created_by ON memories(created_by_agent);
960
+ INSERT OR IGNORE INTO _migrations (id) VALUES (23);
961
+ `,
962
+ `
963
+ ALTER TABLE memories ADD COLUMN trust_score REAL NOT NULL DEFAULT 1.0;
964
+ CREATE INDEX IF NOT EXISTS idx_memories_trust_score ON memories(trust_score);
965
+ INSERT OR IGNORE INTO _migrations (id) VALUES (24);
966
+ `,
967
+ `
968
+ CREATE TABLE IF NOT EXISTS memory_ratings (
969
+ id TEXT PRIMARY KEY,
970
+ memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
971
+ agent_id TEXT,
972
+ useful INTEGER NOT NULL DEFAULT 1,
973
+ context TEXT,
974
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
975
+ );
976
+ CREATE INDEX IF NOT EXISTS idx_memory_ratings_memory ON memory_ratings(memory_id);
977
+ CREATE INDEX IF NOT EXISTS idx_memory_ratings_agent ON memory_ratings(agent_id);
978
+ INSERT OR IGNORE INTO _migrations (id) VALUES (25);
979
+ `,
980
+ `
981
+ CREATE TABLE IF NOT EXISTS memory_acl (
982
+ id TEXT PRIMARY KEY,
983
+ agent_id TEXT NOT NULL,
984
+ key_pattern TEXT NOT NULL,
985
+ permission TEXT NOT NULL DEFAULT 'readwrite' CHECK(permission IN ('read', 'readwrite', 'admin')),
986
+ project_id TEXT,
987
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
988
+ );
989
+ CREATE INDEX IF NOT EXISTS idx_memory_acl_agent ON memory_acl(agent_id);
990
+ CREATE INDEX IF NOT EXISTS idx_memory_acl_project ON memory_acl(project_id);
991
+ INSERT OR IGNORE INTO _migrations (id) VALUES (26);
992
+ `,
993
+ `
994
+ ALTER TABLE memories ADD COLUMN vector_clock TEXT DEFAULT '{}';
995
+ INSERT OR IGNORE INTO _migrations (id) VALUES (27);
996
+ `,
997
+ `
998
+ CREATE TABLE IF NOT EXISTS memory_subscriptions (
999
+ id TEXT PRIMARY KEY,
1000
+ agent_id TEXT NOT NULL,
1001
+ key_pattern TEXT,
1002
+ tag_pattern TEXT,
1003
+ scope TEXT,
1004
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1005
+ );
1006
+ CREATE INDEX IF NOT EXISTS idx_memory_subs_agent ON memory_subscriptions(agent_id);
1007
+ CREATE INDEX IF NOT EXISTS idx_memory_subs_key ON memory_subscriptions(key_pattern);
1008
+ INSERT OR IGNORE INTO _migrations (id) VALUES (28);
1009
+ `,
1010
+ `
1011
+ PRAGMA foreign_keys = OFF;
1012
+
1013
+ -- Fix memory_tags FK if broken
1014
+ CREATE TABLE IF NOT EXISTS _mt_fix AS SELECT * FROM memory_tags;
1015
+ DROP TABLE IF EXISTS memory_tags;
1016
+ CREATE TABLE memory_tags (
1017
+ memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
1018
+ tag TEXT NOT NULL,
1019
+ PRIMARY KEY (memory_id, tag)
1020
+ );
1021
+ INSERT OR IGNORE INTO memory_tags SELECT * FROM _mt_fix;
1022
+ DROP TABLE _mt_fix;
1023
+ CREATE INDEX IF NOT EXISTS idx_memory_tags_tag ON memory_tags(tag);
1024
+ CREATE INDEX IF NOT EXISTS idx_memory_tags_memory ON memory_tags(memory_id);
1025
+
1026
+ -- Fix entity_memories FK if broken
1027
+ CREATE TABLE IF NOT EXISTS _em_fix AS SELECT * FROM entity_memories;
1028
+ DROP TABLE IF EXISTS entity_memories;
1029
+ CREATE TABLE entity_memories (
1030
+ entity_id TEXT NOT NULL,
1031
+ memory_id TEXT NOT NULL,
1032
+ role TEXT NOT NULL DEFAULT 'context' CHECK (role IN ('subject','object','context')),
1033
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1034
+ PRIMARY KEY (entity_id, memory_id),
1035
+ FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE,
1036
+ FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
1037
+ );
1038
+ INSERT OR IGNORE INTO entity_memories SELECT * FROM _em_fix;
1039
+ DROP TABLE _em_fix;
1040
+ CREATE INDEX IF NOT EXISTS idx_entity_memories_memory ON entity_memories(memory_id);
1041
+
1042
+ -- Fix memory_embeddings FK if broken
1043
+ CREATE TABLE IF NOT EXISTS _me_fix AS SELECT * FROM memory_embeddings;
1044
+ DROP TABLE IF EXISTS memory_embeddings;
1045
+ CREATE TABLE memory_embeddings (
1046
+ memory_id TEXT PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
1047
+ embedding TEXT NOT NULL,
1048
+ model TEXT NOT NULL DEFAULT 'tfidf-512',
1049
+ dimensions INTEGER NOT NULL DEFAULT 512,
1050
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1051
+ );
1052
+ INSERT OR IGNORE INTO memory_embeddings SELECT * FROM _me_fix;
1053
+ DROP TABLE _me_fix;
1054
+ CREATE INDEX IF NOT EXISTS idx_memory_embeddings_model ON memory_embeddings(model);
1055
+
1056
+ PRAGMA foreign_keys = ON;
1057
+ INSERT OR IGNORE INTO _migrations (id) VALUES (29);
1058
+ `,
1059
+ `
1060
+ ALTER TABLE memories ADD COLUMN when_to_use TEXT DEFAULT NULL;
1061
+ CREATE INDEX IF NOT EXISTS idx_memories_when_to_use ON memories(when_to_use) WHERE when_to_use IS NOT NULL;
1062
+ ALTER TABLE memory_versions ADD COLUMN when_to_use TEXT;
1063
+ INSERT OR IGNORE INTO _migrations (id) VALUES (30);
1064
+ `,
1065
+ `
1066
+ CREATE TABLE IF NOT EXISTS tool_events (
1067
+ id TEXT PRIMARY KEY,
1068
+ tool_name TEXT NOT NULL,
1069
+ action TEXT,
1070
+ success INTEGER NOT NULL DEFAULT 1,
1071
+ error_type TEXT CHECK(error_type IS NULL OR error_type IN ('timeout', 'permission', 'not_found', 'syntax', 'rate_limit', 'other')),
1072
+ error_message TEXT,
1073
+ tokens_used INTEGER,
1074
+ latency_ms INTEGER,
1075
+ context TEXT,
1076
+ lesson TEXT,
1077
+ when_to_use TEXT,
1078
+ agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
1079
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
1080
+ session_id TEXT,
1081
+ metadata TEXT DEFAULT '{}',
1082
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1083
+ );
1084
+ CREATE INDEX IF NOT EXISTS idx_tool_events_tool_name ON tool_events(tool_name);
1085
+ CREATE INDEX IF NOT EXISTS idx_tool_events_agent ON tool_events(agent_id);
1086
+ CREATE INDEX IF NOT EXISTS idx_tool_events_project ON tool_events(project_id);
1087
+ CREATE INDEX IF NOT EXISTS idx_tool_events_success ON tool_events(success);
1088
+ CREATE INDEX IF NOT EXISTS idx_tool_events_created ON tool_events(created_at);
1089
+ INSERT OR IGNORE INTO _migrations (id) VALUES (31);
1090
+ `,
1091
+ `
1092
+ ALTER TABLE memories ADD COLUMN sequence_group TEXT DEFAULT NULL;
1093
+ ALTER TABLE memories ADD COLUMN sequence_order INTEGER DEFAULT NULL;
1094
+ CREATE INDEX IF NOT EXISTS idx_memories_sequence_group ON memories(sequence_group) WHERE sequence_group IS NOT NULL;
1095
+ INSERT OR IGNORE INTO _migrations (id) VALUES (32);
1096
+ `,
1097
+ `
1098
+ CREATE INDEX IF NOT EXISTS idx_machines_primary ON machines(is_primary);
1099
+ CREATE TRIGGER IF NOT EXISTS machines_single_primary_insert
1100
+ AFTER INSERT ON machines
1101
+ WHEN NEW.is_primary = 1
1102
+ BEGIN
1103
+ UPDATE machines
1104
+ SET is_primary = 0,
1105
+ last_seen_at = COALESCE(NEW.last_seen_at, datetime('now'))
1106
+ WHERE id != NEW.id AND is_primary = 1;
1107
+ END;
1108
+ CREATE TRIGGER IF NOT EXISTS machines_single_primary_update
1109
+ AFTER UPDATE OF is_primary ON machines
1110
+ WHEN NEW.is_primary = 1
1111
+ BEGIN
1112
+ UPDATE machines
1113
+ SET is_primary = 0,
1114
+ last_seen_at = COALESCE(NEW.last_seen_at, datetime('now'))
1115
+ WHERE id != NEW.id AND is_primary = 1;
1116
+ END;
1117
+ CREATE TRIGGER IF NOT EXISTS machines_prevent_delete_primary
1118
+ BEFORE DELETE ON machines
1119
+ WHEN OLD.is_primary = 1
1120
+ BEGIN
1121
+ SELECT RAISE(ABORT, 'Primary machine cannot be deleted');
1122
+ END;
1123
+ INSERT OR IGNORE INTO _migrations (id) VALUES (33);
1124
+ `,
1125
+ `
1126
+ CREATE TABLE IF NOT EXISTS tasks (
1127
+ id TEXT PRIMARY KEY,
1128
+ subject TEXT NOT NULL,
1129
+ description TEXT DEFAULT '',
1130
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'in_progress', 'completed', 'failed', 'cancelled')),
1131
+ priority TEXT NOT NULL DEFAULT 'medium' CHECK(priority IN ('critical', 'high', 'medium', 'low')),
1132
+ tags TEXT NOT NULL DEFAULT '[]',
1133
+ assigned_agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
1134
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
1135
+ session_id TEXT,
1136
+ parent_task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
1137
+ metadata TEXT NOT NULL DEFAULT '{}',
1138
+ progress REAL NOT NULL DEFAULT 0 CHECK(progress >= 0 AND progress <= 1),
1139
+ due_at TEXT,
1140
+ started_at TEXT,
1141
+ completed_at TEXT,
1142
+ failed_at TEXT,
1143
+ error TEXT,
1144
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1145
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
1146
+ );
1147
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
1148
+ CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
1149
+ CREATE INDEX IF NOT EXISTS idx_tasks_agent ON tasks(assigned_agent_id);
1150
+ CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project_id);
1151
+ CREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id);
1152
+ CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task_id);
1153
+
1154
+ CREATE TABLE IF NOT EXISTS task_comments (
1155
+ id TEXT PRIMARY KEY,
1156
+ task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
1157
+ agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
1158
+ body TEXT NOT NULL,
1159
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1160
+ );
1161
+ CREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments(task_id);
1162
+ CREATE INDEX IF NOT EXISTS idx_task_comments_agent ON task_comments(agent_id);
1163
+ INSERT OR IGNORE INTO _migrations (id) VALUES (34);
1164
+ `,
1165
+ `
1166
+ CREATE TABLE IF NOT EXISTS memory_links (
1167
+ id TEXT PRIMARY KEY,
1168
+ source_memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
1169
+ target_memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
1170
+ relation_type TEXT NOT NULL CHECK(relation_type IN ('summarizes','merged_from','promotes','reflects_on','supersedes','related_to')),
1171
+ run_id TEXT,
1172
+ metadata TEXT NOT NULL DEFAULT '{}',
1173
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1174
+ );
1175
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_links_unique ON memory_links(source_memory_id, target_memory_id, relation_type, COALESCE(run_id, ''));
1176
+ CREATE INDEX IF NOT EXISTS idx_memory_links_source ON memory_links(source_memory_id);
1177
+ CREATE INDEX IF NOT EXISTS idx_memory_links_target ON memory_links(target_memory_id);
1178
+ CREATE INDEX IF NOT EXISTS idx_memory_links_relation ON memory_links(relation_type);
1179
+ CREATE INDEX IF NOT EXISTS idx_memory_links_run ON memory_links(run_id);
1180
+
1181
+ CREATE TABLE IF NOT EXISTS memory_consolidation_runs (
1182
+ id TEXT PRIMARY KEY,
1183
+ scope TEXT,
1184
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
1185
+ agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
1186
+ dry_run INTEGER NOT NULL DEFAULT 1,
1187
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','running','completed','failed')),
1188
+ summary TEXT NOT NULL DEFAULT '{}',
1189
+ error TEXT,
1190
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
1191
+ completed_at TEXT
1192
+ );
1193
+ CREATE INDEX IF NOT EXISTS idx_memory_consolidation_runs_project ON memory_consolidation_runs(project_id);
1194
+ CREATE INDEX IF NOT EXISTS idx_memory_consolidation_runs_agent ON memory_consolidation_runs(agent_id);
1195
+ CREATE INDEX IF NOT EXISTS idx_memory_consolidation_runs_status ON memory_consolidation_runs(status);
1196
+ CREATE INDEX IF NOT EXISTS idx_memory_consolidation_runs_started ON memory_consolidation_runs(started_at);
1197
+
1198
+ CREATE TABLE IF NOT EXISTS memory_consolidation_actions (
1199
+ id TEXT PRIMARY KEY,
1200
+ run_id TEXT NOT NULL REFERENCES memory_consolidation_runs(id) ON DELETE CASCADE,
1201
+ action_type TEXT NOT NULL CHECK(action_type IN ('merge_duplicate','promote_semantic','summarize_cluster','decay_forget')),
1202
+ memory_ids TEXT NOT NULL DEFAULT '[]',
1203
+ target_memory_id TEXT REFERENCES memories(id) ON DELETE SET NULL,
1204
+ created_memory_id TEXT REFERENCES memories(id) ON DELETE SET NULL,
1205
+ reason TEXT NOT NULL,
1206
+ planned_changes TEXT NOT NULL DEFAULT '{}',
1207
+ applied INTEGER NOT NULL DEFAULT 0,
1208
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1209
+ );
1210
+ CREATE INDEX IF NOT EXISTS idx_memory_consolidation_actions_run ON memory_consolidation_actions(run_id);
1211
+ CREATE INDEX IF NOT EXISTS idx_memory_consolidation_actions_type ON memory_consolidation_actions(action_type);
1212
+ CREATE INDEX IF NOT EXISTS idx_memory_consolidation_actions_target ON memory_consolidation_actions(target_memory_id);
1213
+
1214
+ CREATE TABLE IF NOT EXISTS memory_reflection_runs (
1215
+ id TEXT PRIMARY KEY,
1216
+ on_type TEXT NOT NULL CHECK(on_type IN ('session','task','range')),
1217
+ source TEXT,
1218
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
1219
+ agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
1220
+ dry_run INTEGER NOT NULL DEFAULT 1,
1221
+ provider TEXT,
1222
+ model TEXT,
1223
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','running','completed','failed')),
1224
+ trajectory_memory_ids TEXT NOT NULL DEFAULT '[]',
1225
+ summary TEXT,
1226
+ error TEXT,
1227
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
1228
+ completed_at TEXT
1229
+ );
1230
+ CREATE INDEX IF NOT EXISTS idx_memory_reflection_runs_source ON memory_reflection_runs(on_type, source);
1231
+ CREATE INDEX IF NOT EXISTS idx_memory_reflection_runs_project ON memory_reflection_runs(project_id);
1232
+ CREATE INDEX IF NOT EXISTS idx_memory_reflection_runs_agent ON memory_reflection_runs(agent_id);
1233
+ CREATE INDEX IF NOT EXISTS idx_memory_reflection_runs_status ON memory_reflection_runs(status);
1234
+
1235
+ CREATE TABLE IF NOT EXISTS memory_reflection_lessons (
1236
+ id TEXT PRIMARY KEY,
1237
+ run_id TEXT NOT NULL REFERENCES memory_reflection_runs(id) ON DELETE CASCADE,
1238
+ memory_id TEXT REFERENCES memories(id) ON DELETE SET NULL,
1239
+ kind TEXT NOT NULL CHECK(kind IN ('worked','failed','do_differently')),
1240
+ lesson TEXT NOT NULL,
1241
+ evidence TEXT NOT NULL DEFAULT '[]',
1242
+ importance INTEGER NOT NULL DEFAULT 6 CHECK(importance >= 1 AND importance <= 10),
1243
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
1244
+ );
1245
+ CREATE INDEX IF NOT EXISTS idx_memory_reflection_lessons_run ON memory_reflection_lessons(run_id);
1246
+ CREATE INDEX IF NOT EXISTS idx_memory_reflection_lessons_memory ON memory_reflection_lessons(memory_id);
1247
+ CREATE INDEX IF NOT EXISTS idx_memory_reflection_lessons_kind ON memory_reflection_lessons(kind);
1248
+
1249
+ INSERT OR IGNORE INTO _migrations (id) VALUES (35);
1250
+ `,
1251
+ `
1252
+ ${MEMORY_VERSION_SNAPSHOT_TRIGGER}
1253
+ INSERT OR IGNORE INTO _migrations (id) VALUES (36);
1254
+ `,
1255
+ `
1256
+ DROP TRIGGER IF EXISTS audit_memory_insert;
1257
+ CREATE TRIGGER audit_memory_insert AFTER INSERT ON memories BEGIN
1258
+ INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, created_at)
1259
+ VALUES (hex(randomblob(4)), new.id, new.key, 'create', new.agent_id, datetime('now'));
1260
+ END;
1261
+
1262
+ DROP TRIGGER IF EXISTS audit_memory_update;
1263
+ CREATE TRIGGER audit_memory_update AFTER UPDATE ON memories BEGIN
1264
+ INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, changes, created_at)
1265
+ VALUES (hex(randomblob(4)), new.id, new.key, 'update', new.agent_id,
1266
+ json_object('version_from', old.version, 'version_to', new.version, 'importance_from', old.importance, 'importance_to', new.importance),
1267
+ datetime('now'));
1268
+ END;
1269
+
1270
+ DROP TRIGGER IF EXISTS audit_memory_delete;
1271
+ CREATE TRIGGER audit_memory_delete AFTER DELETE ON memories BEGIN
1272
+ INSERT INTO memory_audit_log (id, memory_id, memory_key, operation, agent_id, created_at)
1273
+ VALUES (hex(randomblob(4)), old.id, old.key, 'delete', old.agent_id, datetime('now'));
1274
+ END;
1275
+
1276
+ UPDATE memory_audit_log SET old_value_hash = NULL
1277
+ WHERE old_value_hash IS NOT NULL
1278
+ AND length(old_value_hash) = 32
1279
+ AND old_value_hash NOT GLOB '*[^0-9A-F]*';
1280
+
1281
+ UPDATE memory_audit_log SET new_value_hash = NULL
1282
+ WHERE new_value_hash IS NOT NULL
1283
+ AND length(new_value_hash) = 32
1284
+ AND new_value_hash NOT GLOB '*[^0-9A-F]*';
1285
+
1286
+ INSERT OR IGNORE INTO _migrations (id) VALUES (37);
1287
+ `,
1288
+ `
1289
+ ${sqliteMementosProjectRegistrationSchemaSql()}
1290
+ INSERT OR IGNORE INTO _migrations (id) VALUES (38);
1291
+ `,
1292
+ `
1293
+ ${sqliteMementosProjectGuardedUpdateSchemaSql()}
1294
+ INSERT OR IGNORE INTO _migrations (id) VALUES (39);
1295
+ `,
1296
+ `
1297
+ ${sqliteMementosMemoryProjectLinkSchemaSql()}
1298
+ INSERT OR IGNORE INTO _migrations (id) VALUES (40);
1299
+ `
1300
+ ];
1301
+
1302
+ // src/db/database.ts
1303
+ var ALLOWED_TABLES = new Set([
1304
+ "memories",
1305
+ "agents",
1306
+ "entities",
1307
+ "projects",
1308
+ "relations",
1309
+ "memory_audit_log",
1310
+ "locks",
1311
+ "sessions",
1312
+ "session_memory_jobs",
1313
+ "synthesis_runs",
1314
+ "synthesis_proposals",
1315
+ "tool_events",
1316
+ "webhook_hooks"
1317
+ ]);
1318
+
1319
+ // src/project-registration/historical-receipt.ts
1320
+ var ROOT_TENANT_ID = "adfd95c7-ee8b-52cb-ae47-4ae65dae3313";
1321
+ var FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION = {
1322
+ authority: "mementos",
1323
+ authority_route: "mementos.project-registration.v1",
1324
+ package_version: "1.0.0-rc.3",
1325
+ authority_id: "mementos",
1326
+ tenant_id: "default",
1327
+ corpus_id: "default",
1328
+ operation_id: "op-cli-register-full",
1329
+ step_id: "mementos_project",
1330
+ resource_kind: "project",
1331
+ direction: "forward",
1332
+ target_selector: "wks_005285827590a93b70e5",
1333
+ target_id: "mm_project_f75606ef14e51fb577a15882ba0ab8ed333b2c29",
1334
+ idempotency_key: "prk_786cdc6babddeae1be8d5078d0f75322d1ea1010ac5d8a50",
1335
+ receipt_id: "mmpr_a647f9908a33bb64bf137f584202590f91b72a33",
1336
+ request_digest: "8eb0e3fee26fdb7edb8a444aeddaaedb79a84a5a36e742ae03616f62b19f5c22",
1337
+ precondition_digest: "0b6c22fcf75d835f9f27308b1a9d40e8902e48a97da370fd56df5acacfcd968e",
1338
+ result_revision: "2026-08-09T13:37:43.068Z",
1339
+ result_digest: "26fb0dd5c5e20909d3653ab0ddd8fa45e5142728e4e66d595e2419f7ed40b036",
1340
+ created_at: "2026-08-09T13:37:43.068Z",
1341
+ identity_digest: "1108dd972c90e4989036fb6ecec7dde10f54641a697bd53cfcd64017f589ace7",
1342
+ target_digest: "72789fcd60668eeb66ee068936d3afa96deadcb007228830c64b6de783b6dcef",
1343
+ operation_digest: "d7e37bfb2c0d28f8c4086c53224cd855d14694f32807b74c102b61a99963f3c0",
1344
+ idempotency_digest: "b56d8ec37efb818759cdd2ee3b58bc1401ac13384c8d64c1813bb5c198b1e376",
1345
+ receipt_digest: "52ce90c5de6817989f107d9d27e06156962a2b9c365e34e16a57aa37886501a2",
1346
+ response_digest: "2fa3ed508f5f192c18753b1205ced996340fdf02f735420d32db897fc239805c"
1347
+ };
1348
+ var FLEET_RESOURCES_HISTORICAL_RECEIPT = {
1349
+ receipt_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.receipt_id,
1350
+ authority: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.authority,
1351
+ route: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.authority_route,
1352
+ package_version: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.package_version,
1353
+ authority_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.authority_id,
1354
+ tenant_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.tenant_id,
1355
+ corpus_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.corpus_id,
1356
+ operation_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.operation_id,
1357
+ step_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.step_id,
1358
+ resource_kind: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.resource_kind,
1359
+ direction: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.direction,
1360
+ idempotency_key: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.idempotency_key,
1361
+ request_digest: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.request_digest,
1362
+ precondition_digest: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.precondition_digest,
1363
+ outcome: "accepted",
1364
+ reason: null,
1365
+ target_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.target_id,
1366
+ result_revision: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.result_revision,
1367
+ result_digest: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.result_digest,
1368
+ duplicate_of_receipt_id: null,
1369
+ accepted_receipt_id: null,
1370
+ created_by_operation: true,
1371
+ created_at: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.created_at
1372
+ };
1373
+ var FLEET_RESOURCES_HISTORICAL_RESPONSE_CONTROL = {
1374
+ response_byte_limit: 131072,
1375
+ time_budget_ms: 1e4,
1376
+ response_bytes: 1101,
1377
+ elapsed_ms: 2,
1378
+ complete: true,
1379
+ truncated: false
1380
+ };
1381
+ var FLEET_RESOURCES_CURRENT_PROJECT_REGISTRATION_IDENTITY = {
1382
+ authority_id: "mementos",
1383
+ tenant_id: ROOT_TENANT_ID,
1384
+ corpus_id: "mementos:postgresql"
1385
+ };
1386
+ var FLEET_RESOURCES_HISTORICAL_LOOKUP_IDENTITY = {
1387
+ source: {
1388
+ authority: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.authority,
1389
+ authority_route: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.authority_route,
1390
+ package_version: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.package_version,
1391
+ authority_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.authority_id,
1392
+ tenant_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.tenant_id,
1393
+ corpus_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.corpus_id,
1394
+ operation_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.operation_id,
1395
+ step_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.step_id,
1396
+ resource_kind: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.resource_kind,
1397
+ direction: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.direction,
1398
+ target_selector: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.target_selector,
1399
+ target_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.target_id,
1400
+ idempotency_key: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.idempotency_key,
1401
+ receipt_id: FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.receipt_id
1402
+ },
1403
+ destination: FLEET_RESOURCES_CURRENT_PROJECT_REGISTRATION_IDENTITY,
1404
+ lookup_only: true,
1405
+ immutable_receipt: true
1406
+ };
1407
+
1408
+ // src/project-registration/authority.ts
1409
+ function canonicalMementosProjectRegistrationJson(value) {
1410
+ return JSON.stringify(canonicalize(value));
1411
+ }
1412
+ function canonicalize(value) {
1413
+ if (Array.isArray(value))
1414
+ return value.map(canonicalize);
1415
+ if (!value || typeof value !== "object")
1416
+ return value;
1417
+ const out = {};
1418
+ for (const key of Object.keys(value).sort()) {
1419
+ const item = value[key];
1420
+ if (item !== undefined)
1421
+ out[key] = canonicalize(item);
1422
+ }
1423
+ return out;
1424
+ }
1425
+ function digestMementosProjectRegistrationValue(value) {
1426
+ return createHash("sha256").update(canonicalMementosProjectRegistrationJson(value)).digest("hex");
1427
+ }
1428
+
1429
+ // src/diagnostics/historical-project-registration-receipt.ts
1430
+ var SHA256_PATTERN = /^[0-9a-f]{64}$/;
1431
+
1432
+ class HistoricalReceiptDiagnosticError extends Error {
1433
+ code;
1434
+ constructor(code) {
1435
+ super(`historical receipt diagnostic failed: ${code}`);
1436
+ this.code = code;
1437
+ this.name = "HistoricalReceiptDiagnosticError";
1438
+ }
1439
+ }
1440
+ var TABLE_FINGERPRINT_SQL = `
1441
+ SELECT
1442
+ COUNT(*)::text AS count,
1443
+ md5(COALESCE(
1444
+ string_agg(
1445
+ md5(row_to_json(receipt_row)::text),
1446
+ '' ORDER BY receipt_row.receipt_id
1447
+ ),
1448
+ ''
1449
+ )) AS digest
1450
+ FROM mementos_project_registration_receipts AS receipt_row
1451
+ `;
1452
+ var EXACT_RECEIPT_SQL = `
1453
+ SELECT
1454
+ receipt_id, authority, route, package_version, authority_id, tenant_id,
1455
+ corpus_id, operation_id, step_id, resource_kind, direction,
1456
+ target_selector, idempotency_key, request_digest, precondition_digest,
1457
+ normalized_call_digest, outcome, reason, target_id, result_revision,
1458
+ result_digest, duplicate_of_receipt_id, accepted_receipt_id,
1459
+ created_by_operation, created_at
1460
+ FROM mementos_project_registration_receipts
1461
+ WHERE authority_id = $1
1462
+ AND tenant_id = $2
1463
+ AND corpus_id = $3
1464
+ AND operation_id = $4
1465
+ AND step_id = $5
1466
+ AND resource_kind = $6
1467
+ AND direction = $7
1468
+ AND target_selector = $8
1469
+ AND idempotency_key = $9
1470
+ AND target_id = $10
1471
+ ORDER BY created_at ASC, receipt_id ASC
1472
+ LIMIT 2
1473
+ `;
1474
+ function timestamp(value) {
1475
+ if (value instanceof Date)
1476
+ return value.toISOString();
1477
+ return String(value);
1478
+ }
1479
+ function storedReceipt(row) {
1480
+ return {
1481
+ ...row,
1482
+ authority: "mementos",
1483
+ resource_kind: "project",
1484
+ direction: String(row["direction"]),
1485
+ outcome: String(row["outcome"]),
1486
+ created_by_operation: row["created_by_operation"] === true || Number(row["created_by_operation"]) === 1,
1487
+ created_at: timestamp(row["created_at"])
1488
+ };
1489
+ }
1490
+ function publicReceipt(row) {
1491
+ const {
1492
+ target_selector: _targetSelector,
1493
+ normalized_call_digest: _normalizedCallDigest,
1494
+ ...receipt
1495
+ } = row;
1496
+ return receipt;
1497
+ }
1498
+ function assertEqual(actual, wanted, code) {
1499
+ if (actual !== wanted)
1500
+ throw new HistoricalReceiptDiagnosticError(code);
1501
+ }
1502
+ function assertDigest(actual, wanted, code) {
1503
+ if (!SHA256_PATTERN.test(actual) || actual !== wanted) {
1504
+ throw new HistoricalReceiptDiagnosticError(code);
1505
+ }
1506
+ }
1507
+ async function tableFingerprint(query) {
1508
+ const result = await query.query(TABLE_FINGERPRINT_SQL);
1509
+ if (result.rows.length !== 1) {
1510
+ throw new HistoricalReceiptDiagnosticError("table_fingerprint_unavailable");
1511
+ }
1512
+ const fingerprint = result.rows[0];
1513
+ if (!/^\d+$/.test(fingerprint.count) || !/^[0-9a-f]{32}$/.test(fingerprint.digest)) {
1514
+ throw new HistoricalReceiptDiagnosticError("table_fingerprint_invalid");
1515
+ }
1516
+ return fingerprint;
1517
+ }
1518
+ async function diagnoseHistoricalProjectRegistrationReceipt(query) {
1519
+ await query.query("BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY");
1520
+ try {
1521
+ const readOnly = await query.query("SHOW transaction_read_only");
1522
+ assertEqual(readOnly.rows[0]?.transaction_read_only, "on", "transaction_not_read_only");
1523
+ const before = await tableFingerprint(query);
1524
+ const exact = await query.query(EXACT_RECEIPT_SQL, [
1525
+ FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.authority_id,
1526
+ FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.tenant_id,
1527
+ FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.corpus_id,
1528
+ FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.operation_id,
1529
+ FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.step_id,
1530
+ FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.resource_kind,
1531
+ FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.direction,
1532
+ FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.target_selector,
1533
+ FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.idempotency_key,
1534
+ FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.target_id
1535
+ ]);
1536
+ assertEqual(exact.rows.length, 1, "exact_receipt_count_not_one");
1537
+ const stored = storedReceipt(exact.rows[0]);
1538
+ if (!SHA256_PATTERN.test(stored.normalized_call_digest)) {
1539
+ throw new HistoricalReceiptDiagnosticError("normalized_call_digest_invalid");
1540
+ }
1541
+ assertEqual(stored.target_selector, FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.target_selector, "target_selector_mismatch");
1542
+ const receipt = publicReceipt(stored);
1543
+ assertEqual(canonicalMementosProjectRegistrationJson(receipt), canonicalMementosProjectRegistrationJson(FLEET_RESOURCES_HISTORICAL_RECEIPT), "receipt_content_mismatch");
1544
+ const identityDigest = digestMementosProjectRegistrationValue({
1545
+ authority_id: receipt.authority_id,
1546
+ tenant_id: receipt.tenant_id,
1547
+ corpus_id: receipt.corpus_id
1548
+ });
1549
+ const targetDigest = digestMementosProjectRegistrationValue({
1550
+ authority_id: receipt.authority_id,
1551
+ tenant_id: receipt.tenant_id,
1552
+ corpus_id: receipt.corpus_id,
1553
+ target_selector: stored.target_selector,
1554
+ target_id: receipt.target_id
1555
+ });
1556
+ const operationDigest = digestMementosProjectRegistrationValue({
1557
+ operation_id: receipt.operation_id,
1558
+ step_id: receipt.step_id,
1559
+ direction: receipt.direction
1560
+ });
1561
+ const idempotencyDigest = digestMementosProjectRegistrationValue({
1562
+ idempotency_key: receipt.idempotency_key
1563
+ });
1564
+ const receiptDigest = digestMementosProjectRegistrationValue(receipt);
1565
+ const responseDigest = digestMementosProjectRegistrationValue({
1566
+ receipt,
1567
+ response_control: FLEET_RESOURCES_HISTORICAL_RESPONSE_CONTROL
1568
+ });
1569
+ assertDigest(identityDigest, FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.identity_digest, "identity_digest_mismatch");
1570
+ assertDigest(targetDigest, FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.target_digest, "target_digest_mismatch");
1571
+ assertDigest(operationDigest, FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.operation_digest, "operation_digest_mismatch");
1572
+ assertDigest(idempotencyDigest, FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.idempotency_digest, "idempotency_digest_mismatch");
1573
+ assertDigest(receipt.result_digest, FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.result_digest, "result_digest_mismatch");
1574
+ assertDigest(receiptDigest, FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.receipt_digest, "receipt_digest_mismatch");
1575
+ assertDigest(responseDigest, FLEET_RESOURCES_HISTORICAL_PROJECT_REGISTRATION.response_digest, "response_digest_mismatch");
1576
+ const after = await tableFingerprint(query);
1577
+ assertEqual(after.count, before.count, "table_count_changed");
1578
+ assertEqual(after.digest, before.digest, "table_digest_changed");
1579
+ return {
1580
+ schema: "mementos.historical-project-registration-receipt-diagnostic.v1",
1581
+ status: "PASS",
1582
+ transaction_read_only: true,
1583
+ exact_receipt_count: 1,
1584
+ table_count_before: before.count,
1585
+ table_count_after: after.count,
1586
+ table_digest_before: before.digest,
1587
+ table_digest_after: after.digest,
1588
+ table_count_unchanged: true,
1589
+ table_digest_unchanged: true,
1590
+ authority_id: receipt.authority_id,
1591
+ tenant_id: receipt.tenant_id,
1592
+ corpus_id: receipt.corpus_id,
1593
+ operation_id: receipt.operation_id,
1594
+ step_id: receipt.step_id,
1595
+ target_selector: stored.target_selector,
1596
+ target_id: receipt.target_id,
1597
+ receipt_id: receipt.receipt_id,
1598
+ identity_digest: identityDigest,
1599
+ target_digest: targetDigest,
1600
+ operation_digest: operationDigest,
1601
+ idempotency_digest: idempotencyDigest,
1602
+ result_digest: receipt.result_digest,
1603
+ receipt_digest: receiptDigest,
1604
+ response_digest: responseDigest
1605
+ };
1606
+ } finally {
1607
+ await query.query("ROLLBACK");
1608
+ }
1609
+ }
1610
+ async function main() {
1611
+ const connectionString = process.env["HASNA_MEMENTOS_DATABASE_URL"]?.trim() || process.env["DATABASE_URL"]?.trim();
1612
+ if (!connectionString) {
1613
+ throw new HistoricalReceiptDiagnosticError("database_url_unavailable");
1614
+ }
1615
+ const parsed = new URL(connectionString);
1616
+ const sslMode = parsed.searchParams.get("sslmode")?.trim().toLowerCase();
1617
+ const sslEnabled = ["require", "verify-ca", "verify-full"].includes(sslMode ?? "") || ["1", "true", "yes", "on"].includes(parsed.searchParams.get("ssl")?.trim().toLowerCase() ?? "");
1618
+ parsed.searchParams.delete("ssl");
1619
+ parsed.searchParams.delete("sslmode");
1620
+ const pool = new pg2.Pool({
1621
+ connectionString: parsed.toString(),
1622
+ ssl: sslEnabled ? { rejectUnauthorized: sslMode === "verify-ca" || sslMode === "verify-full" } : undefined,
1623
+ max: 1
1624
+ });
1625
+ const client = await pool.connect();
1626
+ try {
1627
+ const report = await diagnoseHistoricalProjectRegistrationReceipt({
1628
+ async query(sql, values) {
1629
+ const result = await client.query(sql, values);
1630
+ return {
1631
+ rows: result.rows,
1632
+ rowCount: result.rowCount
1633
+ };
1634
+ }
1635
+ });
1636
+ process.stdout.write(`${JSON.stringify(report)}
1637
+ `);
1638
+ } finally {
1639
+ client.release();
1640
+ await pool.end();
1641
+ }
1642
+ }
1643
+ var invokedPath = process.argv[1] ? pathToFileURL(process.argv[1]).href : "";
1644
+ var invokedByBunEval = import.meta.url.endsWith("/%5Beval%5D");
1645
+ if (import.meta.url === invokedPath || invokedByBunEval) {
1646
+ main().catch((error) => {
1647
+ const code = error instanceof HistoricalReceiptDiagnosticError ? error.code : "unexpected_failure";
1648
+ process.stderr.write(`historical receipt diagnostic failed: ${code}
1649
+ `);
1650
+ process.exitCode = 1;
1651
+ });
1652
+ }
1653
+ export {
1654
+ diagnoseHistoricalProjectRegistrationReceipt,
1655
+ HistoricalReceiptDiagnosticError
1656
+ };