@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.
Files changed (113) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +19 -0
  3. package/README.md +60 -0
  4. package/npm/cli.cjs +289 -0
  5. package/package.json +32 -0
  6. package/server/LICENSE +202 -0
  7. package/server/NOTICE +19 -0
  8. package/server/VERSION +1 -0
  9. package/server/activation_transaction.py +2458 -0
  10. package/server/agent_server.py +93226 -0
  11. package/server/agentsdock_chats.py +1237 -0
  12. package/server/agentsdock_emergency.py +338 -0
  13. package/server/agentsdock_jobs.py +640 -0
  14. package/server/agentsdock_mail.py +325 -0
  15. package/server/agentsdock_publish.py +404 -0
  16. package/server/agentsdock_team.py +793 -0
  17. package/server/agentsdock_team_hub/__init__.py +33 -0
  18. package/server/agentsdock_team_hub/auth.py +710 -0
  19. package/server/agentsdock_team_hub/cli.py +647 -0
  20. package/server/agentsdock_team_hub/database.py +310 -0
  21. package/server/agentsdock_team_hub/mail_hint_streams.py +176 -0
  22. package/server/agentsdock_team_hub/mail_hints.py +201 -0
  23. package/server/agentsdock_team_hub/migrations/0001_identity_auth.sql +690 -0
  24. package/server/agentsdock_team_hub/migrations/0002_teamspace_ledger.sql +1336 -0
  25. package/server/agentsdock_team_hub/migrations/0003_service_runtime.sql +361 -0
  26. package/server/agentsdock_team_hub/migrations/0004_managed_host_binding.sql +22 -0
  27. package/server/agentsdock_team_hub/migrations/0005_tailnet_bootstrap_delegations.sql +63 -0
  28. package/server/agentsdock_team_hub/migrations/0006_team_network_mailbox.sql +423 -0
  29. package/server/agentsdock_team_hub/migrations/0007_local_agent_mail.sql +63 -0
  30. package/server/agentsdock_team_hub/migrations/0008_managed_server_session.sql +73 -0
  31. package/server/agentsdock_team_hub/migrations/0009_team_messages.sql +324 -0
  32. package/server/agentsdock_team_hub/migrations/0010_team_attachment_orphan_reclamation.sql +52 -0
  33. package/server/agentsdock_team_hub/migrations/0011_human_admin_paging.sql +65 -0
  34. package/server/agentsdock_team_hub/migrations/0012_network_content_deletions.sql +69 -0
  35. package/server/agentsdock_team_hub/migrations/0013_team_message_revisions.sql +48 -0
  36. package/server/agentsdock_team_hub/migrations/0014_managed_network_owner.sql +50 -0
  37. package/server/agentsdock_team_hub/migrations/0015_team_message_inbox_dismissals.sql +3 -0
  38. package/server/agentsdock_team_hub/migrations/0016_team_message_all_servers.sql +4 -0
  39. package/server/agentsdock_team_hub/migrations/0017_skill_announcement_deletions.sql +16 -0
  40. package/server/agentsdock_team_hub/migrations/0018_team_mail_subjects.sql +8 -0
  41. package/server/agentsdock_team_hub/migrations/0019_team_mailbox_state.sql +5 -0
  42. package/server/agentsdock_team_hub/migrations/0020_team_mail_arrivals.sql +44 -0
  43. package/server/agentsdock_team_hub/migrations/0021_team_mail_threads.sql +4 -0
  44. package/server/agentsdock_team_hub/migrations/0022_team_bulletin_changes.sql +49 -0
  45. package/server/agentsdock_team_hub/migrations/0023_team_message_search.sql +93 -0
  46. package/server/agentsdock_team_hub/migrations/__init__.py +1 -0
  47. package/server/agentsdock_team_hub/notification_hints.py +306 -0
  48. package/server/agentsdock_team_hub/secure_peer.py +11215 -0
  49. package/server/agentsdock_team_hub/secure_peer_hub.py +1372 -0
  50. package/server/agentsdock_team_hub/security.py +318 -0
  51. package/server/agentsdock_team_hub/service.py +1737 -0
  52. package/server/agentsdock_team_hub/store.py +16276 -0
  53. package/server/chat_mailbox.py +448 -0
  54. package/server/claude_background_reconciliation.py +92 -0
  55. package/server/claude_goals.py +245 -0
  56. package/server/claude_history_provenance.py +137 -0
  57. package/server/claude_history_repair.py +1601 -0
  58. package/server/claude_sdk_client.py +3731 -0
  59. package/server/claude_side_question.py +212 -0
  60. package/server/codex_app_server.py +3030 -0
  61. package/server/codex_auth.py +93 -0
  62. package/server/codex_history_repair.py +1247 -0
  63. package/server/codex_provider.py +1120 -0
  64. package/server/codex_side_question.py +301 -0
  65. package/server/cursor_agent_client.py +654 -0
  66. package/server/cursor_process_guard.py +347 -0
  67. package/server/execution_activation.py +610 -0
  68. package/server/execution_control.py +233 -0
  69. package/server/execution_durability.py +184 -0
  70. package/server/execution_http.py +256 -0
  71. package/server/execution_install.py +792 -0
  72. package/server/execution_legacy_runner.py +406 -0
  73. package/server/execution_maintenance.py +137 -0
  74. package/server/execution_manage.py +615 -0
  75. package/server/execution_ownership.py +83 -0
  76. package/server/execution_preparation.py +362 -0
  77. package/server/execution_recovery.py +845 -0
  78. package/server/execution_recovery_status.py +195 -0
  79. package/server/execution_service.py +387 -0
  80. package/server/execution_transport.py +622 -0
  81. package/server/execution_uninstall.py +406 -0
  82. package/server/execution_update_status.py +49 -0
  83. package/server/install.sh +7074 -0
  84. package/server/interactive_chat_controls.py +153 -0
  85. package/server/interactive_chat_native.py +66 -0
  86. package/server/interactive_chat_projection.py +152 -0
  87. package/server/interactive_chat_runtime.py +132 -0
  88. package/server/interactive_chat_share_routes.py +534 -0
  89. package/server/interactive_chat_share_web.py +12 -0
  90. package/server/interactive_chat_shares.py +238 -0
  91. package/server/provider_commands.py +425 -0
  92. package/server/public_chat_share_routes.py +426 -0
  93. package/server/public_chat_shares.py +759 -0
  94. package/server/public_chat_transcript.py +357 -0
  95. package/server/pyproject.toml +29 -0
  96. package/server/release-public-key.pem +3 -0
  97. package/server/secure_peer_delivery.py +1236 -0
  98. package/server/secure_peer_runtime.py +7694 -0
  99. package/server/shared_chat_video_stream.py +262 -0
  100. package/server/shared_chat_videos.py +155 -0
  101. package/server/side_questions.py +594 -0
  102. package/server/team_hub_host.py +1354 -0
  103. package/server/team_mail_grants.py +136 -0
  104. package/server/team_mail_runtime.py +302 -0
  105. package/server/team_mail_websocket.py +211 -0
  106. package/server/title_generation.py +333 -0
  107. package/server/uninstall.sh +359 -0
  108. package/server/update_handoff.py +237 -0
  109. package/server/update_preparation.py +264 -0
  110. package/server/update_recovery.py +140 -0
  111. package/server/update_runner.py +1819 -0
  112. package/server/uv.lock +1101 -0
  113. package/server/workspace_git.py +545 -0
