-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathschema.sql
More file actions
480 lines (416 loc) · 23.5 KB
/
schema.sql
File metadata and controls
480 lines (416 loc) · 23.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
-- OpenClaw Scheduler Schema (current: v1.7.0, schema version: 23)
-- Full standalone scheduler + message router
-- ============================================================
-- JOBS: scheduled tasks
-- ============================================================
CREATE TABLE IF NOT EXISTS jobs (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
-- Schedule: cron or one-shot 'at'
schedule_kind TEXT NOT NULL DEFAULT 'cron', -- 'cron' | 'at'
schedule_at TEXT DEFAULT NULL, -- SQLite UTC timestamp ('YYYY-MM-DD HH:MM:SS'), only for kind='at'
schedule_cron TEXT, -- NULL allowed for at-jobs (use sentinel '0 0 31 2 *' on old DBs)
schedule_tz TEXT NOT NULL DEFAULT 'UTC',
-- Execution
session_target TEXT NOT NULL DEFAULT 'isolated', -- 'main' | 'isolated' | 'shell'
agent_id TEXT DEFAULT 'main',
-- Payload
payload_kind TEXT NOT NULL, -- 'systemEvent' | 'agentTurn' | 'shellCommand'
payload_message TEXT NOT NULL,
payload_model TEXT,
payload_thinking TEXT,
payload_timeout_seconds INTEGER DEFAULT 120,
execution_intent TEXT NOT NULL DEFAULT 'execute', -- 'execute' | 'plan'
execution_read_only INTEGER NOT NULL DEFAULT 0,
-- Overlap & timeout
overlap_policy TEXT NOT NULL DEFAULT 'skip', -- 'skip' | 'allow' | 'queue'
run_timeout_ms INTEGER NOT NULL DEFAULT 300000,
max_queued_dispatches INTEGER NOT NULL DEFAULT 25,
max_pending_approvals INTEGER NOT NULL DEFAULT 10,
max_trigger_fanout INTEGER NOT NULL DEFAULT 25,
-- Delivery
delivery_mode TEXT DEFAULT 'announce', -- 'announce' | 'announce-always' | 'none'
delivery_channel TEXT,
delivery_to TEXT,
-- Metadata
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
delete_after_run INTEGER NOT NULL DEFAULT 0,
ttl_hours INTEGER DEFAULT NULL, -- auto-delete N hours after last_run_at if terminal status
-- Workflow chaining (v3)
parent_id TEXT, -- soft ref to parent job id
trigger_on TEXT, -- 'success' | 'failure' | 'complete' | NULL
trigger_delay_s INTEGER DEFAULT 0,
-- Output-based trigger condition (v4)
trigger_condition TEXT DEFAULT NULL, -- 'contains:ALERT' | 'regex:pattern' | NULL
-- Retry logic (v3b)
max_retries INTEGER DEFAULT 0, -- 0 = no retry
-- Queue overlap (v3c)
queued_count INTEGER DEFAULT 0, -- pending dispatches waiting for current run
-- Sub-agent scope (v3c)
payload_scope TEXT NOT NULL DEFAULT 'own', -- 'own' | 'global'
-- Resource pool (concurrency across different jobs)
resource_pool TEXT DEFAULT NULL,
-- Delivery semantics (v5)
delivery_guarantee TEXT DEFAULT 'at-most-once', -- 'at-most-once'|'at-least-once'
job_class TEXT DEFAULT 'standard', -- 'standard'|'pre_compaction_flush'
-- HITL approval gates (v5)
approval_required INTEGER DEFAULT 0,
approval_timeout_s INTEGER DEFAULT 3600,
approval_auto TEXT DEFAULT 'reject', -- 'approve'|'reject'
-- Context retrieval (v5)
context_retrieval TEXT DEFAULT 'none', -- 'none'|'recent'|'hybrid'
context_retrieval_limit INTEGER DEFAULT 5,
-- Output handling (v14)
output_store_limit_bytes INTEGER NOT NULL DEFAULT 65536,
output_excerpt_limit_bytes INTEGER NOT NULL DEFAULT 65536,
output_summary_limit_bytes INTEGER NOT NULL DEFAULT 65536,
output_offload_threshold_bytes INTEGER NOT NULL DEFAULT 65536,
-- Session continuity (v9)
preferred_session_key TEXT DEFAULT NULL, -- pass to gateway for session reuse
-- Auth profile override (v16)
auth_profile TEXT DEFAULT NULL, -- null=default, 'inherit'=main session profile, or 'provider:label'
-- Delivery opt-out (v19)
delivery_opt_out_reason TEXT DEFAULT NULL, -- set when delivery_mode='none' to explicitly skip delivery
-- Origin tracking (v20)
origin TEXT DEFAULT NULL, -- where job was dispatched from: "telegram:<chat_id>", "system", etc.
-- v0.2 Identity (v22)
identity_principal TEXT DEFAULT NULL,
identity_run_as TEXT DEFAULT NULL,
identity_attestation TEXT DEFAULT NULL,
identity_ref TEXT DEFAULT NULL,
identity_subject_kind TEXT DEFAULT NULL,
identity_subject_principal TEXT DEFAULT NULL,
identity_trust_level TEXT DEFAULT NULL,
identity_delegation_mode TEXT DEFAULT NULL,
identity TEXT DEFAULT NULL,
-- v0.2 Authorization Proof (v22)
authorization_proof_ref TEXT DEFAULT NULL,
authorization_proof TEXT DEFAULT NULL,
-- v0.2 Authorization (v22)
authorization_ref TEXT DEFAULT NULL,
authorization TEXT DEFAULT NULL,
-- v0.2 Evidence (v22)
evidence_ref TEXT DEFAULT NULL,
evidence TEXT DEFAULT NULL,
-- v0.2 Contract (v22)
contract_required_trust_level TEXT DEFAULT NULL,
contract_trust_enforcement TEXT DEFAULT NULL,
contract_sandbox TEXT DEFAULT NULL,
contract_allowed_paths TEXT DEFAULT NULL,
contract_network TEXT DEFAULT NULL,
contract_max_cost_usd REAL DEFAULT NULL,
contract_audit TEXT DEFAULT NULL,
-- v0.2 Child Credential Policy (v23)
child_credential_policy TEXT DEFAULT NULL,
-- Watchdog monitoring (v13)
job_type TEXT NOT NULL DEFAULT 'standard', -- 'standard' | 'watchdog'
watchdog_target_label TEXT, -- label of the task being monitored
watchdog_check_cmd TEXT, -- shell command to check target status
watchdog_timeout_min INTEGER, -- alert if target running longer than this
watchdog_alert_channel TEXT, -- e.g. 'telegram'
watchdog_alert_target TEXT, -- e.g. '<telegram-user-id>'
watchdog_self_destruct INTEGER NOT NULL DEFAULT 1, -- delete when target done
watchdog_started_at TEXT, -- ISO timestamp when target was dispatched
-- Scheduling state (denormalized)
next_run_at TEXT,
last_run_at TEXT,
last_status TEXT,
consecutive_errors INTEGER NOT NULL DEFAULT 0,
-- Delivery target constraint: announce modes require a delivery_to
CHECK (
delivery_mode NOT IN ('announce', 'announce-always')
OR (delivery_to IS NOT NULL AND delivery_to != '')
)
);
CREATE INDEX IF NOT EXISTS idx_jobs_next_run ON jobs(next_run_at) WHERE enabled = 1;
CREATE INDEX IF NOT EXISTS idx_jobs_parent ON jobs(parent_id) WHERE parent_id IS NOT NULL;
-- ============================================================
-- RUNS: job execution history with heartbeat tracking
-- ============================================================
CREATE TABLE IF NOT EXISTS runs (
id TEXT PRIMARY KEY,
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
status TEXT NOT NULL DEFAULT 'pending', -- pending|running|ok|error|timeout|skipped|awaiting_approval|approved|cancelled|crashed
started_at TEXT NOT NULL DEFAULT (datetime('now')),
finished_at TEXT,
duration_ms INTEGER,
-- Implicit heartbeat (updated by dispatcher checking session activity)
last_heartbeat TEXT NOT NULL DEFAULT (datetime('now')),
-- Session tracking
session_key TEXT,
session_id TEXT,
-- Result
summary TEXT,
error_message TEXT,
shell_exit_code INTEGER,
shell_signal TEXT,
shell_timed_out INTEGER NOT NULL DEFAULT 0,
shell_stdout TEXT,
shell_stderr TEXT,
shell_stdout_path TEXT,
shell_stderr_path TEXT,
shell_stdout_bytes INTEGER NOT NULL DEFAULT 0,
shell_stderr_bytes INTEGER NOT NULL DEFAULT 0,
dispatched_at TEXT,
run_timeout_ms INTEGER NOT NULL DEFAULT 300000,
-- Retry tracking (v3b)
retry_count INTEGER DEFAULT 0,
retry_of TEXT, -- original run id if this is a retry
triggered_by_run TEXT, -- parent run id if this run was chain-triggered
dispatch_queue_id TEXT REFERENCES job_dispatch_queue(id) ON DELETE SET NULL,
-- Context & replay (v5)
context_summary TEXT, -- JSON: {messages_injected,scope,...}
replay_of TEXT, -- run id if this is a crash replay
-- Idempotency (v7)
idempotency_key TEXT, -- deterministic key for dedup
-- v0.2 Outcomes (v22)
identity_resolved TEXT DEFAULT NULL,
trust_evaluation TEXT DEFAULT NULL,
authorization_decision TEXT DEFAULT NULL,
authorization_proof_verification TEXT DEFAULT NULL,
evidence_record TEXT DEFAULT NULL,
credential_handoff_summary TEXT DEFAULT NULL
);
CREATE INDEX IF NOT EXISTS idx_runs_job_id ON runs(job_id);
CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status) WHERE status = 'running';
CREATE UNIQUE INDEX IF NOT EXISTS idx_runs_idempotency ON runs(idempotency_key) WHERE idempotency_key IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_runs_dispatch_queue ON runs(dispatch_queue_id) WHERE dispatch_queue_id IS NOT NULL;
-- ============================================================
-- MESSAGES: inter-agent message queue
-- ============================================================
CREATE TABLE IF NOT EXISTS messages (
id TEXT PRIMARY KEY,
-- Routing
from_agent TEXT NOT NULL, -- sender agent id or 'scheduler' or 'user'
to_agent TEXT NOT NULL, -- recipient agent id or 'broadcast'
team_id TEXT, -- optional team routing namespace
member_id TEXT, -- optional team member routing key
task_id TEXT, -- optional team task correlation key
reply_to TEXT REFERENCES messages(id) ON DELETE SET NULL, -- threading
-- Content
kind TEXT NOT NULL DEFAULT 'text', -- 'text' | 'task' | 'result' | 'status' | 'system'
subject TEXT, -- optional subject line
body TEXT NOT NULL,
metadata TEXT, -- JSON blob for structured data
-- Priority & delivery
priority INTEGER NOT NULL DEFAULT 0, -- higher = more urgent (0=normal, 1=high, 2=urgent)
channel TEXT, -- optional: route via specific channel
delivery_to TEXT, -- optional: target chat/user id for outbound delivery
-- Status
status TEXT NOT NULL DEFAULT 'pending', -- pending|delivered|read|expired|failed
delivered_at TEXT,
read_at TEXT,
ack_required INTEGER NOT NULL DEFAULT 0, -- message requires explicit ACK
ack_at TEXT, -- explicit acknowledgement timestamp
delivery_attempts INTEGER NOT NULL DEFAULT 0, -- outbound delivery attempts
last_error TEXT, -- last delivery/adapter error
team_mapped_at TEXT, -- when team adapter projected this message
expires_at TEXT, -- optional TTL
-- Metadata
created_at TEXT NOT NULL DEFAULT (datetime('now')),
-- Link to job/run if this message is job-related
job_id TEXT REFERENCES jobs(id) ON DELETE SET NULL,
run_id TEXT REFERENCES runs(id) ON DELETE SET NULL,
-- Typed message owner (v5)
owner TEXT -- originator of typed message
);
CREATE INDEX IF NOT EXISTS idx_messages_to ON messages(to_agent, status);
CREATE INDEX IF NOT EXISTS idx_messages_from ON messages(from_agent);
CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at);
CREATE INDEX IF NOT EXISTS idx_messages_pending ON messages(to_agent, status, priority DESC) WHERE status = 'pending';
CREATE INDEX IF NOT EXISTS idx_messages_team ON messages(team_id, member_id, status) WHERE team_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_messages_task ON messages(team_id, task_id, created_at) WHERE team_id IS NOT NULL AND task_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_messages_ack_pending ON messages(ack_required, ack_at, status) WHERE ack_required = 1 AND ack_at IS NULL;
-- ============================================================
-- AGENTS: registered agents and status
-- ============================================================
CREATE TABLE IF NOT EXISTS agents (
id TEXT PRIMARY KEY, -- agent id (e.g. 'main', 'ops')
name TEXT,
status TEXT NOT NULL DEFAULT 'idle', -- idle|busy|offline
last_seen_at TEXT,
session_key TEXT, -- current active session key
capabilities TEXT, -- JSON array of capability tags
delivery_channel TEXT, -- e.g. 'telegram'
delivery_to TEXT, -- e.g. '<telegram-user-id>'
brand_name TEXT, -- display name for notifications
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- ============================================================
-- DELIVERY ALIASES: named targets for job delivery
-- ============================================================
CREATE TABLE IF NOT EXISTS delivery_aliases (
alias TEXT PRIMARY KEY,
channel TEXT NOT NULL,
target TEXT NOT NULL,
description TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
-- Example delivery aliases -- replace targets with real Telegram chat/user IDs.
-- These placeholder IDs are non-functional; run `openclaw-scheduler aliases update`
-- or INSERT your own rows to configure delivery routing.
-- Example delivery aliases (not seeded — add via CLI or SQL):
-- INSERT INTO delivery_aliases (alias, channel, target, description) VALUES
-- ('team_room', 'telegram', '<your-chat-id>', 'Team room'),
-- ('owner_dm', 'telegram', '<your-user-id>', 'Owner DM');
-- ============================================================
-- APPROVALS: HITL approval gates (v5)
-- ============================================================
CREATE TABLE IF NOT EXISTS approvals (
id TEXT PRIMARY KEY,
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
run_id TEXT REFERENCES runs(id) ON DELETE SET NULL,
dispatch_queue_id TEXT REFERENCES job_dispatch_queue(id) ON DELETE SET NULL,
status TEXT NOT NULL DEFAULT 'pending', -- pending|approved|rejected|timed_out|dispatched
requested_at TEXT NOT NULL DEFAULT (datetime('now')),
resolved_at TEXT,
resolved_by TEXT, -- 'operator'|'timeout'|'api'
notes TEXT
);
CREATE INDEX IF NOT EXISTS idx_approvals_status ON approvals(status) WHERE status = 'pending';
CREATE INDEX IF NOT EXISTS idx_approvals_job ON approvals(job_id);
CREATE INDEX IF NOT EXISTS idx_approvals_dispatch_queue ON approvals(dispatch_queue_id) WHERE dispatch_queue_id IS NOT NULL;
-- ============================================================
-- DISPATCH QUEUE: durable non-cron invocations (v11)
-- ============================================================
CREATE TABLE IF NOT EXISTS job_dispatch_queue (
id TEXT PRIMARY KEY,
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
dispatch_kind TEXT NOT NULL, -- manual|chain|retry
status TEXT NOT NULL DEFAULT 'pending', -- pending|claimed|awaiting_approval|done|cancelled
scheduled_for TEXT NOT NULL,
source_run_id TEXT REFERENCES runs(id) ON DELETE SET NULL,
retry_of_run_id TEXT REFERENCES runs(id) ON DELETE SET NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
claimed_at TEXT,
processed_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_dispatch_queue_due ON job_dispatch_queue(status, scheduled_for);
CREATE INDEX IF NOT EXISTS idx_dispatch_queue_job ON job_dispatch_queue(job_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_dispatch_queue_source_run ON job_dispatch_queue(source_run_id) WHERE source_run_id IS NOT NULL;
-- ============================================================
-- IDEMPOTENCY LEDGER: tracks claimed idempotency keys (v7)
-- ============================================================
CREATE TABLE IF NOT EXISTS idempotency_ledger (
key TEXT PRIMARY KEY,
job_id TEXT NOT NULL,
run_id TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'claimed', -- claimed | released
claimed_at TEXT NOT NULL DEFAULT (datetime('now')),
released_at TEXT,
result_hash TEXT, -- optional: hash of the result for verification
expires_at TEXT NOT NULL -- auto-expire old entries to prevent unbounded growth
);
CREATE INDEX IF NOT EXISTS idx_idem_expires ON idempotency_ledger(expires_at);
CREATE INDEX IF NOT EXISTS idx_idem_job ON idempotency_ledger(job_id);
-- ============================================================
-- TASK TRACKER: dead-man's-switch monitoring for sub-agent teams (v6)
-- ============================================================
CREATE TABLE IF NOT EXISTS task_tracker (
id TEXT PRIMARY KEY, -- unique task group id
name TEXT NOT NULL, -- human label e.g. "v5-agent-team"
created_at TEXT NOT NULL DEFAULT (datetime('now')),
created_by TEXT NOT NULL DEFAULT 'main', -- who spawned the task group
expected_agents TEXT NOT NULL, -- JSON array: ["schema-and-data","runtime-integration","rfc-docs"]
timeout_s INTEGER NOT NULL DEFAULT 600,
status TEXT NOT NULL DEFAULT 'active', -- active|completed|failed|timed_out
completed_at TEXT,
delivery_channel TEXT, -- where to send updates
delivery_to TEXT, -- target for updates
summary TEXT -- final summary on completion
);
CREATE INDEX IF NOT EXISTS idx_task_tracker_status ON task_tracker(status) WHERE status = 'active';
CREATE TABLE IF NOT EXISTS task_tracker_agents (
id TEXT PRIMARY KEY,
tracker_id TEXT NOT NULL REFERENCES task_tracker(id) ON DELETE CASCADE,
agent_label TEXT NOT NULL, -- matches label in expected_agents
status TEXT NOT NULL DEFAULT 'pending', -- pending|running|completed|failed|dead
started_at TEXT,
finished_at TEXT,
exit_message TEXT, -- agent's final status message
error TEXT,
session_key TEXT, -- OpenClaw session key for auto-correlation (v8)
last_heartbeat TEXT -- last activity detected (CLI or auto-correlation)
);
CREATE INDEX IF NOT EXISTS idx_tta_tracker ON task_tracker_agents(tracker_id);
CREATE INDEX IF NOT EXISTS idx_tta_status ON task_tracker_agents(status) WHERE status IN ('pending','running');
CREATE INDEX IF NOT EXISTS idx_tta_session_key ON task_tracker_agents(session_key) WHERE session_key IS NOT NULL;
-- ============================================================
-- MESSAGE RECEIPTS: explicit delivery/ack audit trail (v10)
-- ============================================================
CREATE TABLE IF NOT EXISTS message_receipts (
id TEXT PRIMARY KEY,
message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
event_type TEXT NOT NULL, -- attempt|error|ack|read|adapter
attempt INTEGER,
actor TEXT, -- dispatcher|consumer|agent|team-adapter|operator
detail TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_receipts_message ON message_receipts(message_id, created_at DESC);
-- ============================================================
-- TEAM ADAPTER TABLES: mailbox/task projection + gates (v10)
-- ============================================================
CREATE TABLE IF NOT EXISTS team_tasks (
team_id TEXT NOT NULL,
id TEXT NOT NULL, -- task id within a team namespace
member_id TEXT, -- owner/assignee
source_message_id TEXT REFERENCES messages(id) ON DELETE SET NULL,
title TEXT,
status TEXT NOT NULL DEFAULT 'open', -- open|blocked|completed|failed
gate_tracker_id TEXT REFERENCES task_tracker(id) ON DELETE SET NULL,
gate_status TEXT, -- waiting|passed|failed|NULL
last_error TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
completed_at TEXT,
PRIMARY KEY (team_id, id)
);
CREATE INDEX IF NOT EXISTS idx_team_tasks_status ON team_tasks(team_id, status, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_team_tasks_gate ON team_tasks(gate_tracker_id) WHERE gate_tracker_id IS NOT NULL;
CREATE TABLE IF NOT EXISTS team_mailbox_events (
id TEXT PRIMARY KEY,
team_id TEXT NOT NULL,
member_id TEXT,
task_id TEXT,
message_id TEXT REFERENCES messages(id) ON DELETE SET NULL,
event_type TEXT NOT NULL, -- mailbox|task_created|task_message|gate_open|gate_passed|gate_failed|ack
payload TEXT, -- JSON details
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_team_events_team ON team_mailbox_events(team_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_team_events_task ON team_mailbox_events(team_id, task_id, created_at DESC) WHERE task_id IS NOT NULL;
-- ============================================================
-- MIGRATION LOG
-- ============================================================
CREATE TABLE IF NOT EXISTS schema_migrations (
version INTEGER PRIMARY KEY,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Fresh installs seed all versions 1-23 (all columns already in schema above).
-- Existing installs are brought up to v23 by migrate-consolidate.js.
INSERT OR IGNORE INTO schema_migrations (version) VALUES (1);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (2);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (3);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (4);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (5);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (6);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (7);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (8);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (9);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (10);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (11);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (12);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (13);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (14);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (15);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (16);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (17);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (18);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (19);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (20);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (21);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (22);
INSERT OR IGNORE INTO schema_migrations (version) VALUES (23);