@terminals-tech/sdk 1.0.0-rc.1 → 1.0.0-rc.2

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 (79) hide show
  1. package/dist/WebContainerManager-SHXC5VKI.js +22 -0
  2. package/dist/browser-http-client-ZQLDWZMU.js +317 -0
  3. package/dist/cache-VKYSQRXX.js +45 -0
  4. package/dist/capabilities-MIPUMBLL.js +96 -0
  5. package/dist/chunk-2ESYSVXG.js +48 -0
  6. package/dist/chunk-2WTYE4SW.js +190 -0
  7. package/dist/chunk-3CEM77QZ.js +474 -0
  8. package/dist/chunk-3LFMIVJM.js +40 -0
  9. package/dist/chunk-ABCK4FWN.js +136 -0
  10. package/dist/chunk-AFDUOYHD.js +2060 -0
  11. package/dist/chunk-BCOQMFKT.js +265 -0
  12. package/dist/chunk-BKB3MD5Y.js +723 -0
  13. package/dist/chunk-BYXBJQAS.js +0 -0
  14. package/dist/chunk-DKFJIILR.js +9798 -0
  15. package/dist/chunk-EXI3LJVJ.js +51 -0
  16. package/dist/chunk-FOXUEYWK.js +42 -0
  17. package/dist/chunk-GJWAJAX3.js +173 -0
  18. package/dist/chunk-H3POJCFA.js +333 -0
  19. package/dist/chunk-KHR7ZYCX.js +4034 -0
  20. package/dist/chunk-NTMBOESX.js +152 -0
  21. package/dist/chunk-OSSRZOGC.js +190 -0
  22. package/dist/chunk-PUZ2S62E.js +977 -0
  23. package/dist/chunk-PWAHFID5.js +381 -0
  24. package/dist/chunk-Q2VI6ICE.js +188 -0
  25. package/dist/chunk-Q4B7YS7T.js +557 -0
  26. package/dist/chunk-QJFKEQHF.js +6460 -0
  27. package/dist/chunk-QWXPVB2L.js +320 -0
  28. package/dist/chunk-QWZRZKLZ.js +896 -0
  29. package/dist/chunk-SHPGIVDH.js +5521 -0
  30. package/dist/chunk-TSQ3BGLA.js +11945 -0
  31. package/dist/chunk-UJDUQNE2.js +79 -0
  32. package/dist/chunk-UKVUPKZP.js +296 -0
  33. package/dist/chunk-VZA2NUH3.js +118 -0
  34. package/dist/chunk-WGBCRNMB.js +1817 -0
  35. package/dist/chunk-WU4OTGJE.js +752 -0
  36. package/dist/chunk-XPJ63Y6T.js +70 -0
  37. package/dist/chunk-Y2EULKA2.js +172 -0
  38. package/dist/chunk-YJEZWCYV.js +94 -0
  39. package/dist/chunk-ZVO47SQV.js +150 -0
  40. package/dist/container-lite-KQX3NMPY.js +327 -0
  41. package/dist/core-H2UUDATO.js +146 -0
  42. package/dist/crypto-D4LMI2RN.js +45 -0
  43. package/dist/db-BWC2GGBN.js +50 -0
  44. package/dist/demo-VXMGMJNK.js +87 -0
  45. package/dist/diagnostics-6RQTBR6I.js +113 -0
  46. package/dist/dist-OPDCWARF.js +727 -0
  47. package/dist/dist-VXJEKX3T.js +2441 -0
  48. package/dist/dist-VYGJXGUS.js +1008 -0
  49. package/dist/embeddings-7QXTXUMC.js +15 -0
  50. package/dist/embeddings-MAEWWUHW.js +9 -0
  51. package/dist/graph-RKMNE2X5.js +36 -0
  52. package/dist/hvm-DRQK2MUT.js +126 -0
  53. package/dist/index.cjs +11706 -4004
  54. package/dist/index.d.cts +1299 -1339
  55. package/dist/index.d.ts +1299 -1339
  56. package/dist/index.js +189 -7938
  57. package/dist/mcp-NK34ZNM5.js +101 -0
  58. package/dist/mcp-client-service-browser-SGB2K3VZ.js +14 -0
  59. package/dist/neuro-state-XHRGIRVO.js +498 -0
  60. package/dist/nodes-DXKYDTVO.js +224 -0
  61. package/dist/package-EXUIU2RL.js +93 -0
  62. package/dist/package-VGL7HYTO.js +106 -0
  63. package/dist/package-XHMLOAQ4.js +98 -0
  64. package/dist/pg-events-QJAM2HIP.js +15 -0
  65. package/dist/pgliteService-IUGNNOVU.js +258 -0
  66. package/dist/policy-IRJCM6FS.js +13 -0
  67. package/dist/registry-BL3TDQDB.js +26 -0
  68. package/dist/registry-FW63E7FE.js +16 -0
  69. package/dist/registry-ZQ2IBLF6.js +9 -0
  70. package/dist/scheduler-H6Q53IMI.js +122 -0
  71. package/dist/secret-store-H7273UIT.js +18 -0
  72. package/dist/server-7DM74VFW.js +18 -0
  73. package/dist/skills-KLTTT2RM.js +6375 -0
  74. package/dist/stack-CHDAFU2S.js +103 -0
  75. package/dist/storage-L7MWNSPG.js +13 -0
  76. package/dist/supabaseService-6AYP2VY3.js +476 -0
  77. package/dist/topology-CIWWNVAN.js +13 -0
  78. package/dist/webcontainer-3LDJVIIL.js +281 -0
  79. package/package.json +2 -2