@@ -0,0 +1,423 @@
1
+ -- Passive Team Networks foundation. The existing channel ledger remains the
2
+ -- storage engine for one reserved Bulletin per team; addressed mailbox items
3
+ -- are deliberately separate from channels and from dispatch_requests.
4
+
5
+ CREATE TRIGGER network_agents_limit_per_server
6
+ BEFORE INSERT ON agents
7
+ FOR EACH ROW WHEN (
8
+ SELECT COUNT(*) FROM agents WHERE node_id = NEW.node_id
9
+ ) >= 256
10
+ BEGIN
11
+ SELECT RAISE(ABORT, 'network server agent limit exceeded');
12
+ END;
13
+
14
+ CREATE TABLE network_peer_bindings (
15
+ peer_id TEXT PRIMARY KEY CHECK(length(peer_id) = 36),
16
+ team_id TEXT NOT NULL,
17
+ node_id TEXT NOT NULL,
18
+ service_principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
19
+ peer_server_identity TEXT NOT NULL CHECK(length(trim(peer_server_identity)) BETWEEN 8 AND 240),
20
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'revoked')),
21
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
22
+ revoked_at INTEGER CHECK(revoked_at IS NULL OR revoked_at >= created_at),
23
+ FOREIGN KEY(team_id, node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
24
+ UNIQUE(team_id, peer_id)
25
+ );
26
+
27
+ CREATE UNIQUE INDEX network_one_active_peer_per_server
28
+ ON network_peer_bindings(team_id, node_id)
29
+ WHERE status = 'active';
30
+
31
+ CREATE TRIGGER network_peer_binding_requires_exact_identity
32
+ BEFORE INSERT ON network_peer_bindings
33
+ FOR EACH ROW WHEN NOT EXISTS (
34
+ SELECT 1
35
+ FROM nodes AS n
36
+ JOIN principals AS p ON p.id = NEW.service_principal_id
37
+ JOIN service_accounts AS s ON s.principal_id = p.id
38
+ JOIN memberships AS m
39
+ ON m.team_id = NEW.team_id AND m.principal_id = p.id
40
+ WHERE n.team_id = NEW.team_id
41
+ AND n.id = NEW.node_id
42
+ AND n.server_identity = NEW.peer_server_identity
43
+ AND n.status <> 'revoked'
44
+ AND p.kind = 'service'
45
+ AND p.status = 'active'
46
+ AND s.service_identifier = 'agentsdock.secure-peer.' || NEW.peer_id
47
+ AND m.role = 'automation'
48
+ AND m.status = 'active'
49
+ )
50
+ BEGIN
51
+ SELECT RAISE(ABORT, 'network peer binding requires exact server authority');
52
+ END;
53
+
54
+ CREATE TRIGGER network_peer_binding_identity_is_immutable
55
+ BEFORE UPDATE OF peer_id, team_id, node_id, service_principal_id,
56
+ peer_server_identity, created_at
57
+ ON network_peer_bindings
58
+ BEGIN
59
+ SELECT RAISE(ABORT, 'network peer binding identity is immutable');
60
+ END;
61
+
62
+ CREATE TRIGGER network_peer_binding_revocation_is_terminal
63
+ BEFORE UPDATE ON network_peer_bindings
64
+ FOR EACH ROW WHEN
65
+ (OLD.status = 'revoked' AND NEW.status <> 'revoked')
66
+ OR (OLD.revoked_at IS NOT NULL AND NEW.revoked_at IS NOT OLD.revoked_at)
67
+ OR (NEW.status = 'revoked' AND NEW.revoked_at IS NULL)
68
+ OR (NEW.status = 'active' AND NEW.revoked_at IS NOT NULL)
69
+ BEGIN
70
+ SELECT RAISE(ABORT, 'network peer revocation is terminal');
71
+ END;
72
+
73
+ CREATE TABLE network_boards (
74
+ team_id TEXT PRIMARY KEY REFERENCES teams(id) ON DELETE CASCADE,
75
+ channel_id TEXT NOT NULL UNIQUE,
76
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
77
+ FOREIGN KEY(team_id, channel_id) REFERENCES channels(team_id, id) ON DELETE RESTRICT
78
+ );
79
+
80
+ CREATE TRIGGER network_board_requires_shared_board
81
+ BEFORE INSERT ON network_boards
82
+ FOR EACH ROW WHEN NOT EXISTS (
83
+ SELECT 1 FROM channels AS c
84
+ WHERE c.team_id = NEW.team_id
85
+ AND c.id = NEW.channel_id
86
+ AND c.kind = 'board'
87
+ AND c.visibility = 'team'
88
+ AND c.archived_at IS NULL
89
+ )
90
+ BEGIN
91
+ SELECT RAISE(ABORT, 'network Bulletin requires one live shared board');
92
+ END;
93
+
94
+ CREATE TRIGGER network_board_binding_replacement_is_guarded
95
+ BEFORE UPDATE ON network_boards
96
+ FOR EACH ROW WHEN
97
+ NEW.team_id IS NOT OLD.team_id
98
+ OR NEW.created_at IS NOT OLD.created_at
99
+ OR (
100
+ NEW.channel_id IS NOT OLD.channel_id
101
+ AND (
102
+ EXISTS (
103
+ SELECT 1 FROM channels AS old_channel
104
+ WHERE old_channel.team_id = OLD.team_id
105
+ AND old_channel.id = OLD.channel_id
106
+ AND old_channel.kind = 'board'
107
+ AND old_channel.visibility = 'team'
108
+ AND old_channel.archived_at IS NULL
109
+ )
110
+ OR NOT EXISTS (
111
+ SELECT 1 FROM channels AS new_channel
112
+ WHERE new_channel.team_id = NEW.team_id
113
+ AND new_channel.id = NEW.channel_id
114
+ AND new_channel.kind = 'board'
115
+ AND new_channel.visibility = 'team'
116
+ AND new_channel.archived_at IS NULL
117
+ )
118
+ )
119
+ )
120
+ BEGIN
121
+ SELECT RAISE(ABORT, 'network Bulletin binding replacement is not permitted');
122
+ END;
123
+
124
+ CREATE TRIGGER network_bulletin_body_limit_on_insert
125
+ BEFORE INSERT ON messages
126
+ FOR EACH ROW WHEN EXISTS (
127
+ SELECT 1 FROM network_boards AS b
128
+ WHERE b.team_id = NEW.team_id AND b.channel_id = NEW.channel_id
129
+ ) AND NOT length(CAST(NEW.body AS BLOB)) BETWEEN 1 AND 8192
130
+ BEGIN
131
+ SELECT RAISE(ABORT, 'network Bulletin body exceeds its byte limit');
132
+ END;
133
+
134
+ CREATE TRIGGER network_bulletin_body_limit_on_update
135
+ BEFORE UPDATE OF body ON messages
136
+ FOR EACH ROW WHEN EXISTS (
137
+ SELECT 1 FROM network_boards AS b
138
+ WHERE b.team_id = NEW.team_id AND b.channel_id = NEW.channel_id
139
+ ) AND NOT length(CAST(NEW.body AS BLOB)) BETWEEN 1 AND 8192
140
+ BEGIN
141
+ SELECT RAISE(ABORT, 'network Bulletin body exceeds its byte limit');
142
+ END;
143
+
144
+ CREATE TABLE network_mailbox_items (
145
+ queue_ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
146
+ id TEXT NOT NULL UNIQUE,
147
+ team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE RESTRICT,
148
+ kind TEXT NOT NULL CHECK(kind IN ('message', 'request', 'reply')),
149
+ sender_kind TEXT NOT NULL CHECK(sender_kind IN ('human', 'server', 'agent')),
150
+ sender_principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
151
+ sender_node_id TEXT,
152
+ sender_agent_id TEXT,
153
+ recipient_kind TEXT NOT NULL CHECK(recipient_kind IN ('human', 'server', 'agent')),
154
+ recipient_principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
155
+ recipient_node_id TEXT,
156
+ recipient_agent_id TEXT,
157
+ root_request_item_id TEXT,
158
+ body_format TEXT NOT NULL DEFAULT 'markdown' CHECK(body_format IN ('plain', 'markdown')),
159
+ body TEXT NOT NULL CHECK(length(CAST(body AS BLOB)) BETWEEN 1 AND 8192),
160
+ idempotency_key BLOB NOT NULL
161
+ CHECK(typeof(idempotency_key) = 'blob' AND length(idempotency_key) = 32),
162
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
163
+ expires_at INTEGER CHECK(expires_at IS NULL OR expires_at > created_at),
164
+ FOREIGN KEY(team_id, sender_node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
165
+ FOREIGN KEY(team_id, sender_agent_id) REFERENCES agents(team_id, id) ON DELETE RESTRICT,
166
+ FOREIGN KEY(team_id, recipient_node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
167
+ FOREIGN KEY(team_id, recipient_agent_id) REFERENCES agents(team_id, id) ON DELETE RESTRICT,
168
+ FOREIGN KEY(team_id, root_request_item_id)
169
+ REFERENCES network_mailbox_items(team_id, id) ON DELETE RESTRICT,
170
+ UNIQUE(team_id, id),
171
+ UNIQUE(team_id, idempotency_key),
172
+ CHECK(
173
+ (sender_kind = 'human' AND sender_node_id IS NULL AND sender_agent_id IS NULL)
174
+ OR (sender_kind = 'server' AND sender_node_id IS NOT NULL AND sender_agent_id IS NULL)
175
+ OR (sender_kind = 'agent' AND sender_node_id IS NOT NULL AND sender_agent_id IS NOT NULL)
176
+ ),
177
+ CHECK(
178
+ (recipient_kind = 'human' AND recipient_node_id IS NULL AND recipient_agent_id IS NULL)
179
+ OR (recipient_kind = 'server' AND recipient_node_id IS NOT NULL AND recipient_agent_id IS NULL)
180
+ OR (recipient_kind = 'agent' AND recipient_node_id IS NOT NULL AND recipient_agent_id IS NOT NULL)
181
+ ),
182
+ CHECK(
183
+ (kind IN ('message', 'request') AND root_request_item_id IS NULL)
184
+ OR (kind = 'reply' AND root_request_item_id IS NOT NULL)
185
+ ),
186
+ CHECK(kind = 'request' OR expires_at IS NULL)
187
+ );
188
+
189
+ CREATE UNIQUE INDEX network_one_reply_per_request
190
+ ON network_mailbox_items(root_request_item_id)
191
+ WHERE kind = 'reply';
192
+
193
+ CREATE INDEX network_mailbox_recipient_fifo
194
+ ON network_mailbox_items(
195
+ team_id, recipient_kind, recipient_node_id, recipient_agent_id, queue_ordinal
196
+ );
197
+
198
+ CREATE TRIGGER network_mailbox_sender_is_authorized
199
+ BEFORE INSERT ON network_mailbox_items
200
+ FOR EACH ROW WHEN NOT (
201
+ (
202
+ NEW.sender_kind = 'human'
203
+ AND EXISTS (
204
+ SELECT 1
205
+ FROM principals AS p
206
+ JOIN memberships AS m
207
+ ON m.team_id = NEW.team_id AND m.principal_id = p.id
208
+ WHERE p.id = NEW.sender_principal_id
209
+ AND p.kind = 'human'
210
+ AND p.status = 'active'
211
+ AND m.status = 'active'
212
+ )
213
+ )
214
+ OR (
215
+ NEW.sender_kind IN ('server', 'agent')
216
+ AND EXISTS (
217
+ SELECT 1
218
+ FROM network_peer_bindings AS b
219
+ JOIN nodes AS n
220
+ ON n.team_id = b.team_id AND n.id = b.node_id
221
+ WHERE b.team_id = NEW.team_id
222
+ AND b.node_id = NEW.sender_node_id
223
+ AND b.service_principal_id = NEW.sender_principal_id
224
+ AND b.status = 'active'
225
+ AND n.status = 'active'
226
+ )
227
+ )
228
+ )
229
+ BEGIN
230
+ SELECT RAISE(ABORT, 'network mailbox sender is not authorized');
231
+ END;
232
+
233
+ CREATE TRIGGER network_mailbox_sender_agent_matches_server
234
+ BEFORE INSERT ON network_mailbox_items
235
+ FOR EACH ROW WHEN NEW.sender_kind = 'agent' AND NOT EXISTS (
236
+ SELECT 1 FROM agents AS a
237
+ JOIN principals AS p ON p.id = a.principal_id
238
+ WHERE a.team_id = NEW.team_id
239
+ AND a.node_id = NEW.sender_node_id
240
+ AND a.id = NEW.sender_agent_id
241
+ AND a.status = 'active'
242
+ AND p.status = 'active'
243
+ )
244
+ BEGIN
245
+ SELECT RAISE(ABORT, 'network mailbox sender agent does not belong to server');
246
+ END;
247
+
248
+ CREATE TRIGGER network_mailbox_recipient_is_available
249
+ BEFORE INSERT ON network_mailbox_items
250
+ FOR EACH ROW WHEN NOT (
251
+ (
252
+ NEW.recipient_kind = 'human'
253
+ AND EXISTS (
254
+ SELECT 1 FROM principals AS p
255
+ JOIN memberships AS m
256
+ ON m.team_id = NEW.team_id AND m.principal_id = p.id
257
+ WHERE p.id = NEW.recipient_principal_id
258
+ AND p.kind = 'human'
259
+ AND p.status = 'active'
260
+ AND m.status = 'active'
261
+ )
262
+ )
263
+ OR (
264
+ NEW.recipient_kind = 'server'
265
+ AND EXISTS (
266
+ SELECT 1 FROM nodes AS n
267
+ WHERE n.team_id = NEW.team_id
268
+ AND n.id = NEW.recipient_node_id
269
+ AND n.principal_id = NEW.recipient_principal_id
270
+ AND n.status = 'active'
271
+ )
272
+ )
273
+ OR (
274
+ NEW.recipient_kind = 'agent'
275
+ AND EXISTS (
276
+ SELECT 1 FROM agents AS a
277
+ JOIN principals AS p ON p.id = a.principal_id
278
+ JOIN nodes AS n ON n.team_id = a.team_id AND n.id = a.node_id
279
+ WHERE a.team_id = NEW.team_id
280
+ AND a.node_id = NEW.recipient_node_id
281
+ AND a.id = NEW.recipient_agent_id
282
+ AND a.principal_id = NEW.recipient_principal_id
283
+ AND a.status = 'active'
284
+ AND p.status = 'active'
285
+ AND n.status = 'active'
286
+ )
287
+ )
288
+ )
289
+ BEGIN
290
+ SELECT RAISE(ABORT, 'network mailbox recipient is unavailable');
291
+ END;
292
+
293
+ CREATE TRIGGER network_mailbox_reply_requires_request
294
+ BEFORE INSERT ON network_mailbox_items
295
+ FOR EACH ROW WHEN NEW.kind = 'reply' AND NOT EXISTS (
296
+ SELECT 1 FROM network_mailbox_items AS request_item
297
+ WHERE request_item.team_id = NEW.team_id
298
+ AND request_item.id = NEW.root_request_item_id
299
+ AND request_item.kind = 'request'
300
+ )
301
+ BEGIN
302
+ SELECT RAISE(ABORT, 'network reply requires an exact passive request');
303
+ END;
304
+
305
+ CREATE TRIGGER network_mailbox_authority_is_immutable
306
+ BEFORE UPDATE ON network_mailbox_items
307
+ BEGIN
308
+ SELECT RAISE(ABORT, 'network mailbox items are immutable');
309
+ END;
310
+
311
+ CREATE TRIGGER network_mailbox_items_cannot_be_deleted
312
+ BEFORE DELETE ON network_mailbox_items
313
+ BEGIN
314
+ SELECT RAISE(ABORT, 'network mailbox items cannot be deleted');
315
+ END;
316
+
317
+ CREATE TABLE network_deliveries (
318
+ id TEXT PRIMARY KEY,
319
+ team_id TEXT NOT NULL,
320
+ item_id TEXT NOT NULL UNIQUE,
321
+ state TEXT NOT NULL DEFAULT 'available'
322
+ CHECK(state IN ('available', 'delivered', 'read')),
323
+ available_at INTEGER NOT NULL CHECK(available_at >= 0),
324
+ delivered_at INTEGER CHECK(delivered_at IS NULL OR delivered_at >= available_at),
325
+ read_at INTEGER CHECK(read_at IS NULL OR read_at >= delivered_at),
326
+ FOREIGN KEY(team_id, item_id)
327
+ REFERENCES network_mailbox_items(team_id, id) ON DELETE RESTRICT,
328
+ UNIQUE(team_id, id),
329
+ CHECK(
330
+ (state = 'available' AND delivered_at IS NULL AND read_at IS NULL)
331
+ OR (state = 'delivered' AND delivered_at IS NOT NULL AND read_at IS NULL)
332
+ OR (state = 'read' AND delivered_at IS NOT NULL AND read_at IS NOT NULL)
333
+ )
334
+ );
335
+
336
+ CREATE TRIGGER network_delivery_identity_is_immutable
337
+ BEFORE UPDATE OF id, team_id, item_id, available_at ON network_deliveries
338
+ BEGIN
339
+ SELECT RAISE(ABORT, 'network delivery identity is immutable');
340
+ END;
341
+
342
+ CREATE TRIGGER network_delivery_state_is_monotonic
343
+ BEFORE UPDATE ON network_deliveries
344
+ FOR EACH ROW WHEN
345
+ (OLD.state = 'delivered' AND NEW.state = 'available')
346
+ OR (OLD.state = 'read' AND NEW.state <> 'read')
347
+ OR (OLD.delivered_at IS NOT NULL AND NEW.delivered_at IS NOT OLD.delivered_at)
348
+ OR (OLD.read_at IS NOT NULL AND NEW.read_at IS NOT OLD.read_at)
349
+ BEGIN
350
+ SELECT RAISE(ABORT, 'network delivery receipts are monotonic');
351
+ END;
352
+
353
+ CREATE TRIGGER network_deliveries_cannot_be_deleted
354
+ BEFORE DELETE ON network_deliveries
355
+ BEGIN
356
+ SELECT RAISE(ABORT, 'network deliveries cannot be deleted');
357
+ END;
358
+
359
+ CREATE TABLE network_passive_requests (
360
+ request_item_id TEXT PRIMARY KEY,
361
+ team_id TEXT NOT NULL,
362
+ status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open', 'replied', 'expired')),
363
+ reply_item_id TEXT UNIQUE,
364
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
365
+ expires_at INTEGER NOT NULL CHECK(expires_at > created_at),
366
+ replied_at INTEGER CHECK(replied_at IS NULL OR replied_at >= created_at),
367
+ FOREIGN KEY(team_id, request_item_id)
368
+ REFERENCES network_mailbox_items(team_id, id) ON DELETE RESTRICT,
369
+ FOREIGN KEY(team_id, reply_item_id)
370
+ REFERENCES network_mailbox_items(team_id, id) ON DELETE RESTRICT,
371
+ UNIQUE(team_id, request_item_id),
372
+ CHECK(
373
+ (status = 'open' AND reply_item_id IS NULL AND replied_at IS NULL)
374
+ OR (status = 'replied' AND reply_item_id IS NOT NULL AND replied_at IS NOT NULL)
375
+ OR (status = 'expired' AND reply_item_id IS NULL AND replied_at IS NULL)
376
+ )
377
+ );
378
+
379
+ CREATE TRIGGER network_request_requires_request_item
380
+ BEFORE INSERT ON network_passive_requests
381
+ FOR EACH ROW WHEN NOT EXISTS (
382
+ SELECT 1 FROM network_mailbox_items AS item
383
+ WHERE item.team_id = NEW.team_id
384
+ AND item.id = NEW.request_item_id
385
+ AND item.kind = 'request'
386
+ AND item.created_at = NEW.created_at
387
+ AND item.expires_at = NEW.expires_at
388
+ )
389
+ BEGIN
390
+ SELECT RAISE(ABORT, 'passive request metadata requires its exact item');
391
+ END;
392
+
393
+ CREATE TRIGGER network_request_reply_is_exact
394
+ BEFORE UPDATE OF status, reply_item_id, replied_at ON network_passive_requests
395
+ FOR EACH ROW WHEN NEW.status = 'replied' AND NOT EXISTS (
396
+ SELECT 1 FROM network_mailbox_items AS reply
397
+ WHERE reply.team_id = NEW.team_id
398
+ AND reply.id = NEW.reply_item_id
399
+ AND reply.kind = 'reply'
400
+ AND reply.root_request_item_id = NEW.request_item_id
401
+ )
402
+ BEGIN
403
+ SELECT RAISE(ABORT, 'passive request reply does not match');
404
+ END;
405
+
406
+ CREATE TRIGGER network_request_state_is_forward_only
407
+ BEFORE UPDATE ON network_passive_requests
408
+ FOR EACH ROW WHEN
409
+ OLD.status <> 'open'
410
+ OR NEW.status NOT IN ('replied', 'expired')
411
+ OR NEW.request_item_id IS NOT OLD.request_item_id
412
+ OR NEW.team_id IS NOT OLD.team_id
413
+ OR NEW.created_at IS NOT OLD.created_at
414
+ OR NEW.expires_at IS NOT OLD.expires_at
415
+ BEGIN
416
+ SELECT RAISE(ABORT, 'passive request state is forward-only');
417
+ END;
418
+
419
+ CREATE TRIGGER network_requests_cannot_be_deleted
420
+ BEFORE DELETE ON network_passive_requests
421
+ BEGIN
422
+ SELECT RAISE(ABORT, 'passive requests cannot be deleted');
423
+ END;
@@ -0,0 +1,63 @@
1
+ -- Permit the process-local Team Hub control principal to author passive mail
2
+ -- only as the immutable managed host node. It still cannot authenticate over
3
+ -- HTTP, claim another server, or bypass recipient/agent ownership triggers.
4
+
5
+ DROP TRIGGER network_mailbox_sender_is_authorized;
6
+
7
+ CREATE TRIGGER network_mailbox_sender_is_authorized
8
+ BEFORE INSERT ON network_mailbox_items
9
+ FOR EACH ROW WHEN NOT (
10
+ (
11
+ NEW.sender_kind = 'human'
12
+ AND EXISTS (
13
+ SELECT 1
14
+ FROM principals AS p
15
+ JOIN memberships AS m
16
+ ON m.team_id = NEW.team_id AND m.principal_id = p.id
17
+ WHERE p.id = NEW.sender_principal_id
18
+ AND p.kind = 'human'
19
+ AND p.status = 'active'
20
+ AND m.status = 'active'
21
+ )
22
+ )
23
+ OR (
24
+ NEW.sender_kind IN ('server', 'agent')
25
+ AND (
26
+ EXISTS (
27
+ SELECT 1
28
+ FROM network_peer_bindings AS b
29
+ JOIN nodes AS n
30
+ ON n.team_id = b.team_id AND n.id = b.node_id
31
+ WHERE b.team_id = NEW.team_id
32
+ AND b.node_id = NEW.sender_node_id
33
+ AND b.service_principal_id = NEW.sender_principal_id
34
+ AND b.status = 'active'
35
+ AND n.status = 'active'
36
+ )
37
+ OR EXISTS (
38
+ SELECT 1
39
+ FROM managed_host_bindings AS managed
40
+ JOIN nodes AS n
41
+ ON n.team_id = NEW.team_id
42
+ AND n.id = NEW.sender_node_id
43
+ AND n.server_identity = managed.server_identity
44
+ JOIN principals AS p ON p.id = NEW.sender_principal_id
45
+ JOIN service_accounts AS s ON s.principal_id = p.id
46
+ JOIN memberships AS m
47
+ ON m.team_id = NEW.team_id
48
+ AND m.principal_id = p.id
49
+ WHERE managed.singleton = 1
50
+ AND NEW.sender_principal_id = 'service_local_control'
51
+ AND n.status = 'active'
52
+ AND p.kind = 'service'
53
+ AND p.status = 'active'
54
+ AND s.service_identifier = 'agentsdock.team-hub.local-control'
55
+ AND m.role = 'automation'
56
+ AND m.status = 'active'
57
+ )
58
+ )
59
+ )
60
+ )
61
+ BEGIN
62
+ SELECT RAISE(ABORT, 'network mailbox sender is not authorized');
63
+ END;
@@ -0,0 +1,73 @@
1
+ -- Permit both process-local managed-host actors to author passive mail as the
2
+ -- immutable host node. The interactive managed-server session remains a
3
+ -- distinct service principal from local agent mail and recovery control.
4
+
5
+ DROP TRIGGER network_mailbox_sender_is_authorized;
6
+
7
+ CREATE TRIGGER network_mailbox_sender_is_authorized
8
+ BEFORE INSERT ON network_mailbox_items
9
+ FOR EACH ROW WHEN NOT (
10
+ (
11
+ NEW.sender_kind = 'human'
12
+ AND EXISTS (
13
+ SELECT 1
14
+ FROM principals AS p
15
+ JOIN memberships AS m
16
+ ON m.team_id = NEW.team_id AND m.principal_id = p.id
17
+ WHERE p.id = NEW.sender_principal_id
18
+ AND p.kind = 'human'
19
+ AND p.status = 'active'
20
+ AND m.status = 'active'
21
+ )
22
+ )
23
+ OR (
24
+ NEW.sender_kind IN ('server', 'agent')
25
+ AND (
26
+ EXISTS (
27
+ SELECT 1
28
+ FROM network_peer_bindings AS b
29
+ JOIN nodes AS n
30
+ ON n.team_id = b.team_id AND n.id = b.node_id
31
+ WHERE b.team_id = NEW.team_id
32
+ AND b.node_id = NEW.sender_node_id
33
+ AND b.service_principal_id = NEW.sender_principal_id
34
+ AND b.status = 'active'
35
+ AND n.status = 'active'
36
+ )
37
+ OR EXISTS (
38
+ SELECT 1
39
+ FROM managed_host_bindings AS managed
40
+ JOIN nodes AS n
41
+ ON n.team_id = NEW.team_id
42
+ AND n.id = NEW.sender_node_id
43
+ AND n.server_identity = managed.server_identity
44
+ JOIN principals AS p ON p.id = NEW.sender_principal_id
45
+ JOIN service_accounts AS s ON s.principal_id = p.id
46
+ JOIN memberships AS m
47
+ ON m.team_id = NEW.team_id
48
+ AND m.principal_id = p.id
49
+ WHERE managed.singleton = 1
50
+ AND (
51
+ (
52
+ NEW.sender_principal_id = 'service_local_control'
53
+ AND s.service_identifier =
54
+ 'agentsdock.team-hub.local-control'
55
+ )
56
+ OR (
57
+ NEW.sender_principal_id = 'service_managed_server'
58
+ AND s.service_identifier =
59
+ 'agentsdock.team-hub.managed-server'
60
+ )
61
+ )
62
+ AND n.status = 'active'
63
+ AND p.kind = 'service'
64
+ AND p.status = 'active'
65
+ AND m.role = 'automation'
66
+ AND m.status = 'active'
67
+ )
68
+ )
69
+ )
70
+ )
71
+ BEGIN
72
+ SELECT RAISE(ABORT, 'network mailbox sender is not authorized');
73
+ END;