ai_co_discuss 0.3.71 → 0.3.72

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.
@@ -1,1645 +1,1675 @@
1
- CREATE TABLE IF NOT EXISTS rooms (
2
- id TEXT PRIMARY KEY, title TEXT NOT NULL, goal TEXT NOT NULL,
3
- state_text TEXT NOT NULL DEFAULT '', state_until INTEGER NOT NULL DEFAULT 0,
4
- created_at TEXT NOT NULL
5
- );
6
- CREATE TABLE IF NOT EXISTS room_directory (
7
- room_id TEXT PRIMARY KEY REFERENCES rooms(id),
8
- position INTEGER NOT NULL DEFAULT 0,
9
- archived INTEGER NOT NULL DEFAULT 0 CHECK (archived IN (0,1))
10
- );
11
- INSERT OR IGNORE INTO room_directory (room_id,position)
12
- SELECT id,ROW_NUMBER() OVER (ORDER BY created_at,id) FROM rooms;
13
- CREATE TRIGGER IF NOT EXISTS add_room_to_directory AFTER INSERT ON rooms
14
- BEGIN
15
- INSERT INTO room_directory (room_id,position)
16
- VALUES (NEW.id,(SELECT COALESCE(MAX(position),0)+1 FROM room_directory));
17
- END;
18
- CREATE TABLE IF NOT EXISTS topic_folders (
19
- id TEXT PRIMARY KEY, name TEXT NOT NULL, position INTEGER NOT NULL DEFAULT 0
20
- );
21
- -- Site-wide preferences of the person managing the site; `last_room` is the room they last opened on any device.
22
- CREATE TABLE IF NOT EXISTS site_preferences (
23
- key TEXT PRIMARY KEY,
24
- value TEXT NOT NULL
25
- );
26
- CREATE TABLE IF NOT EXISTS directory_settings (
27
- id INTEGER PRIMARY KEY CHECK (id=1),
28
- uncategorized_position INTEGER CHECK (uncategorized_position>=0)
29
- );
30
- CREATE TABLE IF NOT EXISTS room_folder_assignments (
31
- room_id TEXT PRIMARY KEY REFERENCES rooms(id),
32
- folder_id TEXT REFERENCES topic_folders(id) ON DELETE SET NULL
33
- );
34
- INSERT OR IGNORE INTO room_folder_assignments (room_id,folder_id)
35
- SELECT id,NULL FROM rooms;
36
- CREATE TRIGGER IF NOT EXISTS add_room_to_folder_assignments AFTER INSERT ON rooms
37
- BEGIN
38
- INSERT INTO room_folder_assignments (room_id,folder_id) VALUES (NEW.id,NULL);
39
- END;
40
- CREATE TABLE IF NOT EXISTS room_creations (
41
- submission_id TEXT PRIMARY KEY,
42
- room_id TEXT NOT NULL UNIQUE REFERENCES rooms(id),
43
- request_text TEXT NOT NULL
44
- );
45
- CREATE TABLE IF NOT EXISTS room_user_requests (
46
- room_id TEXT PRIMARY KEY REFERENCES rooms(id),
47
- text TEXT NOT NULL DEFAULT ''
48
- );
49
- CREATE TABLE IF NOT EXISTS room_briefs (
50
- room_id TEXT PRIMARY KEY REFERENCES rooms(id),
51
- text TEXT NOT NULL, until INTEGER NOT NULL, updated_at TEXT NOT NULL
52
- );
53
- CREATE TABLE IF NOT EXISTS messages (
54
- id INTEGER PRIMARY KEY AUTOINCREMENT, room_id TEXT NOT NULL REFERENCES rooms(id),
55
- author TEXT NOT NULL, text TEXT NOT NULL, reply_to INTEGER REFERENCES messages(id),
56
- source TEXT NOT NULL, submission_id TEXT NOT NULL, created_at TEXT NOT NULL,
57
- UNIQUE(room_id, submission_id)
58
- );
59
- CREATE INDEX IF NOT EXISTS messages_room ON messages(room_id, id);
60
- CREATE TABLE IF NOT EXISTS attachments (
61
- id TEXT PRIMARY KEY, room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
62
- message_id INTEGER REFERENCES messages(id) ON DELETE CASCADE,
63
- position INTEGER NOT NULL DEFAULT 0,
64
- file_name TEXT NOT NULL, content_type TEXT NOT NULL,
65
- size INTEGER NOT NULL, sha256 TEXT NOT NULL,
66
- actor TEXT NOT NULL, submission_id TEXT NOT NULL, created_at TEXT NOT NULL,
67
- UNIQUE(room_id, submission_id)
68
- );
69
- CREATE INDEX IF NOT EXISTS attachments_message ON attachments(message_id, position);
70
- CREATE TRIGGER IF NOT EXISTS validate_attachment_link BEFORE UPDATE OF message_id ON attachments
71
- WHEN NEW.message_id IS NOT NULL AND (
72
- (OLD.message_id IS NOT NULL AND OLD.message_id != NEW.message_id)
73
- OR NOT EXISTS (
74
- SELECT 1 FROM messages m JOIN message_details d ON d.message_id=m.id
75
- WHERE m.id=NEW.message_id AND m.room_id=NEW.room_id AND d.submitted_by=NEW.actor
76
- )
77
- )
78
- BEGIN
79
- SELECT RAISE(ABORT, 'attachment_unavailable');
80
- END;
81
- CREATE TABLE IF NOT EXISTS message_summaries (
82
- message_id INTEGER PRIMARY KEY REFERENCES messages(id),
83
- text TEXT NOT NULL DEFAULT '', author TEXT NOT NULL DEFAULT ''
84
- );
85
-
86
- CREATE TABLE IF NOT EXISTS topic_settings (
87
- room_id TEXT PRIMARY KEY REFERENCES rooms(id),
88
- host TEXT NOT NULL, background TEXT NOT NULL DEFAULT '', boundaries TEXT NOT NULL DEFAULT '',
89
- experts TEXT NOT NULL, focus TEXT NOT NULL DEFAULT ''
90
- );
91
- CREATE TABLE IF NOT EXISTS message_details (
92
- message_id INTEGER PRIMARY KEY REFERENCES messages(id),
93
- kind TEXT NOT NULL DEFAULT 'opinion', submitted_by TEXT NOT NULL
94
- );
95
- -- A member's message spoken on its own (its user asked in its own window), bound to no request or execution.
96
- CREATE TABLE IF NOT EXISTS proactive_messages (
97
- message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE
98
- );
99
- -- Site administrator account (single user) and its login sessions. Raw tokens and passwords are never stored.
100
- CREATE TABLE IF NOT EXISTS site_admin (
101
- id INTEGER PRIMARY KEY CHECK(id=1),
102
- username TEXT NOT NULL,
103
- password_hash TEXT NOT NULL,
104
- avatar TEXT NOT NULL DEFAULT '',
105
- failed_attempts INTEGER NOT NULL DEFAULT 0,
106
- locked_until INTEGER NOT NULL DEFAULT 0,
107
- created_at INTEGER NOT NULL
108
- );
109
- -- Logins end after a week unused or a month in all; label is the browser that signed in. The older table
110
- -- admin_sessions held logins that never expired and is no longer read, so upgrading signs everyone out once.
111
- CREATE TABLE IF NOT EXISTS admin_logins (
112
- token_hash TEXT PRIMARY KEY,
113
- label TEXT NOT NULL DEFAULT '',
114
- created_at INTEGER NOT NULL,
115
- last_seen INTEGER NOT NULL
116
- );
117
- -- Wrong-password backoff per client address; the row with source '*' counts all addresses within an hour.
118
- -- site_admin.failed_attempts and locked_until are no longer used.
119
- -- The online website has no accounts: it accepts a device only if the paired computer signed it into its device list.
120
- -- machine_sign_keys holds each computer's signing key; machine_devices its latest signed list (devices' public keys and
121
- -- expiry). Website passkey tables of an earlier version (admin_passkeys, admin_challenges) are no longer read.
122
- CREATE TABLE IF NOT EXISTS machine_sign_keys (
123
- machine_id TEXT PRIMARY KEY REFERENCES local_machines(id) ON DELETE CASCADE,
124
- sign_key TEXT NOT NULL
125
- );
126
- CREATE TABLE IF NOT EXISTS machine_devices (
127
- machine_id TEXT PRIMARY KEY REFERENCES local_machines(id) ON DELETE CASCADE,
128
- body TEXT NOT NULL,
129
- signature TEXT NOT NULL,
130
- issued_at INTEGER NOT NULL
131
- );
132
- CREATE TABLE IF NOT EXISTS admin_attempts (
133
- source TEXT PRIMARY KEY,
134
- failed INTEGER NOT NULL DEFAULT 0,
135
- locked_until INTEGER NOT NULL DEFAULT 0,
136
- updated_at INTEGER NOT NULL
137
- );
138
- -- One-time registration token from `codiscuss token` on the deploying computer; cleared once the administrator registers.
139
- CREATE TABLE IF NOT EXISTS admin_setup (
140
- id INTEGER PRIMARY KEY CHECK(id=1),
141
- token_hash TEXT NOT NULL,
142
- created_at INTEGER NOT NULL
143
- );
144
-
145
- -- Every room is a free collaboration room; a discussion is a session inside it. Holds the owner's control hash.
146
- CREATE TABLE IF NOT EXISTS room_modes (
147
- room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
148
- mode TEXT NOT NULL CHECK(mode='free'),
149
- control_hash TEXT NOT NULL DEFAULT ''
150
- );
151
-
152
- -- A room's limits for its next discussion, set at creation or in the topic while no discussion is open; a started
153
- -- discussion copies them into session_limits.
154
- CREATE TABLE IF NOT EXISTS discussion_limits (
155
- room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
156
- max_rounds INTEGER NOT NULL DEFAULT 0,
157
- max_minutes INTEGER NOT NULL DEFAULT 0,
158
- execution_minutes INTEGER NOT NULL DEFAULT 0
159
- );
160
- INSERT OR IGNORE INTO discussion_limits(room_id,execution_minutes) SELECT id,0 FROM rooms;
161
- DROP TRIGGER IF EXISTS limits_room_created;
162
- CREATE TRIGGER limits_room_created AFTER INSERT ON rooms
163
- BEGIN
164
- INSERT INTO discussion_limits(room_id,execution_minutes) VALUES (NEW.id,0);
165
- END;
166
- -- Freeze dispatch-to-publication time independently of the recent execution list.
167
- CREATE TABLE IF NOT EXISTS message_timings (
168
- message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
169
- elapsed_seconds INTEGER NOT NULL CHECK(elapsed_seconds>=0)
170
- );
171
- -- A failed assertion rolls back the whole D1 batch, including concurrent writes.
172
- CREATE TABLE IF NOT EXISTS runtime_assertions (
173
- id TEXT PRIMARY KEY, allowed INTEGER NOT NULL CHECK(allowed=1)
174
- );
175
- -- Triggers of the retired discussion-room model (speaking floor, turns, relay, room-scoped runtime and phase).
176
- -- Dropped on every start so an older database stops enforcing them; its leftover tables are removed by
177
- -- migrations/drop_legacy_discussion.sql.
178
- DROP TRIGGER IF EXISTS reject_archived_resume;
179
- DROP TRIGGER IF EXISTS validate_turn_answer;
180
- DROP TRIGGER IF EXISTS reject_inactive_opinion;
181
- DROP TRIGGER IF EXISTS reject_inactive_turn;
182
- DROP TRIGGER IF EXISTS reject_conclusion_with_open_turn;
183
- DROP TRIGGER IF EXISTS require_opinion_turn;
184
- DROP TRIGGER IF EXISTS initialize_room_floor;
185
- DROP TRIGGER IF EXISTS transfer_host_floor;
186
- DROP TRIGGER IF EXISTS require_message_floor;
187
- DROP TRIGGER IF EXISTS return_message_floor;
188
- DROP TRIGGER IF EXISTS require_host_floor;
189
- DROP TRIGGER IF EXISTS nominate_expert_floor;
190
- DROP TRIGGER IF EXISTS cancel_turn_floor;
191
- DROP TRIGGER IF EXISTS limits_topic_started;
192
- DROP TRIGGER IF EXISTS limits_phase_started;
193
- DROP TRIGGER IF EXISTS limits_turn_started;
194
- DROP TRIGGER IF EXISTS record_event_timing;
195
- DROP TRIGGER IF EXISTS runtime_generation;
196
- DROP TRIGGER IF EXISTS runtime_turn_created;
197
- DROP TRIGGER IF EXISTS relay_turn_promoted;
198
- DROP TRIGGER IF EXISTS relay_plan_replaced;
199
- DROP TRIGGER IF EXISTS relay_hold_user_message;
200
- DROP TRIGGER IF EXISTS relay_hold_goal;
201
- DROP TRIGGER IF EXISTS relay_hold_topic;
202
- DROP TRIGGER IF EXISTS relay_plan_concluded;
203
- DROP TRIGGER IF EXISTS relay_plan_participants;
204
- DROP TRIGGER IF EXISTS runtime_turn_answered;
205
- DROP TRIGGER IF EXISTS runtime_topic_stop;
206
- DROP TRIGGER IF EXISTS runtime_user_message;
207
- DROP TRIGGER IF EXISTS runtime_turn_cancelled;
208
- DROP TRIGGER IF EXISTS runtime_archive;
209
- DROP TRIGGER IF EXISTS runtime_participants_changed;
210
- DROP TRIGGER IF EXISTS runtime_experts_changed;
211
- DROP TRIGGER IF EXISTS runtime_goal_changed;
212
- DROP TRIGGER IF EXISTS runtime_constraints_changed;
213
- DROP TRIGGER IF EXISTS pause_clock_stopped;
214
- DROP TRIGGER IF EXISTS pause_clock_resumed;
215
- DROP TRIGGER IF EXISTS session_for_new_discussion;
216
- DROP TRIGGER IF EXISTS session_follows_phase_insert;
217
- DROP TRIGGER IF EXISTS session_follows_phase;
218
- DROP TRIGGER IF EXISTS conclusion_snapshot;
219
- DROP TRIGGER IF EXISTS revision_free_settings_insert;
220
- DROP TRIGGER IF EXISTS revision_free_settings_update;
221
- -- Room members; their requests and executions follow.
222
- CREATE TABLE IF NOT EXISTS room_members (
223
- id INTEGER PRIMARY KEY AUTOINCREMENT,
224
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
225
- name TEXT NOT NULL,
226
- adapter TEXT NOT NULL DEFAULT '',
227
- role TEXT NOT NULL DEFAULT 'member' CHECK(role IN ('member','host')),
228
- status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','away','removed')),
229
- token_hash TEXT NOT NULL,
230
- read_until INTEGER NOT NULL DEFAULT 0,
231
- created_at TEXT NOT NULL,
232
- access TEXT NOT NULL DEFAULT 'runner',
233
- session_mode TEXT NOT NULL DEFAULT 'new'
234
- );
235
- CREATE UNIQUE INDEX IF NOT EXISTS member_name ON room_members(room_id,name) WHERE status<>'removed';
236
- CREATE UNIQUE INDEX IF NOT EXISTS member_credential ON room_members(room_id,token_hash);
237
-
238
- -- Local service attestation, never a permission grant. Re-imported rooms require a fresh attestation.
239
- CREATE TABLE IF NOT EXISTS member_review_capabilities (
240
- member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
241
- member_revision TEXT NOT NULL,
242
- config_version TEXT NOT NULL,
243
- adapter TEXT NOT NULL,
244
- recorded_at INTEGER NOT NULL,
245
- generation INTEGER NOT NULL DEFAULT 0
246
- );
247
- CREATE TRIGGER IF NOT EXISTS invalidate_member_review_capability
248
- AFTER UPDATE OF token_hash,status,adapter,access,session_mode ON room_members
249
- WHEN NEW.token_hash<>OLD.token_hash OR NEW.status<>OLD.status OR NEW.adapter<>OLD.adapter
250
- OR NEW.access<>OLD.access OR NEW.session_mode<>OLD.session_mode
251
- BEGIN
252
- INSERT INTO member_review_capabilities(member_id,member_revision,config_version,adapter,recorded_at,generation)
253
- VALUES(NEW.id,'','',NEW.adapter,0,1)
254
- ON CONFLICT(member_id) DO UPDATE SET member_revision='',config_version='',generation=generation+1;
255
- END;
256
- -- A manual member's own software session, reported by its CLI on posting; the local service uses it to take the member over.
257
- CREATE TABLE IF NOT EXISTS member_sessions (
258
- member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
259
- room_id TEXT NOT NULL,
260
- session_id TEXT NOT NULL,
261
- client TEXT NOT NULL
262
- );
263
- -- Why the local service could not take over a manual member's reported session; shown only while that session is still the reported one.
264
- CREATE TABLE IF NOT EXISTS member_adopt_failures (
265
- member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
266
- room_id TEXT NOT NULL,
267
- session_id TEXT NOT NULL,
268
- reason TEXT NOT NULL
269
- );
270
- -- A manual member's own registration; it is bound to the credential it used, so a re-issued credential must register again.
271
- CREATE TABLE IF NOT EXISTS member_registrations (
272
- member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
273
- room_id TEXT NOT NULL,
274
- token_hash TEXT NOT NULL,
275
- registered_at TEXT NOT NULL
276
- );
277
- -- Marks members of the retired web-relay access; no new rows are written.
278
- CREATE TABLE IF NOT EXISTS member_manual_sources (
279
- member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
280
- source TEXT NOT NULL CHECK(source='web')
281
- );
282
- CREATE TABLE IF NOT EXISTS member_executions (
283
- id INTEGER PRIMARY KEY AUTOINCREMENT,
284
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
285
- member_id INTEGER NOT NULL REFERENCES room_members(id) ON DELETE CASCADE,
286
- client_id TEXT NOT NULL,
287
- status TEXT NOT NULL DEFAULT 'dispatched'
288
- CHECK(status IN ('dispatched','started','completed','cancelled','failed')),
289
- session_id TEXT NOT NULL DEFAULT '',
290
- lease_until INTEGER NOT NULL DEFAULT 0,
291
- created_at INTEGER NOT NULL,
292
- finished_at INTEGER NOT NULL DEFAULT 0,
293
- result TEXT NOT NULL DEFAULT '',
294
- changes TEXT NOT NULL DEFAULT '',
295
- -- The changed files the member's own edit tools wrote; '' when its software could not tell (see possible_conflicts).
296
- edited TEXT NOT NULL DEFAULT '',
297
- cwd TEXT NOT NULL DEFAULT '',
298
- activity TEXT NOT NULL DEFAULT '',
299
- activity_at INTEGER NOT NULL DEFAULT 0,
300
- UNIQUE(room_id,client_id)
301
- );
302
- -- What the owner wrote about each member (strengths, division of work), by name so it survives leaving and rejoining.
303
- CREATE TABLE IF NOT EXISTS member_descriptions (
304
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
305
- name TEXT NOT NULL,
306
- description TEXT NOT NULL,
307
- updated_at INTEGER NOT NULL,
308
- PRIMARY KEY(room_id,name)
309
- );
310
- -- Display labels the owner gave members (e.g. from a named preset); screens show them, the name stays the identity.
311
- CREATE TABLE IF NOT EXISTS member_labels (
312
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
313
- name TEXT NOT NULL,
314
- label TEXT NOT NULL,
315
- updated_at INTEGER NOT NULL,
316
- PRIMARY KEY(room_id,name)
317
- );
318
- -- Progress notes the runner saw a member's software write between tool calls; numbered per execution so a retried renewal adds nothing.
319
- CREATE TABLE IF NOT EXISTS execution_notes (
320
- execution_id INTEGER NOT NULL REFERENCES member_executions(id) ON DELETE CASCADE,
321
- seq INTEGER NOT NULL,
322
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
323
- text TEXT NOT NULL,
324
- created_at INTEGER NOT NULL,
325
- PRIMARY KEY(execution_id,seq)
326
- );
327
- -- Operations a member's software refused without asking, since a headless run cannot ask for approval; reported once by the
328
- -- runner after the process ends, as a JSON list of {tool, detail, rule}, so the owner can see and allow them.
329
- CREATE TABLE IF NOT EXISTS execution_denials (
330
- execution_id INTEGER PRIMARY KEY REFERENCES member_executions(id) ON DELETE CASCADE,
331
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
332
- denials TEXT NOT NULL
333
- );
334
- -- A manual member's session refused a delivery: the runner keeps the execution and delivers again at retry_at
335
- -- (0 while delivering). The owner may ask for it now or stop it, and the runner then fails it. Not part of backups.
336
- CREATE TABLE IF NOT EXISTS execution_retries (
337
- execution_id INTEGER PRIMARY KEY REFERENCES member_executions(id) ON DELETE CASCADE,
338
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
339
- attempt INTEGER NOT NULL,
340
- retry_at INTEGER NOT NULL DEFAULT 0,
341
- reason TEXT NOT NULL DEFAULT '',
342
- stopped INTEGER NOT NULL DEFAULT 0
343
- );
344
- -- Phases the runner saw a member's software go through in one execution, as
345
- -- millisecond offsets from its delivery; numbered per execution, and a resent segment keeps the later end. Not part of backups.
346
- CREATE TABLE IF NOT EXISTS execution_segments (
347
- source TEXT NOT NULL CHECK(source='free'),
348
- execution_id INTEGER NOT NULL,
349
- seq INTEGER NOT NULL,
350
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
351
- phase TEXT NOT NULL,
352
- tool TEXT NOT NULL DEFAULT '',
353
- start_ms INTEGER NOT NULL,
354
- end_ms INTEGER NOT NULL,
355
- PRIMARY KEY(source,execution_id,seq)
356
- );
357
- CREATE INDEX IF NOT EXISTS execution_segments_room ON execution_segments(room_id);
358
- -- One live execution per member: different members may run in parallel, one session never twice.
359
- CREATE UNIQUE INDEX IF NOT EXISTS one_member_execution ON member_executions(member_id)
360
- WHERE status IN ('dispatched','started');
361
- CREATE INDEX IF NOT EXISTS member_executions_member ON member_executions(member_id);
362
- -- The client-id uniqueness index cannot satisfy ORDER BY id LIMIT on a room's history.
363
- CREATE INDEX IF NOT EXISTS member_executions_room_recent ON member_executions(room_id,id);
364
- CREATE INDEX IF NOT EXISTS member_executions_room_changes ON member_executions(room_id,id) WHERE changes<>'';
365
- CREATE INDEX IF NOT EXISTS member_executions_room_live ON member_executions(room_id,id) WHERE status IN ('dispatched','started');
366
- CREATE TABLE IF NOT EXISTS member_requests (
367
- id INTEGER PRIMARY KEY AUTOINCREMENT,
368
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
369
- member_id INTEGER NOT NULL REFERENCES room_members(id) ON DELETE CASCADE,
370
- message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
371
- status TEXT NOT NULL DEFAULT 'pending'
372
- CHECK(status IN ('pending','delivering','completed','cancelled','failed')),
373
- execution_id INTEGER REFERENCES member_executions(id),
374
- answer_id INTEGER REFERENCES messages(id),
375
- created_at INTEGER NOT NULL,
376
- chain_id INTEGER NOT NULL DEFAULT 0,
377
- batch_id INTEGER NOT NULL DEFAULT 0,
378
- purpose TEXT NOT NULL DEFAULT 'ask',
379
- created_by TEXT NOT NULL DEFAULT 'user',
380
- UNIQUE(message_id,member_id)
381
- );
382
- CREATE INDEX IF NOT EXISTS member_requests_open ON member_requests(member_id,status);
383
- CREATE INDEX IF NOT EXISTS member_requests_execution ON member_requests(execution_id);
384
- CREATE INDEX IF NOT EXISTS member_requests_room ON member_requests(room_id);
385
- CREATE INDEX IF NOT EXISTS member_requests_room_status ON member_requests(room_id,status);
386
- CREATE INDEX IF NOT EXISTS member_requests_batch ON member_requests(batch_id,status);
387
- -- Web chatbots are no longer members: earlier ones are removed and their open requests cancelled; their messages keep
388
- -- the author name. member_manual_sources stays only to recognise those earlier members.
389
- UPDATE member_requests SET status='cancelled' WHERE status IN ('pending','delivering') AND member_id IN (
390
- SELECT member_id FROM member_manual_sources UNION SELECT id FROM room_members WHERE access='online');
391
- INSERT OR IGNORE INTO member_manual_sources (member_id,source) SELECT id,'web' FROM room_members WHERE access='online';
392
- UPDATE room_members SET access='manual' WHERE access='online';
393
- UPDATE room_members SET status='removed',role='member' WHERE status<>'removed' AND id IN (SELECT member_id FROM member_manual_sources);
394
- -- One durable disposition per @everyone message; also prevents a host fallback from being routed again after restart.
395
- -- The original group request may be deleted for a sequential queue, so request_id is a receipt, not a foreign key.
396
- CREATE TABLE IF NOT EXISTS group_routes (
397
- message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
398
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
399
- request_id INTEGER NOT NULL,
400
- action TEXT NOT NULL CHECK(action IN ('all','sequential','host'))
401
- );
402
- -- Visible record of each smart-scheduling decision in a discussion; it is not a message and moves no round or floor.
403
- CREATE TABLE IF NOT EXISTS scheduler_decisions (
404
- id INTEGER PRIMARY KEY AUTOINCREMENT,
405
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
406
- after_message_id INTEGER NOT NULL DEFAULT 0,
407
- kind TEXT NOT NULL CHECK(kind IN ('interjection','round')),
408
- label TEXT NOT NULL,
409
- confidence REAL,
410
- applied INTEGER NOT NULL DEFAULT 0 CHECK(applied IN (0,1)),
411
- provider TEXT NOT NULL,
412
- created_at TEXT NOT NULL
413
- );
414
- CREATE INDEX IF NOT EXISTS scheduler_decisions_room ON scheduler_decisions(room_id,id);
415
- -- What the decision model was asked and chose, shown folded under the decision; not part of backups.
416
- CREATE TABLE IF NOT EXISTS scheduler_decision_details (
417
- decision_id INTEGER PRIMARY KEY REFERENCES scheduler_decisions(id) ON DELETE CASCADE,
418
- detail TEXT NOT NULL
419
- );
420
- -- The owner's verdict on one decision; a 'bad' one may also say who should have answered and why.
421
- -- Kept apart from the decision so older databases need no column change.
422
- CREATE TABLE IF NOT EXISTS scheduler_feedback (
423
- decision_id INTEGER PRIMARY KEY REFERENCES scheduler_decisions(id) ON DELETE CASCADE,
424
- rating TEXT NOT NULL CHECK(rating IN ('good','bad')),
425
- should_be TEXT NOT NULL DEFAULT '',
426
- note TEXT NOT NULL DEFAULT '',
427
- created_at TEXT NOT NULL
428
- );
429
- -- Messages the local service is still asking the decision model about; a transient marker, not part of backups.
430
- CREATE TABLE IF NOT EXISTS scheduler_routing (
431
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
432
- message_id INTEGER NOT NULL,
433
- created_at INTEGER NOT NULL,
434
- PRIMARY KEY (room_id, message_id)
435
- );
436
- -- Older development databases may retain execution_deliveries; it is no longer read or written.
437
- -- What the runner's adapter reported each execution can do (stop it, see its progress), and whether its software stopped
438
- -- once the execution was voided: the page describes a stop as it really went (a window or App the runner cannot stop
439
- -- keeps generating). A side table so older databases need no column change; executions are not backed up, so neither is this.
440
- CREATE TABLE IF NOT EXISTS execution_capabilities (
441
- execution_id INTEGER PRIMARY KEY REFERENCES member_executions(id) ON DELETE CASCADE,
442
- room_id TEXT NOT NULL,
443
- stoppable INTEGER CHECK(stoppable IN (0,1)),
444
- progress INTEGER CHECK(progress IN (0,1)),
445
- stop_result TEXT NOT NULL DEFAULT '' CHECK(stop_result IN ('','stopped','unstoppable','unconfirmed'))
446
- );
447
- -- What a rewind does to each runner member's session, as its adapter reported: cut by the runner, rebuilt each time
448
- -- (fresh), or kept as it is. A member without a report is kept; manual and online members are always kept.
449
- CREATE TABLE IF NOT EXISTS member_capabilities (
450
- member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
451
- room_id TEXT NOT NULL,
452
- rewind TEXT NOT NULL CHECK(rewind IN ('cut','fresh','kept'))
453
- );
454
- -- What a member submitted for an execution the user had already voided. Refused as an answer, so it never joins the
455
- -- transcript or AI reads; kept so the page can show the late work folded, and so a member the runner could not stop
456
- -- is known to have finished that turn. One per execution; not backed up, like executions.
457
- CREATE TABLE IF NOT EXISTS late_answers (
458
- execution_id INTEGER PRIMARY KEY REFERENCES member_executions(id) ON DELETE CASCADE,
459
- room_id TEXT NOT NULL,
460
- text TEXT NOT NULL,
461
- created_at INTEGER NOT NULL
462
- );
463
- -- Why a request was cancelled; a side table so older databases need no column change. Requests cancelled before it existed have no row.
464
- CREATE TABLE IF NOT EXISTS request_cancel_reasons (
465
- request_id INTEGER PRIMARY KEY REFERENCES member_requests(id) ON DELETE CASCADE,
466
- reason TEXT NOT NULL CHECK(reason IN ('user_stop','member_away','member_removed','rerouted','no_reply','archived'))
467
- );
468
- -- Messages the owner took back: a retracted one no member began to handle, or the history a rewind returned to before.
469
- -- AI readers no longer see them; the page keeps each in place. No room column, so it never clashes with messages.room_id in joins.
470
- CREATE TABLE IF NOT EXISTS message_withdrawals (
471
- message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
472
- kind TEXT NOT NULL CHECK(kind IN ('retracted','rewound')),
473
- created_at INTEGER NOT NULL
474
- );
475
- -- An indexed room lookup without adding a room_id column that would make existing message joins ambiguous.
476
- CREATE TABLE IF NOT EXISTS message_withdrawal_rooms (
477
- message_id INTEGER PRIMARY KEY REFERENCES message_withdrawals(message_id) ON DELETE CASCADE,
478
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE
479
- );
480
- CREATE INDEX IF NOT EXISTS message_withdrawal_rooms_room ON message_withdrawal_rooms(room_id,message_id);
481
- INSERT INTO message_withdrawal_rooms(message_id,room_id)
482
- SELECT w.message_id,m.room_id FROM message_withdrawals w JOIN messages m ON m.id=w.message_id
483
- WHERE NOT EXISTS(SELECT 1 FROM message_withdrawal_rooms r WHERE r.message_id=w.message_id);
484
- CREATE TRIGGER IF NOT EXISTS withdrawal_room_insert AFTER INSERT ON message_withdrawals
485
- BEGIN
486
- INSERT INTO message_withdrawal_rooms(message_id,room_id) SELECT NEW.message_id,room_id FROM messages WHERE id=NEW.message_id;
487
- END;
488
- CREATE TRIGGER IF NOT EXISTS withdrawal_room_move AFTER UPDATE OF room_id ON messages
489
- BEGIN
490
- UPDATE message_withdrawal_rooms SET room_id=NEW.room_id WHERE message_id=NEW.id;
491
- END;
492
- -- Each rewind and, per member id, the executions it took back (JSON {member id: [execution ids]}): before the member's next
493
- -- execution the runner cuts its session before the earliest of them it finds. Not part of backups, since executions are not.
494
- CREATE TABLE IF NOT EXISTS room_rewinds (
495
- id INTEGER PRIMARY KEY AUTOINCREMENT,
496
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
497
- message_id INTEGER NOT NULL,
498
- cuts TEXT NOT NULL,
499
- created_at INTEGER NOT NULL
500
- );
501
- -- What produced a decision (model, threshold, prompt and package versions), so ratings stay comparable across versions.
502
- CREATE TABLE IF NOT EXISTS scheduler_decision_context (
503
- decision_id INTEGER PRIMARY KEY REFERENCES scheduler_decisions(id) ON DELETE CASCADE,
504
- context TEXT NOT NULL
505
- );
506
- -- The exact input the decision model saw (state and full questions with option descriptions); not part of backups.
507
- CREATE TABLE IF NOT EXISTS scheduler_decision_inputs (
508
- decision_id INTEGER PRIMARY KEY REFERENCES scheduler_decisions(id) ON DELETE CASCADE,
509
- input TEXT NOT NULL
510
- );
511
- -- A self-contained copy of each decision the owner currently rates bad, kept when its room is deleted so prompts can be reviewed.
512
- CREATE TABLE IF NOT EXISTS scheduler_feedback_cases (
513
- decision_id INTEGER PRIMARY KEY,
514
- room_id TEXT NOT NULL,
515
- room_title TEXT NOT NULL,
516
- snapshot TEXT NOT NULL,
517
- should_be TEXT NOT NULL DEFAULT '',
518
- note TEXT NOT NULL DEFAULT '',
519
- rated_at TEXT NOT NULL
520
- );
521
- -- Smart scheduling's suggestion that another member answer an unnamed message. The host keeps the message;
522
- -- the user may adopt before expires_at (waiting until the host's request ends for mode 'after'), otherwise it lapses.
523
- CREATE TABLE IF NOT EXISTS member_recommendations (
524
- message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
525
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
526
- member_id INTEGER NOT NULL REFERENCES room_members(id) ON DELETE CASCADE,
527
- provider TEXT NOT NULL,
528
- confidence REAL NOT NULL,
529
- created_at INTEGER NOT NULL,
530
- expires_at INTEGER NOT NULL,
531
- status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open','waiting','adopted')),
532
- mode TEXT NOT NULL DEFAULT ''
533
- );
534
- CREATE INDEX IF NOT EXISTS member_recommendations_room ON member_recommendations(room_id,status);
535
- CREATE INDEX IF NOT EXISTS member_recommendations_recent ON member_recommendations(room_id,message_id);
536
- -- Ordinary members can suggest a response without creating work or spending a chain budget.
537
- CREATE TABLE IF NOT EXISTS member_suggestions (
538
- message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
539
- member_id INTEGER NOT NULL REFERENCES room_members(id) ON DELETE CASCADE,
540
- PRIMARY KEY (message_id,member_id)
541
- );
542
- -- A host's dispatch: its named requests form one batch that hands back to the host once.
543
- CREATE TABLE IF NOT EXISTS free_batches (
544
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
545
- batch_id INTEGER NOT NULL,
546
- chain_id INTEGER NOT NULL,
547
- host_id INTEGER NOT NULL,
548
- settled INTEGER NOT NULL DEFAULT 0,
549
- PRIMARY KEY (room_id,batch_id)
550
- );
551
- -- A host's one-after-another dispatch: each step is a one-member batch hung on the previous answer and costs one chain round.
552
- -- After the last member it hands back to the host; a loop's hand-back is a checkpoint from which the host (or smart
553
- -- scheduling for it) may run the next pass. `steps` lists [pass, member_id, batch_id] for every step started.
554
- CREATE TABLE IF NOT EXISTS free_sequences (
555
- id INTEGER PRIMARY KEY AUTOINCREMENT,
556
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
557
- message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
558
- chain_id INTEGER NOT NULL,
559
- host_id INTEGER NOT NULL,
560
- member_ids TEXT NOT NULL,
561
- loop INTEGER NOT NULL DEFAULT 0,
562
- pass INTEGER NOT NULL DEFAULT 1,
563
- position INTEGER NOT NULL DEFAULT 0,
564
- batch_id INTEGER NOT NULL,
565
- answer_id INTEGER NOT NULL DEFAULT 0,
566
- handoff_request INTEGER NOT NULL DEFAULT 0,
567
- steps TEXT NOT NULL DEFAULT '[]',
568
- status TEXT NOT NULL DEFAULT 'running' CHECK(status IN ('running','checkpoint','done','stopped'))
569
- );
570
- CREATE INDEX IF NOT EXISTS free_sequences_batch ON free_sequences(room_id,batch_id);
571
- CREATE INDEX IF NOT EXISTS free_sequences_handoff ON free_sequences(handoff_request);
572
- -- Members the user named to answer one after another, in the room's speaking order: each gets its request once the one before
573
- -- it ends. Asked rows stay so the speaking order can show who already answered; the owner may reorder rows not yet asked.
574
- CREATE TABLE IF NOT EXISTS free_queue (
575
- id INTEGER PRIMARY KEY AUTOINCREMENT,
576
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
577
- message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
578
- member_id INTEGER NOT NULL REFERENCES room_members(id) ON DELETE CASCADE
579
- );
580
- CREATE INDEX IF NOT EXISTS free_queue_room ON free_queue(room_id,id);
581
- CREATE INDEX IF NOT EXISTS free_queue_message ON free_queue(message_id,id);
582
- -- Only not-yet-requested queue rows participate in advancement. Historical rows remain in free_queue.
583
- CREATE TABLE IF NOT EXISTS free_queue_waiting (
584
- id INTEGER PRIMARY KEY REFERENCES free_queue(id) ON DELETE CASCADE,
585
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
586
- message_id INTEGER NOT NULL,
587
- member_id INTEGER NOT NULL
588
- );
589
- CREATE INDEX IF NOT EXISTS free_queue_waiting_room ON free_queue_waiting(room_id,message_id,id);
590
- CREATE INDEX IF NOT EXISTS free_queue_waiting_message ON free_queue_waiting(message_id,member_id);
591
- INSERT INTO free_queue_waiting(id,room_id,message_id,member_id)
592
- SELECT q.id,q.room_id,q.message_id,q.member_id FROM free_queue q
593
- WHERE NOT EXISTS(SELECT 1 FROM member_requests r WHERE r.message_id=q.message_id AND r.member_id=q.member_id)
594
- AND NOT EXISTS(SELECT 1 FROM free_queue_waiting w WHERE w.id=q.id);
595
- -- Owner messages sent while members still work wait here, in position order, and are posted one by one once the room is idle.
596
- -- `payload` is the message as sent (submission_id included); `failure` holds why posting it failed, which holds the queue.
597
- CREATE TABLE IF NOT EXISTS queued_messages (
598
- id INTEGER PRIMARY KEY AUTOINCREMENT,
599
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
600
- position INTEGER NOT NULL,
601
- submission_id TEXT NOT NULL,
602
- payload TEXT NOT NULL,
603
- failure TEXT NOT NULL DEFAULT '',
604
- created_at INTEGER NOT NULL,
605
- UNIQUE(room_id,submission_id)
606
- );
607
- CREATE INDEX IF NOT EXISTS queued_messages_room ON queued_messages(room_id,position);
608
- -- A room's queue paused because the owner stopped members; the owner resumes it.
609
- CREATE TABLE IF NOT EXISTS queued_message_pauses (
610
- room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE
611
- );
612
- -- Site-wide composer behaviour: whether messages sent while members work are queued (1) or sent at once (0).
613
- CREATE TABLE IF NOT EXISTS composer_settings (
614
- id INTEGER PRIMARY KEY CHECK (id=1),
615
- queueing INTEGER NOT NULL DEFAULT 1 CHECK (queueing IN (0,1))
616
- );
617
- CREATE TRIGGER IF NOT EXISTS queue_waiting_insert AFTER INSERT ON free_queue
618
- BEGIN
619
- INSERT INTO free_queue_waiting(id,room_id,message_id,member_id)
620
- SELECT NEW.id,NEW.room_id,NEW.message_id,NEW.member_id
621
- WHERE NOT EXISTS(SELECT 1 FROM member_requests WHERE message_id=NEW.message_id AND member_id=NEW.member_id);
622
- END;
623
- CREATE TRIGGER IF NOT EXISTS queue_waiting_move AFTER UPDATE OF room_id,message_id,member_id ON free_queue
624
- BEGIN
625
- DELETE FROM free_queue_waiting WHERE id=OLD.id;
626
- INSERT INTO free_queue_waiting(id,room_id,message_id,member_id)
627
- SELECT NEW.id,NEW.room_id,NEW.message_id,NEW.member_id
628
- WHERE NOT EXISTS(SELECT 1 FROM member_requests WHERE message_id=NEW.message_id AND member_id=NEW.member_id);
629
- END;
630
- CREATE TRIGGER IF NOT EXISTS queue_waiting_requested AFTER INSERT ON member_requests
631
- BEGIN
632
- DELETE FROM free_queue_waiting WHERE message_id=NEW.message_id AND member_id=NEW.member_id;
633
- END;
634
- CREATE TRIGGER IF NOT EXISTS queue_waiting_request_deleted AFTER DELETE ON member_requests
635
- BEGIN
636
- INSERT INTO free_queue_waiting(id,room_id,message_id,member_id)
637
- SELECT q.id,q.room_id,q.message_id,q.member_id FROM free_queue q
638
- WHERE q.message_id=OLD.message_id AND q.member_id=OLD.member_id
639
- AND NOT EXISTS(SELECT 1 FROM member_requests WHERE message_id=q.message_id AND member_id=q.member_id)
640
- AND NOT EXISTS(SELECT 1 FROM free_queue_waiting WHERE id=q.id);
641
- END;
642
- CREATE TRIGGER IF NOT EXISTS queue_waiting_request_moved AFTER UPDATE OF message_id,member_id ON member_requests
643
- BEGIN
644
- DELETE FROM free_queue_waiting WHERE message_id=NEW.message_id AND member_id=NEW.member_id;
645
- INSERT INTO free_queue_waiting(id,room_id,message_id,member_id)
646
- SELECT q.id,q.room_id,q.message_id,q.member_id FROM free_queue q
647
- WHERE q.message_id=OLD.message_id AND q.member_id=OLD.member_id
648
- AND NOT EXISTS(SELECT 1 FROM member_requests WHERE message_id=q.message_id AND member_id=q.member_id)
649
- AND NOT EXISTS(SELECT 1 FROM free_queue_waiting WHERE id=q.id);
650
- END;
651
- CREATE INDEX IF NOT EXISTS free_batches_chain ON free_batches(room_id,chain_id);
652
- CREATE INDEX IF NOT EXISTS free_batches_unsettled ON free_batches(room_id,batch_id) WHERE settled=0;
653
- -- The owner's default speaking order (JSON list of member ids); members it lacks follow, the host first, then by joining order.
654
- CREATE TABLE IF NOT EXISTS speaking_orders (
655
- room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
656
- member_ids TEXT NOT NULL
657
- );
658
- -- Chains whose automatic progress stopped (budget reached or every dispatched member failed).
659
- CREATE TABLE IF NOT EXISTS free_chains (
660
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
661
- chain_id INTEGER NOT NULL,
662
- reason TEXT NOT NULL,
663
- PRIMARY KEY (room_id,chain_id)
664
- );
665
- -- A host's dispatch that asked to come back (`handoff`); every batch now comes back, so this only marks the request.
666
- CREATE TABLE IF NOT EXISTS free_handoffs (
667
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
668
- batch_id INTEGER NOT NULL,
669
- PRIMARY KEY (room_id,batch_id)
670
- );
671
- -- A host's one-member dispatch with `no_handoff`: a completed answer goes straight to the user (a failure still comes back).
672
- CREATE TABLE IF NOT EXISTS free_direct_batches (
673
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
674
- batch_id INTEGER NOT NULL,
675
- PRIMARY KEY (room_id,batch_id)
676
- );
677
- -- What the program handed to the host once (a failure, a possible conflict, its own continue): `key` keeps it to one
678
- -- wake-up per event, `chain_id` counts it as a round of that chain, `message_id` is the notice its request hangs on.
679
- CREATE TABLE IF NOT EXISTS free_host_wakes (
680
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
681
- key TEXT NOT NULL,
682
- chain_id INTEGER NOT NULL,
683
- message_id INTEGER NOT NULL,
684
- PRIMARY KEY (room_id,key)
685
- );
686
- -- The host's latest answer asked to continue once the room is idle; a later request for the host or a new owner
687
- -- message supersedes it (`after_request` is the newest request when it was asked).
688
- CREATE TABLE IF NOT EXISTS free_host_continues (
689
- room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
690
- host_id INTEGER NOT NULL,
691
- message_id INTEGER NOT NULL,
692
- chain_id INTEGER NOT NULL,
693
- reason TEXT NOT NULL,
694
- after_request INTEGER NOT NULL
695
- );
696
- -- The owner's per-room limit on rounds a host may run from one owner message (a round is one host dispatch, its members'
697
- -- answers and the hand-back; a whole relay pass is one); rooms without a row use the default, 0 means unlimited.
698
- CREATE TABLE IF NOT EXISTS free_round_limits (
699
- room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
700
- max_rounds INTEGER NOT NULL
701
- );
702
- -- A chain the owner let run on past its limit: the rounds added, and the dispatch the limit held back, which is sent
703
- -- when the owner continues (message_id 0 when nothing is held). asked_rounds/asked_reason: the host's `need_rounds`
704
- -- request that stopped it, the rounds to add for the owner to approve (0 when the plain limit stopped it).
705
- CREATE TABLE IF NOT EXISTS free_chain_holds (
706
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
707
- chain_id INTEGER NOT NULL,
708
- extra_rounds INTEGER NOT NULL DEFAULT 0,
709
- message_id INTEGER NOT NULL DEFAULT 0,
710
- host_id INTEGER NOT NULL DEFAULT 0,
711
- names TEXT NOT NULL DEFAULT '[]',
712
- handoff INTEGER NOT NULL DEFAULT 0,
713
- asked_rounds INTEGER NOT NULL DEFAULT 0,
714
- asked_reason TEXT NOT NULL DEFAULT '',
715
- PRIMARY KEY (room_id,chain_id)
716
- );
717
- -- A relay's later steps within one pass: their batches ride on the pass's first step and cost no round.
718
- CREATE TABLE IF NOT EXISTS free_relay_steps (
719
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
720
- batch_id INTEGER NOT NULL,
721
- PRIMARY KEY (room_id,batch_id)
722
- );
723
- -- A discussion is a switch on a room, not a room type: one thin record per started discussion.
724
- -- Session status is written by the application.
725
- CREATE TABLE IF NOT EXISTS discussion_sessions (
726
- id INTEGER PRIMARY KEY AUTOINCREMENT,
727
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
728
- goal TEXT NOT NULL,
729
- status TEXT NOT NULL DEFAULT 'paused' CHECK(status IN ('active','paused','concluded')),
730
- started_message_id INTEGER NOT NULL DEFAULT 0,
731
- ended_message_id INTEGER NOT NULL DEFAULT 0,
732
- created_at TEXT NOT NULL
733
- );
734
- CREATE UNIQUE INDEX IF NOT EXISTS one_open_session ON discussion_sessions(room_id) WHERE status IN ('active','paused');
735
- CREATE INDEX IF NOT EXISTS discussion_sessions_room ON discussion_sessions(room_id);
736
- -- Historical conclusion snapshots are written by the application and remain readable across later discussions.
737
- CREATE TABLE IF NOT EXISTS discussion_conclusions (
738
- session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
739
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
740
- stop_reason TEXT NOT NULL DEFAULT '',
741
- max_rounds INTEGER NOT NULL DEFAULT 0,
742
- max_minutes INTEGER NOT NULL DEFAULT 0,
743
- brief TEXT NOT NULL DEFAULT '',
744
- state TEXT NOT NULL DEFAULT '',
745
- state_until INTEGER NOT NULL DEFAULT 0,
746
- concluded_at TEXT NOT NULL
747
- );
748
- -- Session-scoped settings and state.
749
- CREATE TABLE IF NOT EXISTS session_start_submissions (
750
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
751
- submission_id TEXT NOT NULL,
752
- session_id INTEGER NOT NULL UNIQUE REFERENCES discussion_sessions(id) ON DELETE CASCADE,
753
- request_json TEXT NOT NULL,
754
- PRIMARY KEY (room_id,submission_id)
755
- );
756
- CREATE TABLE IF NOT EXISTS session_state (
757
- session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
758
- phase TEXT NOT NULL CHECK(phase IN ('initial','discussing')),
759
- revision INTEGER NOT NULL DEFAULT 0,
760
- read_until INTEGER NOT NULL DEFAULT 0,
761
- held_since INTEGER NOT NULL DEFAULT 0
762
- );
763
- CREATE TABLE IF NOT EXISTS session_limits (
764
- session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
765
- max_rounds INTEGER NOT NULL DEFAULT 0,
766
- max_minutes INTEGER NOT NULL DEFAULT 0,
767
- execution_minutes INTEGER NOT NULL DEFAULT 0,
768
- started_at INTEGER NOT NULL DEFAULT 0,
769
- stop_reason TEXT NOT NULL DEFAULT '',
770
- revision INTEGER NOT NULL DEFAULT 0
771
- );
772
- CREATE TABLE IF NOT EXISTS session_round_budgets (
773
- session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
774
- base INTEGER NOT NULL DEFAULT 0,
775
- cap INTEGER NOT NULL DEFAULT 0,
776
- extra INTEGER NOT NULL DEFAULT 0
777
- );
778
- CREATE TABLE IF NOT EXISTS session_pauses (
779
- session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
780
- paused_at INTEGER NOT NULL DEFAULT 0,
781
- paused_seconds INTEGER NOT NULL DEFAULT 0
782
- );
783
- CREATE TABLE IF NOT EXISTS session_options (
784
- session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
785
- named_followup INTEGER NOT NULL DEFAULT 0 CHECK(named_followup IN (0,1))
786
- );
787
- CREATE TABLE IF NOT EXISTS session_requests (
788
- id INTEGER PRIMARY KEY AUTOINCREMENT,
789
- session_id INTEGER NOT NULL REFERENCES discussion_sessions(id) ON DELETE CASCADE,
790
- round INTEGER NOT NULL DEFAULT 0,
791
- position INTEGER NOT NULL DEFAULT 0,
792
- member_id INTEGER NOT NULL REFERENCES room_members(id),
793
- purpose TEXT NOT NULL CHECK(purpose IN ('discuss','discuss_host')),
794
- question TEXT NOT NULL DEFAULT '',
795
- status TEXT NOT NULL DEFAULT 'queued'
796
- CHECK(status IN ('queued','open','answered','failed','skipped','cancelled')),
797
- member_request_id INTEGER UNIQUE REFERENCES member_requests(id),
798
- submission_id TEXT NOT NULL,
799
- read_until INTEGER DEFAULT NULL,
800
- created_at TEXT NOT NULL,
801
- UNIQUE(session_id,submission_id)
802
- );
803
- CREATE INDEX IF NOT EXISTS session_requests_queue ON session_requests(session_id,status,round,position,id);
804
- CREATE INDEX IF NOT EXISTS session_requests_rounds ON session_requests(session_id,round,position,id) WHERE purpose='discuss';
805
- CREATE UNIQUE INDEX IF NOT EXISTS one_open_session_request ON session_requests(session_id)
806
- WHERE status='open' AND purpose='discuss';
807
- CREATE TABLE IF NOT EXISTS task_message_sources (
808
- message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
809
- session_id INTEGER NOT NULL REFERENCES discussion_sessions(id) ON DELETE CASCADE,
810
- session_request_id INTEGER NOT NULL UNIQUE REFERENCES session_requests(id) ON DELETE CASCADE,
811
- cause_kind TEXT NOT NULL CHECK(cause_kind IN ('session_started','scheduled_speaker','speaker_answered','round_complete','user_message','member_change','request_failed')),
812
- cause_message_id INTEGER REFERENCES messages(id),
813
- cause_request_id INTEGER REFERENCES member_requests(id)
814
- );
815
- CREATE INDEX IF NOT EXISTS task_message_sources_session ON task_message_sources(session_id,message_id);
816
-
817
- -- One host-authorized review step for one implementation batch. Imported plans remain history only.
818
- CREATE TABLE IF NOT EXISTS review_plans (
819
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
820
- batch_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
821
- chain_id INTEGER NOT NULL,
822
- host_id INTEGER NOT NULL,
823
- reviewer_id INTEGER NOT NULL,
824
- reviewer_name TEXT NOT NULL,
825
- config_version TEXT NOT NULL,
826
- member_revision TEXT NOT NULL,
827
- scope TEXT NOT NULL,
828
- implementers TEXT NOT NULL,
829
- status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','advanced','imported')),
830
- review_batch_id INTEGER REFERENCES messages(id) ON DELETE SET NULL,
831
- advance_submission TEXT NOT NULL DEFAULT '',
832
- PRIMARY KEY(room_id,batch_id)
833
- );
834
- CREATE UNIQUE INDEX IF NOT EXISTS review_advance_submission ON review_plans(room_id,advance_submission)
835
- WHERE advance_submission<>'';
836
- -- UNION ALL (the two sources never share a message) lets SQLite push a message_id lookup into each indexed arm;
837
- -- plain UNION materializes both tables on every lookup. Recreated on each schema run so existing databases pick it up.
838
- DROP VIEW IF EXISTS internal_task_messages;
839
- CREATE VIEW internal_task_messages AS
840
- SELECT message_id FROM task_message_sources
841
- UNION ALL SELECT review_batch_id AS message_id FROM review_plans WHERE review_batch_id IS NOT NULL;
842
- CREATE INDEX IF NOT EXISTS review_plans_batch_message ON review_plans(review_batch_id);
843
-
844
- -- Cached directory aggregates: backfill existing rooms once, then maintain them in the same write transaction.
845
- -- Withdrawal keeps a transcript card, so it does not change these counts. Internal task/review messages do.
846
- CREATE TABLE IF NOT EXISTS room_message_stats (
847
- room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
848
- message_count INTEGER NOT NULL DEFAULT 0,
849
- latest_id INTEGER NOT NULL DEFAULT 0
850
- );
851
- INSERT INTO room_message_stats(room_id,message_count,latest_id)
852
- SELECT r.id,COUNT(m.id),COALESCE(MAX(m.id),0) FROM rooms r
853
- LEFT JOIN messages m ON m.room_id=r.id
854
- AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)
855
- WHERE NOT EXISTS(SELECT 1 FROM room_message_stats stats WHERE stats.room_id=r.id) GROUP BY r.id;
856
- CREATE TRIGGER IF NOT EXISTS message_stats_room AFTER INSERT ON rooms
857
- BEGIN
858
- INSERT INTO room_message_stats(room_id) VALUES(NEW.id);
859
- END;
860
- CREATE TRIGGER IF NOT EXISTS message_stats_insert AFTER INSERT ON messages
861
- WHEN NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=NEW.id)
862
- BEGIN
863
- UPDATE room_message_stats SET message_count=message_count+1,latest_id=MAX(latest_id,NEW.id)
864
- WHERE room_id=NEW.room_id;
865
- END;
866
- CREATE TRIGGER IF NOT EXISTS message_stats_delete BEFORE DELETE ON messages
867
- WHEN NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=OLD.id)
868
- BEGIN
869
- UPDATE room_message_stats SET message_count=message_count-1,
870
- latest_id=CASE WHEN latest_id=OLD.id THEN (SELECT COALESCE(MAX(m.id),0) FROM messages m
871
- WHERE m.room_id=room_message_stats.room_id AND m.id<>OLD.id AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)) ELSE latest_id END
872
- WHERE room_id=OLD.room_id;
873
- END;
874
- CREATE TRIGGER IF NOT EXISTS message_stats_move_before BEFORE UPDATE OF room_id,id ON messages
875
- WHEN NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=OLD.id)
876
- BEGIN
877
- UPDATE room_message_stats SET message_count=message_count-1,
878
- latest_id=CASE WHEN latest_id=OLD.id THEN (SELECT COALESCE(MAX(m.id),0) FROM messages m
879
- WHERE m.room_id=room_message_stats.room_id AND m.id<>OLD.id AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)) ELSE latest_id END
880
- WHERE room_id=OLD.room_id;
881
- END;
882
- CREATE TRIGGER IF NOT EXISTS message_stats_move_after AFTER UPDATE OF room_id,id ON messages
883
- WHEN NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=NEW.id)
884
- BEGIN
885
- UPDATE room_message_stats SET message_count=message_count+1,latest_id=MAX(latest_id,NEW.id)
886
- WHERE room_id=NEW.room_id;
887
- END;
888
- CREATE TRIGGER IF NOT EXISTS message_stats_task_insert AFTER INSERT ON task_message_sources
889
- WHEN EXISTS(SELECT 1 FROM messages WHERE id=NEW.message_id) AND NOT EXISTS(SELECT 1 FROM review_plans WHERE review_batch_id=NEW.message_id)
890
- BEGIN
891
- UPDATE room_message_stats SET message_count=message_count-1,
892
- latest_id=CASE WHEN latest_id=NEW.message_id THEN (SELECT COALESCE(MAX(m.id),0) FROM messages m
893
- WHERE m.room_id=room_message_stats.room_id AND m.id<>NEW.message_id AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)) ELSE latest_id END
894
- WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.message_id);
895
- END;
896
- CREATE TRIGGER IF NOT EXISTS message_stats_task_delete AFTER DELETE ON task_message_sources
897
- WHEN EXISTS(SELECT 1 FROM messages WHERE id=OLD.message_id) AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=OLD.message_id)
898
- BEGIN
899
- UPDATE room_message_stats SET message_count=message_count+1,latest_id=MAX(latest_id,OLD.message_id)
900
- WHERE room_id=(SELECT room_id FROM messages WHERE id=OLD.message_id);
901
- END;
902
- CREATE TRIGGER IF NOT EXISTS message_stats_task_update_old AFTER UPDATE OF message_id ON task_message_sources
903
- WHEN OLD.message_id IS NOT NEW.message_id AND EXISTS(SELECT 1 FROM messages WHERE id=OLD.message_id) AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=OLD.message_id)
904
- BEGIN
905
- UPDATE room_message_stats SET message_count=message_count+1,latest_id=MAX(latest_id,OLD.message_id)
906
- WHERE room_id=(SELECT room_id FROM messages WHERE id=OLD.message_id);
907
- END;
908
- CREATE TRIGGER IF NOT EXISTS message_stats_task_update_new AFTER UPDATE OF message_id ON task_message_sources
909
- WHEN OLD.message_id IS NOT NEW.message_id AND EXISTS(SELECT 1 FROM messages WHERE id=NEW.message_id) AND NOT EXISTS(SELECT 1 FROM review_plans WHERE review_batch_id=NEW.message_id)
910
- BEGIN
911
- UPDATE room_message_stats SET message_count=message_count-1,
912
- latest_id=CASE WHEN latest_id=NEW.message_id THEN (SELECT COALESCE(MAX(m.id),0) FROM messages m
913
- WHERE m.room_id=room_message_stats.room_id AND m.id<>NEW.message_id AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)) ELSE latest_id END
914
- WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.message_id);
915
- END;
916
- CREATE TRIGGER IF NOT EXISTS message_stats_review_insert AFTER INSERT ON review_plans
917
- WHEN EXISTS(SELECT 1 FROM messages WHERE id=NEW.review_batch_id) AND NOT EXISTS(SELECT 1 FROM task_message_sources WHERE message_id=NEW.review_batch_id)
918
- AND (SELECT COUNT(*) FROM review_plans WHERE review_batch_id=NEW.review_batch_id)=1
919
- BEGIN
920
- UPDATE room_message_stats SET message_count=message_count-1,
921
- latest_id=CASE WHEN latest_id=NEW.review_batch_id THEN (SELECT COALESCE(MAX(m.id),0) FROM messages m
922
- WHERE m.room_id=room_message_stats.room_id AND m.id<>NEW.review_batch_id AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)) ELSE latest_id END
923
- WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.review_batch_id);
924
- END;
925
- CREATE TRIGGER IF NOT EXISTS message_stats_review_delete AFTER DELETE ON review_plans
926
- WHEN EXISTS(SELECT 1 FROM messages WHERE id=OLD.review_batch_id) AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=OLD.review_batch_id)
927
- BEGIN
928
- UPDATE room_message_stats SET message_count=message_count+1,latest_id=MAX(latest_id,OLD.review_batch_id)
929
- WHERE room_id=(SELECT room_id FROM messages WHERE id=OLD.review_batch_id);
930
- END;
931
- CREATE TRIGGER IF NOT EXISTS message_stats_review_update_old AFTER UPDATE OF review_batch_id ON review_plans
932
- WHEN OLD.review_batch_id IS NOT NEW.review_batch_id AND EXISTS(SELECT 1 FROM messages WHERE id=OLD.review_batch_id) AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=OLD.review_batch_id)
933
- BEGIN
934
- UPDATE room_message_stats SET message_count=message_count+1,latest_id=MAX(latest_id,OLD.review_batch_id)
935
- WHERE room_id=(SELECT room_id FROM messages WHERE id=OLD.review_batch_id);
936
- END;
937
- CREATE TRIGGER IF NOT EXISTS message_stats_review_update_new AFTER UPDATE OF review_batch_id ON review_plans
938
- WHEN OLD.review_batch_id IS NOT NEW.review_batch_id AND EXISTS(SELECT 1 FROM messages WHERE id=NEW.review_batch_id) AND NOT EXISTS(SELECT 1 FROM task_message_sources WHERE message_id=NEW.review_batch_id)
939
- AND (SELECT COUNT(*) FROM review_plans WHERE review_batch_id=NEW.review_batch_id)=1
940
- BEGIN
941
- UPDATE room_message_stats SET message_count=message_count-1,
942
- latest_id=CASE WHEN latest_id=NEW.review_batch_id THEN (SELECT COALESCE(MAX(m.id),0) FROM messages m
943
- WHERE m.room_id=room_message_stats.room_id AND m.id<>NEW.review_batch_id AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)) ELSE latest_id END
944
- WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.review_batch_id);
945
- END;
946
-
947
- -- Runtime idempotency receipts for converting one host interjection to an ordinary request.
948
- CREATE TABLE IF NOT EXISTS session_ask_submissions (
949
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
950
- submission_id TEXT NOT NULL,
951
- source_request_id INTEGER NOT NULL UNIQUE REFERENCES member_requests(id) ON DELETE CASCADE,
952
- request_id INTEGER NOT NULL REFERENCES member_requests(id) ON DELETE CASCADE,
953
- PRIMARY KEY (room_id,submission_id)
954
- );
955
-
956
- -- A computer running the local service, paired by the site administrator. Only project names and ids and the
957
- -- configured client of each platform are published; directories and credentials stay on that computer.
958
- CREATE TABLE IF NOT EXISTS local_machines (
959
- id TEXT PRIMARY KEY,
960
- name TEXT NOT NULL,
961
- token_hash TEXT NOT NULL,
962
- projects TEXT NOT NULL DEFAULT '[]',
963
- clients TEXT NOT NULL DEFAULT '{}',
964
- seen_at INTEGER NOT NULL DEFAULT 0,
965
- created_at INTEGER NOT NULL
966
- );
967
- -- A room launch submitted from a device without the local service; the paired computer claims and runs it.
968
- -- The request (with the room's new credentials) is cleared once the launch finishes or fails.
969
- CREATE TABLE IF NOT EXISTS remote_launches (
970
- id TEXT PRIMARY KEY,
971
- machine_id TEXT NOT NULL,
972
- request TEXT NOT NULL DEFAULT '{}',
973
- status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','claimed','done','failed')),
974
- result TEXT NOT NULL DEFAULT '{}',
975
- error TEXT NOT NULL DEFAULT '',
976
- created_at INTEGER NOT NULL,
977
- updated_at INTEGER NOT NULL
978
- );
979
- CREATE INDEX IF NOT EXISTS remote_launches_machine ON remote_launches(machine_id, status);
980
- -- The P-256 public key a paired computer publishes so other devices can encrypt commands only it can read.
981
- CREATE TABLE IF NOT EXISTS machine_keys (
982
- machine_id TEXT PRIMARY KEY,
983
- public_key TEXT NOT NULL
984
- );
985
- -- A local-service operation relayed from a device without the local service. Request and response are end-to-end
986
- -- encrypted between that device and the computer, so the site never holds keys, paths or results in plaintext;
987
- -- the request is cleared once claimed and the row is deleted once read or expired.
988
- CREATE TABLE IF NOT EXISTS machine_commands (
989
- id TEXT PRIMARY KEY,
990
- machine_id TEXT NOT NULL,
991
- request TEXT NOT NULL DEFAULT '{}',
992
- response TEXT NOT NULL DEFAULT '{}',
993
- status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','claimed','done')),
994
- created_at INTEGER NOT NULL,
995
- updated_at INTEGER NOT NULL
996
- );
997
- CREATE INDEX IF NOT EXISTS machine_commands_machine ON machine_commands(machine_id, status);
998
-
999
- -- Change revisions, so idle rooms are not read on every poll. Every write a reader could show or act on gives its room the next
1000
- -- value of one site-wide sequence: a room's revision tells a runner or page that this room changed, and the largest revision
1001
- -- (read through the index) tells a page that anything on the site changed. The '' row stands for directory-only changes.
1002
- -- Triggers update or insert without conflicts, so an outer INSERT OR IGNORE cannot override them into skipping the bump.
1003
- CREATE TABLE IF NOT EXISTS room_revisions (
1004
- room_id TEXT PRIMARY KEY,
1005
- revision INTEGER NOT NULL
1006
- );
1007
- CREATE INDEX IF NOT EXISTS room_revisions_order ON room_revisions(revision);
1008
- DROP TRIGGER IF EXISTS revision_message_insert;
1009
- CREATE TRIGGER revision_message_insert AFTER INSERT ON messages
1010
- BEGIN
1011
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1012
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1013
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1014
- END;
1015
- DROP TRIGGER IF EXISTS revision_message_update;
1016
- CREATE TRIGGER revision_message_update AFTER UPDATE ON messages
1017
- BEGIN
1018
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1019
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1020
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1021
- END;
1022
- DROP TRIGGER IF EXISTS revision_message_withdrawal;
1023
- CREATE TRIGGER revision_message_withdrawal AFTER INSERT ON message_withdrawals
1024
- BEGIN
1025
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.message_id);
1026
- INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM messages WHERE id=NEW.message_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1027
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.message_id));
1028
- END;
1029
- DROP TRIGGER IF EXISTS revision_member_insert;
1030
- CREATE TRIGGER revision_member_insert AFTER INSERT ON room_members
1031
- BEGIN
1032
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1033
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1034
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1035
- END;
1036
- DROP TRIGGER IF EXISTS revision_member_update;
1037
- CREATE TRIGGER revision_member_update AFTER UPDATE ON room_members
1038
- BEGIN
1039
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1040
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1041
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1042
- END;
1043
- DROP TRIGGER IF EXISTS revision_registration_insert;
1044
- CREATE TRIGGER revision_registration_insert AFTER INSERT ON member_registrations
1045
- BEGIN
1046
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1047
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1048
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1049
- END;
1050
- DROP TRIGGER IF EXISTS revision_registration_update;
1051
- CREATE TRIGGER revision_registration_update AFTER UPDATE ON member_registrations
1052
- BEGIN
1053
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1054
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1055
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1056
- END;
1057
- DROP TRIGGER IF EXISTS revision_session_report_insert;
1058
- CREATE TRIGGER revision_session_report_insert AFTER INSERT ON member_sessions
1059
- BEGIN
1060
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1061
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1062
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1063
- END;
1064
- DROP TRIGGER IF EXISTS revision_adopt_failure_insert;
1065
- CREATE TRIGGER revision_adopt_failure_insert AFTER INSERT ON member_adopt_failures
1066
- BEGIN
1067
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1068
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1069
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1070
- END;
1071
- DROP TRIGGER IF EXISTS revision_session_report_update;
1072
- CREATE TRIGGER revision_session_report_update AFTER UPDATE ON member_sessions
1073
- WHEN OLD.session_id IS NOT NEW.session_id OR OLD.client IS NOT NEW.client
1074
- BEGIN
1075
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1076
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1077
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1078
- END;
1079
- DROP TRIGGER IF EXISTS revision_request_insert;
1080
- CREATE TRIGGER revision_request_insert AFTER INSERT ON member_requests
1081
- BEGIN
1082
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1083
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1084
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1085
- END;
1086
- DROP TRIGGER IF EXISTS revision_request_status;
1087
- CREATE TRIGGER revision_request_status AFTER UPDATE OF status ON member_requests
1088
- WHEN OLD.status IS NOT NEW.status
1089
- BEGIN
1090
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1091
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1092
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1093
- END;
1094
- DROP TRIGGER IF EXISTS revision_execution_insert;
1095
- CREATE TRIGGER revision_execution_insert AFTER INSERT ON member_executions
1096
- BEGIN
1097
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1098
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1099
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1100
- END;
1101
- DROP TRIGGER IF EXISTS revision_execution_status;
1102
- CREATE TRIGGER revision_execution_status AFTER UPDATE OF status ON member_executions
1103
- WHEN OLD.status IS NOT NEW.status
1104
- BEGIN
1105
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1106
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1107
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1108
- END;
1109
- -- Non-progress execution reports must invalidate full views even without SSE. Renewals/notes stay lightweight.
1110
- CREATE TRIGGER IF NOT EXISTS revision_execution_report AFTER UPDATE OF changes,result,session_id ON member_executions
1111
- WHEN OLD.changes IS NOT NEW.changes OR OLD.result IS NOT NEW.result OR OLD.session_id IS NOT NEW.session_id
1112
- BEGIN
1113
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1114
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1115
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1116
- END;
1117
- CREATE TRIGGER IF NOT EXISTS revision_capability_insert AFTER INSERT ON execution_capabilities
1118
- BEGIN
1119
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1120
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1121
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1122
- END;
1123
- CREATE TRIGGER IF NOT EXISTS revision_capability_update AFTER UPDATE ON execution_capabilities
1124
- WHEN OLD.stoppable IS NOT NEW.stoppable OR OLD.progress IS NOT NEW.progress OR OLD.stop_result IS NOT NEW.stop_result
1125
- BEGIN
1126
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1127
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1128
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1129
- END;
1130
- CREATE TRIGGER IF NOT EXISTS revision_denials_insert AFTER INSERT ON execution_denials
1131
- BEGIN
1132
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1133
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1134
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1135
- END;
1136
- CREATE TRIGGER IF NOT EXISTS revision_late_answer AFTER INSERT ON late_answers
1137
- BEGIN
1138
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1139
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1140
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1141
- END;
1142
- DROP TRIGGER IF EXISTS revision_retry_insert;
1143
- CREATE TRIGGER revision_retry_insert AFTER INSERT ON execution_retries
1144
- BEGIN
1145
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1146
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1147
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1148
- END;
1149
- DROP TRIGGER IF EXISTS revision_retry_update;
1150
- CREATE TRIGGER revision_retry_update AFTER UPDATE ON execution_retries
1151
- BEGIN
1152
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1153
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1154
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1155
- END;
1156
- DROP TRIGGER IF EXISTS revision_discussion_insert;
1157
- CREATE TRIGGER revision_discussion_insert AFTER INSERT ON discussion_sessions
1158
- BEGIN
1159
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1160
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1161
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1162
- END;
1163
- DROP TRIGGER IF EXISTS revision_discussion_status;
1164
- CREATE TRIGGER revision_discussion_status AFTER UPDATE OF status ON discussion_sessions
1165
- WHEN OLD.status IS NOT NEW.status
1166
- BEGIN
1167
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1168
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1169
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1170
- END;
1171
- DROP TRIGGER IF EXISTS revision_plan_insert;
1172
- CREATE TRIGGER revision_plan_insert AFTER INSERT ON session_requests
1173
- BEGIN
1174
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id);
1175
- INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1176
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id));
1177
- END;
1178
- DROP TRIGGER IF EXISTS revision_plan_status;
1179
- CREATE TRIGGER revision_plan_status AFTER UPDATE OF status ON session_requests
1180
- WHEN OLD.status IS NOT NEW.status
1181
- BEGIN
1182
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id);
1183
- INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1184
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id));
1185
- END;
1186
- DROP TRIGGER IF EXISTS revision_session_phase;
1187
- CREATE TRIGGER revision_session_phase AFTER UPDATE OF phase ON session_state
1188
- WHEN OLD.phase IS NOT NEW.phase
1189
- BEGIN
1190
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id);
1191
- INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1192
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id));
1193
- END;
1194
- DROP TRIGGER IF EXISTS revision_session_limits;
1195
- CREATE TRIGGER revision_session_limits AFTER UPDATE ON session_limits
1196
- BEGIN
1197
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id);
1198
- INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1199
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id));
1200
- END;
1201
- DROP TRIGGER IF EXISTS revision_topic_update;
1202
- CREATE TRIGGER revision_topic_update AFTER UPDATE ON topic_settings
1203
- BEGIN
1204
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1205
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1206
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1207
- END;
1208
- DROP TRIGGER IF EXISTS revision_brief_insert;
1209
- CREATE TRIGGER revision_brief_insert AFTER INSERT ON room_briefs
1210
- BEGIN
1211
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1212
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1213
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1214
- END;
1215
- DROP TRIGGER IF EXISTS revision_brief_update;
1216
- CREATE TRIGGER revision_brief_update AFTER UPDATE ON room_briefs
1217
- BEGIN
1218
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1219
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1220
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1221
- END;
1222
- DROP TRIGGER IF EXISTS revision_user_request_insert;
1223
- CREATE TRIGGER revision_user_request_insert AFTER INSERT ON room_user_requests
1224
- BEGIN
1225
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1226
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1227
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1228
- END;
1229
- DROP TRIGGER IF EXISTS revision_user_request_update;
1230
- CREATE TRIGGER revision_user_request_update AFTER UPDATE ON room_user_requests
1231
- BEGIN
1232
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1233
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1234
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1235
- END;
1236
- DROP TRIGGER IF EXISTS revision_decision_insert;
1237
- CREATE TRIGGER revision_decision_insert AFTER INSERT ON scheduler_decisions
1238
- BEGIN
1239
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1240
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1241
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1242
- END;
1243
- DROP TRIGGER IF EXISTS revision_recommendation_insert;
1244
- CREATE TRIGGER revision_recommendation_insert AFTER INSERT ON member_recommendations
1245
- BEGIN
1246
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1247
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1248
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1249
- END;
1250
- DROP TRIGGER IF EXISTS revision_recommendation_status;
1251
- CREATE TRIGGER revision_recommendation_status AFTER UPDATE OF status ON member_recommendations
1252
- WHEN OLD.status IS NOT NEW.status
1253
- BEGIN
1254
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1255
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1256
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1257
- END;
1258
- DROP TRIGGER IF EXISTS revision_description_insert;
1259
- CREATE TRIGGER revision_description_insert AFTER INSERT ON member_descriptions
1260
- BEGIN
1261
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1262
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1263
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1264
- END;
1265
- DROP TRIGGER IF EXISTS revision_description_update;
1266
- CREATE TRIGGER revision_description_update AFTER UPDATE ON member_descriptions
1267
- BEGIN
1268
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1269
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1270
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1271
- END;
1272
- DROP TRIGGER IF EXISTS revision_label_insert;
1273
- CREATE TRIGGER revision_label_insert AFTER INSERT ON member_labels
1274
- BEGIN
1275
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1276
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1277
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1278
- END;
1279
- DROP TRIGGER IF EXISTS revision_label_update;
1280
- CREATE TRIGGER revision_label_update AFTER UPDATE ON member_labels
1281
- BEGIN
1282
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1283
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1284
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1285
- END;
1286
- DROP TRIGGER IF EXISTS revision_order_insert;
1287
- CREATE TRIGGER revision_order_insert AFTER INSERT ON speaking_orders
1288
- BEGIN
1289
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1290
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1291
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1292
- END;
1293
- DROP TRIGGER IF EXISTS revision_order_update;
1294
- CREATE TRIGGER revision_order_update AFTER UPDATE ON speaking_orders
1295
- BEGIN
1296
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1297
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1298
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1299
- END;
1300
- DROP TRIGGER IF EXISTS revision_room_insert;
1301
- CREATE TRIGGER revision_room_insert AFTER INSERT ON rooms
1302
- BEGIN
1303
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.id;
1304
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1305
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.id);
1306
- END;
1307
- DROP TRIGGER IF EXISTS revision_room_update;
1308
- CREATE TRIGGER revision_room_update AFTER UPDATE ON rooms
1309
- BEGIN
1310
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.id;
1311
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1312
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.id);
1313
- END;
1314
- DROP TRIGGER IF EXISTS revision_room_delete;
1315
- CREATE TRIGGER revision_room_delete AFTER DELETE ON rooms
1316
- BEGIN
1317
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1318
- INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1319
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1320
- END;
1321
- DROP TRIGGER IF EXISTS revision_directory_update;
1322
- CREATE TRIGGER revision_directory_update AFTER UPDATE ON room_directory
1323
- BEGIN
1324
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1325
- INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1326
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1327
- END;
1328
- DROP TRIGGER IF EXISTS revision_assignment_update;
1329
- CREATE TRIGGER revision_assignment_update AFTER UPDATE ON room_folder_assignments
1330
- BEGIN
1331
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1332
- INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1333
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1334
- END;
1335
- DROP TRIGGER IF EXISTS revision_folder_insert;
1336
- CREATE TRIGGER revision_folder_insert AFTER INSERT ON topic_folders
1337
- BEGIN
1338
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1339
- INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1340
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1341
- END;
1342
- DROP TRIGGER IF EXISTS revision_folder_update;
1343
- CREATE TRIGGER revision_folder_update AFTER UPDATE ON topic_folders
1344
- BEGIN
1345
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1346
- INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1347
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1348
- END;
1349
- DROP TRIGGER IF EXISTS revision_folder_delete;
1350
- CREATE TRIGGER revision_folder_delete AFTER DELETE ON topic_folders
1351
- BEGIN
1352
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1353
- INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1354
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1355
- END;
1356
- DROP TRIGGER IF EXISTS revision_queued_message_insert;
1357
- CREATE TRIGGER revision_queued_message_insert AFTER INSERT ON queued_messages
1358
- BEGIN
1359
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1360
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1361
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1362
- END;
1363
- DROP TRIGGER IF EXISTS revision_queued_message_update;
1364
- CREATE TRIGGER revision_queued_message_update AFTER UPDATE ON queued_messages
1365
- BEGIN
1366
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1367
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1368
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1369
- END;
1370
- DROP TRIGGER IF EXISTS revision_queued_message_delete;
1371
- CREATE TRIGGER revision_queued_message_delete AFTER DELETE ON queued_messages
1372
- BEGIN
1373
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=OLD.room_id;
1374
- INSERT INTO room_revisions (room_id,revision) SELECT OLD.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1375
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=OLD.room_id);
1376
- END;
1377
- DROP TRIGGER IF EXISTS revision_queue_pause_insert;
1378
- CREATE TRIGGER revision_queue_pause_insert AFTER INSERT ON queued_message_pauses
1379
- BEGIN
1380
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1381
- INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1382
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1383
- END;
1384
- DROP TRIGGER IF EXISTS revision_queue_pause_delete;
1385
- CREATE TRIGGER revision_queue_pause_delete AFTER DELETE ON queued_message_pauses
1386
- BEGIN
1387
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=OLD.room_id;
1388
- INSERT INTO room_revisions (room_id,revision) SELECT OLD.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1389
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=OLD.room_id);
1390
- END;
1391
- DROP TRIGGER IF EXISTS revision_composer_settings_insert;
1392
- CREATE TRIGGER revision_composer_settings_insert AFTER INSERT ON composer_settings
1393
- BEGIN
1394
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1395
- INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1396
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1397
- END;
1398
- DROP TRIGGER IF EXISTS revision_composer_settings_update;
1399
- CREATE TRIGGER revision_composer_settings_update AFTER UPDATE ON composer_settings
1400
- BEGIN
1401
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1402
- INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1403
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1404
- END;
1405
- DROP TRIGGER IF EXISTS revision_directory_settings;
1406
- CREATE TRIGGER revision_directory_settings AFTER UPDATE ON directory_settings
1407
- BEGIN
1408
- UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1409
- INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1410
- WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1411
- END;
1412
- -- The platform once called Gemini is identified as Antigravity (the software); screens still show the nickname Gemini.
1413
- -- Member names move to the new ID, with any instance suffix (Gemini-2 → Antigravity-2); names inside stored JSON follow.
1414
- -- Message text keeps what was written. Each statement only touches rows still holding the old ID, so reruns change nothing.
1415
- UPDATE room_members SET name='Antigravity'||substr(name,7) WHERE name='Gemini' OR name GLOB 'Gemini-*';
1416
- UPDATE messages SET author='Antigravity'||substr(author,7) WHERE author='Gemini' OR author GLOB 'Gemini-*';
1417
- UPDATE message_summaries SET author='Antigravity'||substr(author,7) WHERE author='Gemini' OR author GLOB 'Gemini-*';
1418
- UPDATE message_details SET submitted_by='Antigravity'||substr(submitted_by,7) WHERE submitted_by='Gemini' OR submitted_by GLOB 'Gemini-*';
1419
- UPDATE attachments SET actor='Antigravity'||substr(actor,7) WHERE actor='Gemini' OR actor GLOB 'Gemini-*';
1420
- UPDATE topic_settings SET host='Antigravity'||substr(host,7) WHERE host='Gemini' OR host GLOB 'Gemini-*';
1421
- UPDATE member_descriptions SET name='Antigravity'||substr(name,7) WHERE name='Gemini' OR name GLOB 'Gemini-*';
1422
- UPDATE member_labels SET name='Antigravity'||substr(name,7) WHERE name='Gemini' OR name GLOB 'Gemini-*';
1423
- UPDATE scheduler_feedback SET should_be='Antigravity'||substr(should_be,7) WHERE should_be='Gemini' OR should_be GLOB 'Gemini-*';
1424
- UPDATE scheduler_feedback_cases SET should_be='Antigravity'||substr(should_be,7) WHERE should_be='Gemini' OR should_be GLOB 'Gemini-*';
1425
- UPDATE review_plans SET reviewer_name='Antigravity'||substr(reviewer_name,7) WHERE reviewer_name='Gemini' OR reviewer_name GLOB 'Gemini-*';
1426
- UPDATE topic_settings SET experts=replace(replace(experts,'"Gemini"','"Antigravity"'),'"Gemini-','"Antigravity-') WHERE experts LIKE '%"Gemini%';
1427
- UPDATE free_chain_holds SET names=replace(replace(names,'"Gemini"','"Antigravity"'),'"Gemini-','"Antigravity-') WHERE names LIKE '%"Gemini%';
1428
- UPDATE room_creations SET request_text=replace(replace(request_text,'"Gemini"','"Antigravity"'),'"Gemini-','"Antigravity-') WHERE request_text LIKE '%"Gemini%';
1429
- UPDATE session_start_submissions SET request_json=replace(replace(request_json,'"Gemini"','"Antigravity"'),'"Gemini-','"Antigravity-') WHERE request_json LIKE '%"Gemini%';
1430
- UPDATE scheduler_decision_inputs SET input=replace(replace(input,'"Gemini"','"Antigravity"'),'"Gemini-','"Antigravity-') WHERE input LIKE '%"Gemini%';
1431
- -- Room event log: an append-only record of each room state change, written by the triggers below so every code path
1432
- -- is covered and later edits cannot forget it. State tables are overwritten in place; this log keeps the order of
1433
- -- what happened (which request was created under which message, why it was cancelled, which batch handed back) so the
1434
- -- timeline page can list and replay it. `at` is epoch milliseconds; `data` holds ids, names resolve at read time.
1435
- CREATE TABLE IF NOT EXISTS room_events (
1436
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1437
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
1438
- at INTEGER NOT NULL,
1439
- kind TEXT NOT NULL,
1440
- data TEXT NOT NULL DEFAULT '{}'
1441
- );
1442
- CREATE INDEX IF NOT EXISTS room_events_room ON room_events(room_id,id);
1443
- DROP TRIGGER IF EXISTS event_message_posted;
1444
- CREATE TRIGGER event_message_posted AFTER INSERT ON messages
1445
- BEGIN
1446
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'message.posted',
1447
- json_object('message_id',NEW.id,'author',NEW.author,'source',NEW.source,'reply_to',NEW.reply_to,'text',substr(NEW.text,1,80)));
1448
- END;
1449
- DROP TRIGGER IF EXISTS event_message_withdrawn;
1450
- CREATE TRIGGER event_message_withdrawn AFTER INSERT ON message_withdrawals
1451
- BEGIN
1452
- INSERT INTO room_events (room_id,at,kind,data) SELECT room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'message.withdrawn',
1453
- json_object('message_id',NEW.message_id,'kind',NEW.kind) FROM messages WHERE id=NEW.message_id;
1454
- END;
1455
- DROP TRIGGER IF EXISTS event_room_rewound;
1456
- CREATE TRIGGER event_room_rewound AFTER INSERT ON room_rewinds
1457
- BEGIN
1458
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'room.rewound',
1459
- json_object('rewind_id',NEW.id,'message_id',NEW.message_id));
1460
- END;
1461
- DROP TRIGGER IF EXISTS event_member_joined;
1462
- CREATE TRIGGER event_member_joined AFTER INSERT ON room_members
1463
- BEGIN
1464
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'member.joined',
1465
- json_object('member_id',NEW.id,'role',NEW.role,'status',NEW.status,'adapter',NEW.adapter));
1466
- END;
1467
- DROP TRIGGER IF EXISTS event_member_changed;
1468
- CREATE TRIGGER event_member_changed AFTER UPDATE OF role,status ON room_members
1469
- WHEN OLD.role IS NOT NEW.role OR OLD.status IS NOT NEW.status
1470
- BEGIN
1471
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'member.changed',
1472
- json_object('member_id',NEW.id,'role',NEW.role,'status',NEW.status,'from_role',OLD.role,'from_status',OLD.status));
1473
- END;
1474
- DROP TRIGGER IF EXISTS event_request_created;
1475
- CREATE TRIGGER event_request_created AFTER INSERT ON member_requests
1476
- BEGIN
1477
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'request.created',
1478
- json_object('request_id',NEW.id,'member_id',NEW.member_id,'message_id',NEW.message_id,'purpose',NEW.purpose,
1479
- 'created_by',NEW.created_by,'chain_id',NEW.chain_id,'batch_id',NEW.batch_id,'status',NEW.status));
1480
- END;
1481
- DROP TRIGGER IF EXISTS event_request_changed;
1482
- CREATE TRIGGER event_request_changed AFTER UPDATE OF status,execution_id,answer_id ON member_requests
1483
- WHEN OLD.status IS NOT NEW.status OR OLD.execution_id IS NOT NEW.execution_id OR OLD.answer_id IS NOT NEW.answer_id
1484
- BEGIN
1485
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'request.changed',
1486
- json_object('request_id',NEW.id,'member_id',NEW.member_id,'message_id',NEW.message_id,'status',NEW.status,
1487
- 'from_status',OLD.status,'execution_id',NEW.execution_id,'answer_id',NEW.answer_id));
1488
- END;
1489
- DROP TRIGGER IF EXISTS event_request_cancel_reason;
1490
- CREATE TRIGGER event_request_cancel_reason AFTER INSERT ON request_cancel_reasons
1491
- BEGIN
1492
- INSERT INTO room_events (room_id,at,kind,data) SELECT room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'request.cancel_reason',
1493
- json_object('request_id',NEW.request_id,'member_id',member_id,'reason',NEW.reason) FROM member_requests WHERE id=NEW.request_id;
1494
- END;
1495
- DROP TRIGGER IF EXISTS event_execution_created;
1496
- CREATE TRIGGER event_execution_created AFTER INSERT ON member_executions
1497
- BEGIN
1498
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'execution.created',
1499
- json_object('execution_id',NEW.id,'member_id',NEW.member_id,'status',NEW.status));
1500
- END;
1501
- DROP TRIGGER IF EXISTS event_execution_changed;
1502
- CREATE TRIGGER event_execution_changed AFTER UPDATE OF status ON member_executions
1503
- WHEN OLD.status IS NOT NEW.status
1504
- BEGIN
1505
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'execution.changed',
1506
- json_object('execution_id',NEW.id,'member_id',NEW.member_id,'status',NEW.status,'from_status',OLD.status,'result',substr(NEW.result,1,200)));
1507
- END;
1508
- DROP TRIGGER IF EXISTS event_execution_retry;
1509
- CREATE TRIGGER event_execution_retry AFTER INSERT ON execution_retries
1510
- BEGIN
1511
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'execution.retry',
1512
- json_object('execution_id',NEW.execution_id,'attempt',NEW.attempt,'retry_at',NEW.retry_at,'reason',substr(NEW.reason,1,200),'stopped',NEW.stopped));
1513
- END;
1514
- DROP TRIGGER IF EXISTS event_execution_retry_changed;
1515
- CREATE TRIGGER event_execution_retry_changed AFTER UPDATE ON execution_retries
1516
- WHEN OLD.attempt IS NOT NEW.attempt OR OLD.stopped IS NOT NEW.stopped
1517
- BEGIN
1518
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'execution.retry',
1519
- json_object('execution_id',NEW.execution_id,'attempt',NEW.attempt,'retry_at',NEW.retry_at,'reason',substr(NEW.reason,1,200),'stopped',NEW.stopped));
1520
- END;
1521
- DROP TRIGGER IF EXISTS event_execution_late_answer;
1522
- CREATE TRIGGER event_execution_late_answer AFTER INSERT ON late_answers
1523
- BEGIN
1524
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'execution.late_answer',
1525
- json_object('execution_id',NEW.execution_id,'text',substr(NEW.text,1,80)));
1526
- END;
1527
- DROP TRIGGER IF EXISTS event_batch_opened;
1528
- CREATE TRIGGER event_batch_opened AFTER INSERT ON free_batches
1529
- BEGIN
1530
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'batch.opened',
1531
- json_object('batch_id',NEW.batch_id,'chain_id',NEW.chain_id,'host_id',NEW.host_id));
1532
- END;
1533
- DROP TRIGGER IF EXISTS event_batch_settled;
1534
- CREATE TRIGGER event_batch_settled AFTER UPDATE OF settled ON free_batches
1535
- WHEN OLD.settled IS NOT NEW.settled
1536
- BEGIN
1537
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'batch.settled',
1538
- json_object('batch_id',NEW.batch_id,'chain_id',NEW.chain_id,'settled',NEW.settled));
1539
- END;
1540
- DROP TRIGGER IF EXISTS event_batch_handoff;
1541
- CREATE TRIGGER event_batch_handoff AFTER INSERT ON free_handoffs
1542
- BEGIN
1543
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'batch.handoff',
1544
- json_object('batch_id',NEW.batch_id));
1545
- END;
1546
- DROP TRIGGER IF EXISTS event_queue_added;
1547
- CREATE TRIGGER event_queue_added AFTER INSERT ON free_queue
1548
- BEGIN
1549
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'queue.added',
1550
- json_object('queue_id',NEW.id,'message_id',NEW.message_id,'member_id',NEW.member_id));
1551
- END;
1552
- DROP TRIGGER IF EXISTS event_queue_removed;
1553
- CREATE TRIGGER event_queue_removed AFTER DELETE ON free_queue
1554
- BEGIN
1555
- INSERT INTO room_events (room_id,at,kind,data) SELECT OLD.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'queue.removed',
1556
- json_object('queue_id',OLD.id,'message_id',OLD.message_id,'member_id',OLD.member_id) WHERE EXISTS (SELECT 1 FROM rooms WHERE id=OLD.room_id);
1557
- END;
1558
- DROP TRIGGER IF EXISTS event_chain_held;
1559
- CREATE TRIGGER event_chain_held AFTER INSERT ON free_chain_holds
1560
- BEGIN
1561
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'chain.held',
1562
- json_object('chain_id',NEW.chain_id,'message_id',NEW.message_id,'names',NEW.names,'handoff',NEW.handoff,'extra_rounds',NEW.extra_rounds));
1563
- END;
1564
- DROP TRIGGER IF EXISTS event_chain_released;
1565
- CREATE TRIGGER event_chain_released AFTER DELETE ON free_chain_holds
1566
- BEGIN
1567
- INSERT INTO room_events (room_id,at,kind,data) SELECT OLD.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'chain.released',
1568
- json_object('chain_id',OLD.chain_id,'message_id',OLD.message_id) WHERE EXISTS (SELECT 1 FROM rooms WHERE id=OLD.room_id);
1569
- END;
1570
- DROP TRIGGER IF EXISTS event_scheduler_decided;
1571
- CREATE TRIGGER event_scheduler_decided AFTER INSERT ON scheduler_decisions
1572
- BEGIN
1573
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'scheduler.decided',
1574
- json_object('decision_id',NEW.id,'after_message_id',NEW.after_message_id,'kind',NEW.kind,'label',NEW.label,
1575
- 'confidence',NEW.confidence,'applied',NEW.applied,'provider',NEW.provider));
1576
- END;
1577
- DROP TRIGGER IF EXISTS event_scheduler_applied;
1578
- CREATE TRIGGER event_scheduler_applied AFTER UPDATE OF applied ON scheduler_decisions
1579
- WHEN OLD.applied IS NOT NEW.applied
1580
- BEGIN
1581
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'scheduler.applied',
1582
- json_object('decision_id',NEW.id,'label',NEW.label,'applied',NEW.applied));
1583
- END;
1584
- DROP TRIGGER IF EXISTS event_discussion_created;
1585
- CREATE TRIGGER event_discussion_created AFTER INSERT ON discussion_sessions
1586
- BEGIN
1587
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'discussion.created',
1588
- json_object('session_id',NEW.id,'status',NEW.status,'goal',substr(NEW.goal,1,80)));
1589
- END;
1590
- DROP TRIGGER IF EXISTS event_discussion_changed;
1591
- CREATE TRIGGER event_discussion_changed AFTER UPDATE OF status ON discussion_sessions
1592
- WHEN OLD.status IS NOT NEW.status
1593
- BEGIN
1594
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'discussion.changed',
1595
- json_object('session_id',NEW.id,'status',NEW.status,'from_status',OLD.status));
1596
- END;
1597
- -- Goal mode: one user-set objective per room at a time that the host keeps pursuing. `armed` lets an idle room wake the host
1598
- -- once; the host dispatching work or the owner posting re-arms it. Times are epoch seconds; 0 budgets mean no limit.
1599
- CREATE TABLE IF NOT EXISTS room_goals (
1600
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1601
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
1602
- objective TEXT NOT NULL,
1603
- status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','paused','complete','blocked','budget_limited','cleared')),
1604
- max_continues INTEGER NOT NULL DEFAULT 0,
1605
- max_minutes INTEGER NOT NULL DEFAULT 0,
1606
- continues INTEGER NOT NULL DEFAULT 0,
1607
- armed INTEGER NOT NULL DEFAULT 1,
1608
- start_request_id INTEGER,
1609
- started_message_id INTEGER NOT NULL DEFAULT 0,
1610
- ended_message_id INTEGER NOT NULL DEFAULT 0,
1611
- started_at INTEGER NOT NULL,
1612
- paused_at INTEGER NOT NULL DEFAULT 0,
1613
- paused_seconds INTEGER NOT NULL DEFAULT 0,
1614
- ended_at INTEGER NOT NULL DEFAULT 0,
1615
- end_note TEXT NOT NULL DEFAULT ''
1616
- );
1617
- CREATE UNIQUE INDEX IF NOT EXISTS one_open_goal ON room_goals(room_id) WHERE status IN ('active','paused');
1618
- CREATE UNIQUE INDEX IF NOT EXISTS room_goals_start_request ON room_goals(start_request_id) WHERE start_request_id IS NOT NULL;
1619
- -- Each automatic wake-up: the visible system message it hangs on and the host request it created.
1620
- CREATE TABLE IF NOT EXISTS goal_continuations (
1621
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1622
- goal_id INTEGER NOT NULL REFERENCES room_goals(id) ON DELETE CASCADE,
1623
- room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
1624
- message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
1625
- request_id INTEGER NOT NULL,
1626
- wrapup INTEGER NOT NULL DEFAULT 0
1627
- );
1628
- CREATE INDEX IF NOT EXISTS goal_continuations_goal ON goal_continuations(goal_id,id);
1629
- DROP TRIGGER IF EXISTS event_goal_created;
1630
- CREATE TRIGGER event_goal_created AFTER INSERT ON room_goals
1631
- BEGIN
1632
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'goal.created',
1633
- json_object('goal_id',NEW.id,'status',NEW.status,'objective',substr(NEW.objective,1,80),'start_request_id',NEW.start_request_id,
1634
- 'max_continues',NEW.max_continues,'max_minutes',NEW.max_minutes));
1635
- END;
1636
- DROP TRIGGER IF EXISTS event_goal_changed;
1637
- CREATE TRIGGER event_goal_changed AFTER UPDATE ON room_goals
1638
- WHEN OLD.status IS NOT NEW.status OR OLD.continues IS NOT NEW.continues OR OLD.objective IS NOT NEW.objective
1639
- OR OLD.max_continues IS NOT NEW.max_continues OR OLD.max_minutes IS NOT NEW.max_minutes
1640
- BEGIN
1641
- INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'goal.changed',
1642
- json_object('goal_id',NEW.id,'status',NEW.status,'from_status',OLD.status,'continues',NEW.continues,
1643
- 'objective',CASE WHEN OLD.objective IS NOT NEW.objective THEN substr(NEW.objective,1,80) END,
1644
- 'max_continues',NEW.max_continues,'max_minutes',NEW.max_minutes,'note',substr(NEW.end_note,1,200)));
1645
- END;
1
+ CREATE TABLE IF NOT EXISTS rooms (
2
+ id TEXT PRIMARY KEY, title TEXT NOT NULL, goal TEXT NOT NULL,
3
+ state_text TEXT NOT NULL DEFAULT '', state_until INTEGER NOT NULL DEFAULT 0,
4
+ created_at TEXT NOT NULL
5
+ );
6
+ CREATE TABLE IF NOT EXISTS room_directory (
7
+ room_id TEXT PRIMARY KEY REFERENCES rooms(id),
8
+ position INTEGER NOT NULL DEFAULT 0,
9
+ archived INTEGER NOT NULL DEFAULT 0 CHECK (archived IN (0,1))
10
+ );
11
+ INSERT OR IGNORE INTO room_directory (room_id,position)
12
+ SELECT id,ROW_NUMBER() OVER (ORDER BY created_at,id) FROM rooms;
13
+ CREATE TRIGGER IF NOT EXISTS add_room_to_directory AFTER INSERT ON rooms
14
+ BEGIN
15
+ INSERT INTO room_directory (room_id,position)
16
+ VALUES (NEW.id,(SELECT COALESCE(MAX(position),0)+1 FROM room_directory));
17
+ END;
18
+ CREATE TABLE IF NOT EXISTS topic_folders (
19
+ id TEXT PRIMARY KEY, name TEXT NOT NULL, position INTEGER NOT NULL DEFAULT 0
20
+ );
21
+ -- Site-wide preferences of the person managing the site; `last_room` is the room they last opened on any device.
22
+ CREATE TABLE IF NOT EXISTS site_preferences (
23
+ key TEXT PRIMARY KEY,
24
+ value TEXT NOT NULL
25
+ );
26
+ CREATE TABLE IF NOT EXISTS directory_settings (
27
+ id INTEGER PRIMARY KEY CHECK (id=1),
28
+ uncategorized_position INTEGER CHECK (uncategorized_position>=0)
29
+ );
30
+ CREATE TABLE IF NOT EXISTS room_folder_assignments (
31
+ room_id TEXT PRIMARY KEY REFERENCES rooms(id),
32
+ folder_id TEXT REFERENCES topic_folders(id) ON DELETE SET NULL
33
+ );
34
+ INSERT OR IGNORE INTO room_folder_assignments (room_id,folder_id)
35
+ SELECT id,NULL FROM rooms;
36
+ CREATE TRIGGER IF NOT EXISTS add_room_to_folder_assignments AFTER INSERT ON rooms
37
+ BEGIN
38
+ INSERT INTO room_folder_assignments (room_id,folder_id) VALUES (NEW.id,NULL);
39
+ END;
40
+ CREATE TABLE IF NOT EXISTS room_creations (
41
+ submission_id TEXT PRIMARY KEY,
42
+ room_id TEXT NOT NULL UNIQUE REFERENCES rooms(id),
43
+ request_text TEXT NOT NULL
44
+ );
45
+ CREATE TABLE IF NOT EXISTS room_user_requests (
46
+ room_id TEXT PRIMARY KEY REFERENCES rooms(id),
47
+ text TEXT NOT NULL DEFAULT ''
48
+ );
49
+ CREATE TABLE IF NOT EXISTS room_briefs (
50
+ room_id TEXT PRIMARY KEY REFERENCES rooms(id),
51
+ text TEXT NOT NULL, until INTEGER NOT NULL, updated_at TEXT NOT NULL
52
+ );
53
+ CREATE TABLE IF NOT EXISTS messages (
54
+ id INTEGER PRIMARY KEY AUTOINCREMENT, room_id TEXT NOT NULL REFERENCES rooms(id),
55
+ author TEXT NOT NULL, text TEXT NOT NULL, reply_to INTEGER REFERENCES messages(id),
56
+ source TEXT NOT NULL, submission_id TEXT NOT NULL, created_at TEXT NOT NULL,
57
+ UNIQUE(room_id, submission_id)
58
+ );
59
+ CREATE INDEX IF NOT EXISTS messages_room ON messages(room_id, id);
60
+ CREATE TABLE IF NOT EXISTS attachments (
61
+ id TEXT PRIMARY KEY, room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
62
+ message_id INTEGER REFERENCES messages(id) ON DELETE CASCADE,
63
+ position INTEGER NOT NULL DEFAULT 0,
64
+ file_name TEXT NOT NULL, content_type TEXT NOT NULL,
65
+ size INTEGER NOT NULL, sha256 TEXT NOT NULL,
66
+ actor TEXT NOT NULL, submission_id TEXT NOT NULL, created_at TEXT NOT NULL,
67
+ UNIQUE(room_id, submission_id)
68
+ );
69
+ CREATE INDEX IF NOT EXISTS attachments_message ON attachments(message_id, position);
70
+ CREATE TRIGGER IF NOT EXISTS validate_attachment_link BEFORE UPDATE OF message_id ON attachments
71
+ WHEN NEW.message_id IS NOT NULL AND (
72
+ (OLD.message_id IS NOT NULL AND OLD.message_id != NEW.message_id)
73
+ OR NOT EXISTS (
74
+ SELECT 1 FROM messages m JOIN message_details d ON d.message_id=m.id
75
+ WHERE m.id=NEW.message_id AND m.room_id=NEW.room_id AND d.submitted_by=NEW.actor
76
+ )
77
+ )
78
+ BEGIN
79
+ SELECT RAISE(ABORT, 'attachment_unavailable');
80
+ END;
81
+ CREATE TABLE IF NOT EXISTS message_summaries (
82
+ message_id INTEGER PRIMARY KEY REFERENCES messages(id),
83
+ text TEXT NOT NULL DEFAULT '', author TEXT NOT NULL DEFAULT ''
84
+ );
85
+
86
+ CREATE TABLE IF NOT EXISTS topic_settings (
87
+ room_id TEXT PRIMARY KEY REFERENCES rooms(id),
88
+ host TEXT NOT NULL, background TEXT NOT NULL DEFAULT '', boundaries TEXT NOT NULL DEFAULT '',
89
+ experts TEXT NOT NULL, focus TEXT NOT NULL DEFAULT ''
90
+ );
91
+ CREATE TABLE IF NOT EXISTS message_details (
92
+ message_id INTEGER PRIMARY KEY REFERENCES messages(id),
93
+ kind TEXT NOT NULL DEFAULT 'opinion', submitted_by TEXT NOT NULL
94
+ );
95
+ -- A member's message spoken on its own (its user asked in its own window), bound to no request or execution.
96
+ CREATE TABLE IF NOT EXISTS proactive_messages (
97
+ message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE
98
+ );
99
+ -- Site administrator account (single user) and its login sessions. Raw tokens and passwords are never stored.
100
+ CREATE TABLE IF NOT EXISTS site_admin (
101
+ id INTEGER PRIMARY KEY CHECK(id=1),
102
+ username TEXT NOT NULL,
103
+ password_hash TEXT NOT NULL,
104
+ avatar TEXT NOT NULL DEFAULT '',
105
+ failed_attempts INTEGER NOT NULL DEFAULT 0,
106
+ locked_until INTEGER NOT NULL DEFAULT 0,
107
+ created_at INTEGER NOT NULL
108
+ );
109
+ -- Logins end after a week unused or a month in all; label is the browser that signed in. The older table
110
+ -- admin_sessions held logins that never expired and is no longer read, so upgrading signs everyone out once.
111
+ CREATE TABLE IF NOT EXISTS admin_logins (
112
+ token_hash TEXT PRIMARY KEY,
113
+ label TEXT NOT NULL DEFAULT '',
114
+ created_at INTEGER NOT NULL,
115
+ last_seen INTEGER NOT NULL
116
+ );
117
+ -- Wrong-password backoff per client address; the row with source '*' counts all addresses within an hour.
118
+ -- site_admin.failed_attempts and locked_until are no longer used.
119
+ -- The online website has no accounts: it accepts a device only if the paired computer signed it into its device list.
120
+ -- machine_sign_keys holds each computer's signing key; machine_devices its latest signed list (devices' public keys and
121
+ -- expiry). Website passkey tables of an earlier version (admin_passkeys, admin_challenges) are no longer read.
122
+ CREATE TABLE IF NOT EXISTS machine_sign_keys (
123
+ machine_id TEXT PRIMARY KEY REFERENCES local_machines(id) ON DELETE CASCADE,
124
+ sign_key TEXT NOT NULL
125
+ );
126
+ CREATE TABLE IF NOT EXISTS machine_devices (
127
+ machine_id TEXT PRIMARY KEY REFERENCES local_machines(id) ON DELETE CASCADE,
128
+ body TEXT NOT NULL,
129
+ signature TEXT NOT NULL,
130
+ issued_at INTEGER NOT NULL
131
+ );
132
+ CREATE TABLE IF NOT EXISTS admin_attempts (
133
+ source TEXT PRIMARY KEY,
134
+ failed INTEGER NOT NULL DEFAULT 0,
135
+ locked_until INTEGER NOT NULL DEFAULT 0,
136
+ updated_at INTEGER NOT NULL
137
+ );
138
+ -- One-time registration token from `codiscuss token` on the deploying computer; cleared once the administrator registers.
139
+ CREATE TABLE IF NOT EXISTS admin_setup (
140
+ id INTEGER PRIMARY KEY CHECK(id=1),
141
+ token_hash TEXT NOT NULL,
142
+ created_at INTEGER NOT NULL
143
+ );
144
+
145
+ -- Every room is a free collaboration room; a discussion is a session inside it. Holds the owner's control hash.
146
+ CREATE TABLE IF NOT EXISTS room_modes (
147
+ room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
148
+ mode TEXT NOT NULL CHECK(mode='free'),
149
+ control_hash TEXT NOT NULL DEFAULT ''
150
+ );
151
+
152
+ -- A room's limits for its next discussion, set at creation or in the topic while no discussion is open; a started
153
+ -- discussion copies them into session_limits.
154
+ CREATE TABLE IF NOT EXISTS discussion_limits (
155
+ room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
156
+ max_rounds INTEGER NOT NULL DEFAULT 0,
157
+ max_minutes INTEGER NOT NULL DEFAULT 0,
158
+ execution_minutes INTEGER NOT NULL DEFAULT 0
159
+ );
160
+ INSERT OR IGNORE INTO discussion_limits(room_id,execution_minutes) SELECT id,0 FROM rooms;
161
+ DROP TRIGGER IF EXISTS limits_room_created;
162
+ CREATE TRIGGER limits_room_created AFTER INSERT ON rooms
163
+ BEGIN
164
+ INSERT INTO discussion_limits(room_id,execution_minutes) VALUES (NEW.id,0);
165
+ END;
166
+ -- Freeze dispatch-to-publication time independently of the recent execution list.
167
+ CREATE TABLE IF NOT EXISTS message_timings (
168
+ message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
169
+ elapsed_seconds INTEGER NOT NULL CHECK(elapsed_seconds>=0)
170
+ );
171
+ -- A failed assertion rolls back the whole D1 batch, including concurrent writes.
172
+ CREATE TABLE IF NOT EXISTS runtime_assertions (
173
+ id TEXT PRIMARY KEY, allowed INTEGER NOT NULL CHECK(allowed=1)
174
+ );
175
+ -- Triggers of the retired discussion-room model (speaking floor, turns, relay, room-scoped runtime and phase).
176
+ -- Dropped on every start so an older database stops enforcing them; its leftover tables are removed by
177
+ -- migrations/drop_legacy_discussion.sql.
178
+ DROP TRIGGER IF EXISTS reject_archived_resume;
179
+ DROP TRIGGER IF EXISTS validate_turn_answer;
180
+ DROP TRIGGER IF EXISTS reject_inactive_opinion;
181
+ DROP TRIGGER IF EXISTS reject_inactive_turn;
182
+ DROP TRIGGER IF EXISTS reject_conclusion_with_open_turn;
183
+ DROP TRIGGER IF EXISTS require_opinion_turn;
184
+ DROP TRIGGER IF EXISTS initialize_room_floor;
185
+ DROP TRIGGER IF EXISTS transfer_host_floor;
186
+ DROP TRIGGER IF EXISTS require_message_floor;
187
+ DROP TRIGGER IF EXISTS return_message_floor;
188
+ DROP TRIGGER IF EXISTS require_host_floor;
189
+ DROP TRIGGER IF EXISTS nominate_expert_floor;
190
+ DROP TRIGGER IF EXISTS cancel_turn_floor;
191
+ DROP TRIGGER IF EXISTS limits_topic_started;
192
+ DROP TRIGGER IF EXISTS limits_phase_started;
193
+ DROP TRIGGER IF EXISTS limits_turn_started;
194
+ DROP TRIGGER IF EXISTS record_event_timing;
195
+ DROP TRIGGER IF EXISTS runtime_generation;
196
+ DROP TRIGGER IF EXISTS runtime_turn_created;
197
+ DROP TRIGGER IF EXISTS relay_turn_promoted;
198
+ DROP TRIGGER IF EXISTS relay_plan_replaced;
199
+ DROP TRIGGER IF EXISTS relay_hold_user_message;
200
+ DROP TRIGGER IF EXISTS relay_hold_goal;
201
+ DROP TRIGGER IF EXISTS relay_hold_topic;
202
+ DROP TRIGGER IF EXISTS relay_plan_concluded;
203
+ DROP TRIGGER IF EXISTS relay_plan_participants;
204
+ DROP TRIGGER IF EXISTS runtime_turn_answered;
205
+ DROP TRIGGER IF EXISTS runtime_topic_stop;
206
+ DROP TRIGGER IF EXISTS runtime_user_message;
207
+ DROP TRIGGER IF EXISTS runtime_turn_cancelled;
208
+ DROP TRIGGER IF EXISTS runtime_archive;
209
+ DROP TRIGGER IF EXISTS runtime_participants_changed;
210
+ DROP TRIGGER IF EXISTS runtime_experts_changed;
211
+ DROP TRIGGER IF EXISTS runtime_goal_changed;
212
+ DROP TRIGGER IF EXISTS runtime_constraints_changed;
213
+ DROP TRIGGER IF EXISTS pause_clock_stopped;
214
+ DROP TRIGGER IF EXISTS pause_clock_resumed;
215
+ DROP TRIGGER IF EXISTS session_for_new_discussion;
216
+ DROP TRIGGER IF EXISTS session_follows_phase_insert;
217
+ DROP TRIGGER IF EXISTS session_follows_phase;
218
+ DROP TRIGGER IF EXISTS conclusion_snapshot;
219
+ DROP TRIGGER IF EXISTS revision_free_settings_insert;
220
+ DROP TRIGGER IF EXISTS revision_free_settings_update;
221
+ -- Room members; their requests and executions follow.
222
+ CREATE TABLE IF NOT EXISTS room_members (
223
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
224
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
225
+ name TEXT NOT NULL,
226
+ adapter TEXT NOT NULL DEFAULT '',
227
+ role TEXT NOT NULL DEFAULT 'member' CHECK(role IN ('member','host')),
228
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','away','removed')),
229
+ token_hash TEXT NOT NULL,
230
+ read_until INTEGER NOT NULL DEFAULT 0,
231
+ created_at TEXT NOT NULL,
232
+ access TEXT NOT NULL DEFAULT 'runner',
233
+ session_mode TEXT NOT NULL DEFAULT 'new'
234
+ );
235
+ CREATE UNIQUE INDEX IF NOT EXISTS member_name ON room_members(room_id,name) WHERE status<>'removed';
236
+ CREATE UNIQUE INDEX IF NOT EXISTS member_credential ON room_members(room_id,token_hash);
237
+
238
+ -- Local service attestation, never a permission grant. Re-imported rooms require a fresh attestation.
239
+ CREATE TABLE IF NOT EXISTS member_review_capabilities (
240
+ member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
241
+ member_revision TEXT NOT NULL,
242
+ config_version TEXT NOT NULL,
243
+ adapter TEXT NOT NULL,
244
+ recorded_at INTEGER NOT NULL,
245
+ generation INTEGER NOT NULL DEFAULT 0
246
+ );
247
+ CREATE TRIGGER IF NOT EXISTS invalidate_member_review_capability
248
+ AFTER UPDATE OF token_hash,status,adapter,access,session_mode ON room_members
249
+ WHEN NEW.token_hash<>OLD.token_hash OR NEW.status<>OLD.status OR NEW.adapter<>OLD.adapter
250
+ OR NEW.access<>OLD.access OR NEW.session_mode<>OLD.session_mode
251
+ BEGIN
252
+ INSERT INTO member_review_capabilities(member_id,member_revision,config_version,adapter,recorded_at,generation)
253
+ VALUES(NEW.id,'','',NEW.adapter,0,1)
254
+ ON CONFLICT(member_id) DO UPDATE SET member_revision='',config_version='',generation=generation+1;
255
+ END;
256
+ -- A manual member's own software session, reported by its CLI on posting; the local service uses it to take the member over.
257
+ CREATE TABLE IF NOT EXISTS member_sessions (
258
+ member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
259
+ room_id TEXT NOT NULL,
260
+ session_id TEXT NOT NULL,
261
+ client TEXT NOT NULL
262
+ );
263
+ -- Why the local service could not take over a manual member's reported session; shown only while that session is still the reported one.
264
+ CREATE TABLE IF NOT EXISTS member_adopt_failures (
265
+ member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
266
+ room_id TEXT NOT NULL,
267
+ session_id TEXT NOT NULL,
268
+ reason TEXT NOT NULL
269
+ );
270
+ -- A manual member's own registration; it is bound to the credential it used, so a re-issued credential must register again.
271
+ CREATE TABLE IF NOT EXISTS member_registrations (
272
+ member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
273
+ room_id TEXT NOT NULL,
274
+ token_hash TEXT NOT NULL,
275
+ registered_at TEXT NOT NULL
276
+ );
277
+ -- Marks members of the retired web-relay access; no new rows are written.
278
+ CREATE TABLE IF NOT EXISTS member_manual_sources (
279
+ member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
280
+ source TEXT NOT NULL CHECK(source='web')
281
+ );
282
+ CREATE TABLE IF NOT EXISTS member_executions (
283
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
284
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
285
+ member_id INTEGER NOT NULL REFERENCES room_members(id) ON DELETE CASCADE,
286
+ client_id TEXT NOT NULL,
287
+ status TEXT NOT NULL DEFAULT 'dispatched'
288
+ CHECK(status IN ('dispatched','started','completed','cancelled','failed')),
289
+ session_id TEXT NOT NULL DEFAULT '',
290
+ lease_until INTEGER NOT NULL DEFAULT 0,
291
+ created_at INTEGER NOT NULL,
292
+ finished_at INTEGER NOT NULL DEFAULT 0,
293
+ result TEXT NOT NULL DEFAULT '',
294
+ changes TEXT NOT NULL DEFAULT '',
295
+ -- The changed files the member's own edit tools wrote; '' when its software could not tell (see possible_conflicts).
296
+ edited TEXT NOT NULL DEFAULT '',
297
+ cwd TEXT NOT NULL DEFAULT '',
298
+ activity TEXT NOT NULL DEFAULT '',
299
+ activity_at INTEGER NOT NULL DEFAULT 0,
300
+ UNIQUE(room_id,client_id)
301
+ );
302
+ -- What the owner wrote about each member (strengths, division of work), by name so it survives leaving and rejoining.
303
+ CREATE TABLE IF NOT EXISTS member_descriptions (
304
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
305
+ name TEXT NOT NULL,
306
+ description TEXT NOT NULL,
307
+ updated_at INTEGER NOT NULL,
308
+ PRIMARY KEY(room_id,name)
309
+ );
310
+ -- Display labels the owner gave members (e.g. from a named preset); screens show them, the name stays the identity.
311
+ CREATE TABLE IF NOT EXISTS member_labels (
312
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
313
+ name TEXT NOT NULL,
314
+ label TEXT NOT NULL,
315
+ updated_at INTEGER NOT NULL,
316
+ PRIMARY KEY(room_id,name)
317
+ );
318
+ -- Progress notes the runner saw a member's software write between tool calls; numbered per execution so a retried renewal adds nothing.
319
+ CREATE TABLE IF NOT EXISTS execution_notes (
320
+ execution_id INTEGER NOT NULL REFERENCES member_executions(id) ON DELETE CASCADE,
321
+ seq INTEGER NOT NULL,
322
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
323
+ text TEXT NOT NULL,
324
+ created_at INTEGER NOT NULL,
325
+ PRIMARY KEY(execution_id,seq)
326
+ );
327
+ -- Operations a member's software refused without asking, since a headless run cannot ask for approval; reported once by the
328
+ -- runner after the process ends, as a JSON list of {tool, detail, rule}, so the owner can see and allow them.
329
+ CREATE TABLE IF NOT EXISTS execution_denials (
330
+ execution_id INTEGER PRIMARY KEY REFERENCES member_executions(id) ON DELETE CASCADE,
331
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
332
+ denials TEXT NOT NULL
333
+ );
334
+ -- A manual member's session refused a delivery: the runner keeps the execution and delivers again at retry_at
335
+ -- (0 while delivering). The owner may ask for it now or stop it, and the runner then fails it. Not part of backups.
336
+ CREATE TABLE IF NOT EXISTS execution_retries (
337
+ execution_id INTEGER PRIMARY KEY REFERENCES member_executions(id) ON DELETE CASCADE,
338
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
339
+ attempt INTEGER NOT NULL,
340
+ retry_at INTEGER NOT NULL DEFAULT 0,
341
+ reason TEXT NOT NULL DEFAULT '',
342
+ stopped INTEGER NOT NULL DEFAULT 0
343
+ );
344
+ -- Phases the runner saw a member's software go through in one execution, as
345
+ -- millisecond offsets from its delivery; numbered per execution, and a resent segment keeps the later end. Not part of backups.
346
+ CREATE TABLE IF NOT EXISTS execution_segments (
347
+ source TEXT NOT NULL CHECK(source='free'),
348
+ execution_id INTEGER NOT NULL,
349
+ seq INTEGER NOT NULL,
350
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
351
+ phase TEXT NOT NULL,
352
+ tool TEXT NOT NULL DEFAULT '',
353
+ start_ms INTEGER NOT NULL,
354
+ end_ms INTEGER NOT NULL,
355
+ PRIMARY KEY(source,execution_id,seq)
356
+ );
357
+ CREATE INDEX IF NOT EXISTS execution_segments_room ON execution_segments(room_id);
358
+ -- One live execution per member: different members may run in parallel, one session never twice.
359
+ CREATE UNIQUE INDEX IF NOT EXISTS one_member_execution ON member_executions(member_id)
360
+ WHERE status IN ('dispatched','started');
361
+ CREATE INDEX IF NOT EXISTS member_executions_member ON member_executions(member_id);
362
+ -- The client-id uniqueness index cannot satisfy ORDER BY id LIMIT on a room's history.
363
+ CREATE INDEX IF NOT EXISTS member_executions_room_recent ON member_executions(room_id,id);
364
+ CREATE INDEX IF NOT EXISTS member_executions_room_changes ON member_executions(room_id,id) WHERE changes<>'';
365
+ CREATE INDEX IF NOT EXISTS member_executions_room_live ON member_executions(room_id,id) WHERE status IN ('dispatched','started');
366
+ CREATE TABLE IF NOT EXISTS member_requests (
367
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
368
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
369
+ member_id INTEGER NOT NULL REFERENCES room_members(id) ON DELETE CASCADE,
370
+ message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
371
+ status TEXT NOT NULL DEFAULT 'pending'
372
+ CHECK(status IN ('pending','delivering','completed','cancelled','failed')),
373
+ execution_id INTEGER REFERENCES member_executions(id),
374
+ answer_id INTEGER REFERENCES messages(id),
375
+ created_at INTEGER NOT NULL,
376
+ chain_id INTEGER NOT NULL DEFAULT 0,
377
+ batch_id INTEGER NOT NULL DEFAULT 0,
378
+ purpose TEXT NOT NULL DEFAULT 'ask',
379
+ created_by TEXT NOT NULL DEFAULT 'user',
380
+ UNIQUE(message_id,member_id)
381
+ );
382
+ CREATE INDEX IF NOT EXISTS member_requests_open ON member_requests(member_id,status);
383
+ CREATE INDEX IF NOT EXISTS member_requests_execution ON member_requests(execution_id);
384
+ CREATE INDEX IF NOT EXISTS member_requests_room ON member_requests(room_id);
385
+ CREATE INDEX IF NOT EXISTS member_requests_room_status ON member_requests(room_id,status);
386
+ CREATE INDEX IF NOT EXISTS member_requests_batch ON member_requests(batch_id,status);
387
+ -- Web chatbots are no longer members: earlier ones are removed and their open requests cancelled; their messages keep
388
+ -- the author name. member_manual_sources stays only to recognise those earlier members.
389
+ UPDATE member_requests SET status='cancelled' WHERE status IN ('pending','delivering') AND member_id IN (
390
+ SELECT member_id FROM member_manual_sources UNION SELECT id FROM room_members WHERE access='online');
391
+ INSERT OR IGNORE INTO member_manual_sources (member_id,source) SELECT id,'web' FROM room_members WHERE access='online';
392
+ UPDATE room_members SET access='manual' WHERE access='online';
393
+ UPDATE room_members SET status='removed',role='member' WHERE status<>'removed' AND id IN (SELECT member_id FROM member_manual_sources);
394
+ -- One durable disposition per @everyone message; also prevents a host fallback from being routed again after restart.
395
+ -- The original group request may be deleted for a sequential queue, so request_id is a receipt, not a foreign key.
396
+ CREATE TABLE IF NOT EXISTS group_routes (
397
+ message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
398
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
399
+ request_id INTEGER NOT NULL,
400
+ action TEXT NOT NULL CHECK(action IN ('all','sequential','host'))
401
+ );
402
+ -- Visible record of each smart-scheduling decision in a discussion; it is not a message and moves no round or floor.
403
+ CREATE TABLE IF NOT EXISTS scheduler_decisions (
404
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
405
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
406
+ after_message_id INTEGER NOT NULL DEFAULT 0,
407
+ kind TEXT NOT NULL CHECK(kind IN ('interjection','round')),
408
+ label TEXT NOT NULL,
409
+ confidence REAL,
410
+ applied INTEGER NOT NULL DEFAULT 0 CHECK(applied IN (0,1)),
411
+ provider TEXT NOT NULL,
412
+ created_at TEXT NOT NULL
413
+ );
414
+ CREATE INDEX IF NOT EXISTS scheduler_decisions_room ON scheduler_decisions(room_id,id);
415
+ -- What the decision model was asked and chose, shown folded under the decision; not part of backups.
416
+ CREATE TABLE IF NOT EXISTS scheduler_decision_details (
417
+ decision_id INTEGER PRIMARY KEY REFERENCES scheduler_decisions(id) ON DELETE CASCADE,
418
+ detail TEXT NOT NULL
419
+ );
420
+ -- The owner's verdict on one decision; a 'bad' one may also say who should have answered and why.
421
+ -- Kept apart from the decision so older databases need no column change.
422
+ CREATE TABLE IF NOT EXISTS scheduler_feedback (
423
+ decision_id INTEGER PRIMARY KEY REFERENCES scheduler_decisions(id) ON DELETE CASCADE,
424
+ rating TEXT NOT NULL CHECK(rating IN ('good','bad')),
425
+ should_be TEXT NOT NULL DEFAULT '',
426
+ note TEXT NOT NULL DEFAULT '',
427
+ created_at TEXT NOT NULL
428
+ );
429
+ -- Messages the local service is still asking the decision model about; a transient marker, not part of backups.
430
+ CREATE TABLE IF NOT EXISTS scheduler_routing (
431
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
432
+ message_id INTEGER NOT NULL,
433
+ created_at INTEGER NOT NULL,
434
+ PRIMARY KEY (room_id, message_id)
435
+ );
436
+ -- Older development databases may retain execution_deliveries; it is no longer read or written.
437
+ -- What the runner's adapter reported each execution can do (stop it, see its progress), and whether its software stopped
438
+ -- once the execution was voided: the page describes a stop as it really went (a window or App the runner cannot stop
439
+ -- keeps generating). A side table so older databases need no column change; executions are not backed up, so neither is this.
440
+ CREATE TABLE IF NOT EXISTS execution_capabilities (
441
+ execution_id INTEGER PRIMARY KEY REFERENCES member_executions(id) ON DELETE CASCADE,
442
+ room_id TEXT NOT NULL,
443
+ stoppable INTEGER CHECK(stoppable IN (0,1)),
444
+ progress INTEGER CHECK(progress IN (0,1)),
445
+ stop_result TEXT NOT NULL DEFAULT '' CHECK(stop_result IN ('','stopped','unstoppable','unconfirmed')),
446
+ steer INTEGER CHECK(steer IN (0,1))
447
+ );
448
+ -- What the owner said into one running execution without ending it (an interjection from the private-chat page), and
449
+ -- whether the runner got it into the member's software: pending until the runner reports delivered or failed. A retry
450
+ -- with the same submission_id is the same interjection. Not backed up, like the executions it belongs to.
451
+ CREATE TABLE IF NOT EXISTS execution_interjections (
452
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
453
+ execution_id INTEGER NOT NULL REFERENCES member_executions(id) ON DELETE CASCADE,
454
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
455
+ submission_id TEXT NOT NULL,
456
+ text TEXT NOT NULL,
457
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','delivered','failed')),
458
+ reason TEXT NOT NULL DEFAULT '',
459
+ created_at INTEGER NOT NULL,
460
+ updated_at INTEGER NOT NULL,
461
+ UNIQUE(room_id,submission_id)
462
+ );
463
+ CREATE INDEX IF NOT EXISTS execution_interjections_execution ON execution_interjections(execution_id);
464
+ -- What a rewind does to each runner member's session, as its adapter reported: cut by the runner, rebuilt each time
465
+ -- (fresh), or kept as it is. A member without a report is kept; manual and online members are always kept.
466
+ CREATE TABLE IF NOT EXISTS member_capabilities (
467
+ member_id INTEGER PRIMARY KEY REFERENCES room_members(id) ON DELETE CASCADE,
468
+ room_id TEXT NOT NULL,
469
+ rewind TEXT NOT NULL CHECK(rewind IN ('cut','fresh','kept'))
470
+ );
471
+ -- What a member submitted for an execution the user had already voided. Refused as an answer, so it never joins the
472
+ -- transcript or AI reads; kept so the page can show the late work folded, and so a member the runner could not stop
473
+ -- is known to have finished that turn. One per execution; not backed up, like executions.
474
+ CREATE TABLE IF NOT EXISTS late_answers (
475
+ execution_id INTEGER PRIMARY KEY REFERENCES member_executions(id) ON DELETE CASCADE,
476
+ room_id TEXT NOT NULL,
477
+ text TEXT NOT NULL,
478
+ created_at INTEGER NOT NULL
479
+ );
480
+ -- Why a request was cancelled; a side table so older databases need no column change. Requests cancelled before it existed have no row.
481
+ CREATE TABLE IF NOT EXISTS request_cancel_reasons (
482
+ request_id INTEGER PRIMARY KEY REFERENCES member_requests(id) ON DELETE CASCADE,
483
+ reason TEXT NOT NULL CHECK(reason IN ('user_stop','member_away','member_removed','rerouted','no_reply','archived'))
484
+ );
485
+ -- Messages the owner took back: a retracted one no member began to handle, or the history a rewind returned to before.
486
+ -- AI readers no longer see them; the page keeps each in place. No room column, so it never clashes with messages.room_id in joins.
487
+ CREATE TABLE IF NOT EXISTS message_withdrawals (
488
+ message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
489
+ kind TEXT NOT NULL CHECK(kind IN ('retracted','rewound')),
490
+ created_at INTEGER NOT NULL
491
+ );
492
+ -- An indexed room lookup without adding a room_id column that would make existing message joins ambiguous.
493
+ CREATE TABLE IF NOT EXISTS message_withdrawal_rooms (
494
+ message_id INTEGER PRIMARY KEY REFERENCES message_withdrawals(message_id) ON DELETE CASCADE,
495
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE
496
+ );
497
+ CREATE INDEX IF NOT EXISTS message_withdrawal_rooms_room ON message_withdrawal_rooms(room_id,message_id);
498
+ INSERT INTO message_withdrawal_rooms(message_id,room_id)
499
+ SELECT w.message_id,m.room_id FROM message_withdrawals w JOIN messages m ON m.id=w.message_id
500
+ WHERE NOT EXISTS(SELECT 1 FROM message_withdrawal_rooms r WHERE r.message_id=w.message_id);
501
+ CREATE TRIGGER IF NOT EXISTS withdrawal_room_insert AFTER INSERT ON message_withdrawals
502
+ BEGIN
503
+ INSERT INTO message_withdrawal_rooms(message_id,room_id) SELECT NEW.message_id,room_id FROM messages WHERE id=NEW.message_id;
504
+ END;
505
+ CREATE TRIGGER IF NOT EXISTS withdrawal_room_move AFTER UPDATE OF room_id ON messages
506
+ BEGIN
507
+ UPDATE message_withdrawal_rooms SET room_id=NEW.room_id WHERE message_id=NEW.id;
508
+ END;
509
+ -- Each rewind and, per member id, the executions it took back (JSON {member id: [execution ids]}): before the member's next
510
+ -- execution the runner cuts its session before the earliest of them it finds. Not part of backups, since executions are not.
511
+ CREATE TABLE IF NOT EXISTS room_rewinds (
512
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
513
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
514
+ message_id INTEGER NOT NULL,
515
+ cuts TEXT NOT NULL,
516
+ created_at INTEGER NOT NULL
517
+ );
518
+ -- What produced a decision (model, threshold, prompt and package versions), so ratings stay comparable across versions.
519
+ CREATE TABLE IF NOT EXISTS scheduler_decision_context (
520
+ decision_id INTEGER PRIMARY KEY REFERENCES scheduler_decisions(id) ON DELETE CASCADE,
521
+ context TEXT NOT NULL
522
+ );
523
+ -- The exact input the decision model saw (state and full questions with option descriptions); not part of backups.
524
+ CREATE TABLE IF NOT EXISTS scheduler_decision_inputs (
525
+ decision_id INTEGER PRIMARY KEY REFERENCES scheduler_decisions(id) ON DELETE CASCADE,
526
+ input TEXT NOT NULL
527
+ );
528
+ -- A self-contained copy of each decision the owner currently rates bad, kept when its room is deleted so prompts can be reviewed.
529
+ CREATE TABLE IF NOT EXISTS scheduler_feedback_cases (
530
+ decision_id INTEGER PRIMARY KEY,
531
+ room_id TEXT NOT NULL,
532
+ room_title TEXT NOT NULL,
533
+ snapshot TEXT NOT NULL,
534
+ should_be TEXT NOT NULL DEFAULT '',
535
+ note TEXT NOT NULL DEFAULT '',
536
+ rated_at TEXT NOT NULL
537
+ );
538
+ -- Smart scheduling's suggestion that another member answer an unnamed message. The host keeps the message;
539
+ -- the user may adopt before expires_at (waiting until the host's request ends for mode 'after'), otherwise it lapses.
540
+ CREATE TABLE IF NOT EXISTS member_recommendations (
541
+ message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
542
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
543
+ member_id INTEGER NOT NULL REFERENCES room_members(id) ON DELETE CASCADE,
544
+ provider TEXT NOT NULL,
545
+ confidence REAL NOT NULL,
546
+ created_at INTEGER NOT NULL,
547
+ expires_at INTEGER NOT NULL,
548
+ status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open','waiting','adopted')),
549
+ mode TEXT NOT NULL DEFAULT ''
550
+ );
551
+ CREATE INDEX IF NOT EXISTS member_recommendations_room ON member_recommendations(room_id,status);
552
+ CREATE INDEX IF NOT EXISTS member_recommendations_recent ON member_recommendations(room_id,message_id);
553
+ -- Ordinary members can suggest a response without creating work or spending a chain budget.
554
+ CREATE TABLE IF NOT EXISTS member_suggestions (
555
+ message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
556
+ member_id INTEGER NOT NULL REFERENCES room_members(id) ON DELETE CASCADE,
557
+ PRIMARY KEY (message_id,member_id)
558
+ );
559
+ -- A host's dispatch: its named requests form one batch that hands back to the host once.
560
+ CREATE TABLE IF NOT EXISTS free_batches (
561
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
562
+ batch_id INTEGER NOT NULL,
563
+ chain_id INTEGER NOT NULL,
564
+ host_id INTEGER NOT NULL,
565
+ settled INTEGER NOT NULL DEFAULT 0,
566
+ PRIMARY KEY (room_id,batch_id)
567
+ );
568
+ -- A host's one-after-another dispatch: each step is a one-member batch hung on the previous answer and costs one chain round.
569
+ -- After the last member it hands back to the host; a loop's hand-back is a checkpoint from which the host (or smart
570
+ -- scheduling for it) may run the next pass. `steps` lists [pass, member_id, batch_id] for every step started.
571
+ CREATE TABLE IF NOT EXISTS free_sequences (
572
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
573
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
574
+ message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
575
+ chain_id INTEGER NOT NULL,
576
+ host_id INTEGER NOT NULL,
577
+ member_ids TEXT NOT NULL,
578
+ loop INTEGER NOT NULL DEFAULT 0,
579
+ pass INTEGER NOT NULL DEFAULT 1,
580
+ position INTEGER NOT NULL DEFAULT 0,
581
+ batch_id INTEGER NOT NULL,
582
+ answer_id INTEGER NOT NULL DEFAULT 0,
583
+ handoff_request INTEGER NOT NULL DEFAULT 0,
584
+ steps TEXT NOT NULL DEFAULT '[]',
585
+ status TEXT NOT NULL DEFAULT 'running' CHECK(status IN ('running','checkpoint','done','stopped'))
586
+ );
587
+ CREATE INDEX IF NOT EXISTS free_sequences_batch ON free_sequences(room_id,batch_id);
588
+ CREATE INDEX IF NOT EXISTS free_sequences_handoff ON free_sequences(handoff_request);
589
+ -- Members the user named to answer one after another, in the room's speaking order: each gets its request once the one before
590
+ -- it ends. Asked rows stay so the speaking order can show who already answered; the owner may reorder rows not yet asked.
591
+ CREATE TABLE IF NOT EXISTS free_queue (
592
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
593
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
594
+ message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
595
+ member_id INTEGER NOT NULL REFERENCES room_members(id) ON DELETE CASCADE
596
+ );
597
+ CREATE INDEX IF NOT EXISTS free_queue_room ON free_queue(room_id,id);
598
+ CREATE INDEX IF NOT EXISTS free_queue_message ON free_queue(message_id,id);
599
+ -- Only not-yet-requested queue rows participate in advancement. Historical rows remain in free_queue.
600
+ CREATE TABLE IF NOT EXISTS free_queue_waiting (
601
+ id INTEGER PRIMARY KEY REFERENCES free_queue(id) ON DELETE CASCADE,
602
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
603
+ message_id INTEGER NOT NULL,
604
+ member_id INTEGER NOT NULL
605
+ );
606
+ CREATE INDEX IF NOT EXISTS free_queue_waiting_room ON free_queue_waiting(room_id,message_id,id);
607
+ CREATE INDEX IF NOT EXISTS free_queue_waiting_message ON free_queue_waiting(message_id,member_id);
608
+ INSERT INTO free_queue_waiting(id,room_id,message_id,member_id)
609
+ SELECT q.id,q.room_id,q.message_id,q.member_id FROM free_queue q
610
+ WHERE NOT EXISTS(SELECT 1 FROM member_requests r WHERE r.message_id=q.message_id AND r.member_id=q.member_id)
611
+ AND NOT EXISTS(SELECT 1 FROM free_queue_waiting w WHERE w.id=q.id);
612
+ -- Owner messages sent while members still work wait here, in position order, and are posted one by one once the room is idle.
613
+ -- `payload` is the message as sent (submission_id included); `failure` holds why posting it failed, which holds the queue.
614
+ CREATE TABLE IF NOT EXISTS queued_messages (
615
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
616
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
617
+ position INTEGER NOT NULL,
618
+ submission_id TEXT NOT NULL,
619
+ payload TEXT NOT NULL,
620
+ failure TEXT NOT NULL DEFAULT '',
621
+ created_at INTEGER NOT NULL,
622
+ UNIQUE(room_id,submission_id)
623
+ );
624
+ CREATE INDEX IF NOT EXISTS queued_messages_room ON queued_messages(room_id,position);
625
+ -- A room's queue paused because the owner stopped members; the owner resumes it.
626
+ CREATE TABLE IF NOT EXISTS queued_message_pauses (
627
+ room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE
628
+ );
629
+ -- Site-wide composer behaviour: whether messages sent while members work are queued (1) or sent at once (0).
630
+ CREATE TABLE IF NOT EXISTS composer_settings (
631
+ id INTEGER PRIMARY KEY CHECK (id=1),
632
+ queueing INTEGER NOT NULL DEFAULT 1 CHECK (queueing IN (0,1))
633
+ );
634
+ CREATE TRIGGER IF NOT EXISTS queue_waiting_insert AFTER INSERT ON free_queue
635
+ BEGIN
636
+ INSERT INTO free_queue_waiting(id,room_id,message_id,member_id)
637
+ SELECT NEW.id,NEW.room_id,NEW.message_id,NEW.member_id
638
+ WHERE NOT EXISTS(SELECT 1 FROM member_requests WHERE message_id=NEW.message_id AND member_id=NEW.member_id);
639
+ END;
640
+ CREATE TRIGGER IF NOT EXISTS queue_waiting_move AFTER UPDATE OF room_id,message_id,member_id ON free_queue
641
+ BEGIN
642
+ DELETE FROM free_queue_waiting WHERE id=OLD.id;
643
+ INSERT INTO free_queue_waiting(id,room_id,message_id,member_id)
644
+ SELECT NEW.id,NEW.room_id,NEW.message_id,NEW.member_id
645
+ WHERE NOT EXISTS(SELECT 1 FROM member_requests WHERE message_id=NEW.message_id AND member_id=NEW.member_id);
646
+ END;
647
+ CREATE TRIGGER IF NOT EXISTS queue_waiting_requested AFTER INSERT ON member_requests
648
+ BEGIN
649
+ DELETE FROM free_queue_waiting WHERE message_id=NEW.message_id AND member_id=NEW.member_id;
650
+ END;
651
+ CREATE TRIGGER IF NOT EXISTS queue_waiting_request_deleted AFTER DELETE ON member_requests
652
+ BEGIN
653
+ INSERT INTO free_queue_waiting(id,room_id,message_id,member_id)
654
+ SELECT q.id,q.room_id,q.message_id,q.member_id FROM free_queue q
655
+ WHERE q.message_id=OLD.message_id AND q.member_id=OLD.member_id
656
+ AND NOT EXISTS(SELECT 1 FROM member_requests WHERE message_id=q.message_id AND member_id=q.member_id)
657
+ AND NOT EXISTS(SELECT 1 FROM free_queue_waiting WHERE id=q.id);
658
+ END;
659
+ CREATE TRIGGER IF NOT EXISTS queue_waiting_request_moved AFTER UPDATE OF message_id,member_id ON member_requests
660
+ BEGIN
661
+ DELETE FROM free_queue_waiting WHERE message_id=NEW.message_id AND member_id=NEW.member_id;
662
+ INSERT INTO free_queue_waiting(id,room_id,message_id,member_id)
663
+ SELECT q.id,q.room_id,q.message_id,q.member_id FROM free_queue q
664
+ WHERE q.message_id=OLD.message_id AND q.member_id=OLD.member_id
665
+ AND NOT EXISTS(SELECT 1 FROM member_requests WHERE message_id=q.message_id AND member_id=q.member_id)
666
+ AND NOT EXISTS(SELECT 1 FROM free_queue_waiting WHERE id=q.id);
667
+ END;
668
+ CREATE INDEX IF NOT EXISTS free_batches_chain ON free_batches(room_id,chain_id);
669
+ CREATE INDEX IF NOT EXISTS free_batches_unsettled ON free_batches(room_id,batch_id) WHERE settled=0;
670
+ -- The owner's default speaking order (JSON list of member ids); members it lacks follow, the host first, then by joining order.
671
+ CREATE TABLE IF NOT EXISTS speaking_orders (
672
+ room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
673
+ member_ids TEXT NOT NULL
674
+ );
675
+ -- Chains whose automatic progress stopped (budget reached or every dispatched member failed).
676
+ CREATE TABLE IF NOT EXISTS free_chains (
677
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
678
+ chain_id INTEGER NOT NULL,
679
+ reason TEXT NOT NULL,
680
+ PRIMARY KEY (room_id,chain_id)
681
+ );
682
+ -- A host's dispatch that asked to come back (`handoff`); every batch now comes back, so this only marks the request.
683
+ CREATE TABLE IF NOT EXISTS free_handoffs (
684
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
685
+ batch_id INTEGER NOT NULL,
686
+ PRIMARY KEY (room_id,batch_id)
687
+ );
688
+ -- A host's one-member dispatch with `no_handoff`: a completed answer goes straight to the user (a failure still comes back).
689
+ CREATE TABLE IF NOT EXISTS free_direct_batches (
690
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
691
+ batch_id INTEGER NOT NULL,
692
+ PRIMARY KEY (room_id,batch_id)
693
+ );
694
+ -- What the program handed to the host once (a failure, a possible conflict, its own continue): `key` keeps it to one
695
+ -- wake-up per event, `chain_id` counts it as a round of that chain, `message_id` is the notice its request hangs on.
696
+ CREATE TABLE IF NOT EXISTS free_host_wakes (
697
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
698
+ key TEXT NOT NULL,
699
+ chain_id INTEGER NOT NULL,
700
+ message_id INTEGER NOT NULL,
701
+ PRIMARY KEY (room_id,key)
702
+ );
703
+ -- The host's latest answer asked to continue once the room is idle; a later request for the host or a new owner
704
+ -- message supersedes it (`after_request` is the newest request when it was asked).
705
+ CREATE TABLE IF NOT EXISTS free_host_continues (
706
+ room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
707
+ host_id INTEGER NOT NULL,
708
+ message_id INTEGER NOT NULL,
709
+ chain_id INTEGER NOT NULL,
710
+ reason TEXT NOT NULL,
711
+ after_request INTEGER NOT NULL
712
+ );
713
+ -- The owner's per-room limit on rounds a host may run from one owner message (a round is one host dispatch, its members'
714
+ -- answers and the hand-back; a whole relay pass is one); rooms without a row use the default, 0 means unlimited.
715
+ CREATE TABLE IF NOT EXISTS free_round_limits (
716
+ room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
717
+ max_rounds INTEGER NOT NULL
718
+ );
719
+ -- A chain the owner let run on past its limit: the rounds added, and the dispatch the limit held back, which is sent
720
+ -- when the owner continues (message_id 0 when nothing is held). asked_rounds/asked_reason: the host's `need_rounds`
721
+ -- request that stopped it, the rounds to add for the owner to approve (0 when the plain limit stopped it).
722
+ CREATE TABLE IF NOT EXISTS free_chain_holds (
723
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
724
+ chain_id INTEGER NOT NULL,
725
+ extra_rounds INTEGER NOT NULL DEFAULT 0,
726
+ message_id INTEGER NOT NULL DEFAULT 0,
727
+ host_id INTEGER NOT NULL DEFAULT 0,
728
+ names TEXT NOT NULL DEFAULT '[]',
729
+ handoff INTEGER NOT NULL DEFAULT 0,
730
+ asked_rounds INTEGER NOT NULL DEFAULT 0,
731
+ asked_reason TEXT NOT NULL DEFAULT '',
732
+ PRIMARY KEY (room_id,chain_id)
733
+ );
734
+ -- A relay's later steps within one pass: their batches ride on the pass's first step and cost no round.
735
+ CREATE TABLE IF NOT EXISTS free_relay_steps (
736
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
737
+ batch_id INTEGER NOT NULL,
738
+ PRIMARY KEY (room_id,batch_id)
739
+ );
740
+ -- A discussion is a switch on a room, not a room type: one thin record per started discussion.
741
+ -- Session status is written by the application.
742
+ CREATE TABLE IF NOT EXISTS discussion_sessions (
743
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
744
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
745
+ goal TEXT NOT NULL,
746
+ status TEXT NOT NULL DEFAULT 'paused' CHECK(status IN ('active','paused','concluded')),
747
+ started_message_id INTEGER NOT NULL DEFAULT 0,
748
+ ended_message_id INTEGER NOT NULL DEFAULT 0,
749
+ created_at TEXT NOT NULL
750
+ );
751
+ CREATE UNIQUE INDEX IF NOT EXISTS one_open_session ON discussion_sessions(room_id) WHERE status IN ('active','paused');
752
+ CREATE INDEX IF NOT EXISTS discussion_sessions_room ON discussion_sessions(room_id);
753
+ -- Historical conclusion snapshots are written by the application and remain readable across later discussions.
754
+ CREATE TABLE IF NOT EXISTS discussion_conclusions (
755
+ session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
756
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
757
+ stop_reason TEXT NOT NULL DEFAULT '',
758
+ max_rounds INTEGER NOT NULL DEFAULT 0,
759
+ max_minutes INTEGER NOT NULL DEFAULT 0,
760
+ brief TEXT NOT NULL DEFAULT '',
761
+ state TEXT NOT NULL DEFAULT '',
762
+ state_until INTEGER NOT NULL DEFAULT 0,
763
+ concluded_at TEXT NOT NULL
764
+ );
765
+ -- Session-scoped settings and state.
766
+ CREATE TABLE IF NOT EXISTS session_start_submissions (
767
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
768
+ submission_id TEXT NOT NULL,
769
+ session_id INTEGER NOT NULL UNIQUE REFERENCES discussion_sessions(id) ON DELETE CASCADE,
770
+ request_json TEXT NOT NULL,
771
+ PRIMARY KEY (room_id,submission_id)
772
+ );
773
+ CREATE TABLE IF NOT EXISTS session_state (
774
+ session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
775
+ phase TEXT NOT NULL CHECK(phase IN ('initial','discussing')),
776
+ revision INTEGER NOT NULL DEFAULT 0,
777
+ read_until INTEGER NOT NULL DEFAULT 0,
778
+ held_since INTEGER NOT NULL DEFAULT 0
779
+ );
780
+ CREATE TABLE IF NOT EXISTS session_limits (
781
+ session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
782
+ max_rounds INTEGER NOT NULL DEFAULT 0,
783
+ max_minutes INTEGER NOT NULL DEFAULT 0,
784
+ execution_minutes INTEGER NOT NULL DEFAULT 0,
785
+ started_at INTEGER NOT NULL DEFAULT 0,
786
+ stop_reason TEXT NOT NULL DEFAULT '',
787
+ revision INTEGER NOT NULL DEFAULT 0
788
+ );
789
+ CREATE TABLE IF NOT EXISTS session_round_budgets (
790
+ session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
791
+ base INTEGER NOT NULL DEFAULT 0,
792
+ cap INTEGER NOT NULL DEFAULT 0,
793
+ extra INTEGER NOT NULL DEFAULT 0
794
+ );
795
+ CREATE TABLE IF NOT EXISTS session_pauses (
796
+ session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
797
+ paused_at INTEGER NOT NULL DEFAULT 0,
798
+ paused_seconds INTEGER NOT NULL DEFAULT 0
799
+ );
800
+ CREATE TABLE IF NOT EXISTS session_options (
801
+ session_id INTEGER PRIMARY KEY REFERENCES discussion_sessions(id) ON DELETE CASCADE,
802
+ named_followup INTEGER NOT NULL DEFAULT 0 CHECK(named_followup IN (0,1))
803
+ );
804
+ CREATE TABLE IF NOT EXISTS session_requests (
805
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
806
+ session_id INTEGER NOT NULL REFERENCES discussion_sessions(id) ON DELETE CASCADE,
807
+ round INTEGER NOT NULL DEFAULT 0,
808
+ position INTEGER NOT NULL DEFAULT 0,
809
+ member_id INTEGER NOT NULL REFERENCES room_members(id),
810
+ purpose TEXT NOT NULL CHECK(purpose IN ('discuss','discuss_host')),
811
+ question TEXT NOT NULL DEFAULT '',
812
+ status TEXT NOT NULL DEFAULT 'queued'
813
+ CHECK(status IN ('queued','open','answered','failed','skipped','cancelled')),
814
+ member_request_id INTEGER UNIQUE REFERENCES member_requests(id),
815
+ submission_id TEXT NOT NULL,
816
+ read_until INTEGER DEFAULT NULL,
817
+ created_at TEXT NOT NULL,
818
+ UNIQUE(session_id,submission_id)
819
+ );
820
+ CREATE INDEX IF NOT EXISTS session_requests_queue ON session_requests(session_id,status,round,position,id);
821
+ CREATE INDEX IF NOT EXISTS session_requests_rounds ON session_requests(session_id,round,position,id) WHERE purpose='discuss';
822
+ CREATE UNIQUE INDEX IF NOT EXISTS one_open_session_request ON session_requests(session_id)
823
+ WHERE status='open' AND purpose='discuss';
824
+ CREATE TABLE IF NOT EXISTS task_message_sources (
825
+ message_id INTEGER PRIMARY KEY REFERENCES messages(id) ON DELETE CASCADE,
826
+ session_id INTEGER NOT NULL REFERENCES discussion_sessions(id) ON DELETE CASCADE,
827
+ session_request_id INTEGER NOT NULL UNIQUE REFERENCES session_requests(id) ON DELETE CASCADE,
828
+ cause_kind TEXT NOT NULL CHECK(cause_kind IN ('session_started','scheduled_speaker','speaker_answered','round_complete','user_message','member_change','request_failed')),
829
+ cause_message_id INTEGER REFERENCES messages(id),
830
+ cause_request_id INTEGER REFERENCES member_requests(id)
831
+ );
832
+ CREATE INDEX IF NOT EXISTS task_message_sources_session ON task_message_sources(session_id,message_id);
833
+
834
+ -- One host-authorized review step for one implementation batch. Imported plans remain history only.
835
+ CREATE TABLE IF NOT EXISTS review_plans (
836
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
837
+ batch_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
838
+ chain_id INTEGER NOT NULL,
839
+ host_id INTEGER NOT NULL,
840
+ reviewer_id INTEGER NOT NULL,
841
+ reviewer_name TEXT NOT NULL,
842
+ config_version TEXT NOT NULL,
843
+ member_revision TEXT NOT NULL,
844
+ scope TEXT NOT NULL,
845
+ implementers TEXT NOT NULL,
846
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','advanced','imported')),
847
+ review_batch_id INTEGER REFERENCES messages(id) ON DELETE SET NULL,
848
+ advance_submission TEXT NOT NULL DEFAULT '',
849
+ PRIMARY KEY(room_id,batch_id)
850
+ );
851
+ CREATE UNIQUE INDEX IF NOT EXISTS review_advance_submission ON review_plans(room_id,advance_submission)
852
+ WHERE advance_submission<>'';
853
+ -- UNION ALL (the two sources never share a message) lets SQLite push a message_id lookup into each indexed arm;
854
+ -- plain UNION materializes both tables on every lookup. Recreated on each schema run so existing databases pick it up.
855
+ DROP VIEW IF EXISTS internal_task_messages;
856
+ CREATE VIEW internal_task_messages AS
857
+ SELECT message_id FROM task_message_sources
858
+ UNION ALL SELECT review_batch_id AS message_id FROM review_plans WHERE review_batch_id IS NOT NULL;
859
+ CREATE INDEX IF NOT EXISTS review_plans_batch_message ON review_plans(review_batch_id);
860
+
861
+ -- Cached directory aggregates: backfill existing rooms once, then maintain them in the same write transaction.
862
+ -- Withdrawal keeps a transcript card, so it does not change these counts. Internal task/review messages do.
863
+ CREATE TABLE IF NOT EXISTS room_message_stats (
864
+ room_id TEXT PRIMARY KEY REFERENCES rooms(id) ON DELETE CASCADE,
865
+ message_count INTEGER NOT NULL DEFAULT 0,
866
+ latest_id INTEGER NOT NULL DEFAULT 0
867
+ );
868
+ INSERT INTO room_message_stats(room_id,message_count,latest_id)
869
+ SELECT r.id,COUNT(m.id),COALESCE(MAX(m.id),0) FROM rooms r
870
+ LEFT JOIN messages m ON m.room_id=r.id
871
+ AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)
872
+ WHERE NOT EXISTS(SELECT 1 FROM room_message_stats stats WHERE stats.room_id=r.id) GROUP BY r.id;
873
+ CREATE TRIGGER IF NOT EXISTS message_stats_room AFTER INSERT ON rooms
874
+ BEGIN
875
+ INSERT INTO room_message_stats(room_id) VALUES(NEW.id);
876
+ END;
877
+ CREATE TRIGGER IF NOT EXISTS message_stats_insert AFTER INSERT ON messages
878
+ WHEN NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=NEW.id)
879
+ BEGIN
880
+ UPDATE room_message_stats SET message_count=message_count+1,latest_id=MAX(latest_id,NEW.id)
881
+ WHERE room_id=NEW.room_id;
882
+ END;
883
+ CREATE TRIGGER IF NOT EXISTS message_stats_delete BEFORE DELETE ON messages
884
+ WHEN NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=OLD.id)
885
+ BEGIN
886
+ UPDATE room_message_stats SET message_count=message_count-1,
887
+ latest_id=CASE WHEN latest_id=OLD.id THEN (SELECT COALESCE(MAX(m.id),0) FROM messages m
888
+ WHERE m.room_id=room_message_stats.room_id AND m.id<>OLD.id AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)) ELSE latest_id END
889
+ WHERE room_id=OLD.room_id;
890
+ END;
891
+ CREATE TRIGGER IF NOT EXISTS message_stats_move_before BEFORE UPDATE OF room_id,id ON messages
892
+ WHEN NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=OLD.id)
893
+ BEGIN
894
+ UPDATE room_message_stats SET message_count=message_count-1,
895
+ latest_id=CASE WHEN latest_id=OLD.id THEN (SELECT COALESCE(MAX(m.id),0) FROM messages m
896
+ WHERE m.room_id=room_message_stats.room_id AND m.id<>OLD.id AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)) ELSE latest_id END
897
+ WHERE room_id=OLD.room_id;
898
+ END;
899
+ CREATE TRIGGER IF NOT EXISTS message_stats_move_after AFTER UPDATE OF room_id,id ON messages
900
+ WHEN NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=NEW.id)
901
+ BEGIN
902
+ UPDATE room_message_stats SET message_count=message_count+1,latest_id=MAX(latest_id,NEW.id)
903
+ WHERE room_id=NEW.room_id;
904
+ END;
905
+ CREATE TRIGGER IF NOT EXISTS message_stats_task_insert AFTER INSERT ON task_message_sources
906
+ WHEN EXISTS(SELECT 1 FROM messages WHERE id=NEW.message_id) AND NOT EXISTS(SELECT 1 FROM review_plans WHERE review_batch_id=NEW.message_id)
907
+ BEGIN
908
+ UPDATE room_message_stats SET message_count=message_count-1,
909
+ latest_id=CASE WHEN latest_id=NEW.message_id THEN (SELECT COALESCE(MAX(m.id),0) FROM messages m
910
+ WHERE m.room_id=room_message_stats.room_id AND m.id<>NEW.message_id AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)) ELSE latest_id END
911
+ WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.message_id);
912
+ END;
913
+ CREATE TRIGGER IF NOT EXISTS message_stats_task_delete AFTER DELETE ON task_message_sources
914
+ WHEN EXISTS(SELECT 1 FROM messages WHERE id=OLD.message_id) AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=OLD.message_id)
915
+ BEGIN
916
+ UPDATE room_message_stats SET message_count=message_count+1,latest_id=MAX(latest_id,OLD.message_id)
917
+ WHERE room_id=(SELECT room_id FROM messages WHERE id=OLD.message_id);
918
+ END;
919
+ CREATE TRIGGER IF NOT EXISTS message_stats_task_update_old AFTER UPDATE OF message_id ON task_message_sources
920
+ WHEN OLD.message_id IS NOT NEW.message_id AND EXISTS(SELECT 1 FROM messages WHERE id=OLD.message_id) AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=OLD.message_id)
921
+ BEGIN
922
+ UPDATE room_message_stats SET message_count=message_count+1,latest_id=MAX(latest_id,OLD.message_id)
923
+ WHERE room_id=(SELECT room_id FROM messages WHERE id=OLD.message_id);
924
+ END;
925
+ CREATE TRIGGER IF NOT EXISTS message_stats_task_update_new AFTER UPDATE OF message_id ON task_message_sources
926
+ WHEN OLD.message_id IS NOT NEW.message_id AND EXISTS(SELECT 1 FROM messages WHERE id=NEW.message_id) AND NOT EXISTS(SELECT 1 FROM review_plans WHERE review_batch_id=NEW.message_id)
927
+ BEGIN
928
+ UPDATE room_message_stats SET message_count=message_count-1,
929
+ latest_id=CASE WHEN latest_id=NEW.message_id THEN (SELECT COALESCE(MAX(m.id),0) FROM messages m
930
+ WHERE m.room_id=room_message_stats.room_id AND m.id<>NEW.message_id AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)) ELSE latest_id END
931
+ WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.message_id);
932
+ END;
933
+ CREATE TRIGGER IF NOT EXISTS message_stats_review_insert AFTER INSERT ON review_plans
934
+ WHEN EXISTS(SELECT 1 FROM messages WHERE id=NEW.review_batch_id) AND NOT EXISTS(SELECT 1 FROM task_message_sources WHERE message_id=NEW.review_batch_id)
935
+ AND (SELECT COUNT(*) FROM review_plans WHERE review_batch_id=NEW.review_batch_id)=1
936
+ BEGIN
937
+ UPDATE room_message_stats SET message_count=message_count-1,
938
+ latest_id=CASE WHEN latest_id=NEW.review_batch_id THEN (SELECT COALESCE(MAX(m.id),0) FROM messages m
939
+ WHERE m.room_id=room_message_stats.room_id AND m.id<>NEW.review_batch_id AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)) ELSE latest_id END
940
+ WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.review_batch_id);
941
+ END;
942
+ CREATE TRIGGER IF NOT EXISTS message_stats_review_delete AFTER DELETE ON review_plans
943
+ WHEN EXISTS(SELECT 1 FROM messages WHERE id=OLD.review_batch_id) AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=OLD.review_batch_id)
944
+ BEGIN
945
+ UPDATE room_message_stats SET message_count=message_count+1,latest_id=MAX(latest_id,OLD.review_batch_id)
946
+ WHERE room_id=(SELECT room_id FROM messages WHERE id=OLD.review_batch_id);
947
+ END;
948
+ CREATE TRIGGER IF NOT EXISTS message_stats_review_update_old AFTER UPDATE OF review_batch_id ON review_plans
949
+ WHEN OLD.review_batch_id IS NOT NEW.review_batch_id AND EXISTS(SELECT 1 FROM messages WHERE id=OLD.review_batch_id) AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=OLD.review_batch_id)
950
+ BEGIN
951
+ UPDATE room_message_stats SET message_count=message_count+1,latest_id=MAX(latest_id,OLD.review_batch_id)
952
+ WHERE room_id=(SELECT room_id FROM messages WHERE id=OLD.review_batch_id);
953
+ END;
954
+ CREATE TRIGGER IF NOT EXISTS message_stats_review_update_new AFTER UPDATE OF review_batch_id ON review_plans
955
+ WHEN OLD.review_batch_id IS NOT NEW.review_batch_id AND EXISTS(SELECT 1 FROM messages WHERE id=NEW.review_batch_id) AND NOT EXISTS(SELECT 1 FROM task_message_sources WHERE message_id=NEW.review_batch_id)
956
+ AND (SELECT COUNT(*) FROM review_plans WHERE review_batch_id=NEW.review_batch_id)=1
957
+ BEGIN
958
+ UPDATE room_message_stats SET message_count=message_count-1,
959
+ latest_id=CASE WHEN latest_id=NEW.review_batch_id THEN (SELECT COALESCE(MAX(m.id),0) FROM messages m
960
+ WHERE m.room_id=room_message_stats.room_id AND m.id<>NEW.review_batch_id AND NOT EXISTS(SELECT 1 FROM internal_task_messages t WHERE t.message_id=m.id)) ELSE latest_id END
961
+ WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.review_batch_id);
962
+ END;
963
+
964
+ -- Runtime idempotency receipts for converting one host interjection to an ordinary request.
965
+ CREATE TABLE IF NOT EXISTS session_ask_submissions (
966
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
967
+ submission_id TEXT NOT NULL,
968
+ source_request_id INTEGER NOT NULL UNIQUE REFERENCES member_requests(id) ON DELETE CASCADE,
969
+ request_id INTEGER NOT NULL REFERENCES member_requests(id) ON DELETE CASCADE,
970
+ PRIMARY KEY (room_id,submission_id)
971
+ );
972
+
973
+ -- A computer running the local service, paired by the site administrator. Only project names and ids and the
974
+ -- configured client of each platform are published; directories and credentials stay on that computer.
975
+ CREATE TABLE IF NOT EXISTS local_machines (
976
+ id TEXT PRIMARY KEY,
977
+ name TEXT NOT NULL,
978
+ token_hash TEXT NOT NULL,
979
+ projects TEXT NOT NULL DEFAULT '[]',
980
+ clients TEXT NOT NULL DEFAULT '{}',
981
+ seen_at INTEGER NOT NULL DEFAULT 0,
982
+ created_at INTEGER NOT NULL
983
+ );
984
+ -- A room launch submitted from a device without the local service; the paired computer claims and runs it.
985
+ -- The request (with the room's new credentials) is cleared once the launch finishes or fails.
986
+ CREATE TABLE IF NOT EXISTS remote_launches (
987
+ id TEXT PRIMARY KEY,
988
+ machine_id TEXT NOT NULL,
989
+ request TEXT NOT NULL DEFAULT '{}',
990
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','claimed','done','failed')),
991
+ result TEXT NOT NULL DEFAULT '{}',
992
+ error TEXT NOT NULL DEFAULT '',
993
+ created_at INTEGER NOT NULL,
994
+ updated_at INTEGER NOT NULL
995
+ );
996
+ CREATE INDEX IF NOT EXISTS remote_launches_machine ON remote_launches(machine_id, status);
997
+ -- The P-256 public key a paired computer publishes so other devices can encrypt commands only it can read.
998
+ CREATE TABLE IF NOT EXISTS machine_keys (
999
+ machine_id TEXT PRIMARY KEY,
1000
+ public_key TEXT NOT NULL
1001
+ );
1002
+ -- A local-service operation relayed from a device without the local service. Request and response are end-to-end
1003
+ -- encrypted between that device and the computer, so the site never holds keys, paths or results in plaintext;
1004
+ -- the request is cleared once claimed and the row is deleted once read or expired.
1005
+ CREATE TABLE IF NOT EXISTS machine_commands (
1006
+ id TEXT PRIMARY KEY,
1007
+ machine_id TEXT NOT NULL,
1008
+ request TEXT NOT NULL DEFAULT '{}',
1009
+ response TEXT NOT NULL DEFAULT '{}',
1010
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','claimed','done')),
1011
+ created_at INTEGER NOT NULL,
1012
+ updated_at INTEGER NOT NULL
1013
+ );
1014
+ CREATE INDEX IF NOT EXISTS machine_commands_machine ON machine_commands(machine_id, status);
1015
+
1016
+ -- Change revisions, so idle rooms are not read on every poll. Every write a reader could show or act on gives its room the next
1017
+ -- value of one site-wide sequence: a room's revision tells a runner or page that this room changed, and the largest revision
1018
+ -- (read through the index) tells a page that anything on the site changed. The '' row stands for directory-only changes.
1019
+ -- Triggers update or insert without conflicts, so an outer INSERT OR IGNORE cannot override them into skipping the bump.
1020
+ CREATE TABLE IF NOT EXISTS room_revisions (
1021
+ room_id TEXT PRIMARY KEY,
1022
+ revision INTEGER NOT NULL
1023
+ );
1024
+ CREATE INDEX IF NOT EXISTS room_revisions_order ON room_revisions(revision);
1025
+ DROP TRIGGER IF EXISTS revision_message_insert;
1026
+ CREATE TRIGGER revision_message_insert AFTER INSERT ON messages
1027
+ BEGIN
1028
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1029
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1030
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1031
+ END;
1032
+ DROP TRIGGER IF EXISTS revision_message_update;
1033
+ CREATE TRIGGER revision_message_update AFTER UPDATE ON messages
1034
+ BEGIN
1035
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1036
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1037
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1038
+ END;
1039
+ DROP TRIGGER IF EXISTS revision_message_withdrawal;
1040
+ CREATE TRIGGER revision_message_withdrawal AFTER INSERT ON message_withdrawals
1041
+ BEGIN
1042
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.message_id);
1043
+ INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM messages WHERE id=NEW.message_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1044
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM messages WHERE id=NEW.message_id));
1045
+ END;
1046
+ DROP TRIGGER IF EXISTS revision_member_insert;
1047
+ CREATE TRIGGER revision_member_insert AFTER INSERT ON room_members
1048
+ BEGIN
1049
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1050
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1051
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1052
+ END;
1053
+ DROP TRIGGER IF EXISTS revision_member_update;
1054
+ CREATE TRIGGER revision_member_update AFTER UPDATE ON room_members
1055
+ BEGIN
1056
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1057
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1058
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1059
+ END;
1060
+ DROP TRIGGER IF EXISTS revision_registration_insert;
1061
+ CREATE TRIGGER revision_registration_insert AFTER INSERT ON member_registrations
1062
+ BEGIN
1063
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1064
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1065
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1066
+ END;
1067
+ DROP TRIGGER IF EXISTS revision_registration_update;
1068
+ CREATE TRIGGER revision_registration_update AFTER UPDATE ON member_registrations
1069
+ BEGIN
1070
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1071
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1072
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1073
+ END;
1074
+ DROP TRIGGER IF EXISTS revision_session_report_insert;
1075
+ CREATE TRIGGER revision_session_report_insert AFTER INSERT ON member_sessions
1076
+ BEGIN
1077
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1078
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1079
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1080
+ END;
1081
+ DROP TRIGGER IF EXISTS revision_adopt_failure_insert;
1082
+ CREATE TRIGGER revision_adopt_failure_insert AFTER INSERT ON member_adopt_failures
1083
+ BEGIN
1084
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1085
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1086
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1087
+ END;
1088
+ DROP TRIGGER IF EXISTS revision_session_report_update;
1089
+ CREATE TRIGGER revision_session_report_update AFTER UPDATE ON member_sessions
1090
+ WHEN OLD.session_id IS NOT NEW.session_id OR OLD.client IS NOT NEW.client
1091
+ BEGIN
1092
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1093
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1094
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1095
+ END;
1096
+ DROP TRIGGER IF EXISTS revision_request_insert;
1097
+ CREATE TRIGGER revision_request_insert AFTER INSERT ON member_requests
1098
+ BEGIN
1099
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1100
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1101
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1102
+ END;
1103
+ DROP TRIGGER IF EXISTS revision_request_status;
1104
+ CREATE TRIGGER revision_request_status AFTER UPDATE OF status ON member_requests
1105
+ WHEN OLD.status IS NOT NEW.status
1106
+ BEGIN
1107
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1108
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1109
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1110
+ END;
1111
+ DROP TRIGGER IF EXISTS revision_execution_insert;
1112
+ CREATE TRIGGER revision_execution_insert AFTER INSERT ON member_executions
1113
+ BEGIN
1114
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1115
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1116
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1117
+ END;
1118
+ DROP TRIGGER IF EXISTS revision_execution_status;
1119
+ CREATE TRIGGER revision_execution_status AFTER UPDATE OF status ON member_executions
1120
+ WHEN OLD.status IS NOT NEW.status
1121
+ BEGIN
1122
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1123
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1124
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1125
+ END;
1126
+ -- Non-progress execution reports must invalidate full views even without SSE. Renewals/notes stay lightweight.
1127
+ CREATE TRIGGER IF NOT EXISTS revision_execution_report AFTER UPDATE OF changes,result,session_id ON member_executions
1128
+ WHEN OLD.changes IS NOT NEW.changes OR OLD.result IS NOT NEW.result OR OLD.session_id IS NOT NEW.session_id
1129
+ BEGIN
1130
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1131
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1132
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1133
+ END;
1134
+ CREATE TRIGGER IF NOT EXISTS revision_interjection_insert AFTER INSERT ON execution_interjections
1135
+ BEGIN
1136
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1137
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1138
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1139
+ END;
1140
+ CREATE TRIGGER IF NOT EXISTS revision_interjection_update AFTER UPDATE OF status ON execution_interjections
1141
+ WHEN OLD.status IS NOT NEW.status
1142
+ BEGIN
1143
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1144
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1145
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1146
+ END;
1147
+ CREATE TRIGGER IF NOT EXISTS revision_capability_insert AFTER INSERT ON execution_capabilities
1148
+ BEGIN
1149
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1150
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1151
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1152
+ END;
1153
+ CREATE TRIGGER IF NOT EXISTS revision_capability_update AFTER UPDATE ON execution_capabilities
1154
+ WHEN OLD.stoppable IS NOT NEW.stoppable OR OLD.progress IS NOT NEW.progress OR OLD.stop_result IS NOT NEW.stop_result OR OLD.steer IS NOT NEW.steer
1155
+ BEGIN
1156
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1157
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1158
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1159
+ END;
1160
+ CREATE TRIGGER IF NOT EXISTS revision_denials_insert AFTER INSERT ON execution_denials
1161
+ BEGIN
1162
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1163
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1164
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1165
+ END;
1166
+ CREATE TRIGGER IF NOT EXISTS revision_late_answer AFTER INSERT ON late_answers
1167
+ BEGIN
1168
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1169
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1170
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1171
+ END;
1172
+ DROP TRIGGER IF EXISTS revision_retry_insert;
1173
+ CREATE TRIGGER revision_retry_insert AFTER INSERT ON execution_retries
1174
+ BEGIN
1175
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1176
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1177
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1178
+ END;
1179
+ DROP TRIGGER IF EXISTS revision_retry_update;
1180
+ CREATE TRIGGER revision_retry_update AFTER UPDATE ON execution_retries
1181
+ BEGIN
1182
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1183
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1184
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1185
+ END;
1186
+ DROP TRIGGER IF EXISTS revision_discussion_insert;
1187
+ CREATE TRIGGER revision_discussion_insert AFTER INSERT ON discussion_sessions
1188
+ BEGIN
1189
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1190
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1191
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1192
+ END;
1193
+ DROP TRIGGER IF EXISTS revision_discussion_status;
1194
+ CREATE TRIGGER revision_discussion_status AFTER UPDATE OF status ON discussion_sessions
1195
+ WHEN OLD.status IS NOT NEW.status
1196
+ BEGIN
1197
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1198
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1199
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1200
+ END;
1201
+ DROP TRIGGER IF EXISTS revision_plan_insert;
1202
+ CREATE TRIGGER revision_plan_insert AFTER INSERT ON session_requests
1203
+ BEGIN
1204
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id);
1205
+ INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1206
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id));
1207
+ END;
1208
+ DROP TRIGGER IF EXISTS revision_plan_status;
1209
+ CREATE TRIGGER revision_plan_status AFTER UPDATE OF status ON session_requests
1210
+ WHEN OLD.status IS NOT NEW.status
1211
+ BEGIN
1212
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id);
1213
+ INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1214
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id));
1215
+ END;
1216
+ DROP TRIGGER IF EXISTS revision_session_phase;
1217
+ CREATE TRIGGER revision_session_phase AFTER UPDATE OF phase ON session_state
1218
+ WHEN OLD.phase IS NOT NEW.phase
1219
+ BEGIN
1220
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id);
1221
+ INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1222
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id));
1223
+ END;
1224
+ DROP TRIGGER IF EXISTS revision_session_limits;
1225
+ CREATE TRIGGER revision_session_limits AFTER UPDATE ON session_limits
1226
+ BEGIN
1227
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id);
1228
+ INSERT INTO room_revisions (room_id,revision) SELECT (SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id),(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1229
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=(SELECT room_id FROM discussion_sessions WHERE id=NEW.session_id));
1230
+ END;
1231
+ DROP TRIGGER IF EXISTS revision_topic_update;
1232
+ CREATE TRIGGER revision_topic_update AFTER UPDATE ON topic_settings
1233
+ BEGIN
1234
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1235
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1236
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1237
+ END;
1238
+ DROP TRIGGER IF EXISTS revision_brief_insert;
1239
+ CREATE TRIGGER revision_brief_insert AFTER INSERT ON room_briefs
1240
+ BEGIN
1241
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1242
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1243
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1244
+ END;
1245
+ DROP TRIGGER IF EXISTS revision_brief_update;
1246
+ CREATE TRIGGER revision_brief_update AFTER UPDATE ON room_briefs
1247
+ BEGIN
1248
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1249
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1250
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1251
+ END;
1252
+ DROP TRIGGER IF EXISTS revision_user_request_insert;
1253
+ CREATE TRIGGER revision_user_request_insert AFTER INSERT ON room_user_requests
1254
+ BEGIN
1255
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1256
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1257
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1258
+ END;
1259
+ DROP TRIGGER IF EXISTS revision_user_request_update;
1260
+ CREATE TRIGGER revision_user_request_update AFTER UPDATE ON room_user_requests
1261
+ BEGIN
1262
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1263
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1264
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1265
+ END;
1266
+ DROP TRIGGER IF EXISTS revision_decision_insert;
1267
+ CREATE TRIGGER revision_decision_insert AFTER INSERT ON scheduler_decisions
1268
+ BEGIN
1269
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1270
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1271
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1272
+ END;
1273
+ DROP TRIGGER IF EXISTS revision_recommendation_insert;
1274
+ CREATE TRIGGER revision_recommendation_insert AFTER INSERT ON member_recommendations
1275
+ BEGIN
1276
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1277
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1278
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1279
+ END;
1280
+ DROP TRIGGER IF EXISTS revision_recommendation_status;
1281
+ CREATE TRIGGER revision_recommendation_status AFTER UPDATE OF status ON member_recommendations
1282
+ WHEN OLD.status IS NOT NEW.status
1283
+ BEGIN
1284
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1285
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1286
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1287
+ END;
1288
+ DROP TRIGGER IF EXISTS revision_description_insert;
1289
+ CREATE TRIGGER revision_description_insert AFTER INSERT ON member_descriptions
1290
+ BEGIN
1291
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1292
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1293
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1294
+ END;
1295
+ DROP TRIGGER IF EXISTS revision_description_update;
1296
+ CREATE TRIGGER revision_description_update AFTER UPDATE ON member_descriptions
1297
+ BEGIN
1298
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1299
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1300
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1301
+ END;
1302
+ DROP TRIGGER IF EXISTS revision_label_insert;
1303
+ CREATE TRIGGER revision_label_insert AFTER INSERT ON member_labels
1304
+ BEGIN
1305
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1306
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1307
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1308
+ END;
1309
+ DROP TRIGGER IF EXISTS revision_label_update;
1310
+ CREATE TRIGGER revision_label_update AFTER UPDATE ON member_labels
1311
+ BEGIN
1312
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1313
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1314
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1315
+ END;
1316
+ DROP TRIGGER IF EXISTS revision_order_insert;
1317
+ CREATE TRIGGER revision_order_insert AFTER INSERT ON speaking_orders
1318
+ BEGIN
1319
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1320
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1321
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1322
+ END;
1323
+ DROP TRIGGER IF EXISTS revision_order_update;
1324
+ CREATE TRIGGER revision_order_update AFTER UPDATE ON speaking_orders
1325
+ BEGIN
1326
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1327
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1328
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1329
+ END;
1330
+ DROP TRIGGER IF EXISTS revision_room_insert;
1331
+ CREATE TRIGGER revision_room_insert AFTER INSERT ON rooms
1332
+ BEGIN
1333
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.id;
1334
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1335
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.id);
1336
+ END;
1337
+ DROP TRIGGER IF EXISTS revision_room_update;
1338
+ CREATE TRIGGER revision_room_update AFTER UPDATE ON rooms
1339
+ BEGIN
1340
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.id;
1341
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1342
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.id);
1343
+ END;
1344
+ DROP TRIGGER IF EXISTS revision_room_delete;
1345
+ CREATE TRIGGER revision_room_delete AFTER DELETE ON rooms
1346
+ BEGIN
1347
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1348
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1349
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1350
+ END;
1351
+ DROP TRIGGER IF EXISTS revision_directory_update;
1352
+ CREATE TRIGGER revision_directory_update AFTER UPDATE ON room_directory
1353
+ BEGIN
1354
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1355
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1356
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1357
+ END;
1358
+ DROP TRIGGER IF EXISTS revision_assignment_update;
1359
+ CREATE TRIGGER revision_assignment_update AFTER UPDATE ON room_folder_assignments
1360
+ BEGIN
1361
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1362
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1363
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1364
+ END;
1365
+ DROP TRIGGER IF EXISTS revision_folder_insert;
1366
+ CREATE TRIGGER revision_folder_insert AFTER INSERT ON topic_folders
1367
+ BEGIN
1368
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1369
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1370
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1371
+ END;
1372
+ DROP TRIGGER IF EXISTS revision_folder_update;
1373
+ CREATE TRIGGER revision_folder_update AFTER UPDATE ON topic_folders
1374
+ BEGIN
1375
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1376
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1377
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1378
+ END;
1379
+ DROP TRIGGER IF EXISTS revision_folder_delete;
1380
+ CREATE TRIGGER revision_folder_delete AFTER DELETE ON topic_folders
1381
+ BEGIN
1382
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1383
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1384
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1385
+ END;
1386
+ DROP TRIGGER IF EXISTS revision_queued_message_insert;
1387
+ CREATE TRIGGER revision_queued_message_insert AFTER INSERT ON queued_messages
1388
+ BEGIN
1389
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1390
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1391
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1392
+ END;
1393
+ DROP TRIGGER IF EXISTS revision_queued_message_update;
1394
+ CREATE TRIGGER revision_queued_message_update AFTER UPDATE ON queued_messages
1395
+ BEGIN
1396
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1397
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1398
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1399
+ END;
1400
+ DROP TRIGGER IF EXISTS revision_queued_message_delete;
1401
+ CREATE TRIGGER revision_queued_message_delete AFTER DELETE ON queued_messages
1402
+ BEGIN
1403
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=OLD.room_id;
1404
+ INSERT INTO room_revisions (room_id,revision) SELECT OLD.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1405
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=OLD.room_id);
1406
+ END;
1407
+ DROP TRIGGER IF EXISTS revision_queue_pause_insert;
1408
+ CREATE TRIGGER revision_queue_pause_insert AFTER INSERT ON queued_message_pauses
1409
+ BEGIN
1410
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=NEW.room_id;
1411
+ INSERT INTO room_revisions (room_id,revision) SELECT NEW.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1412
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=NEW.room_id);
1413
+ END;
1414
+ DROP TRIGGER IF EXISTS revision_queue_pause_delete;
1415
+ CREATE TRIGGER revision_queue_pause_delete AFTER DELETE ON queued_message_pauses
1416
+ BEGIN
1417
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id=OLD.room_id;
1418
+ INSERT INTO room_revisions (room_id,revision) SELECT OLD.room_id,(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1419
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id=OLD.room_id);
1420
+ END;
1421
+ DROP TRIGGER IF EXISTS revision_composer_settings_insert;
1422
+ CREATE TRIGGER revision_composer_settings_insert AFTER INSERT ON composer_settings
1423
+ BEGIN
1424
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1425
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1426
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1427
+ END;
1428
+ DROP TRIGGER IF EXISTS revision_composer_settings_update;
1429
+ CREATE TRIGGER revision_composer_settings_update AFTER UPDATE ON composer_settings
1430
+ BEGIN
1431
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1432
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1433
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1434
+ END;
1435
+ DROP TRIGGER IF EXISTS revision_directory_settings;
1436
+ CREATE TRIGGER revision_directory_settings AFTER UPDATE ON directory_settings
1437
+ BEGIN
1438
+ UPDATE room_revisions SET revision=(SELECT MAX(revision)+1 FROM room_revisions) WHERE room_id='';
1439
+ INSERT INTO room_revisions (room_id,revision) SELECT '',(SELECT COALESCE(MAX(revision),0)+1 FROM room_revisions)
1440
+ WHERE NOT EXISTS (SELECT 1 FROM room_revisions WHERE room_id='');
1441
+ END;
1442
+ -- The platform once called Gemini is identified as Antigravity (the software); screens still show the nickname Gemini.
1443
+ -- Member names move to the new ID, with any instance suffix (Gemini-2 → Antigravity-2); names inside stored JSON follow.
1444
+ -- Message text keeps what was written. Each statement only touches rows still holding the old ID, so reruns change nothing.
1445
+ UPDATE room_members SET name='Antigravity'||substr(name,7) WHERE name='Gemini' OR name GLOB 'Gemini-*';
1446
+ UPDATE messages SET author='Antigravity'||substr(author,7) WHERE author='Gemini' OR author GLOB 'Gemini-*';
1447
+ UPDATE message_summaries SET author='Antigravity'||substr(author,7) WHERE author='Gemini' OR author GLOB 'Gemini-*';
1448
+ UPDATE message_details SET submitted_by='Antigravity'||substr(submitted_by,7) WHERE submitted_by='Gemini' OR submitted_by GLOB 'Gemini-*';
1449
+ UPDATE attachments SET actor='Antigravity'||substr(actor,7) WHERE actor='Gemini' OR actor GLOB 'Gemini-*';
1450
+ UPDATE topic_settings SET host='Antigravity'||substr(host,7) WHERE host='Gemini' OR host GLOB 'Gemini-*';
1451
+ UPDATE member_descriptions SET name='Antigravity'||substr(name,7) WHERE name='Gemini' OR name GLOB 'Gemini-*';
1452
+ UPDATE member_labels SET name='Antigravity'||substr(name,7) WHERE name='Gemini' OR name GLOB 'Gemini-*';
1453
+ UPDATE scheduler_feedback SET should_be='Antigravity'||substr(should_be,7) WHERE should_be='Gemini' OR should_be GLOB 'Gemini-*';
1454
+ UPDATE scheduler_feedback_cases SET should_be='Antigravity'||substr(should_be,7) WHERE should_be='Gemini' OR should_be GLOB 'Gemini-*';
1455
+ UPDATE review_plans SET reviewer_name='Antigravity'||substr(reviewer_name,7) WHERE reviewer_name='Gemini' OR reviewer_name GLOB 'Gemini-*';
1456
+ UPDATE topic_settings SET experts=replace(replace(experts,'"Gemini"','"Antigravity"'),'"Gemini-','"Antigravity-') WHERE experts LIKE '%"Gemini%';
1457
+ UPDATE free_chain_holds SET names=replace(replace(names,'"Gemini"','"Antigravity"'),'"Gemini-','"Antigravity-') WHERE names LIKE '%"Gemini%';
1458
+ UPDATE room_creations SET request_text=replace(replace(request_text,'"Gemini"','"Antigravity"'),'"Gemini-','"Antigravity-') WHERE request_text LIKE '%"Gemini%';
1459
+ UPDATE session_start_submissions SET request_json=replace(replace(request_json,'"Gemini"','"Antigravity"'),'"Gemini-','"Antigravity-') WHERE request_json LIKE '%"Gemini%';
1460
+ UPDATE scheduler_decision_inputs SET input=replace(replace(input,'"Gemini"','"Antigravity"'),'"Gemini-','"Antigravity-') WHERE input LIKE '%"Gemini%';
1461
+ -- Room event log: an append-only record of each room state change, written by the triggers below so every code path
1462
+ -- is covered and later edits cannot forget it. State tables are overwritten in place; this log keeps the order of
1463
+ -- what happened (which request was created under which message, why it was cancelled, which batch handed back) so the
1464
+ -- timeline page can list and replay it. `at` is epoch milliseconds; `data` holds ids, names resolve at read time.
1465
+ CREATE TABLE IF NOT EXISTS room_events (
1466
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1467
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
1468
+ at INTEGER NOT NULL,
1469
+ kind TEXT NOT NULL,
1470
+ data TEXT NOT NULL DEFAULT '{}'
1471
+ );
1472
+ CREATE INDEX IF NOT EXISTS room_events_room ON room_events(room_id,id);
1473
+ DROP TRIGGER IF EXISTS event_message_posted;
1474
+ CREATE TRIGGER event_message_posted AFTER INSERT ON messages
1475
+ BEGIN
1476
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'message.posted',
1477
+ json_object('message_id',NEW.id,'author',NEW.author,'source',NEW.source,'reply_to',NEW.reply_to,'text',substr(NEW.text,1,80)));
1478
+ END;
1479
+ DROP TRIGGER IF EXISTS event_message_withdrawn;
1480
+ CREATE TRIGGER event_message_withdrawn AFTER INSERT ON message_withdrawals
1481
+ BEGIN
1482
+ INSERT INTO room_events (room_id,at,kind,data) SELECT room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'message.withdrawn',
1483
+ json_object('message_id',NEW.message_id,'kind',NEW.kind) FROM messages WHERE id=NEW.message_id;
1484
+ END;
1485
+ DROP TRIGGER IF EXISTS event_room_rewound;
1486
+ CREATE TRIGGER event_room_rewound AFTER INSERT ON room_rewinds
1487
+ BEGIN
1488
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'room.rewound',
1489
+ json_object('rewind_id',NEW.id,'message_id',NEW.message_id));
1490
+ END;
1491
+ DROP TRIGGER IF EXISTS event_member_joined;
1492
+ CREATE TRIGGER event_member_joined AFTER INSERT ON room_members
1493
+ BEGIN
1494
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'member.joined',
1495
+ json_object('member_id',NEW.id,'role',NEW.role,'status',NEW.status,'adapter',NEW.adapter));
1496
+ END;
1497
+ DROP TRIGGER IF EXISTS event_member_changed;
1498
+ CREATE TRIGGER event_member_changed AFTER UPDATE OF role,status ON room_members
1499
+ WHEN OLD.role IS NOT NEW.role OR OLD.status IS NOT NEW.status
1500
+ BEGIN
1501
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'member.changed',
1502
+ json_object('member_id',NEW.id,'role',NEW.role,'status',NEW.status,'from_role',OLD.role,'from_status',OLD.status));
1503
+ END;
1504
+ DROP TRIGGER IF EXISTS event_request_created;
1505
+ CREATE TRIGGER event_request_created AFTER INSERT ON member_requests
1506
+ BEGIN
1507
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'request.created',
1508
+ json_object('request_id',NEW.id,'member_id',NEW.member_id,'message_id',NEW.message_id,'purpose',NEW.purpose,
1509
+ 'created_by',NEW.created_by,'chain_id',NEW.chain_id,'batch_id',NEW.batch_id,'status',NEW.status));
1510
+ END;
1511
+ DROP TRIGGER IF EXISTS event_request_changed;
1512
+ CREATE TRIGGER event_request_changed AFTER UPDATE OF status,execution_id,answer_id ON member_requests
1513
+ WHEN OLD.status IS NOT NEW.status OR OLD.execution_id IS NOT NEW.execution_id OR OLD.answer_id IS NOT NEW.answer_id
1514
+ BEGIN
1515
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'request.changed',
1516
+ json_object('request_id',NEW.id,'member_id',NEW.member_id,'message_id',NEW.message_id,'status',NEW.status,
1517
+ 'from_status',OLD.status,'execution_id',NEW.execution_id,'answer_id',NEW.answer_id));
1518
+ END;
1519
+ DROP TRIGGER IF EXISTS event_request_cancel_reason;
1520
+ CREATE TRIGGER event_request_cancel_reason AFTER INSERT ON request_cancel_reasons
1521
+ BEGIN
1522
+ INSERT INTO room_events (room_id,at,kind,data) SELECT room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'request.cancel_reason',
1523
+ json_object('request_id',NEW.request_id,'member_id',member_id,'reason',NEW.reason) FROM member_requests WHERE id=NEW.request_id;
1524
+ END;
1525
+ DROP TRIGGER IF EXISTS event_execution_created;
1526
+ CREATE TRIGGER event_execution_created AFTER INSERT ON member_executions
1527
+ BEGIN
1528
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'execution.created',
1529
+ json_object('execution_id',NEW.id,'member_id',NEW.member_id,'status',NEW.status));
1530
+ END;
1531
+ DROP TRIGGER IF EXISTS event_execution_changed;
1532
+ CREATE TRIGGER event_execution_changed AFTER UPDATE OF status ON member_executions
1533
+ WHEN OLD.status IS NOT NEW.status
1534
+ BEGIN
1535
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'execution.changed',
1536
+ json_object('execution_id',NEW.id,'member_id',NEW.member_id,'status',NEW.status,'from_status',OLD.status,'result',substr(NEW.result,1,200)));
1537
+ END;
1538
+ DROP TRIGGER IF EXISTS event_execution_retry;
1539
+ CREATE TRIGGER event_execution_retry AFTER INSERT ON execution_retries
1540
+ BEGIN
1541
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'execution.retry',
1542
+ json_object('execution_id',NEW.execution_id,'attempt',NEW.attempt,'retry_at',NEW.retry_at,'reason',substr(NEW.reason,1,200),'stopped',NEW.stopped));
1543
+ END;
1544
+ DROP TRIGGER IF EXISTS event_execution_retry_changed;
1545
+ CREATE TRIGGER event_execution_retry_changed AFTER UPDATE ON execution_retries
1546
+ WHEN OLD.attempt IS NOT NEW.attempt OR OLD.stopped IS NOT NEW.stopped
1547
+ BEGIN
1548
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'execution.retry',
1549
+ json_object('execution_id',NEW.execution_id,'attempt',NEW.attempt,'retry_at',NEW.retry_at,'reason',substr(NEW.reason,1,200),'stopped',NEW.stopped));
1550
+ END;
1551
+ DROP TRIGGER IF EXISTS event_execution_late_answer;
1552
+ CREATE TRIGGER event_execution_late_answer AFTER INSERT ON late_answers
1553
+ BEGIN
1554
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'execution.late_answer',
1555
+ json_object('execution_id',NEW.execution_id,'text',substr(NEW.text,1,80)));
1556
+ END;
1557
+ DROP TRIGGER IF EXISTS event_batch_opened;
1558
+ CREATE TRIGGER event_batch_opened AFTER INSERT ON free_batches
1559
+ BEGIN
1560
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'batch.opened',
1561
+ json_object('batch_id',NEW.batch_id,'chain_id',NEW.chain_id,'host_id',NEW.host_id));
1562
+ END;
1563
+ DROP TRIGGER IF EXISTS event_batch_settled;
1564
+ CREATE TRIGGER event_batch_settled AFTER UPDATE OF settled ON free_batches
1565
+ WHEN OLD.settled IS NOT NEW.settled
1566
+ BEGIN
1567
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'batch.settled',
1568
+ json_object('batch_id',NEW.batch_id,'chain_id',NEW.chain_id,'settled',NEW.settled));
1569
+ END;
1570
+ DROP TRIGGER IF EXISTS event_batch_handoff;
1571
+ CREATE TRIGGER event_batch_handoff AFTER INSERT ON free_handoffs
1572
+ BEGIN
1573
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'batch.handoff',
1574
+ json_object('batch_id',NEW.batch_id));
1575
+ END;
1576
+ DROP TRIGGER IF EXISTS event_queue_added;
1577
+ CREATE TRIGGER event_queue_added AFTER INSERT ON free_queue
1578
+ BEGIN
1579
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'queue.added',
1580
+ json_object('queue_id',NEW.id,'message_id',NEW.message_id,'member_id',NEW.member_id));
1581
+ END;
1582
+ DROP TRIGGER IF EXISTS event_queue_removed;
1583
+ CREATE TRIGGER event_queue_removed AFTER DELETE ON free_queue
1584
+ BEGIN
1585
+ INSERT INTO room_events (room_id,at,kind,data) SELECT OLD.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'queue.removed',
1586
+ json_object('queue_id',OLD.id,'message_id',OLD.message_id,'member_id',OLD.member_id) WHERE EXISTS (SELECT 1 FROM rooms WHERE id=OLD.room_id);
1587
+ END;
1588
+ DROP TRIGGER IF EXISTS event_chain_held;
1589
+ CREATE TRIGGER event_chain_held AFTER INSERT ON free_chain_holds
1590
+ BEGIN
1591
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'chain.held',
1592
+ json_object('chain_id',NEW.chain_id,'message_id',NEW.message_id,'names',NEW.names,'handoff',NEW.handoff,'extra_rounds',NEW.extra_rounds));
1593
+ END;
1594
+ DROP TRIGGER IF EXISTS event_chain_released;
1595
+ CREATE TRIGGER event_chain_released AFTER DELETE ON free_chain_holds
1596
+ BEGIN
1597
+ INSERT INTO room_events (room_id,at,kind,data) SELECT OLD.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'chain.released',
1598
+ json_object('chain_id',OLD.chain_id,'message_id',OLD.message_id) WHERE EXISTS (SELECT 1 FROM rooms WHERE id=OLD.room_id);
1599
+ END;
1600
+ DROP TRIGGER IF EXISTS event_scheduler_decided;
1601
+ CREATE TRIGGER event_scheduler_decided AFTER INSERT ON scheduler_decisions
1602
+ BEGIN
1603
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'scheduler.decided',
1604
+ json_object('decision_id',NEW.id,'after_message_id',NEW.after_message_id,'kind',NEW.kind,'label',NEW.label,
1605
+ 'confidence',NEW.confidence,'applied',NEW.applied,'provider',NEW.provider));
1606
+ END;
1607
+ DROP TRIGGER IF EXISTS event_scheduler_applied;
1608
+ CREATE TRIGGER event_scheduler_applied AFTER UPDATE OF applied ON scheduler_decisions
1609
+ WHEN OLD.applied IS NOT NEW.applied
1610
+ BEGIN
1611
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'scheduler.applied',
1612
+ json_object('decision_id',NEW.id,'label',NEW.label,'applied',NEW.applied));
1613
+ END;
1614
+ DROP TRIGGER IF EXISTS event_discussion_created;
1615
+ CREATE TRIGGER event_discussion_created AFTER INSERT ON discussion_sessions
1616
+ BEGIN
1617
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'discussion.created',
1618
+ json_object('session_id',NEW.id,'status',NEW.status,'goal',substr(NEW.goal,1,80)));
1619
+ END;
1620
+ DROP TRIGGER IF EXISTS event_discussion_changed;
1621
+ CREATE TRIGGER event_discussion_changed AFTER UPDATE OF status ON discussion_sessions
1622
+ WHEN OLD.status IS NOT NEW.status
1623
+ BEGIN
1624
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'discussion.changed',
1625
+ json_object('session_id',NEW.id,'status',NEW.status,'from_status',OLD.status));
1626
+ END;
1627
+ -- Goal mode: one user-set objective per room at a time that the host keeps pursuing. `armed` lets an idle room wake the host
1628
+ -- once; the host dispatching work or the owner posting re-arms it. Times are epoch seconds; 0 budgets mean no limit.
1629
+ CREATE TABLE IF NOT EXISTS room_goals (
1630
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1631
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
1632
+ objective TEXT NOT NULL,
1633
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','paused','complete','blocked','budget_limited','cleared')),
1634
+ max_continues INTEGER NOT NULL DEFAULT 0,
1635
+ max_minutes INTEGER NOT NULL DEFAULT 0,
1636
+ continues INTEGER NOT NULL DEFAULT 0,
1637
+ armed INTEGER NOT NULL DEFAULT 1,
1638
+ start_request_id INTEGER,
1639
+ started_message_id INTEGER NOT NULL DEFAULT 0,
1640
+ ended_message_id INTEGER NOT NULL DEFAULT 0,
1641
+ started_at INTEGER NOT NULL,
1642
+ paused_at INTEGER NOT NULL DEFAULT 0,
1643
+ paused_seconds INTEGER NOT NULL DEFAULT 0,
1644
+ ended_at INTEGER NOT NULL DEFAULT 0,
1645
+ end_note TEXT NOT NULL DEFAULT ''
1646
+ );
1647
+ CREATE UNIQUE INDEX IF NOT EXISTS one_open_goal ON room_goals(room_id) WHERE status IN ('active','paused');
1648
+ CREATE UNIQUE INDEX IF NOT EXISTS room_goals_start_request ON room_goals(start_request_id) WHERE start_request_id IS NOT NULL;
1649
+ -- Each automatic wake-up: the visible system message it hangs on and the host request it created.
1650
+ CREATE TABLE IF NOT EXISTS goal_continuations (
1651
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1652
+ goal_id INTEGER NOT NULL REFERENCES room_goals(id) ON DELETE CASCADE,
1653
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
1654
+ message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
1655
+ request_id INTEGER NOT NULL,
1656
+ wrapup INTEGER NOT NULL DEFAULT 0
1657
+ );
1658
+ CREATE INDEX IF NOT EXISTS goal_continuations_goal ON goal_continuations(goal_id,id);
1659
+ DROP TRIGGER IF EXISTS event_goal_created;
1660
+ CREATE TRIGGER event_goal_created AFTER INSERT ON room_goals
1661
+ BEGIN
1662
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'goal.created',
1663
+ json_object('goal_id',NEW.id,'status',NEW.status,'objective',substr(NEW.objective,1,80),'start_request_id',NEW.start_request_id,
1664
+ 'max_continues',NEW.max_continues,'max_minutes',NEW.max_minutes));
1665
+ END;
1666
+ DROP TRIGGER IF EXISTS event_goal_changed;
1667
+ CREATE TRIGGER event_goal_changed AFTER UPDATE ON room_goals
1668
+ WHEN OLD.status IS NOT NEW.status OR OLD.continues IS NOT NEW.continues OR OLD.objective IS NOT NEW.objective
1669
+ OR OLD.max_continues IS NOT NEW.max_continues OR OLD.max_minutes IS NOT NEW.max_minutes
1670
+ BEGIN
1671
+ INSERT INTO room_events (room_id,at,kind,data) VALUES (NEW.room_id,CAST((julianday('now')-2440587.5)*86400000 AS INTEGER),'goal.changed',
1672
+ json_object('goal_id',NEW.id,'status',NEW.status,'from_status',OLD.status,'continues',NEW.continues,
1673
+ 'objective',CASE WHEN OLD.objective IS NOT NEW.objective THEN substr(NEW.objective,1,80) END,
1674
+ 'max_continues',NEW.max_continues,'max_minutes',NEW.max_minutes,'note',substr(NEW.end_note,1,200)));
1675
+ END;