@@ -0,0 +1,2060 @@
1
+ import {
2
+ createCircuitBreaker,
3
+ withRetry
4
+ } from "./chunk-NTMBOESX.js";
5
+
6
+ // ../../lib/pglite/db.ts
7
+ import { PGliteWorker } from "@electric-sql/pglite/worker";
8
+ import { live } from "@electric-sql/pglite/live";
9
+
10
+ // ../../lib/biosignal/domain-taxonomy.ts
11
+ var SIGNAL_DOMAIN_ORDER = [
12
+ "neural",
13
+ "physical",
14
+ "digital",
15
+ "environmental",
16
+ "social",
17
+ "derived"
18
+ ];
19
+ function classifySignalFamilyDomains(families) {
20
+ const domains = /* @__PURE__ */ new Set();
21
+ for (const family of families) {
22
+ switch (family) {
23
+ case "eeg":
24
+ case "eog":
25
+ case "emg":
26
+ domains.add("neural");
27
+ break;
28
+ case "ppg":
29
+ case "eda":
30
+ case "imu":
31
+ domains.add("physical");
32
+ break;
33
+ }
34
+ }
35
+ return SIGNAL_DOMAIN_ORDER.filter((domain) => domains.has(domain));
36
+ }
37
+
38
+ // ../../lib/biosignal/bridge-catalog.ts
39
+ var DORMANT_BIOSIGNAL_BRIDGES = [
40
+ {
41
+ id: "muse-ble",
42
+ label: "Muse BLE ingest",
43
+ transport: "ble",
44
+ signalFamilies: ["eeg", "ppg", "imu"],
45
+ domains: classifySignalFamilyDomains(["eeg", "ppg", "imu"]),
46
+ status: "dormant",
47
+ enabled: false,
48
+ config: {
49
+ transportHint: "web-bluetooth-or-native",
50
+ notes: "Scaffold only. Not wired to acquisition runtime."
51
+ }
52
+ },
53
+ {
54
+ id: "openbci-ganglion",
55
+ label: "OpenBCI Ganglion ingest",
56
+ transport: "ble",
57
+ signalFamilies: ["eeg"],
58
+ domains: classifySignalFamilyDomains(["eeg"]),
59
+ status: "dormant",
60
+ enabled: false,
61
+ config: {
62
+ transportHint: "ble",
63
+ notes: "Scaffold only. Not wired to acquisition runtime."
64
+ }
65
+ },
66
+ {
67
+ id: "openbci-cyton-serial",
68
+ label: "OpenBCI Cyton serial ingest",
69
+ transport: "serial",
70
+ signalFamilies: ["eeg", "emg"],
71
+ domains: classifySignalFamilyDomains(["eeg", "emg"]),
72
+ status: "dormant",
73
+ enabled: false,
74
+ config: {
75
+ transportHint: "usb-serial",
76
+ notes: "Scaffold only. Not wired to acquisition runtime."
77
+ }
78
+ },
79
+ {
80
+ id: "openbci-cyton-wifi",
81
+ label: "OpenBCI Cyton WiFi ingest",
82
+ transport: "tcp",
83
+ signalFamilies: ["eeg", "emg"],
84
+ domains: classifySignalFamilyDomains(["eeg", "emg"]),
85
+ status: "dormant",
86
+ enabled: false,
87
+ config: {
88
+ transportHint: "wifi-or-udp",
89
+ notes: "Scaffold only. Not wired to acquisition runtime."
90
+ }
91
+ },
92
+ {
93
+ id: "brainflow-gateway",
94
+ label: "BrainFlow gateway ingest",
95
+ transport: "process",
96
+ signalFamilies: ["eeg", "ppg", "eda", "emg", "imu"],
97
+ domains: classifySignalFamilyDomains(["eeg", "ppg", "eda", "emg", "imu"]),
98
+ status: "dormant",
99
+ enabled: false,
100
+ config: {
101
+ transportHint: "native-sidecar",
102
+ notes: "Scaffold only. Not wired to acquisition runtime."
103
+ }
104
+ },
105
+ {
106
+ id: "lsl-ingest",
107
+ label: "LSL ingest",
108
+ transport: "tcp",
109
+ signalFamilies: ["eeg", "ppg", "eda", "emg", "imu"],
110
+ domains: classifySignalFamilyDomains(["eeg", "ppg", "eda", "emg", "imu"]),
111
+ status: "dormant",
112
+ enabled: false,
113
+ config: {
114
+ transportHint: "lab-streaming-layer-bridge",
115
+ notes: "Scaffold only. Not wired to acquisition runtime."
116
+ }
117
+ }
118
+ ];
119
+ var BIOSIGNAL_BRIDGE_ID_SET = new Set(
120
+ DORMANT_BIOSIGNAL_BRIDGES.map((bridge) => bridge.id)
121
+ );
122
+ function isBiosignalBridgeId(value) {
123
+ return BIOSIGNAL_BRIDGE_ID_SET.has(value);
124
+ }
125
+
126
+ // ../../lib/biosignal/provider-catalog.ts
127
+ var DORMANT_BIOSIGNAL_PROVIDERS = [
128
+ {
129
+ id: "apple-healthkit",
130
+ label: "Apple HealthKit",
131
+ providerType: "platform",
132
+ acquisitionMode: "hybrid",
133
+ domains: ["physical", "digital"],
134
+ signalFamilies: ["ppg", "imu"],
135
+ metrics: ["heart_rate", "hrv", "sleep", "steps", "workouts", "respiration"],
136
+ status: "dormant",
137
+ enabled: false,
138
+ notes: "Best-in-class Apple ecosystem physical health substrate; keep dormant until entitlement/runtime path exists."
139
+ },
140
+ {
141
+ id: "google-health-connect",
142
+ label: "Google Health Connect",
143
+ providerType: "platform",
144
+ acquisitionMode: "hybrid",
145
+ domains: ["physical", "digital"],
146
+ signalFamilies: ["ppg", "imu"],
147
+ metrics: ["heart_rate", "hrv", "sleep", "steps", "exercise", "oxygen_saturation"],
148
+ status: "dormant",
149
+ enabled: false,
150
+ notes: "Android-first physical health substrate; preferred over legacy Google Fit paths."
151
+ },
152
+ {
153
+ id: "oura-cloud",
154
+ label: "Oura Cloud",
155
+ providerType: "wearable",
156
+ acquisitionMode: "cloud-sync",
157
+ domains: ["physical", "derived"],
158
+ signalFamilies: ["ppg", "imu"],
159
+ metrics: ["heart_rate", "hrv", "sleep_stages", "temperature", "readiness"],
160
+ status: "dormant",
161
+ enabled: false,
162
+ notes: "High-signal recovery and sleep feed for longitudinal physical baselines."
163
+ },
164
+ {
165
+ id: "whoop-cloud",
166
+ label: "WHOOP",
167
+ providerType: "wearable",
168
+ acquisitionMode: "cloud-sync",
169
+ domains: ["physical", "derived"],
170
+ signalFamilies: ["ppg", "imu"],
171
+ metrics: ["heart_rate", "hrv", "sleep", "strain", "recovery", "respiration"],
172
+ status: "dormant",
173
+ enabled: false,
174
+ notes: "High-value performance/recovery stream for physical state modeling."
175
+ },
176
+ {
177
+ id: "garmin-health",
178
+ label: "Garmin Health",
179
+ providerType: "wearable",
180
+ acquisitionMode: "cloud-sync",
181
+ domains: ["physical", "digital"],
182
+ signalFamilies: ["ppg", "imu"],
183
+ metrics: ["heart_rate", "hrv", "sleep", "body_battery", "stress", "activity"],
184
+ status: "dormant",
185
+ enabled: false,
186
+ notes: "Strong all-day physical telemetry with durable longitudinal fitness coverage."
187
+ },
188
+ {
189
+ id: "polar-flow",
190
+ label: "Polar Flow",
191
+ providerType: "wearable",
192
+ acquisitionMode: "cloud-sync",
193
+ domains: ["physical"],
194
+ signalFamilies: ["ppg"],
195
+ metrics: ["heart_rate", "rr_intervals", "training_load", "sleep"],
196
+ status: "dormant",
197
+ enabled: false,
198
+ notes: "Useful when RR intervals and HR precision matter more than general wellness UX."
199
+ },
200
+ {
201
+ id: "withings",
202
+ label: "Withings",
203
+ providerType: "wearable",
204
+ acquisitionMode: "cloud-sync",
205
+ domains: ["physical"],
206
+ signalFamilies: ["ppg", "imu"],
207
+ metrics: ["heart_rate", "sleep", "weight", "blood_pressure", "spo2"],
208
+ status: "dormant",
209
+ enabled: false,
210
+ notes: "Useful for home-health physical context and longer-horizon body-state tracking."
211
+ },
212
+ {
213
+ id: "muse-live",
214
+ label: "Muse Live",
215
+ providerType: "neural",
216
+ acquisitionMode: "local",
217
+ domains: ["neural", "physical"],
218
+ signalFamilies: ["eeg", "ppg", "imu"],
219
+ metrics: ["raw_eeg", "band_power", "faa", "heart_rate", "hrv"],
220
+ status: "dormant",
221
+ enabled: false,
222
+ notes: "Primary neural consumer-grade live interface when direct headset support lands."
223
+ },
224
+ {
225
+ id: "openbci-live",
226
+ label: "OpenBCI Live",
227
+ providerType: "neural",
228
+ acquisitionMode: "local",
229
+ domains: ["neural"],
230
+ signalFamilies: ["eeg", "emg", "eog"],
231
+ metrics: ["raw_eeg", "multi_channel_bands", "event_markers"],
232
+ status: "dormant",
233
+ enabled: false,
234
+ notes: "Research-grade neural path for richer channel counts and protocol flexibility."
235
+ },
236
+ {
237
+ id: "brainflow-gateway",
238
+ label: "BrainFlow Gateway",
239
+ providerType: "gateway",
240
+ acquisitionMode: "hybrid",
241
+ domains: ["neural", "physical", "digital"],
242
+ signalFamilies: ["eeg", "ppg", "eda", "emg", "imu"],
243
+ metrics: ["board_stream", "markers", "derived_bands"],
244
+ status: "dormant",
245
+ enabled: false,
246
+ notes: "Fastest route to broad board coverage with one adapter surface."
247
+ },
248
+ {
249
+ id: "lsl-lab",
250
+ label: "LSL Lab Feed",
251
+ providerType: "gateway",
252
+ acquisitionMode: "local",
253
+ domains: ["neural", "physical", "digital", "social"],
254
+ signalFamilies: ["eeg", "ppg", "eda", "emg", "imu"],
255
+ metrics: ["stream_sync", "markers", "cross_device_alignment"],
256
+ status: "dormant",
257
+ enabled: false,
258
+ notes: "Best lab interop surface for cross-device synchronization and experiment markers."
259
+ }
260
+ ];
261
+ var BIOSIGNAL_PROVIDER_ID_SET = new Set(
262
+ DORMANT_BIOSIGNAL_PROVIDERS.map((provider) => provider.id)
263
+ );
264
+ function isBiosignalProviderId(value) {
265
+ return BIOSIGNAL_PROVIDER_ID_SET.has(value);
266
+ }
267
+
268
+ // ../../lib/pglite/schema.ts
269
+ var LATEST_SCHEMA_VERSION = "1.18";
270
+ var USER_PROFILES_TABLE_SQL = `
271
+ CREATE TABLE IF NOT EXISTS user_profiles (
272
+ user_id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
273
+ username VARCHAR(255),
274
+ avatar_url TEXT,
275
+ xverse_ord_address VARCHAR(255) UNIQUE,
276
+ shape_hash TEXT,
277
+ api_keys JSONB DEFAULT '{}',
278
+ preferences JSONB DEFAULT '{"unlocked_reward_ids": []}',
279
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
280
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
281
+ );
282
+ `;
283
+ function versionLt(a, b) {
284
+ const pa = a.split(".").map(Number);
285
+ const pb = b.split(".").map(Number);
286
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
287
+ const na = pa[i] ?? 0;
288
+ const nb = pb[i] ?? 0;
289
+ if (na < nb) return true;
290
+ if (na > nb) return false;
291
+ }
292
+ return false;
293
+ }
294
+ async function initializeSchema(db, onProgress) {
295
+ try {
296
+ await db.transaction(async (tx) => {
297
+ console.log("Initializing database schema...");
298
+ onProgress?.({ step: "connect", label: "Connecting", status: "active" });
299
+ await tx.query("SELECT 1 as test");
300
+ console.log("Database connectivity confirmed");
301
+ onProgress?.({ step: "connect", label: "Connected", status: "done" });
302
+ await tx.query(`CREATE TABLE IF NOT EXISTS schema_version (version TEXT PRIMARY KEY);`);
303
+ const versionResult = await tx.query(
304
+ `SELECT version FROM schema_version`
305
+ );
306
+ let currentVersion = versionResult.rows[0]?.version || "0";
307
+ console.log(`Current DB schema version: ${currentVersion}`);
308
+ if (!versionLt(currentVersion, LATEST_SCHEMA_VERSION)) {
309
+ console.log(
310
+ `Schema already at latest version (${LATEST_SCHEMA_VERSION}) \u2014 skipping initialization`
311
+ );
312
+ onProgress?.({ step: "done", label: "Ready", status: "done" });
313
+ return;
314
+ }
315
+ if (currentVersion === "0") {
316
+ const extensions = ["uuid-ossp", "pg_trgm", "vector", "ltree", "seg", "tcn", "tablefunc"];
317
+ for (const ext of extensions) {
318
+ try {
319
+ await tx.query(`CREATE EXTENSION IF NOT EXISTS "${ext}";`);
320
+ } catch (extErr) {
321
+ console.warn(`[schema] Extension "${ext}" not available:`, extErr);
322
+ }
323
+ }
324
+ }
325
+ await tx.query(USER_PROFILES_TABLE_SQL);
326
+ if (versionLt(currentVersion, "1.1")) {
327
+ onProgress?.({ step: "v1.1", label: "v1.1 Core Tables", status: "active" });
328
+ console.log("Migrating schema to version 1.1...");
329
+ await tx.query(`
330
+ CREATE TABLE IF NOT EXISTS user_artifacts (
331
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
332
+ user_id UUID,
333
+ source_applet VARCHAR(50) NOT NULL,
334
+ artifact_type VARCHAR(50) NOT NULL,
335
+ artifact_name TEXT NOT NULL,
336
+ content TEXT,
337
+ blob_content BYTEA,
338
+ metadata JSONB,
339
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
340
+ );
341
+ `);
342
+ const columns = await tx.query(`
343
+ SELECT column_name FROM information_schema.columns
344
+ WHERE table_name = 'user_artifacts' AND column_name = 'display_type'
345
+ `);
346
+ if (columns.rows.length === 0) {
347
+ await tx.query(`ALTER TABLE user_artifacts ADD COLUMN display_type VARCHAR(50);`);
348
+ }
349
+ await tx.query(`DELETE FROM schema_version;`);
350
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.1');`);
351
+ console.log("Schema migration to 1.1 successful.");
352
+ onProgress?.({ step: "v1.1", label: "v1.1 Core Tables", status: "done" });
353
+ currentVersion = "1.1";
354
+ }
355
+ if (versionLt(currentVersion, "1.2")) {
356
+ onProgress?.({ step: "v1.2", label: "v1.2 Mesh Events", status: "active" });
357
+ console.log("Migrating schema to version 1.2 (Zero-Lite Mesh)...");
358
+ await tx.query(`
359
+ CREATE TABLE IF NOT EXISTS mesh_events (
360
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
361
+ user_id UUID,
362
+ interaction_id TEXT,
363
+ run_id TEXT,
364
+ stack_id TEXT,
365
+ prev_hash TEXT,
366
+ trace_path ltree NOT NULL,
367
+ type TEXT NOT NULL,
368
+ payload JSONB NOT NULL,
369
+ metadata JSONB,
370
+ embedding vector(384),
371
+ span JSONB,
372
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
373
+ );
374
+ `);
375
+ await tx.query(
376
+ `CREATE INDEX IF NOT EXISTS idx_mesh_events_path ON mesh_events USING GIST (trace_path);`
377
+ );
378
+ await tx.query(
379
+ `CREATE INDEX IF NOT EXISTS idx_mesh_events_interaction ON mesh_events(interaction_id);`
380
+ );
381
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_mesh_events_run ON mesh_events(run_id);`);
382
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_mesh_events_type ON mesh_events(type);`);
383
+ await tx.query(
384
+ `CREATE INDEX IF NOT EXISTS idx_mesh_events_embedding ON mesh_events USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);`
385
+ );
386
+ await tx.query(
387
+ `CREATE INDEX IF NOT EXISTS idx_mesh_events_created ON mesh_events(created_at);`
388
+ );
389
+ try {
390
+ await tx.query(
391
+ `CREATE TRIGGER mesh_events_tcn_trigger AFTER INSERT OR UPDATE OR DELETE ON mesh_events FOR EACH ROW EXECUTE FUNCTION triggered_change_notification();`
392
+ );
393
+ } catch (_e) {
394
+ console.warn("TCN trigger creation failed:", _e);
395
+ }
396
+ await tx.query(`DELETE FROM schema_version;`);
397
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.2');`);
398
+ console.log("Schema migration to 1.2 successful.");
399
+ onProgress?.({ step: "v1.2", label: "v1.2 Mesh Events", status: "done" });
400
+ currentVersion = "1.2";
401
+ }
402
+ if (versionLt(currentVersion, "1.3")) {
403
+ onProgress?.({ step: "v1.3", label: "v1.3 Ralph Loop", status: "active" });
404
+ console.log("Migrating schema to version 1.3 (Ralph Loop)...");
405
+ await tx.query(`
406
+ CREATE TABLE IF NOT EXISTS state_spans (
407
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
408
+ session_id UUID NOT NULL,
409
+ start_time BIGINT NOT NULL,
410
+ end_time BIGINT,
411
+ mode VARCHAR(20) NOT NULL CHECK (mode IN ('live', 'recorded')),
412
+ start_clock JSONB NOT NULL DEFAULT '[]',
413
+ end_clock JSONB,
414
+ start_state JSONB NOT NULL,
415
+ end_state JSONB,
416
+ resonance JSONB NOT NULL DEFAULT '{"semantic": 0, "hierarchical": 0, "phaseLock": 0}',
417
+ signature TEXT NOT NULL,
418
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
419
+ );
420
+ `);
421
+ await tx.query(`
422
+ CREATE TABLE IF NOT EXISTS span_events (
423
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
424
+ span_id UUID NOT NULL REFERENCES state_spans(id) ON DELETE CASCADE,
425
+ event_type VARCHAR(100) NOT NULL,
426
+ timestamp BIGINT NOT NULL,
427
+ payload JSONB,
428
+ source VARCHAR(255),
429
+ caused_by UUID[],
430
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
431
+ );
432
+ `);
433
+ await tx.query(`
434
+ CREATE TABLE IF NOT EXISTS ralph_worlds (
435
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
436
+ name TEXT NOT NULL,
437
+ config JSONB NOT NULL,
438
+ node_count INTEGER NOT NULL DEFAULT 1,
439
+ state_backend VARCHAR(20) NOT NULL CHECK (state_backend IN ('pglite', 'file', 'git')),
440
+ hvm_enabled BOOLEAN NOT NULL DEFAULT false,
441
+ convergence_config JSONB NOT NULL DEFAULT '{"type": "resonance", "threshold": 0.8}',
442
+ loom_config JSONB NOT NULL DEFAULT '{"topology": "linear", "choicePoints": []}',
443
+ status VARCHAR(20) NOT NULL DEFAULT 'created' CHECK (status IN ('created', 'running', 'paused', 'completed', 'failed')),
444
+ current_tick BIGINT NOT NULL DEFAULT 0,
445
+ total_ticks BIGINT,
446
+ started_at TIMESTAMPTZ,
447
+ completed_at TIMESTAMPTZ,
448
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
449
+ );
450
+ `);
451
+ await tx.query(`
452
+ CREATE TABLE IF NOT EXISTS ralph_paths (
453
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
454
+ world_id UUID NOT NULL REFERENCES ralph_worlds(id) ON DELETE CASCADE,
455
+ parent_path_id UUID REFERENCES ralph_paths(id) ON DELETE SET NULL,
456
+ branch_point TEXT,
457
+ choice_id TEXT,
458
+ pulse_id TEXT NOT NULL,
459
+ status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'completed', 'pruned', 'failed')),
460
+ tick_start BIGINT NOT NULL DEFAULT 0,
461
+ tick_end BIGINT,
462
+ resonance_final JSONB,
463
+ checkpoint_signature TEXT,
464
+ metadata JSONB DEFAULT '{}',
465
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
466
+ );
467
+ `);
468
+ await tx.query(
469
+ `CREATE INDEX IF NOT EXISTS idx_state_spans_session ON state_spans(session_id);`
470
+ );
471
+ await tx.query(
472
+ `CREATE INDEX IF NOT EXISTS idx_state_spans_signature ON state_spans(signature);`
473
+ );
474
+ await tx.query(
475
+ `CREATE INDEX IF NOT EXISTS idx_state_spans_time ON state_spans(start_time, end_time);`
476
+ );
477
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_span_events_span ON span_events(span_id);`);
478
+ await tx.query(
479
+ `CREATE INDEX IF NOT EXISTS idx_span_events_type ON span_events(event_type);`
480
+ );
481
+ await tx.query(
482
+ `CREATE INDEX IF NOT EXISTS idx_span_events_timestamp ON span_events(timestamp);`
483
+ );
484
+ await tx.query(
485
+ `CREATE INDEX IF NOT EXISTS idx_ralph_worlds_status ON ralph_worlds(status);`
486
+ );
487
+ await tx.query(
488
+ `CREATE INDEX IF NOT EXISTS idx_ralph_paths_world ON ralph_paths(world_id);`
489
+ );
490
+ await tx.query(
491
+ `CREATE INDEX IF NOT EXISTS idx_ralph_paths_parent ON ralph_paths(parent_path_id);`
492
+ );
493
+ await tx.query(
494
+ `CREATE INDEX IF NOT EXISTS idx_ralph_paths_pulse ON ralph_paths(pulse_id);`
495
+ );
496
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_ralph_paths_status ON ralph_paths(status);`);
497
+ await tx.query(`DELETE FROM schema_version;`);
498
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.3');`);
499
+ console.log("Schema migration to 1.3 (Ralph Loop) successful.");
500
+ onProgress?.({ step: "v1.3", label: "v1.3 Ralph Loop", status: "done" });
501
+ currentVersion = "1.3";
502
+ }
503
+ if (versionLt(currentVersion, "1.4")) {
504
+ onProgress?.({ step: "v1.4", label: "v1.4 Mesh Metadata", status: "active" });
505
+ console.log("Migrating schema to version 1.4 (Mesh Events Metadata)...");
506
+ const columns = await tx.query(`
507
+ SELECT column_name FROM information_schema.columns
508
+ WHERE table_name = 'mesh_events' AND column_name = 'metadata'
509
+ `);
510
+ if (columns.rows.length === 0) {
511
+ await tx.query(`ALTER TABLE mesh_events ADD COLUMN metadata JSONB;`);
512
+ }
513
+ await tx.query(`DELETE FROM schema_version;`);
514
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.4');`);
515
+ console.log("Schema migration to 1.4 (Mesh Events Metadata) successful.");
516
+ onProgress?.({ step: "v1.4", label: "v1.4 Mesh Metadata", status: "done" });
517
+ currentVersion = "1.4";
518
+ }
519
+ if (versionLt(currentVersion, "1.5")) {
520
+ onProgress?.({ step: "v1.5", label: "v1.5 Supabase Parity", status: "active" });
521
+ console.log("Migrating schema to version 1.5 (Mesh Events Supabase Parity)...");
522
+ await tx.query(`ALTER TABLE mesh_events ADD COLUMN IF NOT EXISTS interaction_id TEXT;`);
523
+ await tx.query(`ALTER TABLE mesh_events ADD COLUMN IF NOT EXISTS run_id TEXT;`);
524
+ await tx.query(`ALTER TABLE mesh_events ADD COLUMN IF NOT EXISTS stack_id TEXT;`);
525
+ await tx.query(`ALTER TABLE mesh_events ADD COLUMN IF NOT EXISTS type TEXT;`);
526
+ await tx.query(`ALTER TABLE mesh_events ADD COLUMN IF NOT EXISTS user_id UUID;`);
527
+ await tx.query(`ALTER TABLE mesh_events ADD COLUMN IF NOT EXISTS metadata JSONB;`);
528
+ await tx.query(`
529
+ UPDATE mesh_events
530
+ SET trace_path = COALESCE(trace_path, 'root.unknown'::ltree),
531
+ payload = COALESCE(payload, 'null'::jsonb)
532
+ WHERE trace_path IS NULL OR payload IS NULL;
533
+ `);
534
+ await tx.query(`
535
+ UPDATE mesh_events
536
+ SET type = COALESCE(
537
+ type,
538
+ CASE
539
+ WHEN trace_path IS NOT NULL THEN subpath(trace_path, nlevel(trace_path) - 1, 1)::text
540
+ ELSE 'event'
541
+ END
542
+ )
543
+ WHERE type IS NULL;
544
+ `);
545
+ await tx.query(`
546
+ UPDATE mesh_events
547
+ SET created_at = COALESCE(created_at, CURRENT_TIMESTAMP)
548
+ WHERE created_at IS NULL;
549
+ `);
550
+ await tx.query(`ALTER TABLE mesh_events ALTER COLUMN trace_path SET NOT NULL;`);
551
+ await tx.query(`ALTER TABLE mesh_events ALTER COLUMN type SET NOT NULL;`);
552
+ await tx.query(`ALTER TABLE mesh_events ALTER COLUMN payload SET NOT NULL;`);
553
+ await tx.query(`ALTER TABLE mesh_events ALTER COLUMN created_at SET NOT NULL;`);
554
+ await tx.query(
555
+ `CREATE INDEX IF NOT EXISTS idx_mesh_events_interaction ON mesh_events(interaction_id);`
556
+ );
557
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_mesh_events_run ON mesh_events(run_id);`);
558
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_mesh_events_type ON mesh_events(type);`);
559
+ await tx.query(`DELETE FROM schema_version;`);
560
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.5');`);
561
+ console.log("Schema migration to 1.5 (Mesh Events Supabase Parity) successful.");
562
+ onProgress?.({ step: "v1.5", label: "v1.5 Supabase Parity", status: "done" });
563
+ currentVersion = "1.5";
564
+ }
565
+ if (versionLt(currentVersion, "1.6")) {
566
+ onProgress?.({ step: "v1.6", label: "v1.6 Deploy History", status: "active" });
567
+ console.log("Migrating schema to version 1.6 (Deploy History + Workspace State)...");
568
+ await tx.query(`
569
+ CREATE TABLE IF NOT EXISTS deployments (
570
+ id TEXT PRIMARY KEY,
571
+ user_id TEXT,
572
+ stack_id TEXT,
573
+ provider TEXT NOT NULL,
574
+ status TEXT NOT NULL DEFAULT 'idle',
575
+ deploy_url TEXT,
576
+ preview_url TEXT,
577
+ inspector_url TEXT,
578
+ error TEXT,
579
+ config JSONB,
580
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
581
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
582
+ );
583
+ `);
584
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_deployments_user ON deployments(user_id);`);
585
+ await tx.query(
586
+ `CREATE INDEX IF NOT EXISTS idx_deployments_stack ON deployments(stack_id);`
587
+ );
588
+ await tx.query(`
589
+ CREATE TABLE IF NOT EXISTS workspace_state (
590
+ id TEXT PRIMARY KEY,
591
+ user_id TEXT NOT NULL,
592
+ stack_id TEXT,
593
+ open_files JSONB DEFAULT '[]',
594
+ active_file TEXT,
595
+ cursor_positions JSONB DEFAULT '{}',
596
+ scroll_positions JSONB DEFAULT '{}',
597
+ pane_layout JSONB,
598
+ editor_settings JSONB,
599
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
600
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
601
+ );
602
+ `);
603
+ await tx.query(
604
+ `CREATE INDEX IF NOT EXISTS idx_workspace_user ON workspace_state(user_id);`
605
+ );
606
+ await tx.query(
607
+ `CREATE INDEX IF NOT EXISTS idx_workspace_stack ON workspace_state(user_id, stack_id);`
608
+ );
609
+ await tx.query(`DELETE FROM schema_version;`);
610
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.6');`);
611
+ console.log("Schema migration to 1.6 (Deploy History + Workspace State) successful.");
612
+ onProgress?.({ step: "v1.6", label: "v1.6 Deploy History", status: "done" });
613
+ currentVersion = "1.6";
614
+ }
615
+ if (versionLt(currentVersion, "1.7")) {
616
+ onProgress?.({ step: "v1.7", label: "v1.7 Skill Runs", status: "active" });
617
+ console.log("Migrating schema to version 1.7 (Skill Runs)...");
618
+ await tx.query(`
619
+ CREATE TABLE IF NOT EXISTS skill_runs (
620
+ id TEXT PRIMARY KEY,
621
+ user_id TEXT,
622
+ skill_slug TEXT NOT NULL,
623
+ status TEXT NOT NULL DEFAULT 'pending',
624
+ input JSONB,
625
+ artifacts JSONB DEFAULT '[]',
626
+ agent_log JSONB DEFAULT '[]',
627
+ started_at TIMESTAMP,
628
+ completed_at TIMESTAMP,
629
+ error TEXT,
630
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
631
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
632
+ );
633
+ `);
634
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_skill_runs_user ON skill_runs(user_id);`);
635
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_skill_runs_slug ON skill_runs(skill_slug);`);
636
+ await tx.query(`DELETE FROM schema_version;`);
637
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.7');`);
638
+ console.log("Schema migration to 1.7 (Skill Runs) successful.");
639
+ onProgress?.({ step: "v1.7", label: "v1.7 Skill Runs", status: "done" });
640
+ currentVersion = "1.7";
641
+ }
642
+ if (versionLt(currentVersion, "1.8")) {
643
+ onProgress?.({ step: "v1.8", label: "v1.8 User Skills", status: "active" });
644
+ console.log("Migrating schema to version 1.8...");
645
+ await tx.query(`
646
+ CREATE TABLE IF NOT EXISTS user_skills (
647
+ id TEXT PRIMARY KEY,
648
+ user_id TEXT NOT NULL,
649
+ slug TEXT NOT NULL,
650
+ manifest JSONB NOT NULL,
651
+ markdown TEXT,
652
+ visibility TEXT DEFAULT 'private',
653
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
654
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
655
+ UNIQUE(user_id, slug)
656
+ );
657
+ `);
658
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_user_skills_user ON user_skills(user_id);`);
659
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_user_skills_slug ON user_skills(slug);`);
660
+ await tx.query(
661
+ `CREATE INDEX IF NOT EXISTS idx_user_skills_visibility ON user_skills(visibility);`
662
+ );
663
+ await tx.query(`DELETE FROM schema_version;`);
664
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.8');`);
665
+ console.log("Schema migrated to version 1.8 (user_skills table)");
666
+ onProgress?.({ step: "v1.8", label: "v1.8 User Skills", status: "done" });
667
+ currentVersion = "1.8";
668
+ }
669
+ if (versionLt(currentVersion, "1.9")) {
670
+ onProgress?.({ step: "v1.9", label: "v1.9 Projects", status: "active" });
671
+ console.log("Migrating schema to version 1.9 (Projects & Knowledge Base)...");
672
+ await tx.query(`
673
+ CREATE TABLE IF NOT EXISTS projects (
674
+ id TEXT PRIMARY KEY,
675
+ user_id TEXT NOT NULL,
676
+ name TEXT NOT NULL,
677
+ description TEXT,
678
+ settings JSONB DEFAULT '{}',
679
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
680
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
681
+ );
682
+ `);
683
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_projects_user ON projects(user_id);`);
684
+ await tx.query(`
685
+ CREATE TABLE IF NOT EXISTS project_files (
686
+ id TEXT PRIMARY KEY,
687
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
688
+ filename TEXT NOT NULL,
689
+ mime_type TEXT NOT NULL,
690
+ size_bytes INTEGER NOT NULL DEFAULT 0,
691
+ content TEXT,
692
+ embedding vector(384),
693
+ metadata JSONB,
694
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
695
+ );
696
+ `);
697
+ await tx.query(
698
+ `CREATE INDEX IF NOT EXISTS idx_project_files_project ON project_files(project_id);`
699
+ );
700
+ await tx.query(`DELETE FROM schema_version;`);
701
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.9');`);
702
+ console.log("Schema migrated to version 1.9 (Projects & Knowledge Base)");
703
+ onProgress?.({ step: "v1.9", label: "v1.9 Projects", status: "done" });
704
+ currentVersion = "1.9";
705
+ }
706
+ if (versionLt(currentVersion, "1.10")) {
707
+ onProgress?.({ step: "v1.10", label: "v1.10 Project Scope", status: "active" });
708
+ console.log("Migrating schema to version 1.10 (Project Scope)...");
709
+ await tx.query(`ALTER TABLE projects ADD COLUMN IF NOT EXISTS color TEXT;`);
710
+ await tx.query(
711
+ `ALTER TABLE projects ADD COLUMN IF NOT EXISTS focus_level TEXT DEFAULT 'ambient';`
712
+ );
713
+ await tx.query(`
714
+ CREATE TABLE IF NOT EXISTS project_artifacts (
715
+ id TEXT PRIMARY KEY,
716
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
717
+ artifact_id TEXT NOT NULL,
718
+ source_type TEXT NOT NULL,
719
+ source_id TEXT,
720
+ title TEXT NOT NULL,
721
+ content_type TEXT NOT NULL,
722
+ pinned BOOLEAN DEFAULT FALSE,
723
+ content_preview TEXT,
724
+ metadata JSONB DEFAULT '{}',
725
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
726
+ UNIQUE (project_id, artifact_id)
727
+ );
728
+ `);
729
+ await tx.query(
730
+ `CREATE INDEX IF NOT EXISTS idx_pa_project ON project_artifacts(project_id);`
731
+ );
732
+ await tx.query(
733
+ `CREATE INDEX IF NOT EXISTS idx_pa_pinned ON project_artifacts(project_id, pinned);`
734
+ );
735
+ await tx.query(`
736
+ CREATE TABLE IF NOT EXISTS project_sessions (
737
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
738
+ session_id TEXT NOT NULL,
739
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
740
+ PRIMARY KEY (project_id, session_id)
741
+ );
742
+ `);
743
+ await tx.query(`
744
+ CREATE TABLE IF NOT EXISTS project_skill_runs (
745
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
746
+ run_id TEXT NOT NULL,
747
+ skill_slug TEXT NOT NULL,
748
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
749
+ PRIMARY KEY (project_id, run_id)
750
+ );
751
+ `);
752
+ await tx.query(`DELETE FROM schema_version;`);
753
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.10');`);
754
+ console.log("Schema migrated to version 1.10 (Project Scope)");
755
+ onProgress?.({ step: "v1.10", label: "v1.10 Project Scope", status: "done" });
756
+ currentVersion = "1.10";
757
+ }
758
+ if (versionLt(currentVersion, "1.11")) {
759
+ onProgress?.({ step: "v1.11", label: "v1.11 Dashboard & Checkpoints", status: "active" });
760
+ console.log(
761
+ "Migrating schema to version 1.11 (Dashboard Widgets, Checkpoints, Effective Dating)..."
762
+ );
763
+ await tx.query(`
764
+ CREATE TABLE IF NOT EXISTS skill_run_checkpoints (
765
+ id TEXT PRIMARY KEY,
766
+ run_id TEXT NOT NULL,
767
+ level_index INTEGER NOT NULL,
768
+ completed_agents JSONB DEFAULT '[]',
769
+ pending_agents JSONB DEFAULT '[]',
770
+ artifacts JSONB DEFAULT '[]',
771
+ state_snapshot JSONB DEFAULT '{}',
772
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
773
+ );
774
+ `);
775
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_src_run ON skill_run_checkpoints(run_id);`);
776
+ await tx.query(
777
+ `CREATE INDEX IF NOT EXISTS idx_src_level ON skill_run_checkpoints(run_id, level_index);`
778
+ );
779
+ await tx.query(`
780
+ CREATE TABLE IF NOT EXISTS dashboard_widgets (
781
+ id TEXT PRIMARY KEY,
782
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
783
+ user_id TEXT NOT NULL,
784
+ position JSONB NOT NULL DEFAULT '{"x":0,"y":0,"w":1,"h":1}',
785
+ config JSONB NOT NULL DEFAULT '{"type":"note"}',
786
+ data_cache JSONB,
787
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
788
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
789
+ );
790
+ `);
791
+ await tx.query(
792
+ `CREATE INDEX IF NOT EXISTS idx_dw_project ON dashboard_widgets(project_id);`
793
+ );
794
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_dw_user ON dashboard_widgets(user_id);`);
795
+ await tx.query(`
796
+ CREATE TABLE IF NOT EXISTS effective_dated_bindings (
797
+ id TEXT PRIMARY KEY,
798
+ entity_id TEXT NOT NULL,
799
+ seat_type TEXT NOT NULL,
800
+ seat_id TEXT NOT NULL,
801
+ registered_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
802
+ active_from TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
803
+ active_to TIMESTAMP,
804
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
805
+ );
806
+ `);
807
+ await tx.query(
808
+ `CREATE INDEX IF NOT EXISTS idx_edb_entity ON effective_dated_bindings(entity_id);`
809
+ );
810
+ await tx.query(
811
+ `CREATE INDEX IF NOT EXISTS idx_edb_seat ON effective_dated_bindings(seat_type, seat_id);`
812
+ );
813
+ await tx.query(
814
+ `CREATE INDEX IF NOT EXISTS idx_edb_active ON effective_dated_bindings(active_from, active_to);`
815
+ );
816
+ await tx.query(`DELETE FROM schema_version;`);
817
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.11');`);
818
+ console.log(
819
+ "Schema migrated to version 1.11 (Dashboard Widgets, Checkpoints, Effective Dating)"
820
+ );
821
+ onProgress?.({ step: "v1.11", label: "v1.11 Dashboard & Checkpoints", status: "done" });
822
+ currentVersion = "1.11";
823
+ }
824
+ if (versionLt(currentVersion, "1.12")) {
825
+ onProgress?.({ step: "v1.12", label: "v1.12 Forge Tables", status: "active" });
826
+ console.log("Migrating schema to version 1.12...");
827
+ await tx.query(`
828
+ CREATE TABLE IF NOT EXISTS forge_scenes (
829
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
830
+ user_id UUID,
831
+ name TEXT NOT NULL,
832
+ scene_data JSONB NOT NULL,
833
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
834
+ updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
835
+ );
836
+ `);
837
+ await tx.query(`
838
+ CREATE TABLE IF NOT EXISTS forge_iterations (
839
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
840
+ scene_id UUID REFERENCES forge_scenes(id) ON DELETE CASCADE,
841
+ user_id UUID,
842
+ tau REAL NOT NULL,
843
+ sync_order REAL,
844
+ coherence JSONB,
845
+ parameter_snapshot JSONB,
846
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
847
+ );
848
+ `);
849
+ await tx.query(`
850
+ CREATE TABLE IF NOT EXISTS forge_material_profiles (
851
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
852
+ user_id UUID,
853
+ name TEXT NOT NULL,
854
+ properties JSONB NOT NULL,
855
+ acoustic_signature JSONB,
856
+ neural_baseline JSONB,
857
+ crystallization REAL DEFAULT 0,
858
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
859
+ updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
860
+ );
861
+ `);
862
+ await tx.query(
863
+ `CREATE INDEX IF NOT EXISTS idx_forge_iterations_scene ON forge_iterations(scene_id);`
864
+ );
865
+ await tx.query(
866
+ `CREATE INDEX IF NOT EXISTS idx_forge_profiles_user ON forge_material_profiles(user_id);`
867
+ );
868
+ await tx.query(`DELETE FROM schema_version;`);
869
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.12');`);
870
+ console.log("Schema migration to 1.12 successful.");
871
+ onProgress?.({ step: "v1.12", label: "v1.12 Forge Tables", status: "done" });
872
+ currentVersion = "1.12";
873
+ }
874
+ if (versionLt(currentVersion, "1.13")) {
875
+ onProgress?.({ step: "v1.13", label: "v1.13 Resonant Sessions", status: "active" });
876
+ console.log("Migrating schema to version 1.13 (Resonant Sessions, Frames, Onboarding)...");
877
+ await tx.query(`
878
+ CREATE TABLE IF NOT EXISTS resonant_sessions (
879
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
880
+ user_id UUID,
881
+ audio_fingerprint TEXT NOT NULL,
882
+ audio_file_name TEXT NOT NULL,
883
+ duration_ms INTEGER NOT NULL,
884
+ sampling_rate INTEGER NOT NULL DEFAULT 30,
885
+ config JSONB NOT NULL,
886
+ summary JSONB NOT NULL,
887
+ frame_count INTEGER NOT NULL,
888
+ attribution JSONB NOT NULL,
889
+ tier TEXT NOT NULL DEFAULT 'FREE_LOCAL',
890
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
891
+ );
892
+ `);
893
+ await tx.query(`
894
+ CREATE TABLE IF NOT EXISTS resonant_session_frames (
895
+ id BIGSERIAL PRIMARY KEY,
896
+ session_id UUID NOT NULL REFERENCES resonant_sessions(id) ON DELETE CASCADE,
897
+ frame_index INTEGER NOT NULL,
898
+ data JSONB NOT NULL
899
+ );
900
+ `);
901
+ await tx.query(
902
+ `CREATE INDEX IF NOT EXISTS idx_resonant_sessions_user ON resonant_sessions(user_id);`
903
+ );
904
+ await tx.query(
905
+ `CREATE INDEX IF NOT EXISTS idx_resonant_frames_session_idx ON resonant_session_frames(session_id, frame_index);`
906
+ );
907
+ await tx.query(`
908
+ CREATE TABLE IF NOT EXISTS resonant_onboarding (
909
+ user_id UUID PRIMARY KEY,
910
+ welcome_seen BOOLEAN NOT NULL DEFAULT false,
911
+ consent_accepted BOOLEAN NOT NULL DEFAULT false,
912
+ consent_timestamp TIMESTAMPTZ,
913
+ keys_configured BOOLEAN NOT NULL DEFAULT false,
914
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
915
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
916
+ );
917
+ `);
918
+ await tx.query(`DELETE FROM schema_version;`);
919
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.13');`);
920
+ console.log("Schema migration to 1.13 (Resonant Sessions, Frames, Onboarding) successful.");
921
+ onProgress?.({ step: "v1.13", label: "v1.13 Resonant Sessions", status: "done" });
922
+ currentVersion = "1.13";
923
+ }
924
+ if (versionLt(currentVersion, "1.14")) {
925
+ onProgress?.({ step: "v1.14", label: "v1.14 Society Run Ledger", status: "active" });
926
+ console.log("Migrating schema to version 1.14 (Society Run Ledger)...");
927
+ await tx.query(`
928
+ CREATE TABLE IF NOT EXISTS society_runs (
929
+ id TEXT PRIMARY KEY,
930
+ plan_id TEXT NOT NULL,
931
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
932
+ user_id TEXT,
933
+ user_request TEXT NOT NULL,
934
+ status TEXT NOT NULL,
935
+ plan_json JSONB NOT NULL,
936
+ skill_artifacts JSONB DEFAULT '{}',
937
+ synthesized_output TEXT,
938
+ error TEXT,
939
+ started_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
940
+ completed_at TIMESTAMPTZ,
941
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
942
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
943
+ );
944
+ `);
945
+ await tx.query(`
946
+ CREATE TABLE IF NOT EXISTS society_run_nodes (
947
+ id TEXT PRIMARY KEY,
948
+ run_id TEXT NOT NULL REFERENCES society_runs(id) ON DELETE CASCADE,
949
+ node_id TEXT NOT NULL,
950
+ skill_slug TEXT NOT NULL,
951
+ status TEXT NOT NULL,
952
+ artifacts JSONB DEFAULT '[]',
953
+ error TEXT,
954
+ started_at TIMESTAMPTZ,
955
+ completed_at TIMESTAMPTZ,
956
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
957
+ );
958
+ `);
959
+ await tx.query(`
960
+ CREATE TABLE IF NOT EXISTS society_run_events (
961
+ id TEXT PRIMARY KEY,
962
+ run_id TEXT NOT NULL REFERENCES society_runs(id) ON DELETE CASCADE,
963
+ event_type TEXT NOT NULL,
964
+ payload JSONB DEFAULT '{}',
965
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
966
+ );
967
+ `);
968
+ await tx.query(`
969
+ CREATE TABLE IF NOT EXISTS project_society_runs (
970
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
971
+ run_id TEXT NOT NULL,
972
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
973
+ PRIMARY KEY (project_id, run_id)
974
+ );
975
+ `);
976
+ await tx.query(
977
+ `CREATE INDEX IF NOT EXISTS idx_society_runs_project ON society_runs(project_id);`
978
+ );
979
+ await tx.query(
980
+ `CREATE INDEX IF NOT EXISTS idx_society_runs_status ON society_runs(status);`
981
+ );
982
+ await tx.query(
983
+ `CREATE INDEX IF NOT EXISTS idx_society_run_nodes_run ON society_run_nodes(run_id);`
984
+ );
985
+ await tx.query(
986
+ `CREATE INDEX IF NOT EXISTS idx_society_run_events_run ON society_run_events(run_id);`
987
+ );
988
+ await tx.query(`DELETE FROM schema_version;`);
989
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.14');`);
990
+ console.log("Schema migration to 1.14 (Society Run Ledger) successful.");
991
+ onProgress?.({ step: "v1.14", label: "v1.14 Society Run Ledger", status: "done" });
992
+ currentVersion = "1.14";
993
+ }
994
+ if (versionLt(currentVersion, "1.15")) {
995
+ onProgress?.({ step: "v1.15", label: "v1.15 Intent Oscillator", status: "active" });
996
+ console.log("Migrating schema to version 1.15...");
997
+ await tx.query(`
998
+ CREATE TABLE IF NOT EXISTS intent_signals (
999
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1000
+ user_id TEXT NOT NULL,
1001
+ project_id TEXT NOT NULL,
1002
+ signal_type TEXT NOT NULL,
1003
+ dimension TEXT,
1004
+ payload JSONB,
1005
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
1006
+ );
1007
+ `);
1008
+ await tx.query(
1009
+ `CREATE INDEX IF NOT EXISTS idx_intent_signals_user_time ON intent_signals(user_id, created_at DESC);`
1010
+ );
1011
+ await tx.query(
1012
+ `CREATE INDEX IF NOT EXISTS idx_intent_signals_project ON intent_signals(project_id, created_at DESC);`
1013
+ );
1014
+ await tx.query(`
1015
+ CREATE TABLE IF NOT EXISTS oscillator_state (
1016
+ id TEXT PRIMARY KEY,
1017
+ phases JSONB NOT NULL DEFAULT '{}',
1018
+ frequencies JSONB NOT NULL DEFAULT '{}',
1019
+ coupling_k FLOAT DEFAULT 0.5,
1020
+ order_parameter_r FLOAT DEFAULT 0.0,
1021
+ updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
1022
+ );
1023
+ `);
1024
+ await tx.query(`DELETE FROM schema_version;`);
1025
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.15');`);
1026
+ console.log("Schema migration to 1.15 (Intent Oscillator) successful.");
1027
+ onProgress?.({ step: "v1.15", label: "v1.15 Intent Oscillator", status: "done" });
1028
+ currentVersion = "1.15";
1029
+ }
1030
+ if (versionLt(currentVersion, "1.16")) {
1031
+ onProgress?.({ step: "v1.16", label: "v1.16 API Keys", status: "active" });
1032
+ console.log("Migrating schema to version 1.16...");
1033
+ await tx.query(`DELETE FROM schema_version;`);
1034
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.16');`);
1035
+ console.log("Schema migration to 1.16 (API Keys) successful.");
1036
+ onProgress?.({ step: "v1.16", label: "v1.16 API Keys", status: "done" });
1037
+ currentVersion = "1.16";
1038
+ }
1039
+ if (versionLt(currentVersion, "1.17")) {
1040
+ onProgress?.({ step: "v1.17", label: "v1.17 Biosignal Substrate", status: "active" });
1041
+ console.log("Migrating schema to version 1.17...");
1042
+ await tx.query(`
1043
+ CREATE TABLE IF NOT EXISTS biosignal_bridge_config (
1044
+ id TEXT PRIMARY KEY,
1045
+ label TEXT NOT NULL,
1046
+ transport TEXT NOT NULL,
1047
+ domains JSONB NOT NULL DEFAULT '[]',
1048
+ signal_families JSONB NOT NULL DEFAULT '[]',
1049
+ status TEXT NOT NULL DEFAULT 'dormant',
1050
+ enabled BOOLEAN NOT NULL DEFAULT false,
1051
+ config JSONB NOT NULL DEFAULT '{}',
1052
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
1053
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
1054
+ );
1055
+ `);
1056
+ await tx.query(`
1057
+ CREATE TABLE IF NOT EXISTS biosignal_sessions (
1058
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
1059
+ user_id UUID,
1060
+ bridge_id TEXT NOT NULL REFERENCES biosignal_bridge_config(id) ON DELETE RESTRICT,
1061
+ signal_family TEXT NOT NULL,
1062
+ sampling_hz REAL,
1063
+ channel_count INTEGER,
1064
+ status TEXT NOT NULL DEFAULT 'created',
1065
+ metadata JSONB NOT NULL DEFAULT '{}',
1066
+ provenance JSONB NOT NULL DEFAULT '{}',
1067
+ started_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
1068
+ ended_at TIMESTAMPTZ,
1069
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
1070
+ );
1071
+ `);
1072
+ await tx.query(`
1073
+ CREATE TABLE IF NOT EXISTS biosignal_epochs (
1074
+ id BIGSERIAL PRIMARY KEY,
1075
+ session_id UUID NOT NULL REFERENCES biosignal_sessions(id) ON DELETE CASCADE,
1076
+ epoch_index INTEGER NOT NULL,
1077
+ window_started_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
1078
+ window_ended_at TIMESTAMPTZ,
1079
+ raw_pointer JSONB,
1080
+ signals JSONB NOT NULL DEFAULT '{}',
1081
+ metrics JSONB NOT NULL DEFAULT '{}',
1082
+ quality JSONB NOT NULL DEFAULT '{}',
1083
+ labels JSONB NOT NULL DEFAULT '[]',
1084
+ embedding vector(384),
1085
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
1086
+ );
1087
+ `);
1088
+ await tx.query(
1089
+ `CREATE INDEX IF NOT EXISTS idx_biosignal_bridge_status ON biosignal_bridge_config(status, enabled);`
1090
+ );
1091
+ await tx.query(
1092
+ `CREATE INDEX IF NOT EXISTS idx_biosignal_sessions_user_time ON biosignal_sessions(user_id, started_at DESC);`
1093
+ );
1094
+ await tx.query(
1095
+ `CREATE INDEX IF NOT EXISTS idx_biosignal_sessions_bridge_time ON biosignal_sessions(bridge_id, started_at DESC);`
1096
+ );
1097
+ await tx.query(
1098
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_biosignal_epochs_session_idx ON biosignal_epochs(session_id, epoch_index);`
1099
+ );
1100
+ await tx.query(
1101
+ `CREATE INDEX IF NOT EXISTS idx_biosignal_epochs_time ON biosignal_epochs(window_started_at DESC);`
1102
+ );
1103
+ await tx.query(
1104
+ `CREATE INDEX IF NOT EXISTS idx_biosignal_epochs_embedding ON biosignal_epochs USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);`
1105
+ );
1106
+ for (const bridge of DORMANT_BIOSIGNAL_BRIDGES) {
1107
+ await tx.query(
1108
+ `INSERT INTO biosignal_bridge_config
1109
+ (id, label, transport, domains, signal_families, status, enabled, config)
1110
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
1111
+ ON CONFLICT (id) DO NOTHING`,
1112
+ [
1113
+ bridge.id,
1114
+ bridge.label,
1115
+ bridge.transport,
1116
+ JSON.stringify(bridge.domains),
1117
+ JSON.stringify(bridge.signalFamilies),
1118
+ bridge.status,
1119
+ bridge.enabled,
1120
+ JSON.stringify(bridge.config)
1121
+ ]
1122
+ );
1123
+ }
1124
+ await tx.query(`DELETE FROM schema_version;`);
1125
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.17');`);
1126
+ console.log("Schema migration to 1.17 (Dormant Biosignal Bridge Substrate) successful.");
1127
+ onProgress?.({ step: "v1.17", label: "v1.17 Biosignal Substrate", status: "done" });
1128
+ currentVersion = "1.17";
1129
+ }
1130
+ if (versionLt(currentVersion, "1.18")) {
1131
+ onProgress?.({ step: "v1.18", label: "v1.18 Biosignal Providers", status: "active" });
1132
+ console.log("Migrating schema to version 1.18...");
1133
+ await tx.query(`
1134
+ CREATE TABLE IF NOT EXISTS biosignal_provider_config (
1135
+ id TEXT PRIMARY KEY,
1136
+ label TEXT NOT NULL,
1137
+ provider_type TEXT NOT NULL,
1138
+ acquisition_mode TEXT NOT NULL,
1139
+ domains JSONB NOT NULL DEFAULT '[]',
1140
+ signal_families JSONB NOT NULL DEFAULT '[]',
1141
+ metrics JSONB NOT NULL DEFAULT '[]',
1142
+ status TEXT NOT NULL DEFAULT 'dormant',
1143
+ enabled BOOLEAN NOT NULL DEFAULT false,
1144
+ notes TEXT NOT NULL DEFAULT '',
1145
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
1146
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
1147
+ );
1148
+ `);
1149
+ await tx.query(
1150
+ `CREATE INDEX IF NOT EXISTS idx_biosignal_provider_status ON biosignal_provider_config(status, enabled);`
1151
+ );
1152
+ for (const provider of DORMANT_BIOSIGNAL_PROVIDERS) {
1153
+ await tx.query(
1154
+ `INSERT INTO biosignal_provider_config
1155
+ (id, label, provider_type, acquisition_mode, domains, signal_families, metrics, status, enabled, notes)
1156
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
1157
+ ON CONFLICT (id) DO NOTHING`,
1158
+ [
1159
+ provider.id,
1160
+ provider.label,
1161
+ provider.providerType,
1162
+ provider.acquisitionMode,
1163
+ JSON.stringify(provider.domains),
1164
+ JSON.stringify(provider.signalFamilies),
1165
+ JSON.stringify(provider.metrics),
1166
+ provider.status,
1167
+ provider.enabled,
1168
+ provider.notes
1169
+ ]
1170
+ );
1171
+ }
1172
+ await tx.query(`DELETE FROM schema_version;`);
1173
+ await tx.query(`INSERT INTO schema_version (version) VALUES ('1.18');`);
1174
+ console.log("Schema migration to 1.18 (Dormant Biosignal Provider Catalog) successful.");
1175
+ onProgress?.({ step: "v1.18", label: "v1.18 Biosignal Providers", status: "done" });
1176
+ currentVersion = "1.18";
1177
+ }
1178
+ await tx.query(USER_PROFILES_TABLE_SQL);
1179
+ await tx.query(`
1180
+ CREATE TABLE IF NOT EXISTS chat_sessions (
1181
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
1182
+ user_id UUID REFERENCES user_profiles(user_id) ON DELETE CASCADE,
1183
+ session_title VARCHAR(500),
1184
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1185
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1186
+ metadata JSONB DEFAULT '{}'
1187
+ );
1188
+ `);
1189
+ await tx.query(`
1190
+ CREATE TABLE IF NOT EXISTS chat_messages (
1191
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
1192
+ session_id UUID REFERENCES chat_sessions(id) ON DELETE CASCADE,
1193
+ user_id UUID REFERENCES user_profiles(user_id) ON DELETE SET NULL,
1194
+ role VARCHAR(20) NOT NULL CHECK (role IN ('user', 'assistant', 'system', 'tool')),
1195
+ content TEXT NOT NULL,
1196
+ tool_call_id VARCHAR(255),
1197
+ tool_calls JSONB,
1198
+ name VARCHAR(255),
1199
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1200
+ metadata JSONB DEFAULT '{}'
1201
+ );
1202
+ `);
1203
+ await tx.query(`
1204
+ CREATE TABLE IF NOT EXISTS quizzes (
1205
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
1206
+ title VARCHAR(255) NOT NULL,
1207
+ description TEXT,
1208
+ questions JSONB NOT NULL DEFAULT '[]',
1209
+ associated_resource_slug VARCHAR(255),
1210
+ difficulty VARCHAR(50),
1211
+ tags TEXT[],
1212
+ reward_id UUID,
1213
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1214
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1215
+ );
1216
+ `);
1217
+ await tx.query(`
1218
+ CREATE TABLE IF NOT EXISTS user_artifacts (
1219
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
1220
+ user_id UUID REFERENCES user_profiles(user_id) ON DELETE CASCADE,
1221
+ source_applet VARCHAR(50) NOT NULL,
1222
+ artifact_type VARCHAR(50) NOT NULL,
1223
+ display_type VARCHAR(50),
1224
+ artifact_name TEXT NOT NULL,
1225
+ content TEXT,
1226
+ blob_content BYTEA,
1227
+ metadata JSONB,
1228
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1229
+ );
1230
+ `);
1231
+ await tx.query(`
1232
+ CREATE TABLE IF NOT EXISTS rewards (
1233
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
1234
+ title VARCHAR(255) NOT NULL,
1235
+ description TEXT,
1236
+ type VARCHAR(100),
1237
+ unlock_criteria JSONB,
1238
+ asset_url TEXT,
1239
+ quiz_ids_required UUID[],
1240
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1241
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1242
+ );
1243
+ `);
1244
+ await tx.query(`
1245
+ CREATE TABLE IF NOT EXISTS user_progress (
1246
+ user_id UUID REFERENCES user_profiles(user_id) ON DELETE CASCADE,
1247
+ quiz_id UUID REFERENCES quizzes(id) ON DELETE CASCADE,
1248
+ highest_score INTEGER,
1249
+ completed_at TIMESTAMP,
1250
+ unlocked_rewards UUID[],
1251
+ current_question_index INTEGER,
1252
+ answers JSONB,
1253
+ last_attempted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1254
+ PRIMARY KEY (user_id, quiz_id)
1255
+ );
1256
+ `);
1257
+ await tx.query(`
1258
+ CREATE TABLE IF NOT EXISTS resource_categories (
1259
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
1260
+ name VARCHAR(255) NOT NULL,
1261
+ description TEXT,
1262
+ icon_identifier VARCHAR(100),
1263
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1264
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1265
+ );
1266
+ `);
1267
+ await tx.query(`
1268
+ CREATE TABLE IF NOT EXISTS resource_documents (
1269
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
1270
+ title VARCHAR(255) NOT NULL,
1271
+ description TEXT,
1272
+ category_id UUID REFERENCES resource_categories(id) ON DELETE SET NULL,
1273
+ content TEXT NOT NULL,
1274
+ icon_identifier VARCHAR(100),
1275
+ tags TEXT[],
1276
+ has_quiz BOOLEAN DEFAULT FALSE,
1277
+ quiz_id UUID REFERENCES quizzes(id) ON DELETE SET NULL,
1278
+ embedding vector(384),
1279
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1280
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1281
+ );
1282
+ `);
1283
+ await tx.query(`
1284
+ CREATE TABLE IF NOT EXISTS friends (
1285
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
1286
+ name VARCHAR(255) NOT NULL,
1287
+ description TEXT,
1288
+ url TEXT,
1289
+ logo_url TEXT,
1290
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1291
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1292
+ );
1293
+ `);
1294
+ await tx.query(`
1295
+ CREATE TABLE IF NOT EXISTS inspirations (
1296
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
1297
+ name VARCHAR(255) NOT NULL,
1298
+ description TEXT,
1299
+ url TEXT,
1300
+ type VARCHAR(50) CHECK (type IN ('person', 'project', 'company')),
1301
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1302
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1303
+ );
1304
+ `);
1305
+ await tx.query(`
1306
+ CREATE TABLE IF NOT EXISTS key_value_store (
1307
+ key TEXT PRIMARY KEY,
1308
+ value JSONB
1309
+ );
1310
+ `);
1311
+ await tx.query(
1312
+ `CREATE INDEX IF NOT EXISTS idx_chat_messages_session_id ON chat_messages(session_id);`
1313
+ );
1314
+ await tx.query(
1315
+ `CREATE INDEX IF NOT EXISTS idx_chat_messages_timestamp ON chat_messages(timestamp);`
1316
+ );
1317
+ await tx.query(
1318
+ `CREATE INDEX IF NOT EXISTS idx_user_progress_user_id ON user_progress(user_id);`
1319
+ );
1320
+ await tx.query(
1321
+ `CREATE INDEX IF NOT EXISTS idx_resource_documents_category_id ON resource_documents(category_id);`
1322
+ );
1323
+ await tx.query(
1324
+ `CREATE INDEX IF NOT EXISTS idx_quizzes_title_trgm ON quizzes USING gin (title gin_trgm_ops);`
1325
+ );
1326
+ await tx.query(
1327
+ `CREATE INDEX IF NOT EXISTS idx_resource_documents_title_trgm ON resource_documents USING gin (title gin_trgm_ops);`
1328
+ );
1329
+ await tx.query(
1330
+ `CREATE INDEX IF NOT EXISTS idx_user_artifacts_user_id ON user_artifacts(user_id);`
1331
+ );
1332
+ await tx.query(
1333
+ `CREATE INDEX IF NOT EXISTS idx_user_artifacts_source_applet ON user_artifacts(source_applet);`
1334
+ );
1335
+ await tx.query(`
1336
+ CREATE TABLE IF NOT EXISTS s4_usage (
1337
+ id TEXT PRIMARY KEY,
1338
+ user_id TEXT NOT NULL,
1339
+ model_name TEXT,
1340
+ tokens_used INTEGER DEFAULT 0,
1341
+ endpoint TEXT NOT NULL,
1342
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1343
+ metadata JSONB
1344
+ );
1345
+ `);
1346
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_s4_usage_user_id ON s4_usage(user_id);`);
1347
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_s4_usage_timestamp ON s4_usage(timestamp);`);
1348
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_s4_usage_model_name ON s4_usage(model_name);`);
1349
+ await tx.query(`
1350
+ CREATE TABLE IF NOT EXISTS conversation_embeddings (
1351
+ session_id UUID NOT NULL,
1352
+ message_id UUID NOT NULL,
1353
+ role VARCHAR(20) NOT NULL,
1354
+ content_snippet TEXT,
1355
+ embedding vector(384),
1356
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1357
+ PRIMARY KEY (session_id, message_id)
1358
+ );
1359
+ `);
1360
+ await tx.query(`
1361
+ CREATE TABLE IF NOT EXISTS context_anchors (
1362
+ session_id UUID NOT NULL,
1363
+ anchor_id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
1364
+ title TEXT,
1365
+ summary TEXT,
1366
+ embedding vector(384),
1367
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1368
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1369
+ );
1370
+ `);
1371
+ await tx.query(
1372
+ `CREATE INDEX IF NOT EXISTS idx_conv_emb_session ON conversation_embeddings(session_id);`
1373
+ );
1374
+ await tx.query(
1375
+ `CREATE INDEX IF NOT EXISTS idx_ctx_anchor_session ON context_anchors(session_id);`
1376
+ );
1377
+ await tx.query(`
1378
+ CREATE TABLE IF NOT EXISTS run_spans (
1379
+ trace_id TEXT NOT NULL,
1380
+ span_id TEXT NOT NULL,
1381
+ name TEXT NOT NULL,
1382
+ start_time TIMESTAMP NOT NULL,
1383
+ end_time TIMESTAMP,
1384
+ attributes JSONB,
1385
+ PRIMARY KEY (trace_id, span_id)
1386
+ );
1387
+ `);
1388
+ await tx.query(`
1389
+ CREATE TABLE IF NOT EXISTS run_span_events (
1390
+ trace_id TEXT NOT NULL,
1391
+ span_id TEXT NOT NULL,
1392
+ ts TIMESTAMP NOT NULL,
1393
+ event TEXT NOT NULL,
1394
+ attributes JSONB,
1395
+ PRIMARY KEY (trace_id, span_id, ts, event)
1396
+ );
1397
+ `);
1398
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_run_spans_trace ON run_spans(trace_id);`);
1399
+ await tx.query(
1400
+ `CREATE INDEX IF NOT EXISTS idx_run_span_events_trace ON run_span_events(trace_id);`
1401
+ );
1402
+ await tx.query(`
1403
+ CREATE TABLE IF NOT EXISTS stacks (
1404
+ id TEXT PRIMARY KEY,
1405
+ name TEXT,
1406
+ manifest JSONB NOT NULL,
1407
+ shape_hash TEXT,
1408
+ deleted_at TIMESTAMP,
1409
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1410
+ );
1411
+ `);
1412
+ await tx.query(`
1413
+ CREATE TABLE IF NOT EXISTS policy_asks (
1414
+ id UUID PRIMARY KEY DEFAULT COALESCE(uuid_generate_v4(), gen_random_uuid()),
1415
+ stack_id TEXT NOT NULL,
1416
+ node_id TEXT NOT NULL,
1417
+ url TEXT NOT NULL,
1418
+ status TEXT NOT NULL DEFAULT 'pending',
1419
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1420
+ decided_at TIMESTAMP
1421
+ );
1422
+ `);
1423
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_policy_asks_stack ON policy_asks(stack_id);`);
1424
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_policy_asks_status ON policy_asks(status);`);
1425
+ await tx.query(`
1426
+ CREATE TABLE IF NOT EXISTS replays (
1427
+ id TEXT PRIMARY KEY,
1428
+ owner_user_id UUID REFERENCES user_profiles(user_id) ON DELETE SET NULL,
1429
+ blob_url TEXT NOT NULL,
1430
+ events_count INTEGER,
1431
+ visibility TEXT DEFAULT 'public',
1432
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1433
+ );
1434
+ `);
1435
+ await tx.query(`CREATE INDEX IF NOT EXISTS idx_replays_owner ON replays(owner_user_id);`);
1436
+ console.log("Database schema initialized successfully");
1437
+ onProgress?.({ step: "done", label: "Ready", status: "done" });
1438
+ });
1439
+ } catch (error) {
1440
+ const errorMessage = error instanceof Error ? error.message : String(error);
1441
+ console.error("Failed to initialize database schema:", errorMessage);
1442
+ if (errorMessage.includes("syntax error")) {
1443
+ console.error("SQL syntax error during initialization. Check PGLite compatibility.");
1444
+ } else if (errorMessage.includes("extension")) {
1445
+ console.error("Extension loading failed. Some features may not be available.");
1446
+ }
1447
+ throw new Error(`Database initialization failed: ${errorMessage}`);
1448
+ }
1449
+ }
1450
+
1451
+ // ../../lib/pglite/db.ts
1452
+ var DEFAULT_SCOPE = { kind: "global", id: "main" };
1453
+ var currentScope = DEFAULT_SCOPE;
1454
+ function sanitizeScopeId(input) {
1455
+ return input.toLowerCase().replace(/[^a-z0-9_-]+/g, "_").slice(0, 64);
1456
+ }
1457
+ function scopeToId(scope) {
1458
+ switch (scope.kind) {
1459
+ case "global":
1460
+ return scope.id ?? "main";
1461
+ case "user":
1462
+ return scope.userId;
1463
+ case "session":
1464
+ return scope.sessionId;
1465
+ case "custom":
1466
+ return scope.name;
1467
+ }
1468
+ }
1469
+ function getDataDirForScope(scope) {
1470
+ const id = sanitizeScopeId(scopeToId(scope));
1471
+ return `idb://terminals_${scope.kind}_${id}`;
1472
+ }
1473
+ var currentDataDir = getDataDirForScope(currentScope);
1474
+ var PGLiteConnectionPool = class _PGLiteConnectionPool {
1475
+ static instance = null;
1476
+ connection = null;
1477
+ isInitialized = false;
1478
+ initializationPromise = null;
1479
+ dataDir = currentDataDir;
1480
+ initGeneration = 0;
1481
+ // Monotonic counter — only the latest init can mutate status
1482
+ // Status tracking (consumed by usePGLiteStatus hook via useSyncExternalStore)
1483
+ initStatus = "idle";
1484
+ initError = null;
1485
+ statusListeners = /* @__PURE__ */ new Set();
1486
+ progressSteps = [];
1487
+ cachedStatusSnapshot = { status: "idle", error: null, steps: [] };
1488
+ // L1 Core circuit breaker — prevents cascading init attempts after repeated failures.
1489
+ // Opens after 3 failures, half-open after 30s allows one probe attempt.
1490
+ circuitBreaker = createCircuitBreaker({
1491
+ failureThreshold: 3,
1492
+ resetTimeoutMs: 3e4,
1493
+ halfOpenRequests: 1
1494
+ });
1495
+ // Health monitor state
1496
+ healthMonitorInterval = null;
1497
+ healthMonitorFailures = 0;
1498
+ static MAX_HEALTH_FAILURES = 5;
1499
+ static HEALTH_MONITOR_INTERVAL_MS = 35e3;
1500
+ constructor() {
1501
+ }
1502
+ notifyStatusChange() {
1503
+ this.cachedStatusSnapshot = {
1504
+ status: this.initStatus,
1505
+ error: this.initError,
1506
+ steps: this.progressSteps
1507
+ };
1508
+ for (const cb of this.statusListeners) {
1509
+ try {
1510
+ cb();
1511
+ } catch (e) {
1512
+ console.warn("[PGLite] Status listener error:", e);
1513
+ }
1514
+ }
1515
+ }
1516
+ addProgressStep(p) {
1517
+ const idx = this.progressSteps.findIndex((s) => s.step === p.step);
1518
+ if (idx >= 0) {
1519
+ this.progressSteps[idx] = p;
1520
+ } else {
1521
+ this.progressSteps.push(p);
1522
+ }
1523
+ this.progressSteps = [...this.progressSteps];
1524
+ this.notifyStatusChange();
1525
+ }
1526
+ getInitStatus() {
1527
+ return this.cachedStatusSnapshot;
1528
+ }
1529
+ onInitStatusChange(cb) {
1530
+ this.statusListeners.add(cb);
1531
+ return () => {
1532
+ this.statusListeners.delete(cb);
1533
+ };
1534
+ }
1535
+ /** Expose circuit breaker state for health monitoring */
1536
+ getCircuitState() {
1537
+ return this.circuitBreaker.getState();
1538
+ }
1539
+ static getInstance() {
1540
+ if (!_PGLiteConnectionPool.instance) {
1541
+ _PGLiteConnectionPool.instance = new _PGLiteConnectionPool();
1542
+ }
1543
+ return _PGLiteConnectionPool.instance;
1544
+ }
1545
+ async initialize() {
1546
+ if (typeof window === "undefined" && typeof document === "undefined") return;
1547
+ if (this.isInitialized) return;
1548
+ if (this.initializationPromise) return this.initializationPromise;
1549
+ const cbState = this.circuitBreaker.getState();
1550
+ if (cbState.isOpen && cbState.nextAttempt && Date.now() < cbState.nextAttempt) {
1551
+ this.initStatus = "failed";
1552
+ this.initError = `Circuit open \u2014 too many init failures. Next attempt after ${new Date(cbState.nextAttempt).toISOString()}`;
1553
+ this.notifyStatusChange();
1554
+ throw new Error(this.initError);
1555
+ }
1556
+ this.initializationPromise = this.circuitBreaker.execute(() => this.doInitialize()).catch((err) => {
1557
+ this.initializationPromise = null;
1558
+ if (this.circuitBreaker.getState().isOpen) {
1559
+ this.startHealthMonitor();
1560
+ }
1561
+ throw err;
1562
+ });
1563
+ return this.initializationPromise;
1564
+ }
1565
+ /**
1566
+ * Boot the WASM worker. Extracted so `withRetry` can wrap just this step.
1567
+ * Each retry creates a fresh Worker — previous failed workers are abandoned.
1568
+ */
1569
+ /** Max time to wait for WASM worker boot before giving up. */
1570
+ static BOOT_TIMEOUT_MS = 3e4;
1571
+ async bootInMainThreadFallback(expectedDataDir) {
1572
+ const [
1573
+ { PGlite },
1574
+ { vector },
1575
+ { uuid_ossp },
1576
+ { pg_trgm },
1577
+ { ltree },
1578
+ { seg },
1579
+ { tcn },
1580
+ { tablefunc }
1581
+ ] = await Promise.all([
1582
+ import("@electric-sql/pglite"),
1583
+ import("@electric-sql/pglite/vector"),
1584
+ import("@electric-sql/pglite/contrib/uuid_ossp"),
1585
+ import("@electric-sql/pglite/contrib/pg_trgm"),
1586
+ import("@electric-sql/pglite/contrib/ltree"),
1587
+ import("@electric-sql/pglite/contrib/seg"),
1588
+ import("@electric-sql/pglite/contrib/tcn"),
1589
+ import("@electric-sql/pglite/contrib/tablefunc")
1590
+ ]);
1591
+ const db = new PGlite(expectedDataDir, {
1592
+ extensions: {
1593
+ vector,
1594
+ uuid_ossp,
1595
+ pg_trgm,
1596
+ ltree,
1597
+ seg,
1598
+ tcn,
1599
+ tablefunc,
1600
+ live
1601
+ }
1602
+ });
1603
+ await db.waitReady;
1604
+ return db;
1605
+ }
1606
+ shouldPreferCompatibilityMode() {
1607
+ if (typeof window === "undefined") return false;
1608
+ if (process.env.NEXT_PUBLIC_PGLITE_FORCE_COMPAT_MODE === "true") return true;
1609
+ const host = window.location.hostname;
1610
+ const isVercelPreview = host.endsWith(".vercel.app");
1611
+ return isVercelPreview;
1612
+ }
1613
+ async bootWorker(expectedDataDir) {
1614
+ if (this.dataDir !== expectedDataDir) {
1615
+ throw new Error("Scope changed during boot");
1616
+ }
1617
+ if (this.shouldPreferCompatibilityMode()) {
1618
+ this.addProgressStep({
1619
+ step: "fallback",
1620
+ label: "Compatibility mode (preview)",
1621
+ status: "active"
1622
+ });
1623
+ const db = await this.bootInMainThreadFallback(expectedDataDir);
1624
+ this.addProgressStep({
1625
+ step: "fallback",
1626
+ label: "Compatibility mode active",
1627
+ status: "done"
1628
+ });
1629
+ return db;
1630
+ }
1631
+ const workerScript = new Worker(
1632
+ new URL("../../public/workers/pglite-worker.js", import.meta.url),
1633
+ { type: "module" }
1634
+ );
1635
+ const workerOptions = {
1636
+ extensions: { live },
1637
+ dataDir: this.dataDir
1638
+ };
1639
+ let timeoutId = null;
1640
+ const bootPromise = PGliteWorker.create(workerScript, workerOptions).then(async (db) => {
1641
+ await db.waitReady;
1642
+ return db;
1643
+ });
1644
+ const timeoutPromise = new Promise((_, reject) => {
1645
+ timeoutId = setTimeout(() => {
1646
+ try {
1647
+ workerScript.terminate();
1648
+ } catch {
1649
+ }
1650
+ reject(new Error("Database loading timed out \u2014 click retry"));
1651
+ }, _PGLiteConnectionPool.BOOT_TIMEOUT_MS);
1652
+ });
1653
+ try {
1654
+ return await Promise.race([bootPromise, timeoutPromise]);
1655
+ } catch (workerErr) {
1656
+ this.addProgressStep({
1657
+ step: "fallback",
1658
+ label: "Switching to compatibility mode",
1659
+ status: "active"
1660
+ });
1661
+ try {
1662
+ const fallbackDb = await this.bootInMainThreadFallback(expectedDataDir);
1663
+ this.addProgressStep({
1664
+ step: "fallback",
1665
+ label: "Compatibility mode active",
1666
+ status: "done"
1667
+ });
1668
+ return fallbackDb;
1669
+ } catch (fallbackErr) {
1670
+ this.addProgressStep({
1671
+ step: "fallback",
1672
+ label: fallbackErr instanceof Error ? `Compatibility mode failed: ${fallbackErr.message}` : "Compatibility mode failed",
1673
+ status: "done"
1674
+ });
1675
+ throw workerErr;
1676
+ }
1677
+ } finally {
1678
+ if (timeoutId) clearTimeout(timeoutId);
1679
+ }
1680
+ }
1681
+ async doInitialize() {
1682
+ if (typeof window === "undefined" && typeof document === "undefined") return;
1683
+ const initDataDir = this.dataDir;
1684
+ const myGeneration = ++this.initGeneration;
1685
+ const isStale = () => this.initGeneration !== myGeneration;
1686
+ this.initStatus = "initializing";
1687
+ this.initError = null;
1688
+ this.progressSteps = [];
1689
+ this.notifyStatusChange();
1690
+ try {
1691
+ this.addProgressStep({ step: "wasm", label: "Loading database", status: "active" });
1692
+ const typedDb = await withRetry(() => this.bootWorker(initDataDir), {
1693
+ maxAttempts: 3,
1694
+ baseDelayMs: 1500,
1695
+ maxDelayMs: 12e3,
1696
+ jitter: 0.2,
1697
+ shouldRetry: (error, _attempt) => {
1698
+ if (isStale()) return false;
1699
+ if (this.dataDir !== initDataDir) return false;
1700
+ if (error.message.includes("not found") || error.message.includes("404")) return false;
1701
+ if (error.message === "Scope changed during boot") return false;
1702
+ return true;
1703
+ },
1704
+ onRetry: (error, attempt, delayMs) => {
1705
+ if (isStale()) return;
1706
+ console.warn(`[PGLite] WASM boot retry ${attempt}/3 in ${delayMs}ms:`, error.message);
1707
+ this.addProgressStep({
1708
+ step: "wasm",
1709
+ label: `Retrying (${attempt}/3)...`,
1710
+ status: "active"
1711
+ });
1712
+ }
1713
+ });
1714
+ if (isStale()) {
1715
+ try {
1716
+ typedDb.close?.();
1717
+ } catch (e) {
1718
+ console.warn("[PGLite] Close on stale:", e);
1719
+ }
1720
+ return;
1721
+ }
1722
+ this.addProgressStep({ step: "wasm", label: "Database loaded", status: "done" });
1723
+ if (this.dataDir !== initDataDir) {
1724
+ try {
1725
+ typedDb.close?.();
1726
+ } catch (e) {
1727
+ console.warn("[PGLite] Close on stale:", e);
1728
+ }
1729
+ this.initializationPromise = null;
1730
+ this.initStatus = "idle";
1731
+ this.notifyStatusChange();
1732
+ return;
1733
+ }
1734
+ await initializeSchema(typedDb, (p) => {
1735
+ if (!isStale()) this.addProgressStep(p);
1736
+ });
1737
+ if (this.dataDir !== initDataDir || isStale()) {
1738
+ try {
1739
+ typedDb.close?.();
1740
+ } catch (e) {
1741
+ console.warn("[PGLite] Close on stale:", e);
1742
+ }
1743
+ this.initializationPromise = null;
1744
+ if (!isStale()) {
1745
+ this.initStatus = "idle";
1746
+ this.notifyStatusChange();
1747
+ }
1748
+ return;
1749
+ }
1750
+ this.addProgressStep({ step: "done", label: "Database ready", status: "done" });
1751
+ this.connection = typedDb;
1752
+ this.isInitialized = true;
1753
+ this.initStatus = "ready";
1754
+ this.notifyStatusChange();
1755
+ } catch (error) {
1756
+ if (this.dataDir === initDataDir && !isStale()) {
1757
+ const msg = error instanceof Error ? error.message : String(error);
1758
+ this.initStatus = "failed";
1759
+ this.initError = msg;
1760
+ this.notifyStatusChange();
1761
+ console.error("[PGLite] Init failed after retries:", msg);
1762
+ throw error;
1763
+ }
1764
+ this.initializationPromise = null;
1765
+ }
1766
+ }
1767
+ /** Return the single connection. Must be initialized first. */
1768
+ getConnection() {
1769
+ if (!this.connection) {
1770
+ throw new Error("[PGLite] No connection available \u2014 not initialized");
1771
+ }
1772
+ return this.connection;
1773
+ }
1774
+ /** Run an operation against the single connection. */
1775
+ async withConnection(operation) {
1776
+ await this.initialize();
1777
+ return operation(this.getConnection());
1778
+ }
1779
+ /** Close current connection and prepare for new scope. */
1780
+ resetForScope(dataDir) {
1781
+ this.stopHealthMonitor();
1782
+ this.healthMonitorFailures = 0;
1783
+ if (this.connection) {
1784
+ try {
1785
+ this.connection.close?.();
1786
+ } catch (e) {
1787
+ console.warn("[PGLite] Close error:", e);
1788
+ }
1789
+ }
1790
+ this.connection = null;
1791
+ this.isInitialized = false;
1792
+ this.initializationPromise = null;
1793
+ this.dataDir = dataDir;
1794
+ this.initStatus = "idle";
1795
+ this.initError = null;
1796
+ this.progressSteps = [];
1797
+ this.circuitBreaker.reset();
1798
+ this.notifyStatusChange();
1799
+ }
1800
+ shutdown() {
1801
+ this.stopHealthMonitor();
1802
+ this.healthMonitorFailures = 0;
1803
+ if (this.connection) {
1804
+ try {
1805
+ this.connection.close?.();
1806
+ } catch (e) {
1807
+ console.warn("[PGLite] Close error:", e);
1808
+ }
1809
+ }
1810
+ this.connection = null;
1811
+ this.isInitialized = false;
1812
+ this.initializationPromise = null;
1813
+ this.circuitBreaker.reset();
1814
+ }
1815
+ getStats() {
1816
+ return {
1817
+ poolSize: this.connection ? 1 : 0,
1818
+ availableConnections: this.connection && this.isInitialized ? 1 : 0,
1819
+ busyConnections: 0,
1820
+ isInitialized: this.isInitialized,
1821
+ averageQueryTime: 0
1822
+ };
1823
+ }
1824
+ getPoolStatus() {
1825
+ return { active: 0, idle: this.connection ? 1 : 0, total: this.connection ? 1 : 0 };
1826
+ }
1827
+ async healthCheck() {
1828
+ try {
1829
+ if (!this.connection) return false;
1830
+ await this.connection.query("SELECT 1");
1831
+ return true;
1832
+ } catch {
1833
+ return false;
1834
+ }
1835
+ }
1836
+ /**
1837
+ * Start automatic health monitoring for circuit breaker recovery.
1838
+ * When circuit is open, periodically probes via half-open path.
1839
+ * Stops after MAX_HEALTH_FAILURES consecutive probe failures.
1840
+ */
1841
+ startHealthMonitor() {
1842
+ if (this.healthMonitorInterval) return;
1843
+ this.healthMonitorInterval = setInterval(async () => {
1844
+ const cbState = this.circuitBreaker.getState();
1845
+ if (!cbState.isOpen) {
1846
+ this.healthMonitorFailures = 0;
1847
+ return;
1848
+ }
1849
+ if (this.healthMonitorFailures >= _PGLiteConnectionPool.MAX_HEALTH_FAILURES) {
1850
+ this.stopHealthMonitor();
1851
+ console.warn(
1852
+ "[PGLite] Health monitor stopped after",
1853
+ this.healthMonitorFailures,
1854
+ "consecutive failures. Call retryDbInit() manually."
1855
+ );
1856
+ return;
1857
+ }
1858
+ try {
1859
+ this.initializationPromise = null;
1860
+ await this.initialize();
1861
+ this.healthMonitorFailures = 0;
1862
+ console.info("[PGLite] Health monitor: circuit recovered");
1863
+ } catch {
1864
+ this.healthMonitorFailures++;
1865
+ console.warn(
1866
+ `[PGLite] Health monitor probe failed (${this.healthMonitorFailures}/${_PGLiteConnectionPool.MAX_HEALTH_FAILURES})`
1867
+ );
1868
+ }
1869
+ }, _PGLiteConnectionPool.HEALTH_MONITOR_INTERVAL_MS);
1870
+ }
1871
+ stopHealthMonitor() {
1872
+ if (this.healthMonitorInterval) {
1873
+ clearInterval(this.healthMonitorInterval);
1874
+ this.healthMonitorInterval = null;
1875
+ }
1876
+ }
1877
+ // Kept for import compat — no-ops for single connection
1878
+ releaseConnection(_connection) {
1879
+ }
1880
+ forceRecycleStale(_maxAgeMs) {
1881
+ return 0;
1882
+ }
1883
+ };
1884
+ var connectionManager = PGLiteConnectionPool.getInstance();
1885
+ function getDbInitStatus() {
1886
+ return connectionManager.getInitStatus();
1887
+ }
1888
+ function onDbInitStatusChange(cb) {
1889
+ return connectionManager.onInitStatusChange(cb);
1890
+ }
1891
+ async function retryDbInit() {
1892
+ const { status } = connectionManager.getInitStatus();
1893
+ if (status === "ready") {
1894
+ const healthy = await connectionManager.healthCheck();
1895
+ if (healthy) return;
1896
+ }
1897
+ connectionManager.resetForScope(currentDataDir);
1898
+ await connectionManager.initialize();
1899
+ }
1900
+ async function restartDbInit() {
1901
+ connectionManager.resetForScope(currentDataDir);
1902
+ await connectionManager.initialize();
1903
+ }
1904
+ async function forceHardReset() {
1905
+ connectionManager.shutdown();
1906
+ try {
1907
+ const { deleteDatabaseByDataDir } = await import("./storage-L7MWNSPG.js");
1908
+ await deleteDatabaseByDataDir(currentDataDir);
1909
+ } catch (err) {
1910
+ console.warn("[PGLite] forceHardReset: IDB delete failed (continuing anyway):", err);
1911
+ }
1912
+ connectionManager.resetForScope(currentDataDir);
1913
+ await connectionManager.initialize();
1914
+ }
1915
+ function isDatabaseReady() {
1916
+ if (typeof window === "undefined" && typeof document === "undefined") return false;
1917
+ return connectionManager.getStats().isInitialized;
1918
+ }
1919
+ async function getDB() {
1920
+ if (typeof window === "undefined" && typeof document === "undefined") {
1921
+ throw new Error("PGLite is browser-only");
1922
+ }
1923
+ const { status, error } = connectionManager.getInitStatus();
1924
+ if (status === "idle") {
1925
+ throw new Error("Database not yet initialized \u2014 PGLiteProvider has not set scope");
1926
+ }
1927
+ if (status === "failed") {
1928
+ console.warn("[PGLite] getDB() called in failed state \u2014 attempting via circuit breaker");
1929
+ try {
1930
+ await connectionManager.initialize();
1931
+ return connectionManager.getConnection();
1932
+ } catch (retryErr) {
1933
+ throw new Error(
1934
+ `Database initialization failed: ${error ?? (retryErr instanceof Error ? retryErr.message : "unknown error")}`
1935
+ );
1936
+ }
1937
+ }
1938
+ await connectionManager.initialize();
1939
+ return connectionManager.getConnection();
1940
+ }
1941
+ async function withDB(operation) {
1942
+ const db = await getDB();
1943
+ return operation(db);
1944
+ }
1945
+ async function withTransaction(operation) {
1946
+ return withDB((db) => db.transaction(operation));
1947
+ }
1948
+ function getConnectionPoolStats() {
1949
+ return connectionManager.getStats();
1950
+ }
1951
+ async function checkConnectionPoolHealth() {
1952
+ return connectionManager.healthCheck();
1953
+ }
1954
+ function getDbHealth() {
1955
+ return {
1956
+ isReady: isDatabaseReady(),
1957
+ stats: connectionManager.getStats(),
1958
+ circuit: connectionManager.getCircuitState()
1959
+ };
1960
+ }
1961
+ function getPoolStatus() {
1962
+ return connectionManager.getPoolStatus();
1963
+ }
1964
+ function forceRecycleStale(_maxAgeMs) {
1965
+ return 0;
1966
+ }
1967
+ async function getSchemaVersion() {
1968
+ try {
1969
+ const pool = PGLiteConnectionPool.getInstance();
1970
+ const db = pool.getConnection();
1971
+ const result = await db.query(
1972
+ "SELECT version FROM schema_version LIMIT 1"
1973
+ );
1974
+ return result.rows[0]?.version ?? null;
1975
+ } catch {
1976
+ return null;
1977
+ }
1978
+ }
1979
+ function shutdownPool() {
1980
+ connectionManager.shutdown();
1981
+ pgInstance = null;
1982
+ instancePromise = null;
1983
+ }
1984
+ function getCurrentScope() {
1985
+ return currentScope;
1986
+ }
1987
+ function getCurrentDataDir() {
1988
+ return currentDataDir;
1989
+ }
1990
+ function setDbScope(scope) {
1991
+ const nextDataDir = getDataDirForScope(scope);
1992
+ if (nextDataDir === currentDataDir) {
1993
+ currentScope = scope;
1994
+ return;
1995
+ }
1996
+ currentScope = scope;
1997
+ currentDataDir = nextDataDir;
1998
+ connectionManager.resetForScope(nextDataDir);
1999
+ pgInstance = null;
2000
+ instancePromise = null;
2001
+ }
2002
+ var pgInstance = null;
2003
+ var instancePromise = null;
2004
+ async function getDatabase() {
2005
+ if (pgInstance) return pgInstance;
2006
+ if (instancePromise) return instancePromise;
2007
+ instancePromise = (async () => {
2008
+ try {
2009
+ const workerScript = new Worker(
2010
+ new URL("../../public/workers/pglite-worker.js", import.meta.url),
2011
+ { type: "module" }
2012
+ );
2013
+ const workerOptions = {
2014
+ extensions: { live },
2015
+ dataDir: currentDataDir
2016
+ };
2017
+ const dbFromCreate = await PGliteWorker.create(workerScript, workerOptions);
2018
+ await dbFromCreate.waitReady;
2019
+ const fullyTypedDb = dbFromCreate;
2020
+ await initializeSchema(fullyTypedDb);
2021
+ pgInstance = fullyTypedDb;
2022
+ return fullyTypedDb;
2023
+ } catch (error) {
2024
+ console.error("Failed to initialize PGLite:", error);
2025
+ instancePromise = null;
2026
+ throw error;
2027
+ }
2028
+ })();
2029
+ return instancePromise;
2030
+ }
2031
+
2032
+ export {
2033
+ DORMANT_BIOSIGNAL_BRIDGES,
2034
+ isBiosignalBridgeId,
2035
+ DORMANT_BIOSIGNAL_PROVIDERS,
2036
+ isBiosignalProviderId,
2037
+ initializeSchema,
2038
+ getDataDirForScope,
2039
+ PGLiteConnectionPool,
2040
+ getDbInitStatus,
2041
+ onDbInitStatusChange,
2042
+ retryDbInit,
2043
+ restartDbInit,
2044
+ forceHardReset,
2045
+ isDatabaseReady,
2046
+ getDB,
2047
+ withDB,
2048
+ withTransaction,
2049
+ getConnectionPoolStats,
2050
+ checkConnectionPoolHealth,
2051
+ getDbHealth,
2052
+ getPoolStatus,
2053
+ forceRecycleStale,
2054
+ getSchemaVersion,
2055
+ shutdownPool,
2056
+ getCurrentScope,
2057
+ getCurrentDataDir,
2058
+ setDbScope,
2059
+ getDatabase
2060
+ };