@agentsdock/server 1.0.7-beta.5
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.
- package/LICENSE +202 -0
- package/NOTICE +19 -0
- package/README.md +60 -0
- package/npm/cli.cjs +289 -0
- package/package.json +32 -0
- package/server/LICENSE +202 -0
- package/server/NOTICE +19 -0
- package/server/VERSION +1 -0
- package/server/activation_transaction.py +2458 -0
- package/server/agent_server.py +93226 -0
- package/server/agentsdock_chats.py +1237 -0
- package/server/agentsdock_emergency.py +338 -0
- package/server/agentsdock_jobs.py +640 -0
- package/server/agentsdock_mail.py +325 -0
- package/server/agentsdock_publish.py +404 -0
- package/server/agentsdock_team.py +793 -0
- package/server/agentsdock_team_hub/__init__.py +33 -0
- package/server/agentsdock_team_hub/auth.py +710 -0
- package/server/agentsdock_team_hub/cli.py +647 -0
- package/server/agentsdock_team_hub/database.py +310 -0
- package/server/agentsdock_team_hub/mail_hint_streams.py +176 -0
- package/server/agentsdock_team_hub/mail_hints.py +201 -0
- package/server/agentsdock_team_hub/migrations/0001_identity_auth.sql +690 -0
- package/server/agentsdock_team_hub/migrations/0002_teamspace_ledger.sql +1336 -0
- package/server/agentsdock_team_hub/migrations/0003_service_runtime.sql +361 -0
- package/server/agentsdock_team_hub/migrations/0004_managed_host_binding.sql +22 -0
- package/server/agentsdock_team_hub/migrations/0005_tailnet_bootstrap_delegations.sql +63 -0
- package/server/agentsdock_team_hub/migrations/0006_team_network_mailbox.sql +423 -0
- package/server/agentsdock_team_hub/migrations/0007_local_agent_mail.sql +63 -0
- package/server/agentsdock_team_hub/migrations/0008_managed_server_session.sql +73 -0
- package/server/agentsdock_team_hub/migrations/0009_team_messages.sql +324 -0
- package/server/agentsdock_team_hub/migrations/0010_team_attachment_orphan_reclamation.sql +52 -0
- package/server/agentsdock_team_hub/migrations/0011_human_admin_paging.sql +65 -0
- package/server/agentsdock_team_hub/migrations/0012_network_content_deletions.sql +69 -0
- package/server/agentsdock_team_hub/migrations/0013_team_message_revisions.sql +48 -0
- package/server/agentsdock_team_hub/migrations/0014_managed_network_owner.sql +50 -0
- package/server/agentsdock_team_hub/migrations/0015_team_message_inbox_dismissals.sql +3 -0
- package/server/agentsdock_team_hub/migrations/0016_team_message_all_servers.sql +4 -0
- package/server/agentsdock_team_hub/migrations/0017_skill_announcement_deletions.sql +16 -0
- package/server/agentsdock_team_hub/migrations/0018_team_mail_subjects.sql +8 -0
- package/server/agentsdock_team_hub/migrations/0019_team_mailbox_state.sql +5 -0
- package/server/agentsdock_team_hub/migrations/0020_team_mail_arrivals.sql +44 -0
- package/server/agentsdock_team_hub/migrations/0021_team_mail_threads.sql +4 -0
- package/server/agentsdock_team_hub/migrations/0022_team_bulletin_changes.sql +49 -0
- package/server/agentsdock_team_hub/migrations/0023_team_message_search.sql +93 -0
- package/server/agentsdock_team_hub/migrations/__init__.py +1 -0
- package/server/agentsdock_team_hub/notification_hints.py +306 -0
- package/server/agentsdock_team_hub/secure_peer.py +11215 -0
- package/server/agentsdock_team_hub/secure_peer_hub.py +1372 -0
- package/server/agentsdock_team_hub/security.py +318 -0
- package/server/agentsdock_team_hub/service.py +1737 -0
- package/server/agentsdock_team_hub/store.py +16276 -0
- package/server/chat_mailbox.py +448 -0
- package/server/claude_background_reconciliation.py +92 -0
- package/server/claude_goals.py +245 -0
- package/server/claude_history_provenance.py +137 -0
- package/server/claude_history_repair.py +1601 -0
- package/server/claude_sdk_client.py +3731 -0
- package/server/claude_side_question.py +212 -0
- package/server/codex_app_server.py +3030 -0
- package/server/codex_auth.py +93 -0
- package/server/codex_history_repair.py +1247 -0
- package/server/codex_provider.py +1120 -0
- package/server/codex_side_question.py +301 -0
- package/server/cursor_agent_client.py +654 -0
- package/server/cursor_process_guard.py +347 -0
- package/server/execution_activation.py +610 -0
- package/server/execution_control.py +233 -0
- package/server/execution_durability.py +184 -0
- package/server/execution_http.py +256 -0
- package/server/execution_install.py +792 -0
- package/server/execution_legacy_runner.py +406 -0
- package/server/execution_maintenance.py +137 -0
- package/server/execution_manage.py +615 -0
- package/server/execution_ownership.py +83 -0
- package/server/execution_preparation.py +362 -0
- package/server/execution_recovery.py +845 -0
- package/server/execution_recovery_status.py +195 -0
- package/server/execution_service.py +387 -0
- package/server/execution_transport.py +622 -0
- package/server/execution_uninstall.py +406 -0
- package/server/execution_update_status.py +49 -0
- package/server/install.sh +7074 -0
- package/server/interactive_chat_controls.py +153 -0
- package/server/interactive_chat_native.py +66 -0
- package/server/interactive_chat_projection.py +152 -0
- package/server/interactive_chat_runtime.py +132 -0
- package/server/interactive_chat_share_routes.py +534 -0
- package/server/interactive_chat_share_web.py +12 -0
- package/server/interactive_chat_shares.py +238 -0
- package/server/provider_commands.py +425 -0
- package/server/public_chat_share_routes.py +426 -0
- package/server/public_chat_shares.py +759 -0
- package/server/public_chat_transcript.py +357 -0
- package/server/pyproject.toml +29 -0
- package/server/release-public-key.pem +3 -0
- package/server/secure_peer_delivery.py +1236 -0
- package/server/secure_peer_runtime.py +7694 -0
- package/server/shared_chat_video_stream.py +262 -0
- package/server/shared_chat_videos.py +155 -0
- package/server/side_questions.py +594 -0
- package/server/team_hub_host.py +1354 -0
- package/server/team_mail_grants.py +136 -0
- package/server/team_mail_runtime.py +302 -0
- package/server/team_mail_websocket.py +211 -0
- package/server/title_generation.py +333 -0
- package/server/uninstall.sh +359 -0
- package/server/update_handoff.py +237 -0
- package/server/update_preparation.py +264 -0
- package/server/update_recovery.py +140 -0
- package/server/update_runner.py +1819 -0
- package/server/uv.lock +1101 -0
- package/server/workspace_git.py +545 -0
|
@@ -0,0 +1,1336 @@
|
|
|
1
|
+
-- Teamspace collaboration ledger. Messages are passive records. Explicit
|
|
2
|
+
-- dispatch requests live in a separate table and are the only future source
|
|
3
|
+
-- of agent execution admission.
|
|
4
|
+
|
|
5
|
+
CREATE TABLE channels (
|
|
6
|
+
id TEXT PRIMARY KEY,
|
|
7
|
+
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
|
8
|
+
kind TEXT NOT NULL CHECK(kind IN ('direct', 'board', 'announcements')),
|
|
9
|
+
visibility TEXT NOT NULL DEFAULT 'private' CHECK(visibility IN ('private', 'team')),
|
|
10
|
+
slug TEXT CHECK(slug IS NULL OR (
|
|
11
|
+
slug = lower(trim(slug)) AND length(slug) BETWEEN 1 AND 80
|
|
12
|
+
)),
|
|
13
|
+
display_name TEXT CHECK(display_name IS NULL OR length(trim(display_name)) BETWEEN 1 AND 160),
|
|
14
|
+
direct_pair_key BLOB CHECK(direct_pair_key IS NULL OR (
|
|
15
|
+
typeof(direct_pair_key) = 'blob' AND length(direct_pair_key) = 32
|
|
16
|
+
)),
|
|
17
|
+
created_by_principal_id TEXT NOT NULL,
|
|
18
|
+
next_message_sequence INTEGER NOT NULL DEFAULT 1 CHECK(next_message_sequence >= 1),
|
|
19
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
20
|
+
updated_at INTEGER NOT NULL CHECK(updated_at >= created_at),
|
|
21
|
+
archived_at INTEGER CHECK(archived_at IS NULL OR archived_at >= created_at),
|
|
22
|
+
FOREIGN KEY(team_id, created_by_principal_id)
|
|
23
|
+
REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
|
|
24
|
+
UNIQUE(team_id, id),
|
|
25
|
+
UNIQUE(team_id, slug),
|
|
26
|
+
UNIQUE(team_id, direct_pair_key),
|
|
27
|
+
CHECK(
|
|
28
|
+
(kind = 'direct' AND direct_pair_key IS NOT NULL AND slug IS NULL)
|
|
29
|
+
OR (kind <> 'direct' AND direct_pair_key IS NULL AND slug IS NOT NULL)
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE TABLE channel_acl_entries (
|
|
34
|
+
id TEXT PRIMARY KEY,
|
|
35
|
+
team_id TEXT NOT NULL,
|
|
36
|
+
channel_id TEXT NOT NULL,
|
|
37
|
+
subject_kind TEXT NOT NULL CHECK(subject_kind IN ('principal', 'role')),
|
|
38
|
+
subject_principal_id TEXT REFERENCES principals(id) ON DELETE CASCADE,
|
|
39
|
+
subject_role TEXT CHECK(subject_role IN ('owner', 'admin', 'member', 'guest', 'automation')),
|
|
40
|
+
can_read INTEGER NOT NULL CHECK(can_read IN (0, 1)),
|
|
41
|
+
can_post INTEGER NOT NULL CHECK(can_post IN (0, 1)),
|
|
42
|
+
can_manage INTEGER NOT NULL CHECK(can_manage IN (0, 1)),
|
|
43
|
+
can_dispatch INTEGER NOT NULL CHECK(can_dispatch IN (0, 1)),
|
|
44
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
45
|
+
FOREIGN KEY(team_id, channel_id) REFERENCES channels(team_id, id) ON DELETE CASCADE,
|
|
46
|
+
CHECK(
|
|
47
|
+
(subject_kind = 'principal' AND subject_principal_id IS NOT NULL AND subject_role IS NULL)
|
|
48
|
+
OR (subject_kind = 'role' AND subject_principal_id IS NULL AND subject_role IS NOT NULL)
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
CREATE UNIQUE INDEX channel_acl_one_principal_entry
|
|
53
|
+
ON channel_acl_entries(channel_id, subject_principal_id)
|
|
54
|
+
WHERE subject_kind = 'principal';
|
|
55
|
+
|
|
56
|
+
CREATE UNIQUE INDEX channel_acl_one_role_entry
|
|
57
|
+
ON channel_acl_entries(channel_id, subject_role)
|
|
58
|
+
WHERE subject_kind = 'role';
|
|
59
|
+
|
|
60
|
+
CREATE TRIGGER channel_acl_require_team_principal
|
|
61
|
+
BEFORE INSERT ON channel_acl_entries
|
|
62
|
+
FOR EACH ROW WHEN NEW.subject_kind = 'principal' AND NOT EXISTS (
|
|
63
|
+
SELECT 1 FROM principals AS p
|
|
64
|
+
WHERE p.id = NEW.subject_principal_id
|
|
65
|
+
AND p.status = 'active'
|
|
66
|
+
AND (
|
|
67
|
+
p.scope_team_id = NEW.team_id
|
|
68
|
+
OR EXISTS (
|
|
69
|
+
SELECT 1 FROM memberships AS m
|
|
70
|
+
WHERE m.team_id = NEW.team_id
|
|
71
|
+
AND m.principal_id = p.id
|
|
72
|
+
AND m.status = 'active'
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
BEGIN
|
|
77
|
+
SELECT RAISE(ABORT, 'channel ACL principal does not belong to this team');
|
|
78
|
+
END;
|
|
79
|
+
|
|
80
|
+
CREATE TRIGGER channel_acl_subject_is_immutable
|
|
81
|
+
BEFORE UPDATE OF id, team_id, channel_id, subject_kind,
|
|
82
|
+
subject_principal_id, subject_role, created_at
|
|
83
|
+
ON channel_acl_entries
|
|
84
|
+
BEGIN
|
|
85
|
+
SELECT RAISE(ABORT, 'channel ACL subject is immutable');
|
|
86
|
+
END;
|
|
87
|
+
|
|
88
|
+
CREATE TABLE channel_participants (
|
|
89
|
+
team_id TEXT NOT NULL,
|
|
90
|
+
channel_id TEXT NOT NULL,
|
|
91
|
+
principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
|
|
92
|
+
participant_role TEXT NOT NULL DEFAULT 'member' CHECK(participant_role IN ('member', 'manager')),
|
|
93
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'left', 'removed')),
|
|
94
|
+
last_read_sequence INTEGER NOT NULL DEFAULT 0 CHECK(last_read_sequence >= 0),
|
|
95
|
+
joined_at INTEGER NOT NULL CHECK(joined_at >= 0),
|
|
96
|
+
updated_at INTEGER NOT NULL CHECK(updated_at >= joined_at),
|
|
97
|
+
PRIMARY KEY(channel_id, principal_id),
|
|
98
|
+
FOREIGN KEY(team_id, channel_id) REFERENCES channels(team_id, id) ON DELETE CASCADE,
|
|
99
|
+
UNIQUE(team_id, channel_id, principal_id)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
CREATE TRIGGER channel_participants_require_team_principal
|
|
103
|
+
BEFORE INSERT ON channel_participants
|
|
104
|
+
FOR EACH ROW WHEN NOT EXISTS (
|
|
105
|
+
SELECT 1 FROM principals AS p
|
|
106
|
+
WHERE p.id = NEW.principal_id
|
|
107
|
+
AND p.status = 'active'
|
|
108
|
+
AND (
|
|
109
|
+
p.scope_team_id = NEW.team_id
|
|
110
|
+
OR (
|
|
111
|
+
p.scope_team_id IS NULL
|
|
112
|
+
AND EXISTS (
|
|
113
|
+
SELECT 1 FROM memberships AS m
|
|
114
|
+
WHERE m.team_id = NEW.team_id
|
|
115
|
+
AND m.principal_id = p.id
|
|
116
|
+
AND m.status = 'active'
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
BEGIN
|
|
122
|
+
SELECT RAISE(ABORT, 'channel participant does not belong to this team');
|
|
123
|
+
END;
|
|
124
|
+
|
|
125
|
+
CREATE TRIGGER channel_participant_identity_is_immutable
|
|
126
|
+
BEFORE UPDATE OF team_id, channel_id, principal_id, joined_at
|
|
127
|
+
ON channel_participants
|
|
128
|
+
BEGIN
|
|
129
|
+
SELECT RAISE(ABORT, 'channel participant identity is immutable');
|
|
130
|
+
END;
|
|
131
|
+
|
|
132
|
+
CREATE TABLE messages (
|
|
133
|
+
id TEXT PRIMARY KEY,
|
|
134
|
+
team_id TEXT NOT NULL,
|
|
135
|
+
channel_id TEXT NOT NULL,
|
|
136
|
+
channel_sequence INTEGER NOT NULL CHECK(channel_sequence >= 1),
|
|
137
|
+
kind TEXT NOT NULL CHECK(kind IN ('post', 'announcement', 'system')),
|
|
138
|
+
thread_root_message_id TEXT,
|
|
139
|
+
parent_message_id TEXT,
|
|
140
|
+
author_principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
|
|
141
|
+
acting_human_principal_id TEXT,
|
|
142
|
+
provenance_node_id TEXT,
|
|
143
|
+
provenance_agent_id TEXT,
|
|
144
|
+
provenance_chat_id TEXT,
|
|
145
|
+
provenance_run_id TEXT,
|
|
146
|
+
body_format TEXT NOT NULL DEFAULT 'markdown' CHECK(body_format IN ('plain', 'markdown')),
|
|
147
|
+
body TEXT NOT NULL CHECK(length(body) BETWEEN 1 AND 65536),
|
|
148
|
+
idempotency_key BLOB NOT NULL
|
|
149
|
+
CHECK(typeof(idempotency_key) = 'blob' AND length(idempotency_key) = 32),
|
|
150
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
151
|
+
edited_at INTEGER CHECK(edited_at IS NULL OR edited_at >= created_at),
|
|
152
|
+
deleted_at INTEGER CHECK(deleted_at IS NULL OR deleted_at >= created_at),
|
|
153
|
+
expires_at INTEGER CHECK(expires_at IS NULL OR expires_at > created_at),
|
|
154
|
+
FOREIGN KEY(team_id, channel_id) REFERENCES channels(team_id, id) ON DELETE CASCADE,
|
|
155
|
+
FOREIGN KEY(team_id, channel_id, thread_root_message_id)
|
|
156
|
+
REFERENCES messages(team_id, channel_id, id) ON DELETE RESTRICT,
|
|
157
|
+
FOREIGN KEY(team_id, channel_id, parent_message_id)
|
|
158
|
+
REFERENCES messages(team_id, channel_id, id) ON DELETE RESTRICT,
|
|
159
|
+
FOREIGN KEY(team_id, acting_human_principal_id)
|
|
160
|
+
REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
|
|
161
|
+
FOREIGN KEY(team_id, provenance_node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
|
|
162
|
+
FOREIGN KEY(team_id, provenance_agent_id) REFERENCES agents(team_id, id) ON DELETE RESTRICT,
|
|
163
|
+
FOREIGN KEY(team_id, provenance_chat_id) REFERENCES chats(team_id, id) ON DELETE RESTRICT,
|
|
164
|
+
FOREIGN KEY(team_id, provenance_run_id) REFERENCES runs(team_id, id) ON DELETE RESTRICT,
|
|
165
|
+
UNIQUE(channel_id, channel_sequence),
|
|
166
|
+
UNIQUE(team_id, idempotency_key),
|
|
167
|
+
UNIQUE(team_id, id),
|
|
168
|
+
UNIQUE(team_id, channel_id, id),
|
|
169
|
+
CHECK(parent_message_id IS NULL OR thread_root_message_id IS NOT NULL),
|
|
170
|
+
CHECK(parent_message_id IS NULL OR parent_message_id <> id),
|
|
171
|
+
CHECK(thread_root_message_id IS NULL OR thread_root_message_id <> id),
|
|
172
|
+
CHECK(
|
|
173
|
+
(
|
|
174
|
+
acting_human_principal_id IS NULL
|
|
175
|
+
AND provenance_agent_id IS NULL
|
|
176
|
+
AND provenance_node_id IS NULL
|
|
177
|
+
AND provenance_chat_id IS NULL
|
|
178
|
+
AND provenance_run_id IS NULL
|
|
179
|
+
)
|
|
180
|
+
OR (
|
|
181
|
+
acting_human_principal_id IS NOT NULL
|
|
182
|
+
AND provenance_agent_id IS NOT NULL
|
|
183
|
+
AND provenance_node_id IS NOT NULL
|
|
184
|
+
AND provenance_chat_id IS NOT NULL
|
|
185
|
+
AND provenance_run_id IS NOT NULL
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
CREATE TRIGGER messages_require_team_author
|
|
191
|
+
BEFORE INSERT ON messages
|
|
192
|
+
FOR EACH ROW WHEN NOT EXISTS (
|
|
193
|
+
SELECT 1 FROM principals AS p
|
|
194
|
+
WHERE p.id = NEW.author_principal_id
|
|
195
|
+
AND p.kind IN ('human', 'service', 'agent')
|
|
196
|
+
AND p.status = 'active'
|
|
197
|
+
AND (
|
|
198
|
+
p.scope_team_id = NEW.team_id
|
|
199
|
+
OR (
|
|
200
|
+
p.scope_team_id IS NULL
|
|
201
|
+
AND EXISTS (
|
|
202
|
+
SELECT 1 FROM memberships AS m
|
|
203
|
+
WHERE m.team_id = NEW.team_id
|
|
204
|
+
AND m.principal_id = p.id
|
|
205
|
+
AND m.status = 'active'
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
BEGIN
|
|
211
|
+
SELECT RAISE(ABORT, 'message author does not belong to this team');
|
|
212
|
+
END;
|
|
213
|
+
|
|
214
|
+
CREATE TRIGGER messages_reject_false_agent_provenance
|
|
215
|
+
BEFORE INSERT ON messages
|
|
216
|
+
FOR EACH ROW WHEN NEW.provenance_agent_id IS NOT NULL AND COALESCE(
|
|
217
|
+
(SELECT kind FROM principals WHERE id = NEW.author_principal_id), ''
|
|
218
|
+
) <> 'agent'
|
|
219
|
+
BEGIN
|
|
220
|
+
SELECT RAISE(ABORT, 'only an agent author may claim agent provenance');
|
|
221
|
+
END;
|
|
222
|
+
|
|
223
|
+
CREATE TRIGGER messages_require_agent_provenance
|
|
224
|
+
BEFORE INSERT ON messages
|
|
225
|
+
FOR EACH ROW WHEN COALESCE(
|
|
226
|
+
(SELECT kind FROM principals WHERE id = NEW.author_principal_id), ''
|
|
227
|
+
) = 'agent' AND NOT EXISTS (
|
|
228
|
+
SELECT 1
|
|
229
|
+
FROM agents AS a
|
|
230
|
+
JOIN chats AS c ON c.team_id = a.team_id AND c.id = NEW.provenance_chat_id
|
|
231
|
+
JOIN runs AS r ON r.team_id = a.team_id AND r.id = NEW.provenance_run_id
|
|
232
|
+
WHERE a.team_id = NEW.team_id
|
|
233
|
+
AND a.id = NEW.provenance_agent_id
|
|
234
|
+
AND a.principal_id = NEW.author_principal_id
|
|
235
|
+
AND a.node_id = NEW.provenance_node_id
|
|
236
|
+
AND c.node_id = a.node_id
|
|
237
|
+
AND c.agent_id = a.id
|
|
238
|
+
AND r.node_id = a.node_id
|
|
239
|
+
AND r.agent_id = a.id
|
|
240
|
+
AND r.chat_id = c.id
|
|
241
|
+
AND r.acting_human_principal_id = NEW.acting_human_principal_id
|
|
242
|
+
)
|
|
243
|
+
BEGIN
|
|
244
|
+
SELECT RAISE(ABORT, 'agent-authored message requires exact node, agent, chat, and run provenance');
|
|
245
|
+
END;
|
|
246
|
+
|
|
247
|
+
CREATE TRIGGER message_authority_is_immutable
|
|
248
|
+
BEFORE UPDATE OF id, team_id, channel_id, channel_sequence, kind,
|
|
249
|
+
thread_root_message_id, parent_message_id, author_principal_id,
|
|
250
|
+
acting_human_principal_id, provenance_node_id, provenance_agent_id,
|
|
251
|
+
provenance_chat_id, provenance_run_id, idempotency_key, created_at
|
|
252
|
+
ON messages
|
|
253
|
+
BEGIN
|
|
254
|
+
SELECT RAISE(ABORT, 'message routing and provenance are immutable');
|
|
255
|
+
END;
|
|
256
|
+
|
|
257
|
+
CREATE TABLE message_recipients (
|
|
258
|
+
id TEXT PRIMARY KEY,
|
|
259
|
+
team_id TEXT NOT NULL,
|
|
260
|
+
message_id TEXT NOT NULL,
|
|
261
|
+
recipient_principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
|
|
262
|
+
reason TEXT NOT NULL CHECK(reason IN ('direct', 'mention', 'subscription', 'assignment')),
|
|
263
|
+
delivery_key BLOB NOT NULL UNIQUE
|
|
264
|
+
CHECK(typeof(delivery_key) = 'blob' AND length(delivery_key) = 32),
|
|
265
|
+
state TEXT NOT NULL DEFAULT 'pending' CHECK(state IN ('pending', 'available', 'delivered', 'expired')),
|
|
266
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
267
|
+
expires_at INTEGER CHECK(expires_at IS NULL OR expires_at > created_at),
|
|
268
|
+
FOREIGN KEY(team_id, message_id) REFERENCES messages(team_id, id) ON DELETE CASCADE,
|
|
269
|
+
UNIQUE(message_id, recipient_principal_id, reason),
|
|
270
|
+
UNIQUE(team_id, id)
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
CREATE TRIGGER message_recipients_require_team_principal
|
|
274
|
+
BEFORE INSERT ON message_recipients
|
|
275
|
+
FOR EACH ROW WHEN NOT EXISTS (
|
|
276
|
+
SELECT 1 FROM principals AS p
|
|
277
|
+
WHERE p.id = NEW.recipient_principal_id
|
|
278
|
+
AND p.status = 'active'
|
|
279
|
+
AND (
|
|
280
|
+
p.scope_team_id = NEW.team_id
|
|
281
|
+
OR EXISTS (
|
|
282
|
+
SELECT 1 FROM memberships AS m
|
|
283
|
+
WHERE m.team_id = NEW.team_id
|
|
284
|
+
AND m.principal_id = p.id
|
|
285
|
+
AND m.status = 'active'
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
BEGIN
|
|
290
|
+
SELECT RAISE(ABORT, 'message recipient does not belong to this team');
|
|
291
|
+
END;
|
|
292
|
+
|
|
293
|
+
CREATE TRIGGER message_recipient_identity_is_immutable
|
|
294
|
+
BEFORE UPDATE OF id, team_id, message_id, recipient_principal_id,
|
|
295
|
+
reason, delivery_key, created_at
|
|
296
|
+
ON message_recipients
|
|
297
|
+
BEGIN
|
|
298
|
+
SELECT RAISE(ABORT, 'message recipient identity is immutable');
|
|
299
|
+
END;
|
|
300
|
+
|
|
301
|
+
CREATE TABLE message_receipts (
|
|
302
|
+
id TEXT PRIMARY KEY,
|
|
303
|
+
team_id TEXT NOT NULL,
|
|
304
|
+
recipient_id TEXT NOT NULL UNIQUE,
|
|
305
|
+
delivered_at INTEGER CHECK(delivered_at IS NULL OR delivered_at >= 0),
|
|
306
|
+
read_at INTEGER CHECK(read_at IS NULL OR read_at >= 0),
|
|
307
|
+
acknowledged_at INTEGER CHECK(acknowledged_at IS NULL OR acknowledged_at >= 0),
|
|
308
|
+
FOREIGN KEY(team_id, recipient_id) REFERENCES message_recipients(team_id, id) ON DELETE CASCADE,
|
|
309
|
+
CHECK(read_at IS NULL OR delivered_at IS NOT NULL),
|
|
310
|
+
CHECK(acknowledged_at IS NULL OR delivered_at IS NOT NULL)
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
CREATE TABLE message_mentions (
|
|
314
|
+
team_id TEXT NOT NULL,
|
|
315
|
+
message_id TEXT NOT NULL,
|
|
316
|
+
principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
|
|
317
|
+
start_offset INTEGER NOT NULL CHECK(start_offset >= 0),
|
|
318
|
+
end_offset INTEGER NOT NULL CHECK(end_offset > start_offset),
|
|
319
|
+
PRIMARY KEY(message_id, principal_id, start_offset),
|
|
320
|
+
FOREIGN KEY(team_id, message_id) REFERENCES messages(team_id, id) ON DELETE CASCADE
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
CREATE TRIGGER message_mentions_require_team_principal
|
|
324
|
+
BEFORE INSERT ON message_mentions
|
|
325
|
+
FOR EACH ROW WHEN NOT EXISTS (
|
|
326
|
+
SELECT 1 FROM principals AS p
|
|
327
|
+
WHERE p.id = NEW.principal_id
|
|
328
|
+
AND p.status = 'active'
|
|
329
|
+
AND (
|
|
330
|
+
p.scope_team_id = NEW.team_id
|
|
331
|
+
OR EXISTS (
|
|
332
|
+
SELECT 1 FROM memberships AS m
|
|
333
|
+
WHERE m.team_id = NEW.team_id
|
|
334
|
+
AND m.principal_id = p.id
|
|
335
|
+
AND m.status = 'active'
|
|
336
|
+
)
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
BEGIN
|
|
340
|
+
SELECT RAISE(ABORT, 'mentioned principal does not belong to this team');
|
|
341
|
+
END;
|
|
342
|
+
|
|
343
|
+
CREATE TRIGGER message_mentions_are_immutable
|
|
344
|
+
BEFORE UPDATE ON message_mentions
|
|
345
|
+
BEGIN
|
|
346
|
+
SELECT RAISE(ABORT, 'message mentions are immutable');
|
|
347
|
+
END;
|
|
348
|
+
|
|
349
|
+
CREATE TABLE tags (
|
|
350
|
+
id TEXT PRIMARY KEY,
|
|
351
|
+
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
|
352
|
+
name TEXT NOT NULL CHECK(name = lower(trim(name))) CHECK(length(name) BETWEEN 1 AND 64),
|
|
353
|
+
color TEXT CHECK(color IS NULL OR length(color) BETWEEN 1 AND 32),
|
|
354
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
355
|
+
UNIQUE(team_id, name),
|
|
356
|
+
UNIQUE(team_id, id)
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
CREATE TABLE message_tags (
|
|
360
|
+
team_id TEXT NOT NULL,
|
|
361
|
+
message_id TEXT NOT NULL,
|
|
362
|
+
tag_id TEXT NOT NULL,
|
|
363
|
+
added_by_principal_id TEXT NOT NULL,
|
|
364
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
365
|
+
PRIMARY KEY(message_id, tag_id),
|
|
366
|
+
FOREIGN KEY(team_id, message_id) REFERENCES messages(team_id, id) ON DELETE CASCADE,
|
|
367
|
+
FOREIGN KEY(team_id, tag_id) REFERENCES tags(team_id, id) ON DELETE CASCADE,
|
|
368
|
+
FOREIGN KEY(team_id, added_by_principal_id)
|
|
369
|
+
REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
CREATE TABLE pins (
|
|
373
|
+
id TEXT PRIMARY KEY,
|
|
374
|
+
team_id TEXT NOT NULL,
|
|
375
|
+
channel_id TEXT NOT NULL,
|
|
376
|
+
message_id TEXT NOT NULL,
|
|
377
|
+
pinned_by_principal_id TEXT NOT NULL,
|
|
378
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
379
|
+
FOREIGN KEY(team_id, channel_id) REFERENCES channels(team_id, id) ON DELETE CASCADE,
|
|
380
|
+
FOREIGN KEY(team_id, channel_id, message_id)
|
|
381
|
+
REFERENCES messages(team_id, channel_id, id) ON DELETE CASCADE,
|
|
382
|
+
FOREIGN KEY(team_id, pinned_by_principal_id)
|
|
383
|
+
REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
|
|
384
|
+
UNIQUE(channel_id, message_id)
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
CREATE TABLE assignments (
|
|
388
|
+
id TEXT PRIMARY KEY,
|
|
389
|
+
team_id TEXT NOT NULL,
|
|
390
|
+
message_id TEXT NOT NULL,
|
|
391
|
+
assignee_principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
|
|
392
|
+
assigned_by_principal_id TEXT NOT NULL,
|
|
393
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open', 'in_progress', 'done', 'cancelled')),
|
|
394
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
395
|
+
updated_at INTEGER NOT NULL CHECK(updated_at >= created_at),
|
|
396
|
+
FOREIGN KEY(team_id, message_id) REFERENCES messages(team_id, id) ON DELETE CASCADE,
|
|
397
|
+
FOREIGN KEY(team_id, assigned_by_principal_id)
|
|
398
|
+
REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
|
|
399
|
+
UNIQUE(message_id, assignee_principal_id)
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
CREATE TRIGGER assignments_require_team_principal
|
|
403
|
+
BEFORE INSERT ON assignments
|
|
404
|
+
FOR EACH ROW WHEN NOT EXISTS (
|
|
405
|
+
SELECT 1 FROM principals AS p
|
|
406
|
+
WHERE p.id = NEW.assignee_principal_id
|
|
407
|
+
AND p.status = 'active'
|
|
408
|
+
AND (
|
|
409
|
+
p.scope_team_id = NEW.team_id
|
|
410
|
+
OR EXISTS (
|
|
411
|
+
SELECT 1 FROM memberships AS m
|
|
412
|
+
WHERE m.team_id = NEW.team_id
|
|
413
|
+
AND m.principal_id = p.id
|
|
414
|
+
AND m.status = 'active'
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
)
|
|
418
|
+
BEGIN
|
|
419
|
+
SELECT RAISE(ABORT, 'assignee does not belong to this team');
|
|
420
|
+
END;
|
|
421
|
+
|
|
422
|
+
CREATE TRIGGER assignment_identity_is_immutable
|
|
423
|
+
BEFORE UPDATE OF id, team_id, message_id, assignee_principal_id,
|
|
424
|
+
assigned_by_principal_id, created_at
|
|
425
|
+
ON assignments
|
|
426
|
+
BEGIN
|
|
427
|
+
SELECT RAISE(ABORT, 'assignment identity is immutable');
|
|
428
|
+
END;
|
|
429
|
+
|
|
430
|
+
CREATE TABLE dispatch_requests (
|
|
431
|
+
queue_ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
432
|
+
id TEXT NOT NULL UNIQUE,
|
|
433
|
+
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
|
434
|
+
requester_kind TEXT NOT NULL DEFAULT 'human' CHECK(requester_kind IN ('human', 'agent')),
|
|
435
|
+
requested_by_human_principal_id TEXT NOT NULL,
|
|
436
|
+
requesting_node_id TEXT,
|
|
437
|
+
requesting_agent_id TEXT,
|
|
438
|
+
requesting_chat_id TEXT,
|
|
439
|
+
requesting_run_id TEXT,
|
|
440
|
+
authorization_capability_id TEXT,
|
|
441
|
+
source_message_id TEXT,
|
|
442
|
+
target_kind TEXT NOT NULL CHECK(target_kind IN ('agent', 'chat')),
|
|
443
|
+
target_node_id TEXT NOT NULL,
|
|
444
|
+
target_agent_id TEXT,
|
|
445
|
+
target_chat_id TEXT,
|
|
446
|
+
request_text TEXT NOT NULL CHECK(length(request_text) BETWEEN 1 AND 65536),
|
|
447
|
+
idempotency_key BLOB NOT NULL
|
|
448
|
+
CHECK(typeof(idempotency_key) = 'blob' AND length(idempotency_key) = 32),
|
|
449
|
+
status TEXT NOT NULL DEFAULT 'registered'
|
|
450
|
+
CHECK(status IN ('registered', 'queued', 'running', 'succeeded', 'failed', 'cancelled', 'expired')),
|
|
451
|
+
target_run_id TEXT,
|
|
452
|
+
causation_dispatch_id TEXT,
|
|
453
|
+
root_dispatch_id TEXT,
|
|
454
|
+
hop_count INTEGER NOT NULL DEFAULT 0 CHECK(hop_count BETWEEN 0 AND 4),
|
|
455
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
456
|
+
updated_at INTEGER NOT NULL CHECK(updated_at >= created_at),
|
|
457
|
+
expires_at INTEGER NOT NULL CHECK(expires_at > created_at),
|
|
458
|
+
terminal_at INTEGER CHECK(terminal_at IS NULL OR terminal_at >= created_at),
|
|
459
|
+
FOREIGN KEY(team_id, requested_by_human_principal_id)
|
|
460
|
+
REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
|
|
461
|
+
FOREIGN KEY(team_id, requesting_node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
|
|
462
|
+
FOREIGN KEY(team_id, requesting_agent_id) REFERENCES agents(team_id, id) ON DELETE RESTRICT,
|
|
463
|
+
FOREIGN KEY(team_id, requesting_chat_id) REFERENCES chats(team_id, id) ON DELETE RESTRICT,
|
|
464
|
+
FOREIGN KEY(team_id, requesting_run_id) REFERENCES runs(team_id, id) ON DELETE RESTRICT,
|
|
465
|
+
FOREIGN KEY(team_id, authorization_capability_id)
|
|
466
|
+
REFERENCES turn_capabilities(team_id, id) ON DELETE RESTRICT,
|
|
467
|
+
FOREIGN KEY(team_id, source_message_id) REFERENCES messages(team_id, id) ON DELETE RESTRICT,
|
|
468
|
+
FOREIGN KEY(team_id, target_node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
|
|
469
|
+
FOREIGN KEY(team_id, target_node_id, target_agent_id)
|
|
470
|
+
REFERENCES agents(team_id, node_id, id) ON DELETE RESTRICT,
|
|
471
|
+
FOREIGN KEY(team_id, target_node_id, target_chat_id)
|
|
472
|
+
REFERENCES chats(team_id, node_id, id) ON DELETE RESTRICT,
|
|
473
|
+
FOREIGN KEY(team_id, target_run_id) REFERENCES runs(team_id, id) ON DELETE RESTRICT,
|
|
474
|
+
FOREIGN KEY(team_id, causation_dispatch_id)
|
|
475
|
+
REFERENCES dispatch_requests(team_id, id) ON DELETE RESTRICT,
|
|
476
|
+
FOREIGN KEY(team_id, root_dispatch_id)
|
|
477
|
+
REFERENCES dispatch_requests(team_id, id) ON DELETE RESTRICT,
|
|
478
|
+
UNIQUE(team_id, idempotency_key),
|
|
479
|
+
UNIQUE(team_id, id),
|
|
480
|
+
CHECK(
|
|
481
|
+
(
|
|
482
|
+
requester_kind = 'human'
|
|
483
|
+
AND requesting_node_id IS NULL
|
|
484
|
+
AND requesting_agent_id IS NULL
|
|
485
|
+
AND requesting_chat_id IS NULL
|
|
486
|
+
AND requesting_run_id IS NULL
|
|
487
|
+
AND authorization_capability_id IS NULL
|
|
488
|
+
)
|
|
489
|
+
OR (
|
|
490
|
+
requester_kind = 'agent'
|
|
491
|
+
AND requesting_node_id IS NOT NULL
|
|
492
|
+
AND requesting_agent_id IS NOT NULL
|
|
493
|
+
AND requesting_chat_id IS NOT NULL
|
|
494
|
+
AND requesting_run_id IS NOT NULL
|
|
495
|
+
AND authorization_capability_id IS NOT NULL
|
|
496
|
+
)
|
|
497
|
+
),
|
|
498
|
+
CHECK(requester_kind = 'human' OR causation_dispatch_id IS NOT NULL),
|
|
499
|
+
CHECK(
|
|
500
|
+
(target_kind = 'agent' AND target_agent_id IS NOT NULL AND target_chat_id IS NULL)
|
|
501
|
+
OR (
|
|
502
|
+
target_kind = 'chat'
|
|
503
|
+
AND target_agent_id IS NULL
|
|
504
|
+
AND target_chat_id IS NOT NULL
|
|
505
|
+
)
|
|
506
|
+
),
|
|
507
|
+
CHECK(causation_dispatch_id IS NULL OR causation_dispatch_id <> id),
|
|
508
|
+
CHECK(root_dispatch_id IS NULL OR root_dispatch_id <> id),
|
|
509
|
+
CHECK(
|
|
510
|
+
(
|
|
511
|
+
causation_dispatch_id IS NULL
|
|
512
|
+
AND root_dispatch_id IS NULL
|
|
513
|
+
AND hop_count = 0
|
|
514
|
+
)
|
|
515
|
+
OR (
|
|
516
|
+
causation_dispatch_id IS NOT NULL
|
|
517
|
+
AND root_dispatch_id IS NOT NULL
|
|
518
|
+
AND hop_count BETWEEN 1 AND 4
|
|
519
|
+
)
|
|
520
|
+
)
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
CREATE TRIGGER dispatch_requires_active_human_requester
|
|
524
|
+
BEFORE INSERT ON dispatch_requests
|
|
525
|
+
FOR EACH ROW WHEN NOT EXISTS (
|
|
526
|
+
SELECT 1
|
|
527
|
+
FROM memberships AS m
|
|
528
|
+
JOIN principals AS p ON p.id = m.principal_id
|
|
529
|
+
WHERE m.team_id = NEW.team_id
|
|
530
|
+
AND m.principal_id = NEW.requested_by_human_principal_id
|
|
531
|
+
AND m.status = 'active'
|
|
532
|
+
AND p.kind = 'human'
|
|
533
|
+
AND p.status = 'active'
|
|
534
|
+
)
|
|
535
|
+
BEGIN
|
|
536
|
+
SELECT RAISE(ABORT, 'dispatch requires an active human requester');
|
|
537
|
+
END;
|
|
538
|
+
|
|
539
|
+
CREATE TRIGGER dispatch_agent_request_requires_exact_provenance
|
|
540
|
+
BEFORE INSERT ON dispatch_requests
|
|
541
|
+
FOR EACH ROW WHEN NEW.requester_kind = 'agent' AND NOT EXISTS (
|
|
542
|
+
SELECT 1
|
|
543
|
+
FROM agents AS a
|
|
544
|
+
JOIN chats AS c
|
|
545
|
+
ON c.team_id = a.team_id
|
|
546
|
+
AND c.node_id = a.node_id
|
|
547
|
+
AND c.agent_id = a.id
|
|
548
|
+
AND c.id = NEW.requesting_chat_id
|
|
549
|
+
JOIN runs AS r
|
|
550
|
+
ON r.team_id = a.team_id
|
|
551
|
+
AND r.node_id = a.node_id
|
|
552
|
+
AND r.agent_id = a.id
|
|
553
|
+
AND r.chat_id = c.id
|
|
554
|
+
AND r.id = NEW.requesting_run_id
|
|
555
|
+
JOIN turn_capabilities AS capability
|
|
556
|
+
ON capability.team_id = r.team_id
|
|
557
|
+
AND capability.issued_to_run_id = r.id
|
|
558
|
+
AND capability.id = NEW.authorization_capability_id
|
|
559
|
+
WHERE a.team_id = NEW.team_id
|
|
560
|
+
AND a.id = NEW.requesting_agent_id
|
|
561
|
+
AND a.node_id = NEW.requesting_node_id
|
|
562
|
+
AND r.acting_human_principal_id = NEW.requested_by_human_principal_id
|
|
563
|
+
AND capability.issued_by_principal_id = NEW.requested_by_human_principal_id
|
|
564
|
+
AND capability.action = 'dispatch'
|
|
565
|
+
AND capability.resource_type = NEW.target_kind
|
|
566
|
+
AND capability.resource_id = CASE NEW.target_kind
|
|
567
|
+
WHEN 'agent' THEN NEW.target_agent_id
|
|
568
|
+
ELSE NEW.target_chat_id
|
|
569
|
+
END
|
|
570
|
+
)
|
|
571
|
+
BEGIN
|
|
572
|
+
SELECT RAISE(ABORT, 'agent dispatch requires exact delegated provenance');
|
|
573
|
+
END;
|
|
574
|
+
|
|
575
|
+
CREATE TRIGGER dispatch_target_run_must_match
|
|
576
|
+
BEFORE INSERT ON dispatch_requests
|
|
577
|
+
FOR EACH ROW WHEN NEW.target_run_id IS NOT NULL AND NOT EXISTS (
|
|
578
|
+
SELECT 1 FROM runs AS r
|
|
579
|
+
WHERE r.team_id = NEW.team_id
|
|
580
|
+
AND r.id = NEW.target_run_id
|
|
581
|
+
AND r.node_id = NEW.target_node_id
|
|
582
|
+
AND (
|
|
583
|
+
(NEW.target_kind = 'agent' AND r.agent_id = NEW.target_agent_id)
|
|
584
|
+
OR (NEW.target_kind = 'chat' AND r.chat_id = NEW.target_chat_id)
|
|
585
|
+
)
|
|
586
|
+
)
|
|
587
|
+
BEGIN
|
|
588
|
+
SELECT RAISE(ABORT, 'dispatch target run does not match its exact target');
|
|
589
|
+
END;
|
|
590
|
+
|
|
591
|
+
CREATE TRIGGER dispatch_target_run_update_must_match
|
|
592
|
+
BEFORE UPDATE OF target_run_id ON dispatch_requests
|
|
593
|
+
FOR EACH ROW WHEN NEW.target_run_id IS NOT NULL AND NOT EXISTS (
|
|
594
|
+
SELECT 1 FROM runs AS r
|
|
595
|
+
WHERE r.team_id = NEW.team_id
|
|
596
|
+
AND r.id = NEW.target_run_id
|
|
597
|
+
AND r.node_id = NEW.target_node_id
|
|
598
|
+
AND (
|
|
599
|
+
(NEW.target_kind = 'agent' AND r.agent_id = NEW.target_agent_id)
|
|
600
|
+
OR (NEW.target_kind = 'chat' AND r.chat_id = NEW.target_chat_id)
|
|
601
|
+
)
|
|
602
|
+
)
|
|
603
|
+
BEGIN
|
|
604
|
+
SELECT RAISE(ABORT, 'dispatch target run does not match its exact target');
|
|
605
|
+
END;
|
|
606
|
+
|
|
607
|
+
CREATE TRIGGER dispatch_target_run_is_one_way
|
|
608
|
+
BEFORE UPDATE OF target_run_id ON dispatch_requests
|
|
609
|
+
FOR EACH ROW WHEN OLD.target_run_id IS NOT NULL AND NEW.target_run_id IS NOT OLD.target_run_id
|
|
610
|
+
BEGIN
|
|
611
|
+
SELECT RAISE(ABORT, 'dispatch target run binding is one-way');
|
|
612
|
+
END;
|
|
613
|
+
|
|
614
|
+
CREATE TRIGGER dispatch_causation_must_be_bounded
|
|
615
|
+
BEFORE INSERT ON dispatch_requests
|
|
616
|
+
FOR EACH ROW WHEN NEW.causation_dispatch_id IS NOT NULL AND NOT EXISTS (
|
|
617
|
+
SELECT 1 FROM dispatch_requests AS parent
|
|
618
|
+
WHERE parent.team_id = NEW.team_id
|
|
619
|
+
AND parent.id = NEW.causation_dispatch_id
|
|
620
|
+
AND NEW.root_dispatch_id = COALESCE(parent.root_dispatch_id, parent.id)
|
|
621
|
+
AND NEW.hop_count = parent.hop_count + 1
|
|
622
|
+
AND parent.hop_count < 4
|
|
623
|
+
AND (
|
|
624
|
+
NEW.requester_kind = 'human'
|
|
625
|
+
OR parent.target_run_id = NEW.requesting_run_id
|
|
626
|
+
)
|
|
627
|
+
)
|
|
628
|
+
BEGIN
|
|
629
|
+
SELECT RAISE(ABORT, 'dispatch causation must extend its bounded team-local chain');
|
|
630
|
+
END;
|
|
631
|
+
|
|
632
|
+
CREATE TRIGGER dispatch_authority_is_immutable
|
|
633
|
+
BEFORE UPDATE OF queue_ordinal, id, team_id, requester_kind,
|
|
634
|
+
requested_by_human_principal_id,
|
|
635
|
+
requesting_node_id, requesting_agent_id, requesting_chat_id,
|
|
636
|
+
requesting_run_id, authorization_capability_id,
|
|
637
|
+
source_message_id, target_kind, target_node_id, target_agent_id,
|
|
638
|
+
target_chat_id, request_text, idempotency_key, causation_dispatch_id,
|
|
639
|
+
root_dispatch_id, hop_count, created_at, expires_at
|
|
640
|
+
ON dispatch_requests
|
|
641
|
+
BEGIN
|
|
642
|
+
SELECT RAISE(ABORT, 'dispatch authority is immutable');
|
|
643
|
+
END;
|
|
644
|
+
|
|
645
|
+
CREATE INDEX dispatch_requests_target_fifo
|
|
646
|
+
ON dispatch_requests(team_id, target_node_id, target_chat_id, queue_ordinal)
|
|
647
|
+
WHERE status IN ('registered', 'queued');
|
|
648
|
+
|
|
649
|
+
CREATE TABLE subscriptions (
|
|
650
|
+
id TEXT PRIMARY KEY,
|
|
651
|
+
team_id TEXT NOT NULL,
|
|
652
|
+
subscriber_principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE CASCADE,
|
|
653
|
+
channel_id TEXT NOT NULL,
|
|
654
|
+
level TEXT NOT NULL CHECK(level IN ('mentions', 'all')),
|
|
655
|
+
delivery_mode TEXT NOT NULL DEFAULT 'inbox' CHECK(delivery_mode = 'inbox'),
|
|
656
|
+
wake_mode TEXT NOT NULL DEFAULT 'never' CHECK(wake_mode = 'never'),
|
|
657
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'paused', 'revoked')),
|
|
658
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
659
|
+
updated_at INTEGER NOT NULL CHECK(updated_at >= created_at),
|
|
660
|
+
FOREIGN KEY(team_id, channel_id) REFERENCES channels(team_id, id) ON DELETE CASCADE,
|
|
661
|
+
UNIQUE(channel_id, subscriber_principal_id),
|
|
662
|
+
UNIQUE(team_id, id)
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
CREATE TRIGGER subscriptions_require_team_principal
|
|
666
|
+
BEFORE INSERT ON subscriptions
|
|
667
|
+
FOR EACH ROW WHEN NOT EXISTS (
|
|
668
|
+
SELECT 1 FROM principals AS p
|
|
669
|
+
WHERE p.id = NEW.subscriber_principal_id
|
|
670
|
+
AND p.status = 'active'
|
|
671
|
+
AND (
|
|
672
|
+
p.scope_team_id = NEW.team_id
|
|
673
|
+
OR EXISTS (
|
|
674
|
+
SELECT 1 FROM memberships AS m
|
|
675
|
+
WHERE m.team_id = NEW.team_id
|
|
676
|
+
AND m.principal_id = p.id
|
|
677
|
+
AND m.status = 'active'
|
|
678
|
+
)
|
|
679
|
+
)
|
|
680
|
+
)
|
|
681
|
+
BEGIN
|
|
682
|
+
SELECT RAISE(ABORT, 'subscriber does not belong to this team');
|
|
683
|
+
END;
|
|
684
|
+
|
|
685
|
+
CREATE TRIGGER subscription_identity_is_immutable
|
|
686
|
+
BEFORE UPDATE OF id, team_id, subscriber_principal_id, channel_id,
|
|
687
|
+
delivery_mode, wake_mode, created_at
|
|
688
|
+
ON subscriptions
|
|
689
|
+
BEGIN
|
|
690
|
+
SELECT RAISE(ABORT, 'subscription identity is immutable');
|
|
691
|
+
END;
|
|
692
|
+
|
|
693
|
+
CREATE TABLE artifacts (
|
|
694
|
+
id TEXT PRIMARY KEY,
|
|
695
|
+
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
|
696
|
+
kind TEXT NOT NULL CHECK(kind IN ('attachment', 'runbook', 'skill', 'diff', 'signature')),
|
|
697
|
+
display_name TEXT NOT NULL CHECK(length(trim(display_name)) BETWEEN 1 AND 240),
|
|
698
|
+
created_by_principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
|
|
699
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
700
|
+
retention_until INTEGER CHECK(retention_until IS NULL OR retention_until > created_at),
|
|
701
|
+
UNIQUE(team_id, id)
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
CREATE TRIGGER artifacts_require_team_creator
|
|
705
|
+
BEFORE INSERT ON artifacts
|
|
706
|
+
FOR EACH ROW WHEN NOT EXISTS (
|
|
707
|
+
SELECT 1 FROM principals AS p
|
|
708
|
+
WHERE p.id = NEW.created_by_principal_id
|
|
709
|
+
AND p.status = 'active'
|
|
710
|
+
AND p.kind IN ('human', 'service', 'agent')
|
|
711
|
+
AND (
|
|
712
|
+
p.scope_team_id = NEW.team_id
|
|
713
|
+
OR EXISTS (
|
|
714
|
+
SELECT 1 FROM memberships AS m
|
|
715
|
+
WHERE m.team_id = NEW.team_id
|
|
716
|
+
AND m.principal_id = p.id
|
|
717
|
+
AND m.status = 'active'
|
|
718
|
+
)
|
|
719
|
+
)
|
|
720
|
+
)
|
|
721
|
+
BEGIN
|
|
722
|
+
SELECT RAISE(ABORT, 'artifact creator does not belong to this team');
|
|
723
|
+
END;
|
|
724
|
+
|
|
725
|
+
CREATE TRIGGER artifact_identity_is_immutable
|
|
726
|
+
BEFORE UPDATE OF id, team_id, kind, created_by_principal_id, created_at
|
|
727
|
+
ON artifacts
|
|
728
|
+
BEGIN
|
|
729
|
+
SELECT RAISE(ABORT, 'artifact identity is immutable');
|
|
730
|
+
END;
|
|
731
|
+
|
|
732
|
+
CREATE TABLE artifact_versions (
|
|
733
|
+
id TEXT PRIMARY KEY,
|
|
734
|
+
team_id TEXT NOT NULL,
|
|
735
|
+
artifact_id TEXT NOT NULL,
|
|
736
|
+
version INTEGER NOT NULL CHECK(version >= 1),
|
|
737
|
+
media_type TEXT NOT NULL CHECK(length(trim(media_type)) BETWEEN 3 AND 160),
|
|
738
|
+
byte_size INTEGER NOT NULL CHECK(byte_size BETWEEN 0 AND 10485760),
|
|
739
|
+
sha256 BLOB NOT NULL CHECK(typeof(sha256) = 'blob' AND length(sha256) = 32),
|
|
740
|
+
storage_key TEXT NOT NULL UNIQUE CHECK(length(storage_key) BETWEEN 8 AND 512),
|
|
741
|
+
signature_artifact_version_id TEXT,
|
|
742
|
+
scan_state TEXT NOT NULL DEFAULT 'pending' CHECK(scan_state IN ('pending', 'clean', 'rejected')),
|
|
743
|
+
created_by_principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
|
|
744
|
+
acting_human_principal_id TEXT,
|
|
745
|
+
provenance_node_id TEXT,
|
|
746
|
+
provenance_agent_id TEXT,
|
|
747
|
+
provenance_chat_id TEXT,
|
|
748
|
+
provenance_run_id TEXT,
|
|
749
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
750
|
+
FOREIGN KEY(team_id, artifact_id) REFERENCES artifacts(team_id, id) ON DELETE CASCADE,
|
|
751
|
+
FOREIGN KEY(team_id, signature_artifact_version_id)
|
|
752
|
+
REFERENCES artifact_versions(team_id, id) ON DELETE RESTRICT,
|
|
753
|
+
FOREIGN KEY(team_id, acting_human_principal_id)
|
|
754
|
+
REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
|
|
755
|
+
FOREIGN KEY(team_id, provenance_node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
|
|
756
|
+
FOREIGN KEY(team_id, provenance_agent_id) REFERENCES agents(team_id, id) ON DELETE RESTRICT,
|
|
757
|
+
FOREIGN KEY(team_id, provenance_chat_id) REFERENCES chats(team_id, id) ON DELETE RESTRICT,
|
|
758
|
+
FOREIGN KEY(team_id, provenance_run_id) REFERENCES runs(team_id, id) ON DELETE RESTRICT,
|
|
759
|
+
UNIQUE(artifact_id, version),
|
|
760
|
+
UNIQUE(team_id, id),
|
|
761
|
+
CHECK(
|
|
762
|
+
(
|
|
763
|
+
acting_human_principal_id IS NULL
|
|
764
|
+
AND provenance_agent_id IS NULL
|
|
765
|
+
AND provenance_node_id IS NULL
|
|
766
|
+
AND provenance_chat_id IS NULL
|
|
767
|
+
AND provenance_run_id IS NULL
|
|
768
|
+
)
|
|
769
|
+
OR (
|
|
770
|
+
acting_human_principal_id IS NOT NULL
|
|
771
|
+
AND provenance_agent_id IS NOT NULL
|
|
772
|
+
AND provenance_node_id IS NOT NULL
|
|
773
|
+
AND provenance_chat_id IS NOT NULL
|
|
774
|
+
AND provenance_run_id IS NOT NULL
|
|
775
|
+
)
|
|
776
|
+
)
|
|
777
|
+
);
|
|
778
|
+
|
|
779
|
+
CREATE TRIGGER artifact_versions_require_team_creator
|
|
780
|
+
BEFORE INSERT ON artifact_versions
|
|
781
|
+
FOR EACH ROW WHEN NOT EXISTS (
|
|
782
|
+
SELECT 1 FROM principals AS p
|
|
783
|
+
WHERE p.id = NEW.created_by_principal_id
|
|
784
|
+
AND p.status = 'active'
|
|
785
|
+
AND p.kind IN ('human', 'service', 'agent')
|
|
786
|
+
AND (
|
|
787
|
+
p.scope_team_id = NEW.team_id
|
|
788
|
+
OR EXISTS (
|
|
789
|
+
SELECT 1 FROM memberships AS m
|
|
790
|
+
WHERE m.team_id = NEW.team_id
|
|
791
|
+
AND m.principal_id = p.id
|
|
792
|
+
AND m.status = 'active'
|
|
793
|
+
)
|
|
794
|
+
)
|
|
795
|
+
)
|
|
796
|
+
BEGIN
|
|
797
|
+
SELECT RAISE(ABORT, 'artifact version creator does not belong to this team');
|
|
798
|
+
END;
|
|
799
|
+
|
|
800
|
+
CREATE TRIGGER artifact_versions_reject_false_agent_provenance
|
|
801
|
+
BEFORE INSERT ON artifact_versions
|
|
802
|
+
FOR EACH ROW WHEN NEW.provenance_agent_id IS NOT NULL AND COALESCE(
|
|
803
|
+
(SELECT kind FROM principals WHERE id = NEW.created_by_principal_id), ''
|
|
804
|
+
) <> 'agent'
|
|
805
|
+
BEGIN
|
|
806
|
+
SELECT RAISE(ABORT, 'only an agent creator may claim agent provenance');
|
|
807
|
+
END;
|
|
808
|
+
|
|
809
|
+
CREATE TRIGGER artifact_versions_require_agent_provenance
|
|
810
|
+
BEFORE INSERT ON artifact_versions
|
|
811
|
+
FOR EACH ROW WHEN COALESCE(
|
|
812
|
+
(SELECT kind FROM principals WHERE id = NEW.created_by_principal_id), ''
|
|
813
|
+
) = 'agent' AND NOT EXISTS (
|
|
814
|
+
SELECT 1
|
|
815
|
+
FROM agents AS a
|
|
816
|
+
JOIN chats AS c ON c.team_id = a.team_id AND c.id = NEW.provenance_chat_id
|
|
817
|
+
JOIN runs AS r ON r.team_id = a.team_id AND r.id = NEW.provenance_run_id
|
|
818
|
+
WHERE a.team_id = NEW.team_id
|
|
819
|
+
AND a.id = NEW.provenance_agent_id
|
|
820
|
+
AND a.principal_id = NEW.created_by_principal_id
|
|
821
|
+
AND a.node_id = NEW.provenance_node_id
|
|
822
|
+
AND c.node_id = a.node_id
|
|
823
|
+
AND c.agent_id = a.id
|
|
824
|
+
AND r.node_id = a.node_id
|
|
825
|
+
AND r.agent_id = a.id
|
|
826
|
+
AND r.chat_id = c.id
|
|
827
|
+
AND r.acting_human_principal_id = NEW.acting_human_principal_id
|
|
828
|
+
)
|
|
829
|
+
BEGIN
|
|
830
|
+
SELECT RAISE(ABORT, 'agent-created artifact version requires exact provenance');
|
|
831
|
+
END;
|
|
832
|
+
|
|
833
|
+
CREATE TRIGGER artifact_version_content_is_immutable
|
|
834
|
+
BEFORE UPDATE OF id, team_id, artifact_id, version, media_type, byte_size,
|
|
835
|
+
sha256, storage_key, signature_artifact_version_id,
|
|
836
|
+
created_by_principal_id, acting_human_principal_id, provenance_node_id,
|
|
837
|
+
provenance_agent_id, provenance_chat_id, provenance_run_id, created_at
|
|
838
|
+
ON artifact_versions
|
|
839
|
+
BEGIN
|
|
840
|
+
SELECT RAISE(ABORT, 'artifact version content and provenance are immutable');
|
|
841
|
+
END;
|
|
842
|
+
|
|
843
|
+
CREATE TABLE artifact_acl_entries (
|
|
844
|
+
id TEXT PRIMARY KEY,
|
|
845
|
+
team_id TEXT NOT NULL,
|
|
846
|
+
artifact_id TEXT NOT NULL,
|
|
847
|
+
subject_kind TEXT NOT NULL CHECK(subject_kind IN ('principal', 'role')),
|
|
848
|
+
subject_principal_id TEXT REFERENCES principals(id) ON DELETE CASCADE,
|
|
849
|
+
subject_role TEXT CHECK(subject_role IN ('owner', 'admin', 'member', 'guest', 'automation')),
|
|
850
|
+
can_read INTEGER NOT NULL CHECK(can_read IN (0, 1)),
|
|
851
|
+
can_manage INTEGER NOT NULL CHECK(can_manage IN (0, 1)),
|
|
852
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
853
|
+
FOREIGN KEY(team_id, artifact_id) REFERENCES artifacts(team_id, id) ON DELETE CASCADE,
|
|
854
|
+
CHECK(
|
|
855
|
+
(subject_kind = 'principal' AND subject_principal_id IS NOT NULL AND subject_role IS NULL)
|
|
856
|
+
OR (subject_kind = 'role' AND subject_principal_id IS NULL AND subject_role IS NOT NULL)
|
|
857
|
+
)
|
|
858
|
+
);
|
|
859
|
+
|
|
860
|
+
CREATE TRIGGER artifact_acl_require_team_principal
|
|
861
|
+
BEFORE INSERT ON artifact_acl_entries
|
|
862
|
+
FOR EACH ROW WHEN NEW.subject_kind = 'principal' AND NOT EXISTS (
|
|
863
|
+
SELECT 1 FROM principals AS p
|
|
864
|
+
WHERE p.id = NEW.subject_principal_id
|
|
865
|
+
AND p.status = 'active'
|
|
866
|
+
AND (
|
|
867
|
+
p.scope_team_id = NEW.team_id
|
|
868
|
+
OR EXISTS (
|
|
869
|
+
SELECT 1 FROM memberships AS m
|
|
870
|
+
WHERE m.team_id = NEW.team_id
|
|
871
|
+
AND m.principal_id = p.id
|
|
872
|
+
AND m.status = 'active'
|
|
873
|
+
)
|
|
874
|
+
)
|
|
875
|
+
)
|
|
876
|
+
BEGIN
|
|
877
|
+
SELECT RAISE(ABORT, 'artifact ACL principal does not belong to this team');
|
|
878
|
+
END;
|
|
879
|
+
|
|
880
|
+
CREATE TRIGGER artifact_acl_subject_is_immutable
|
|
881
|
+
BEFORE UPDATE OF id, team_id, artifact_id, subject_kind,
|
|
882
|
+
subject_principal_id, subject_role, created_at
|
|
883
|
+
ON artifact_acl_entries
|
|
884
|
+
BEGIN
|
|
885
|
+
SELECT RAISE(ABORT, 'artifact ACL subject is immutable');
|
|
886
|
+
END;
|
|
887
|
+
|
|
888
|
+
CREATE UNIQUE INDEX artifact_acl_one_principal_entry
|
|
889
|
+
ON artifact_acl_entries(artifact_id, subject_principal_id)
|
|
890
|
+
WHERE subject_kind = 'principal';
|
|
891
|
+
|
|
892
|
+
CREATE UNIQUE INDEX artifact_acl_one_role_entry
|
|
893
|
+
ON artifact_acl_entries(artifact_id, subject_role)
|
|
894
|
+
WHERE subject_kind = 'role';
|
|
895
|
+
|
|
896
|
+
CREATE TABLE message_attachments (
|
|
897
|
+
team_id TEXT NOT NULL,
|
|
898
|
+
message_id TEXT NOT NULL,
|
|
899
|
+
artifact_version_id TEXT NOT NULL,
|
|
900
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
901
|
+
PRIMARY KEY(message_id, artifact_version_id),
|
|
902
|
+
FOREIGN KEY(team_id, message_id) REFERENCES messages(team_id, id) ON DELETE CASCADE,
|
|
903
|
+
FOREIGN KEY(team_id, artifact_version_id)
|
|
904
|
+
REFERENCES artifact_versions(team_id, id) ON DELETE RESTRICT
|
|
905
|
+
);
|
|
906
|
+
|
|
907
|
+
CREATE TABLE library_items (
|
|
908
|
+
id TEXT PRIMARY KEY,
|
|
909
|
+
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
|
910
|
+
kind TEXT NOT NULL CHECK(kind IN ('runbook', 'skill')),
|
|
911
|
+
slug TEXT NOT NULL CHECK(slug = lower(trim(slug))) CHECK(length(slug) BETWEEN 1 AND 100),
|
|
912
|
+
display_name TEXT NOT NULL CHECK(length(trim(display_name)) BETWEEN 1 AND 200),
|
|
913
|
+
created_by_principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
|
|
914
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
915
|
+
updated_at INTEGER NOT NULL CHECK(updated_at >= created_at),
|
|
916
|
+
archived_at INTEGER CHECK(archived_at IS NULL OR archived_at >= created_at),
|
|
917
|
+
UNIQUE(team_id, slug),
|
|
918
|
+
UNIQUE(team_id, id)
|
|
919
|
+
);
|
|
920
|
+
|
|
921
|
+
CREATE TRIGGER library_items_require_team_creator
|
|
922
|
+
BEFORE INSERT ON library_items
|
|
923
|
+
FOR EACH ROW WHEN NOT EXISTS (
|
|
924
|
+
SELECT 1 FROM principals AS p
|
|
925
|
+
WHERE p.id = NEW.created_by_principal_id
|
|
926
|
+
AND p.status = 'active'
|
|
927
|
+
AND p.kind IN ('human', 'service', 'agent')
|
|
928
|
+
AND (
|
|
929
|
+
p.scope_team_id = NEW.team_id
|
|
930
|
+
OR EXISTS (
|
|
931
|
+
SELECT 1 FROM memberships AS m
|
|
932
|
+
WHERE m.team_id = NEW.team_id
|
|
933
|
+
AND m.principal_id = p.id
|
|
934
|
+
AND m.status = 'active'
|
|
935
|
+
)
|
|
936
|
+
)
|
|
937
|
+
)
|
|
938
|
+
BEGIN
|
|
939
|
+
SELECT RAISE(ABORT, 'library creator does not belong to this team');
|
|
940
|
+
END;
|
|
941
|
+
|
|
942
|
+
CREATE TRIGGER library_item_identity_is_immutable
|
|
943
|
+
BEFORE UPDATE OF id, team_id, kind, slug, created_by_principal_id, created_at
|
|
944
|
+
ON library_items
|
|
945
|
+
BEGIN
|
|
946
|
+
SELECT RAISE(ABORT, 'library item identity is immutable');
|
|
947
|
+
END;
|
|
948
|
+
|
|
949
|
+
CREATE TABLE library_versions (
|
|
950
|
+
id TEXT PRIMARY KEY,
|
|
951
|
+
team_id TEXT NOT NULL,
|
|
952
|
+
library_item_id TEXT NOT NULL,
|
|
953
|
+
version INTEGER NOT NULL CHECK(version >= 1),
|
|
954
|
+
artifact_version_id TEXT NOT NULL,
|
|
955
|
+
status TEXT NOT NULL DEFAULT 'draft'
|
|
956
|
+
CHECK(status IN ('draft', 'in_review', 'approved', 'rejected', 'withdrawn')),
|
|
957
|
+
permissions_json TEXT NOT NULL DEFAULT '{}'
|
|
958
|
+
CHECK(json_valid(permissions_json) AND json_type(permissions_json) = 'object'),
|
|
959
|
+
provenance_node_id TEXT,
|
|
960
|
+
provenance_agent_id TEXT,
|
|
961
|
+
provenance_chat_id TEXT,
|
|
962
|
+
provenance_run_id TEXT,
|
|
963
|
+
acting_human_principal_id TEXT,
|
|
964
|
+
published_by_principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
|
|
965
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
966
|
+
reviewed_at INTEGER CHECK(reviewed_at IS NULL OR reviewed_at >= created_at),
|
|
967
|
+
FOREIGN KEY(team_id, library_item_id) REFERENCES library_items(team_id, id) ON DELETE CASCADE,
|
|
968
|
+
FOREIGN KEY(team_id, artifact_version_id)
|
|
969
|
+
REFERENCES artifact_versions(team_id, id) ON DELETE RESTRICT,
|
|
970
|
+
FOREIGN KEY(team_id, provenance_node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
|
|
971
|
+
FOREIGN KEY(team_id, provenance_agent_id) REFERENCES agents(team_id, id) ON DELETE RESTRICT,
|
|
972
|
+
FOREIGN KEY(team_id, provenance_chat_id) REFERENCES chats(team_id, id) ON DELETE RESTRICT,
|
|
973
|
+
FOREIGN KEY(team_id, provenance_run_id) REFERENCES runs(team_id, id) ON DELETE RESTRICT,
|
|
974
|
+
FOREIGN KEY(team_id, acting_human_principal_id)
|
|
975
|
+
REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
|
|
976
|
+
UNIQUE(library_item_id, version),
|
|
977
|
+
UNIQUE(team_id, id),
|
|
978
|
+
CHECK(
|
|
979
|
+
(
|
|
980
|
+
acting_human_principal_id IS NULL
|
|
981
|
+
AND provenance_agent_id IS NULL
|
|
982
|
+
AND provenance_node_id IS NULL
|
|
983
|
+
AND provenance_chat_id IS NULL
|
|
984
|
+
AND provenance_run_id IS NULL
|
|
985
|
+
)
|
|
986
|
+
OR (
|
|
987
|
+
acting_human_principal_id IS NOT NULL
|
|
988
|
+
AND provenance_agent_id IS NOT NULL
|
|
989
|
+
AND provenance_node_id IS NOT NULL
|
|
990
|
+
AND provenance_chat_id IS NOT NULL
|
|
991
|
+
AND provenance_run_id IS NOT NULL
|
|
992
|
+
)
|
|
993
|
+
)
|
|
994
|
+
);
|
|
995
|
+
|
|
996
|
+
CREATE TRIGGER library_versions_require_team_publisher
|
|
997
|
+
BEFORE INSERT ON library_versions
|
|
998
|
+
FOR EACH ROW WHEN NOT EXISTS (
|
|
999
|
+
SELECT 1 FROM principals AS p
|
|
1000
|
+
WHERE p.id = NEW.published_by_principal_id
|
|
1001
|
+
AND p.status = 'active'
|
|
1002
|
+
AND p.kind IN ('human', 'service', 'agent')
|
|
1003
|
+
AND (
|
|
1004
|
+
p.scope_team_id = NEW.team_id
|
|
1005
|
+
OR EXISTS (
|
|
1006
|
+
SELECT 1 FROM memberships AS m
|
|
1007
|
+
WHERE m.team_id = NEW.team_id
|
|
1008
|
+
AND m.principal_id = p.id
|
|
1009
|
+
AND m.status = 'active'
|
|
1010
|
+
)
|
|
1011
|
+
)
|
|
1012
|
+
)
|
|
1013
|
+
BEGIN
|
|
1014
|
+
SELECT RAISE(ABORT, 'library publisher does not belong to this team');
|
|
1015
|
+
END;
|
|
1016
|
+
|
|
1017
|
+
CREATE TRIGGER library_versions_reject_false_agent_provenance
|
|
1018
|
+
BEFORE INSERT ON library_versions
|
|
1019
|
+
FOR EACH ROW WHEN NEW.provenance_agent_id IS NOT NULL AND COALESCE(
|
|
1020
|
+
(SELECT kind FROM principals WHERE id = NEW.published_by_principal_id), ''
|
|
1021
|
+
) <> 'agent'
|
|
1022
|
+
BEGIN
|
|
1023
|
+
SELECT RAISE(ABORT, 'only an agent publisher may claim agent provenance');
|
|
1024
|
+
END;
|
|
1025
|
+
|
|
1026
|
+
CREATE TRIGGER library_versions_require_agent_provenance
|
|
1027
|
+
BEFORE INSERT ON library_versions
|
|
1028
|
+
FOR EACH ROW WHEN COALESCE(
|
|
1029
|
+
(SELECT kind FROM principals WHERE id = NEW.published_by_principal_id), ''
|
|
1030
|
+
) = 'agent' AND NOT EXISTS (
|
|
1031
|
+
SELECT 1
|
|
1032
|
+
FROM agents AS a
|
|
1033
|
+
JOIN chats AS c ON c.team_id = a.team_id AND c.id = NEW.provenance_chat_id
|
|
1034
|
+
JOIN runs AS r ON r.team_id = a.team_id AND r.id = NEW.provenance_run_id
|
|
1035
|
+
WHERE a.team_id = NEW.team_id
|
|
1036
|
+
AND a.id = NEW.provenance_agent_id
|
|
1037
|
+
AND a.principal_id = NEW.published_by_principal_id
|
|
1038
|
+
AND a.node_id = NEW.provenance_node_id
|
|
1039
|
+
AND c.node_id = a.node_id
|
|
1040
|
+
AND c.agent_id = a.id
|
|
1041
|
+
AND r.node_id = a.node_id
|
|
1042
|
+
AND r.agent_id = a.id
|
|
1043
|
+
AND r.chat_id = c.id
|
|
1044
|
+
AND r.acting_human_principal_id = NEW.acting_human_principal_id
|
|
1045
|
+
)
|
|
1046
|
+
BEGIN
|
|
1047
|
+
SELECT RAISE(ABORT, 'agent-published library version requires exact provenance');
|
|
1048
|
+
END;
|
|
1049
|
+
|
|
1050
|
+
CREATE TRIGGER library_version_content_is_immutable
|
|
1051
|
+
BEFORE UPDATE OF id, team_id, library_item_id, version, artifact_version_id,
|
|
1052
|
+
permissions_json, provenance_node_id, provenance_agent_id,
|
|
1053
|
+
provenance_chat_id, provenance_run_id, acting_human_principal_id,
|
|
1054
|
+
published_by_principal_id, created_at
|
|
1055
|
+
ON library_versions
|
|
1056
|
+
BEGIN
|
|
1057
|
+
SELECT RAISE(ABORT, 'library version content and provenance are immutable');
|
|
1058
|
+
END;
|
|
1059
|
+
|
|
1060
|
+
CREATE TABLE library_reviews (
|
|
1061
|
+
id TEXT PRIMARY KEY,
|
|
1062
|
+
team_id TEXT NOT NULL,
|
|
1063
|
+
library_version_id TEXT NOT NULL,
|
|
1064
|
+
reviewer_principal_id TEXT NOT NULL,
|
|
1065
|
+
decision TEXT NOT NULL CHECK(decision IN ('approved', 'changes_requested', 'rejected')),
|
|
1066
|
+
review_note TEXT CHECK(review_note IS NULL OR length(review_note) <= 16000),
|
|
1067
|
+
reviewed_at INTEGER NOT NULL CHECK(reviewed_at >= 0),
|
|
1068
|
+
FOREIGN KEY(team_id, library_version_id)
|
|
1069
|
+
REFERENCES library_versions(team_id, id) ON DELETE CASCADE,
|
|
1070
|
+
FOREIGN KEY(team_id, reviewer_principal_id)
|
|
1071
|
+
REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
|
|
1072
|
+
UNIQUE(library_version_id, reviewer_principal_id)
|
|
1073
|
+
);
|
|
1074
|
+
|
|
1075
|
+
CREATE TABLE skill_install_requests (
|
|
1076
|
+
id TEXT PRIMARY KEY,
|
|
1077
|
+
team_id TEXT NOT NULL,
|
|
1078
|
+
library_version_id TEXT NOT NULL,
|
|
1079
|
+
target_node_id TEXT NOT NULL,
|
|
1080
|
+
target_agent_id TEXT,
|
|
1081
|
+
diff_artifact_version_id TEXT NOT NULL,
|
|
1082
|
+
requested_by_principal_id TEXT NOT NULL,
|
|
1083
|
+
approved_by_principal_id TEXT,
|
|
1084
|
+
status TEXT NOT NULL DEFAULT 'requested'
|
|
1085
|
+
CHECK(status IN ('requested', 'approved', 'rejected', 'installed', 'failed', 'cancelled')),
|
|
1086
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
1087
|
+
updated_at INTEGER NOT NULL CHECK(updated_at >= created_at),
|
|
1088
|
+
FOREIGN KEY(team_id, library_version_id)
|
|
1089
|
+
REFERENCES library_versions(team_id, id) ON DELETE RESTRICT,
|
|
1090
|
+
FOREIGN KEY(team_id, target_node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
|
|
1091
|
+
FOREIGN KEY(team_id, target_node_id, target_agent_id)
|
|
1092
|
+
REFERENCES agents(team_id, node_id, id) ON DELETE RESTRICT,
|
|
1093
|
+
FOREIGN KEY(team_id, diff_artifact_version_id)
|
|
1094
|
+
REFERENCES artifact_versions(team_id, id) ON DELETE RESTRICT,
|
|
1095
|
+
FOREIGN KEY(team_id, requested_by_principal_id)
|
|
1096
|
+
REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
|
|
1097
|
+
FOREIGN KEY(team_id, approved_by_principal_id)
|
|
1098
|
+
REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
|
|
1099
|
+
UNIQUE(team_id, id),
|
|
1100
|
+
CHECK(status NOT IN ('approved', 'installed') OR approved_by_principal_id IS NOT NULL)
|
|
1101
|
+
);
|
|
1102
|
+
|
|
1103
|
+
CREATE TABLE audit_events (
|
|
1104
|
+
id TEXT PRIMARY KEY,
|
|
1105
|
+
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE RESTRICT,
|
|
1106
|
+
actor_principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
|
|
1107
|
+
acting_human_principal_id TEXT,
|
|
1108
|
+
node_id TEXT,
|
|
1109
|
+
agent_id TEXT,
|
|
1110
|
+
chat_id TEXT,
|
|
1111
|
+
run_id TEXT,
|
|
1112
|
+
action TEXT NOT NULL CHECK(length(trim(action)) BETWEEN 1 AND 160),
|
|
1113
|
+
resource_type TEXT NOT NULL CHECK(length(trim(resource_type)) BETWEEN 1 AND 80),
|
|
1114
|
+
resource_id TEXT NOT NULL CHECK(length(trim(resource_id)) BETWEEN 1 AND 240),
|
|
1115
|
+
outcome TEXT NOT NULL CHECK(outcome IN ('accepted', 'denied', 'succeeded', 'failed', 'cancelled')),
|
|
1116
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
1117
|
+
CHECK(json_valid(metadata_json) AND json_type(metadata_json) = 'object'),
|
|
1118
|
+
previous_event_hash BLOB CHECK(previous_event_hash IS NULL OR (
|
|
1119
|
+
typeof(previous_event_hash) = 'blob' AND length(previous_event_hash) = 32
|
|
1120
|
+
)),
|
|
1121
|
+
event_hash BLOB NOT NULL CHECK(typeof(event_hash) = 'blob' AND length(event_hash) = 32),
|
|
1122
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
1123
|
+
FOREIGN KEY(team_id, acting_human_principal_id)
|
|
1124
|
+
REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
|
|
1125
|
+
FOREIGN KEY(team_id, node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
|
|
1126
|
+
FOREIGN KEY(team_id, agent_id) REFERENCES agents(team_id, id) ON DELETE RESTRICT,
|
|
1127
|
+
FOREIGN KEY(team_id, chat_id) REFERENCES chats(team_id, id) ON DELETE RESTRICT,
|
|
1128
|
+
FOREIGN KEY(team_id, run_id) REFERENCES runs(team_id, id) ON DELETE RESTRICT,
|
|
1129
|
+
UNIQUE(team_id, event_hash),
|
|
1130
|
+
CHECK(
|
|
1131
|
+
(
|
|
1132
|
+
acting_human_principal_id IS NULL
|
|
1133
|
+
AND
|
|
1134
|
+
agent_id IS NULL
|
|
1135
|
+
AND node_id IS NULL
|
|
1136
|
+
AND chat_id IS NULL
|
|
1137
|
+
AND run_id IS NULL
|
|
1138
|
+
)
|
|
1139
|
+
OR (
|
|
1140
|
+
acting_human_principal_id IS NULL
|
|
1141
|
+
AND node_id IS NOT NULL
|
|
1142
|
+
AND agent_id IS NULL
|
|
1143
|
+
AND chat_id IS NULL
|
|
1144
|
+
AND run_id IS NULL
|
|
1145
|
+
)
|
|
1146
|
+
OR (
|
|
1147
|
+
acting_human_principal_id IS NOT NULL
|
|
1148
|
+
AND agent_id IS NOT NULL
|
|
1149
|
+
AND node_id IS NOT NULL
|
|
1150
|
+
AND chat_id IS NOT NULL
|
|
1151
|
+
AND run_id IS NOT NULL
|
|
1152
|
+
)
|
|
1153
|
+
)
|
|
1154
|
+
);
|
|
1155
|
+
|
|
1156
|
+
CREATE TRIGGER audit_events_require_team_actor
|
|
1157
|
+
BEFORE INSERT ON audit_events
|
|
1158
|
+
FOR EACH ROW WHEN NOT EXISTS (
|
|
1159
|
+
SELECT 1 FROM principals AS p
|
|
1160
|
+
WHERE p.id = NEW.actor_principal_id
|
|
1161
|
+
AND p.kind IN ('human', 'service', 'node', 'agent')
|
|
1162
|
+
AND (
|
|
1163
|
+
p.scope_team_id = NEW.team_id
|
|
1164
|
+
OR EXISTS (
|
|
1165
|
+
SELECT 1 FROM memberships AS m
|
|
1166
|
+
WHERE m.team_id = NEW.team_id
|
|
1167
|
+
AND m.principal_id = p.id
|
|
1168
|
+
)
|
|
1169
|
+
)
|
|
1170
|
+
)
|
|
1171
|
+
BEGIN
|
|
1172
|
+
SELECT RAISE(ABORT, 'audit actor does not belong to this team');
|
|
1173
|
+
END;
|
|
1174
|
+
|
|
1175
|
+
CREATE TRIGGER audit_events_reject_false_node_provenance
|
|
1176
|
+
BEFORE INSERT ON audit_events
|
|
1177
|
+
FOR EACH ROW WHEN NEW.node_id IS NOT NULL AND COALESCE(
|
|
1178
|
+
(SELECT kind FROM principals WHERE id = NEW.actor_principal_id), ''
|
|
1179
|
+
) NOT IN ('node', 'agent')
|
|
1180
|
+
BEGIN
|
|
1181
|
+
SELECT RAISE(ABORT, 'only a node or agent actor may claim node provenance');
|
|
1182
|
+
END;
|
|
1183
|
+
|
|
1184
|
+
CREATE TRIGGER audit_events_require_node_provenance
|
|
1185
|
+
BEFORE INSERT ON audit_events
|
|
1186
|
+
FOR EACH ROW WHEN COALESCE(
|
|
1187
|
+
(SELECT kind FROM principals WHERE id = NEW.actor_principal_id), ''
|
|
1188
|
+
) = 'node' AND NOT EXISTS (
|
|
1189
|
+
SELECT 1 FROM nodes AS n
|
|
1190
|
+
WHERE n.team_id = NEW.team_id
|
|
1191
|
+
AND n.id = NEW.node_id
|
|
1192
|
+
AND n.principal_id = NEW.actor_principal_id
|
|
1193
|
+
)
|
|
1194
|
+
BEGIN
|
|
1195
|
+
SELECT RAISE(ABORT, 'node audit event requires exact node provenance');
|
|
1196
|
+
END;
|
|
1197
|
+
|
|
1198
|
+
CREATE TRIGGER audit_events_reject_false_agent_provenance
|
|
1199
|
+
BEFORE INSERT ON audit_events
|
|
1200
|
+
FOR EACH ROW WHEN NEW.agent_id IS NOT NULL AND COALESCE(
|
|
1201
|
+
(SELECT kind FROM principals WHERE id = NEW.actor_principal_id), ''
|
|
1202
|
+
) <> 'agent'
|
|
1203
|
+
BEGIN
|
|
1204
|
+
SELECT RAISE(ABORT, 'only an agent actor may claim agent provenance');
|
|
1205
|
+
END;
|
|
1206
|
+
|
|
1207
|
+
CREATE TRIGGER audit_events_require_agent_provenance
|
|
1208
|
+
BEFORE INSERT ON audit_events
|
|
1209
|
+
FOR EACH ROW WHEN COALESCE(
|
|
1210
|
+
(SELECT kind FROM principals WHERE id = NEW.actor_principal_id), ''
|
|
1211
|
+
) = 'agent' AND NOT EXISTS (
|
|
1212
|
+
SELECT 1
|
|
1213
|
+
FROM agents AS a
|
|
1214
|
+
JOIN chats AS c ON c.team_id = a.team_id AND c.id = NEW.chat_id
|
|
1215
|
+
JOIN runs AS r ON r.team_id = a.team_id AND r.id = NEW.run_id
|
|
1216
|
+
WHERE a.team_id = NEW.team_id
|
|
1217
|
+
AND a.id = NEW.agent_id
|
|
1218
|
+
AND a.principal_id = NEW.actor_principal_id
|
|
1219
|
+
AND a.node_id = NEW.node_id
|
|
1220
|
+
AND c.node_id = a.node_id
|
|
1221
|
+
AND c.agent_id = a.id
|
|
1222
|
+
AND r.node_id = a.node_id
|
|
1223
|
+
AND r.agent_id = a.id
|
|
1224
|
+
AND r.chat_id = c.id
|
|
1225
|
+
AND r.acting_human_principal_id = NEW.acting_human_principal_id
|
|
1226
|
+
)
|
|
1227
|
+
BEGIN
|
|
1228
|
+
SELECT RAISE(ABORT, 'agent audit event requires exact provenance');
|
|
1229
|
+
END;
|
|
1230
|
+
|
|
1231
|
+
CREATE TRIGGER audit_events_are_immutable_update
|
|
1232
|
+
BEFORE UPDATE ON audit_events
|
|
1233
|
+
BEGIN
|
|
1234
|
+
SELECT RAISE(ABORT, 'audit events are immutable');
|
|
1235
|
+
END;
|
|
1236
|
+
|
|
1237
|
+
CREATE TRIGGER audit_events_are_immutable_delete
|
|
1238
|
+
BEFORE DELETE ON audit_events
|
|
1239
|
+
BEGIN
|
|
1240
|
+
SELECT RAISE(ABORT, 'audit events are immutable');
|
|
1241
|
+
END;
|
|
1242
|
+
|
|
1243
|
+
CREATE TABLE outbox_events (
|
|
1244
|
+
id TEXT PRIMARY KEY,
|
|
1245
|
+
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
|
1246
|
+
aggregate_type TEXT NOT NULL CHECK(length(trim(aggregate_type)) BETWEEN 1 AND 80),
|
|
1247
|
+
aggregate_id TEXT NOT NULL CHECK(length(trim(aggregate_id)) BETWEEN 1 AND 240),
|
|
1248
|
+
event_type TEXT NOT NULL CHECK(length(trim(event_type)) BETWEEN 1 AND 160),
|
|
1249
|
+
metadata_json TEXT NOT NULL CHECK(json_valid(metadata_json) AND json_type(metadata_json) = 'object'),
|
|
1250
|
+
idempotency_key BLOB NOT NULL UNIQUE
|
|
1251
|
+
CHECK(typeof(idempotency_key) = 'blob' AND length(idempotency_key) = 32),
|
|
1252
|
+
state TEXT NOT NULL DEFAULT 'pending' CHECK(state IN ('pending', 'leased', 'delivered', 'dead_letter')),
|
|
1253
|
+
available_at INTEGER NOT NULL CHECK(available_at >= 0),
|
|
1254
|
+
lease_owner TEXT,
|
|
1255
|
+
lease_expires_at INTEGER,
|
|
1256
|
+
attempt_count INTEGER NOT NULL DEFAULT 0 CHECK(attempt_count BETWEEN 0 AND 100),
|
|
1257
|
+
delivered_at INTEGER CHECK(delivered_at IS NULL OR delivered_at >= available_at),
|
|
1258
|
+
created_at INTEGER NOT NULL CHECK(created_at >= 0),
|
|
1259
|
+
CHECK(
|
|
1260
|
+
(state = 'leased' AND lease_owner IS NOT NULL AND lease_expires_at IS NOT NULL)
|
|
1261
|
+
OR (state <> 'leased')
|
|
1262
|
+
)
|
|
1263
|
+
);
|
|
1264
|
+
|
|
1265
|
+
CREATE TRIGGER outbox_event_identity_is_immutable
|
|
1266
|
+
BEFORE UPDATE OF id, team_id, aggregate_type, aggregate_id, event_type,
|
|
1267
|
+
metadata_json, idempotency_key, created_at
|
|
1268
|
+
ON outbox_events
|
|
1269
|
+
BEGIN
|
|
1270
|
+
SELECT RAISE(ABORT, 'outbox event identity and payload are immutable');
|
|
1271
|
+
END;
|
|
1272
|
+
|
|
1273
|
+
CREATE TRIGGER outbox_delivery_state_is_monotonic
|
|
1274
|
+
BEFORE UPDATE ON outbox_events
|
|
1275
|
+
FOR EACH ROW WHEN
|
|
1276
|
+
NEW.attempt_count < OLD.attempt_count
|
|
1277
|
+
OR (OLD.delivered_at IS NOT NULL AND NEW.delivered_at IS NOT OLD.delivered_at)
|
|
1278
|
+
BEGIN
|
|
1279
|
+
SELECT RAISE(ABORT, 'outbox delivery counters are monotonic');
|
|
1280
|
+
END;
|
|
1281
|
+
|
|
1282
|
+
CREATE INDEX outbox_pending_delivery
|
|
1283
|
+
ON outbox_events(state, available_at, id)
|
|
1284
|
+
WHERE state IN ('pending', 'leased');
|
|
1285
|
+
|
|
1286
|
+
CREATE TABLE rate_limit_buckets (
|
|
1287
|
+
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
|
1288
|
+
subject_key TEXT NOT NULL CHECK(length(subject_key) BETWEEN 1 AND 240),
|
|
1289
|
+
action TEXT NOT NULL CHECK(length(action) BETWEEN 1 AND 120),
|
|
1290
|
+
window_started_at INTEGER NOT NULL CHECK(window_started_at >= 0),
|
|
1291
|
+
count INTEGER NOT NULL CHECK(count >= 0),
|
|
1292
|
+
updated_at INTEGER NOT NULL CHECK(updated_at >= window_started_at),
|
|
1293
|
+
PRIMARY KEY(team_id, subject_key, action)
|
|
1294
|
+
);
|
|
1295
|
+
|
|
1296
|
+
CREATE VIRTUAL TABLE message_search USING fts5(
|
|
1297
|
+
team_id UNINDEXED,
|
|
1298
|
+
channel_id UNINDEXED,
|
|
1299
|
+
id UNINDEXED,
|
|
1300
|
+
body,
|
|
1301
|
+
content = 'messages',
|
|
1302
|
+
content_rowid = 'rowid',
|
|
1303
|
+
tokenize = 'unicode61 remove_diacritics 2'
|
|
1304
|
+
);
|
|
1305
|
+
|
|
1306
|
+
CREATE TRIGGER messages_search_insert
|
|
1307
|
+
AFTER INSERT ON messages
|
|
1308
|
+
FOR EACH ROW WHEN NEW.deleted_at IS NULL
|
|
1309
|
+
BEGIN
|
|
1310
|
+
INSERT INTO message_search(rowid, team_id, channel_id, id, body)
|
|
1311
|
+
VALUES (NEW.rowid, NEW.team_id, NEW.channel_id, NEW.id, NEW.body);
|
|
1312
|
+
END;
|
|
1313
|
+
|
|
1314
|
+
CREATE TRIGGER messages_search_delete
|
|
1315
|
+
AFTER DELETE ON messages
|
|
1316
|
+
FOR EACH ROW WHEN OLD.deleted_at IS NULL
|
|
1317
|
+
BEGIN
|
|
1318
|
+
INSERT INTO message_search(message_search, rowid, team_id, channel_id, id, body)
|
|
1319
|
+
VALUES ('delete', OLD.rowid, OLD.team_id, OLD.channel_id, OLD.id, OLD.body);
|
|
1320
|
+
END;
|
|
1321
|
+
|
|
1322
|
+
CREATE TRIGGER messages_search_update_remove
|
|
1323
|
+
AFTER UPDATE ON messages
|
|
1324
|
+
FOR EACH ROW WHEN OLD.deleted_at IS NULL
|
|
1325
|
+
BEGIN
|
|
1326
|
+
INSERT INTO message_search(message_search, rowid, team_id, channel_id, id, body)
|
|
1327
|
+
VALUES ('delete', OLD.rowid, OLD.team_id, OLD.channel_id, OLD.id, OLD.body);
|
|
1328
|
+
END;
|
|
1329
|
+
|
|
1330
|
+
CREATE TRIGGER messages_search_update_add
|
|
1331
|
+
AFTER UPDATE ON messages
|
|
1332
|
+
FOR EACH ROW WHEN NEW.deleted_at IS NULL
|
|
1333
|
+
BEGIN
|
|
1334
|
+
INSERT INTO message_search(rowid, team_id, channel_id, id, body)
|
|
1335
|
+
VALUES (NEW.rowid, NEW.team_id, NEW.channel_id, NEW.id, NEW.body);
|
|
1336
|
+
END;
|