ai_co_discuss 0.3.46 → 0.3.49

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