@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,690 @@
1
+ -- Team Hub identity and authentication roots. Secret material is represented
2
+ -- only by one-way hashes; node private keys and AgentsServer bearers never
3
+ -- belong in this database.
4
+
5
+ CREATE TABLE principals (
6
+ id TEXT PRIMARY KEY,
7
+ kind TEXT NOT NULL CHECK(kind IN ('human', 'service', 'node', 'agent', 'chat')),
8
+ scope_team_id TEXT,
9
+ display_name TEXT NOT NULL CHECK(length(trim(display_name)) BETWEEN 1 AND 160),
10
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'suspended', 'revoked')),
11
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
12
+ updated_at INTEGER NOT NULL CHECK(updated_at >= created_at),
13
+ UNIQUE(scope_team_id, id),
14
+ CHECK(
15
+ (kind IN ('human', 'service') AND scope_team_id IS NULL)
16
+ OR (kind IN ('node', 'agent', 'chat') AND scope_team_id IS NOT NULL)
17
+ ),
18
+ FOREIGN KEY(scope_team_id) REFERENCES teams(id) ON DELETE CASCADE
19
+ );
20
+
21
+ CREATE TRIGGER principals_scope_is_immutable
22
+ BEFORE UPDATE OF id, kind, scope_team_id, created_at ON principals
23
+ BEGIN
24
+ SELECT RAISE(ABORT, 'principal identity and scope are immutable');
25
+ END;
26
+
27
+ CREATE TRIGGER principal_revocation_is_terminal
28
+ BEFORE UPDATE OF status ON principals
29
+ FOR EACH ROW WHEN OLD.status = 'revoked' AND NEW.status <> 'revoked'
30
+ BEGIN
31
+ SELECT RAISE(ABORT, 'principal revocation is terminal');
32
+ END;
33
+
34
+ CREATE TABLE human_accounts (
35
+ principal_id TEXT PRIMARY KEY REFERENCES principals(id) ON DELETE RESTRICT,
36
+ email_normalized TEXT NOT NULL UNIQUE
37
+ CHECK(email_normalized = lower(trim(email_normalized)))
38
+ CHECK(length(email_normalized) BETWEEN 3 AND 320),
39
+ created_at INTEGER NOT NULL CHECK(created_at >= 0)
40
+ );
41
+
42
+ CREATE TRIGGER human_accounts_require_human_principal
43
+ BEFORE INSERT ON human_accounts
44
+ FOR EACH ROW WHEN COALESCE(
45
+ (SELECT kind FROM principals WHERE id = NEW.principal_id), ''
46
+ ) <> 'human'
47
+ BEGIN
48
+ SELECT RAISE(ABORT, 'human account requires a human principal');
49
+ END;
50
+
51
+ CREATE TRIGGER human_account_identity_is_immutable
52
+ BEFORE UPDATE OF principal_id, email_normalized, created_at ON human_accounts
53
+ BEGIN
54
+ SELECT RAISE(ABORT, 'human account identity is immutable');
55
+ END;
56
+
57
+ CREATE TABLE service_accounts (
58
+ principal_id TEXT PRIMARY KEY REFERENCES principals(id) ON DELETE RESTRICT,
59
+ service_identifier TEXT NOT NULL UNIQUE
60
+ CHECK(length(service_identifier) BETWEEN 1 AND 160),
61
+ created_at INTEGER NOT NULL CHECK(created_at >= 0)
62
+ );
63
+
64
+ CREATE TRIGGER service_accounts_require_service_principal
65
+ BEFORE INSERT ON service_accounts
66
+ FOR EACH ROW WHEN COALESCE(
67
+ (SELECT kind FROM principals WHERE id = NEW.principal_id), ''
68
+ ) <> 'service'
69
+ BEGIN
70
+ SELECT RAISE(ABORT, 'service account requires a service principal');
71
+ END;
72
+
73
+ CREATE TRIGGER service_account_identity_is_immutable
74
+ BEFORE UPDATE OF principal_id, service_identifier, created_at ON service_accounts
75
+ BEGIN
76
+ SELECT RAISE(ABORT, 'service account identity is immutable');
77
+ END;
78
+
79
+ CREATE TABLE teams (
80
+ id TEXT PRIMARY KEY,
81
+ kind TEXT NOT NULL CHECK(kind IN ('personal', 'shared')),
82
+ slug TEXT NOT NULL UNIQUE
83
+ CHECK(slug = lower(trim(slug)))
84
+ CHECK(length(slug) BETWEEN 3 AND 80),
85
+ display_name TEXT NOT NULL CHECK(length(trim(display_name)) BETWEEN 1 AND 160),
86
+ personal_owner_principal_id TEXT REFERENCES human_accounts(principal_id) ON DELETE RESTRICT,
87
+ retention_days INTEGER NOT NULL DEFAULT 365 CHECK(retention_days BETWEEN 1 AND 3650),
88
+ created_by_principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
89
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
90
+ updated_at INTEGER NOT NULL CHECK(updated_at >= created_at),
91
+ UNIQUE(personal_owner_principal_id),
92
+ UNIQUE(id, personal_owner_principal_id),
93
+ CHECK(
94
+ (kind = 'personal' AND personal_owner_principal_id IS NOT NULL)
95
+ OR (kind = 'shared' AND personal_owner_principal_id IS NULL)
96
+ )
97
+ );
98
+
99
+ CREATE TRIGGER team_identity_is_immutable
100
+ BEFORE UPDATE OF id, kind, slug, personal_owner_principal_id,
101
+ created_by_principal_id, created_at
102
+ ON teams
103
+ BEGIN
104
+ SELECT RAISE(ABORT, 'team identity and personal owner are immutable');
105
+ END;
106
+
107
+ CREATE TABLE memberships (
108
+ id TEXT PRIMARY KEY,
109
+ team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
110
+ principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
111
+ role TEXT NOT NULL CHECK(role IN ('owner', 'admin', 'member', 'guest', 'automation')),
112
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'suspended', 'revoked')),
113
+ invited_by_principal_id TEXT REFERENCES principals(id) ON DELETE SET NULL,
114
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
115
+ updated_at INTEGER NOT NULL CHECK(updated_at >= created_at),
116
+ UNIQUE(team_id, principal_id),
117
+ UNIQUE(team_id, id)
118
+ );
119
+
120
+ CREATE UNIQUE INDEX memberships_one_active_owner_per_team
121
+ ON memberships(team_id)
122
+ WHERE role = 'owner' AND status = 'active';
123
+
124
+ CREATE TRIGGER memberships_require_compatible_principal
125
+ BEFORE INSERT ON memberships
126
+ FOR EACH ROW WHEN
127
+ (NEW.role = 'automation' AND COALESCE(
128
+ (SELECT kind FROM principals WHERE id = NEW.principal_id), ''
129
+ ) <> 'service')
130
+ OR
131
+ (NEW.role <> 'automation' AND COALESCE(
132
+ (SELECT kind FROM principals WHERE id = NEW.principal_id), ''
133
+ ) <> 'human')
134
+ BEGIN
135
+ SELECT RAISE(ABORT, 'membership role is incompatible with principal kind');
136
+ END;
137
+
138
+ CREATE TRIGGER memberships_update_requires_compatible_principal
139
+ BEFORE UPDATE OF principal_id, role ON memberships
140
+ FOR EACH ROW WHEN
141
+ (NEW.role = 'automation' AND COALESCE(
142
+ (SELECT kind FROM principals WHERE id = NEW.principal_id), ''
143
+ ) <> 'service')
144
+ OR
145
+ (NEW.role <> 'automation' AND COALESCE(
146
+ (SELECT kind FROM principals WHERE id = NEW.principal_id), ''
147
+ ) <> 'human')
148
+ BEGIN
149
+ SELECT RAISE(ABORT, 'membership role is incompatible with principal kind');
150
+ END;
151
+
152
+ CREATE TRIGGER memberships_identity_is_immutable
153
+ BEFORE UPDATE OF id, team_id, principal_id, invited_by_principal_id, created_at
154
+ ON memberships
155
+ BEGIN
156
+ SELECT RAISE(ABORT, 'membership identity is immutable');
157
+ END;
158
+
159
+ CREATE TRIGGER membership_revocation_is_terminal
160
+ BEFORE UPDATE OF status ON memberships
161
+ FOR EACH ROW WHEN OLD.status = 'revoked' AND NEW.status <> 'revoked'
162
+ BEGIN
163
+ SELECT RAISE(ABORT, 'membership revocation is terminal');
164
+ END;
165
+
166
+ CREATE TRIGGER memberships_cannot_be_deleted
167
+ BEFORE DELETE ON memberships
168
+ BEGIN
169
+ SELECT RAISE(ABORT, 'membership ledger rows cannot be deleted');
170
+ END;
171
+
172
+ CREATE TABLE invitations (
173
+ id TEXT PRIMARY KEY,
174
+ team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
175
+ token_hash BLOB NOT NULL UNIQUE
176
+ CHECK(typeof(token_hash) = 'blob' AND length(token_hash) = 32),
177
+ invitee_email_normalized TEXT
178
+ CHECK(invitee_email_normalized IS NULL OR (
179
+ invitee_email_normalized = lower(trim(invitee_email_normalized))
180
+ AND length(invitee_email_normalized) BETWEEN 3 AND 320
181
+ )),
182
+ role TEXT NOT NULL CHECK(role IN ('admin', 'member', 'guest')),
183
+ issued_by_principal_id TEXT NOT NULL,
184
+ expires_at INTEGER NOT NULL CHECK(expires_at >= 0),
185
+ created_at INTEGER NOT NULL CHECK(created_at >= 0 AND expires_at > created_at),
186
+ redeemed_at INTEGER CHECK(redeemed_at IS NULL OR redeemed_at >= created_at),
187
+ redeemed_by_principal_id TEXT REFERENCES human_accounts(principal_id) ON DELETE RESTRICT,
188
+ revoked_at INTEGER CHECK(revoked_at IS NULL OR revoked_at >= created_at),
189
+ FOREIGN KEY(team_id, issued_by_principal_id)
190
+ REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
191
+ CHECK((redeemed_at IS NULL) = (redeemed_by_principal_id IS NULL)),
192
+ CHECK(redeemed_at IS NULL OR revoked_at IS NULL)
193
+ );
194
+
195
+ CREATE TRIGGER invitation_authority_is_immutable
196
+ BEFORE UPDATE OF id, team_id, token_hash, invitee_email_normalized,
197
+ role, issued_by_principal_id, expires_at, created_at
198
+ ON invitations
199
+ BEGIN
200
+ SELECT RAISE(ABORT, 'invitation authority is immutable');
201
+ END;
202
+
203
+ CREATE TRIGGER invitation_terminal_state_is_one_way
204
+ BEFORE UPDATE ON invitations
205
+ FOR EACH ROW WHEN
206
+ (
207
+ OLD.redeemed_at IS NOT NULL
208
+ AND (
209
+ NEW.redeemed_at IS NOT OLD.redeemed_at
210
+ OR NEW.redeemed_by_principal_id IS NOT OLD.redeemed_by_principal_id
211
+ )
212
+ )
213
+ OR (OLD.revoked_at IS NOT NULL AND NEW.revoked_at IS NOT OLD.revoked_at)
214
+ BEGIN
215
+ SELECT RAISE(ABORT, 'invitation terminal state is one-way');
216
+ END;
217
+
218
+ CREATE TRIGGER invitations_cannot_be_deleted
219
+ BEFORE DELETE ON invitations
220
+ BEGIN
221
+ SELECT RAISE(ABORT, 'invitation ledger rows cannot be deleted');
222
+ END;
223
+
224
+ CREATE INDEX invitations_team_expiry
225
+ ON invitations(team_id, expires_at)
226
+ WHERE redeemed_at IS NULL AND revoked_at IS NULL;
227
+
228
+ CREATE TABLE device_sessions (
229
+ id TEXT PRIMARY KEY,
230
+ human_principal_id TEXT NOT NULL REFERENCES human_accounts(principal_id) ON DELETE CASCADE,
231
+ device_label TEXT NOT NULL CHECK(length(trim(device_label)) BETWEEN 1 AND 160),
232
+ device_public_key TEXT CHECK(device_public_key IS NULL OR length(device_public_key) BETWEEN 32 AND 16384),
233
+ refresh_generation INTEGER NOT NULL DEFAULT 0 CHECK(refresh_generation >= 0),
234
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
235
+ last_seen_at INTEGER NOT NULL CHECK(last_seen_at >= created_at),
236
+ expires_at INTEGER NOT NULL CHECK(expires_at > created_at),
237
+ revoked_at INTEGER CHECK(revoked_at IS NULL OR revoked_at >= created_at)
238
+ );
239
+
240
+ CREATE TRIGGER device_session_authority_is_immutable
241
+ BEFORE UPDATE OF id, human_principal_id, device_public_key, created_at, expires_at
242
+ ON device_sessions
243
+ BEGIN
244
+ SELECT RAISE(ABORT, 'device session authority is immutable');
245
+ END;
246
+
247
+ CREATE TRIGGER device_session_counters_are_monotonic
248
+ BEFORE UPDATE ON device_sessions
249
+ FOR EACH ROW WHEN
250
+ NEW.refresh_generation < OLD.refresh_generation
251
+ OR NEW.last_seen_at < OLD.last_seen_at
252
+ OR (OLD.revoked_at IS NOT NULL AND NEW.revoked_at IS NOT OLD.revoked_at)
253
+ BEGIN
254
+ SELECT RAISE(ABORT, 'device session counters and revocation are monotonic');
255
+ END;
256
+
257
+ CREATE TRIGGER device_sessions_cannot_be_deleted
258
+ BEFORE DELETE ON device_sessions
259
+ BEGIN
260
+ SELECT RAISE(ABORT, 'device session ledger rows cannot be deleted');
261
+ END;
262
+
263
+ CREATE TABLE refresh_tokens (
264
+ id TEXT PRIMARY KEY,
265
+ device_session_id TEXT NOT NULL REFERENCES device_sessions(id) ON DELETE CASCADE,
266
+ token_hash BLOB NOT NULL UNIQUE
267
+ CHECK(typeof(token_hash) = 'blob' AND length(token_hash) = 32),
268
+ generation INTEGER NOT NULL CHECK(generation >= 0),
269
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
270
+ expires_at INTEGER NOT NULL CHECK(expires_at > created_at),
271
+ consumed_at INTEGER CHECK(consumed_at IS NULL OR consumed_at >= created_at),
272
+ replaced_by_token_id TEXT,
273
+ revoked_at INTEGER CHECK(revoked_at IS NULL OR revoked_at >= created_at),
274
+ UNIQUE(device_session_id, generation),
275
+ UNIQUE(device_session_id, id),
276
+ FOREIGN KEY(device_session_id, replaced_by_token_id)
277
+ REFERENCES refresh_tokens(device_session_id, id) ON DELETE RESTRICT,
278
+ CHECK((consumed_at IS NULL) = (replaced_by_token_id IS NULL)),
279
+ CHECK(consumed_at IS NULL OR revoked_at IS NULL)
280
+ );
281
+
282
+ CREATE TRIGGER refresh_token_authority_is_immutable
283
+ BEFORE UPDATE OF id, device_session_id, token_hash, generation, created_at, expires_at
284
+ ON refresh_tokens
285
+ BEGIN
286
+ SELECT RAISE(ABORT, 'refresh token authority is immutable');
287
+ END;
288
+
289
+ CREATE TRIGGER refresh_token_terminal_state_is_one_way
290
+ BEFORE UPDATE ON refresh_tokens
291
+ FOR EACH ROW WHEN
292
+ (
293
+ OLD.consumed_at IS NOT NULL
294
+ AND (
295
+ NEW.consumed_at IS NOT OLD.consumed_at
296
+ OR NEW.replaced_by_token_id IS NOT OLD.replaced_by_token_id
297
+ )
298
+ )
299
+ OR (OLD.revoked_at IS NOT NULL AND NEW.revoked_at IS NOT OLD.revoked_at)
300
+ BEGIN
301
+ SELECT RAISE(ABORT, 'refresh token terminal state is one-way');
302
+ END;
303
+
304
+ CREATE TRIGGER refresh_token_rotation_stays_in_session
305
+ BEFORE UPDATE OF consumed_at, replaced_by_token_id ON refresh_tokens
306
+ FOR EACH ROW WHEN NEW.consumed_at IS NOT NULL AND NOT EXISTS (
307
+ SELECT 1 FROM refresh_tokens AS replacement
308
+ WHERE replacement.device_session_id = OLD.device_session_id
309
+ AND replacement.id = NEW.replaced_by_token_id
310
+ AND replacement.generation = OLD.generation + 1
311
+ AND replacement.consumed_at IS NULL
312
+ AND replacement.revoked_at IS NULL
313
+ )
314
+ BEGIN
315
+ SELECT RAISE(ABORT, 'refresh token rotation requires the next token in this session');
316
+ END;
317
+
318
+ CREATE TRIGGER refresh_tokens_cannot_be_deleted
319
+ BEFORE DELETE ON refresh_tokens
320
+ BEGIN
321
+ SELECT RAISE(ABORT, 'refresh token ledger rows cannot be deleted');
322
+ END;
323
+
324
+ CREATE TABLE access_token_revocations (
325
+ jti_hash BLOB PRIMARY KEY
326
+ CHECK(typeof(jti_hash) = 'blob' AND length(jti_hash) = 32),
327
+ device_session_id TEXT NOT NULL REFERENCES device_sessions(id) ON DELETE CASCADE,
328
+ expires_at INTEGER NOT NULL CHECK(expires_at >= 0),
329
+ revoked_at INTEGER NOT NULL CHECK(revoked_at >= 0),
330
+ reason TEXT NOT NULL CHECK(length(trim(reason)) BETWEEN 1 AND 240)
331
+ );
332
+
333
+ CREATE TRIGGER access_token_revocations_are_immutable_update
334
+ BEFORE UPDATE ON access_token_revocations
335
+ BEGIN
336
+ SELECT RAISE(ABORT, 'access token revocations are immutable');
337
+ END;
338
+
339
+ CREATE TRIGGER access_token_revocations_are_immutable_delete
340
+ BEFORE DELETE ON access_token_revocations
341
+ BEGIN
342
+ SELECT RAISE(ABORT, 'access token revocations are immutable');
343
+ END;
344
+
345
+ CREATE TABLE node_enrollment_grants (
346
+ id TEXT PRIMARY KEY,
347
+ team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
348
+ token_hash BLOB NOT NULL UNIQUE
349
+ CHECK(typeof(token_hash) = 'blob' AND length(token_hash) = 32),
350
+ issued_by_principal_id TEXT NOT NULL,
351
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
352
+ expires_at INTEGER NOT NULL CHECK(expires_at > created_at),
353
+ consumed_at INTEGER CHECK(consumed_at IS NULL OR consumed_at >= created_at),
354
+ consumed_by_node_id TEXT,
355
+ revoked_at INTEGER CHECK(revoked_at IS NULL OR revoked_at >= created_at),
356
+ FOREIGN KEY(team_id, issued_by_principal_id)
357
+ REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
358
+ FOREIGN KEY(team_id, consumed_by_node_id)
359
+ REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
360
+ CHECK((consumed_at IS NULL) = (consumed_by_node_id IS NULL)),
361
+ CHECK(consumed_at IS NULL OR revoked_at IS NULL)
362
+ );
363
+
364
+ CREATE TRIGGER node_enrollment_authority_is_immutable
365
+ BEFORE UPDATE OF id, team_id, token_hash, issued_by_principal_id,
366
+ created_at, expires_at
367
+ ON node_enrollment_grants
368
+ BEGIN
369
+ SELECT RAISE(ABORT, 'node enrollment authority is immutable');
370
+ END;
371
+
372
+ CREATE TRIGGER node_enrollment_terminal_state_is_one_way
373
+ BEFORE UPDATE ON node_enrollment_grants
374
+ FOR EACH ROW WHEN
375
+ (
376
+ OLD.consumed_at IS NOT NULL
377
+ AND (
378
+ NEW.consumed_at IS NOT OLD.consumed_at
379
+ OR NEW.consumed_by_node_id IS NOT OLD.consumed_by_node_id
380
+ )
381
+ )
382
+ OR (OLD.revoked_at IS NOT NULL AND NEW.revoked_at IS NOT OLD.revoked_at)
383
+ BEGIN
384
+ SELECT RAISE(ABORT, 'node enrollment terminal state is one-way');
385
+ END;
386
+
387
+ CREATE TRIGGER node_enrollment_grants_cannot_be_deleted
388
+ BEFORE DELETE ON node_enrollment_grants
389
+ BEGIN
390
+ SELECT RAISE(ABORT, 'node enrollment ledger rows cannot be deleted');
391
+ END;
392
+
393
+ CREATE TABLE nodes (
394
+ id TEXT PRIMARY KEY,
395
+ team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
396
+ principal_id TEXT NOT NULL UNIQUE REFERENCES principals(id) ON DELETE RESTRICT,
397
+ server_identity TEXT NOT NULL UNIQUE CHECK(length(trim(server_identity)) BETWEEN 8 AND 240),
398
+ display_name TEXT NOT NULL CHECK(length(trim(display_name)) BETWEEN 1 AND 160),
399
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'offline', 'suspended', 'revoked')),
400
+ enrolled_at INTEGER NOT NULL CHECK(enrolled_at >= 0),
401
+ last_seen_at INTEGER CHECK(last_seen_at IS NULL OR last_seen_at >= enrolled_at),
402
+ UNIQUE(team_id, id),
403
+ UNIQUE(team_id, principal_id)
404
+ );
405
+
406
+ CREATE TRIGGER nodes_identity_is_immutable
407
+ BEFORE UPDATE OF id, team_id, principal_id, server_identity, enrolled_at ON nodes
408
+ BEGIN
409
+ SELECT RAISE(ABORT, 'node identity is immutable');
410
+ END;
411
+
412
+ CREATE TRIGGER node_revocation_is_terminal
413
+ BEFORE UPDATE OF status ON nodes
414
+ FOR EACH ROW WHEN OLD.status = 'revoked' AND NEW.status <> 'revoked'
415
+ BEGIN
416
+ SELECT RAISE(ABORT, 'node revocation is terminal');
417
+ END;
418
+
419
+ CREATE TRIGGER nodes_require_scoped_node_principal
420
+ BEFORE INSERT ON nodes
421
+ FOR EACH ROW WHEN NOT EXISTS (
422
+ SELECT 1 FROM principals
423
+ WHERE id = NEW.principal_id AND kind = 'node' AND scope_team_id = NEW.team_id
424
+ )
425
+ BEGIN
426
+ SELECT RAISE(ABORT, 'node requires a node principal scoped to the same team');
427
+ END;
428
+
429
+ CREATE TABLE node_credentials (
430
+ id TEXT PRIMARY KEY,
431
+ team_id TEXT NOT NULL,
432
+ node_id TEXT NOT NULL,
433
+ credential_kind TEXT NOT NULL CHECK(credential_kind = 'ed25519'),
434
+ public_material TEXT NOT NULL CHECK(length(public_material) BETWEEN 32 AND 16384),
435
+ fingerprint_sha256 BLOB NOT NULL UNIQUE
436
+ CHECK(typeof(fingerprint_sha256) = 'blob' AND length(fingerprint_sha256) = 32),
437
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
438
+ expires_at INTEGER CHECK(expires_at IS NULL OR expires_at > created_at),
439
+ revoked_at INTEGER CHECK(revoked_at IS NULL OR revoked_at >= created_at),
440
+ FOREIGN KEY(team_id, node_id) REFERENCES nodes(team_id, id) ON DELETE CASCADE,
441
+ UNIQUE(team_id, id)
442
+ );
443
+
444
+ CREATE TRIGGER node_credential_authority_is_immutable
445
+ BEFORE UPDATE OF id, team_id, node_id, credential_kind, public_material,
446
+ fingerprint_sha256, created_at, expires_at
447
+ ON node_credentials
448
+ BEGIN
449
+ SELECT RAISE(ABORT, 'node credential authority is immutable');
450
+ END;
451
+
452
+ CREATE TRIGGER node_credential_revocation_is_one_way
453
+ BEFORE UPDATE OF revoked_at ON node_credentials
454
+ FOR EACH ROW WHEN OLD.revoked_at IS NOT NULL AND NEW.revoked_at IS NOT OLD.revoked_at
455
+ BEGIN
456
+ SELECT RAISE(ABORT, 'node credential revocation is one-way');
457
+ END;
458
+
459
+ CREATE TRIGGER node_credentials_cannot_be_deleted
460
+ BEFORE DELETE ON node_credentials
461
+ BEGIN
462
+ SELECT RAISE(ABORT, 'node credential ledger rows cannot be deleted');
463
+ END;
464
+
465
+ CREATE TABLE legacy_server_bindings (
466
+ id TEXT PRIMARY KEY,
467
+ team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
468
+ server_identity TEXT NOT NULL UNIQUE CHECK(length(trim(server_identity)) BETWEEN 8 AND 240),
469
+ node_id TEXT,
470
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
471
+ FOREIGN KEY(team_id, node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
472
+ UNIQUE(team_id, id)
473
+ );
474
+
475
+ CREATE TRIGGER legacy_server_binding_identity_is_immutable
476
+ BEFORE UPDATE OF id, team_id, server_identity, created_at ON legacy_server_bindings
477
+ BEGIN
478
+ SELECT RAISE(ABORT, 'legacy server binding identity is immutable');
479
+ END;
480
+
481
+ CREATE TRIGGER legacy_server_binding_node_is_one_way
482
+ BEFORE UPDATE OF node_id ON legacy_server_bindings
483
+ FOR EACH ROW WHEN OLD.node_id IS NOT NULL OR NEW.node_id IS NULL
484
+ BEGIN
485
+ SELECT RAISE(ABORT, 'legacy server node binding is one-way');
486
+ END;
487
+
488
+ CREATE TABLE agents (
489
+ id TEXT PRIMARY KEY,
490
+ team_id TEXT NOT NULL,
491
+ principal_id TEXT NOT NULL UNIQUE REFERENCES principals(id) ON DELETE RESTRICT,
492
+ node_id TEXT NOT NULL,
493
+ external_agent_id TEXT NOT NULL CHECK(length(trim(external_agent_id)) BETWEEN 1 AND 240),
494
+ backend TEXT NOT NULL CHECK(backend IN ('codex', 'claude', 'other')),
495
+ display_name TEXT NOT NULL CHECK(length(trim(display_name)) BETWEEN 1 AND 160),
496
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'offline', 'suspended', 'retired')),
497
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
498
+ updated_at INTEGER NOT NULL CHECK(updated_at >= created_at),
499
+ FOREIGN KEY(team_id, node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
500
+ UNIQUE(node_id, external_agent_id),
501
+ UNIQUE(team_id, id),
502
+ UNIQUE(team_id, node_id, id),
503
+ UNIQUE(team_id, principal_id)
504
+ );
505
+
506
+ CREATE TRIGGER agents_identity_is_immutable
507
+ BEFORE UPDATE OF id, team_id, principal_id, node_id, external_agent_id, backend, created_at
508
+ ON agents
509
+ BEGIN
510
+ SELECT RAISE(ABORT, 'agent identity is immutable');
511
+ END;
512
+
513
+ CREATE TRIGGER agent_retirement_is_terminal
514
+ BEFORE UPDATE OF status ON agents
515
+ FOR EACH ROW WHEN OLD.status = 'retired' AND NEW.status <> 'retired'
516
+ BEGIN
517
+ SELECT RAISE(ABORT, 'agent retirement is terminal');
518
+ END;
519
+
520
+ CREATE TRIGGER agents_require_scoped_agent_principal
521
+ BEFORE INSERT ON agents
522
+ FOR EACH ROW WHEN NOT EXISTS (
523
+ SELECT 1 FROM principals
524
+ WHERE id = NEW.principal_id AND kind = 'agent' AND scope_team_id = NEW.team_id
525
+ )
526
+ BEGIN
527
+ SELECT RAISE(ABORT, 'agent requires an agent principal scoped to the same team');
528
+ END;
529
+
530
+ CREATE TABLE chats (
531
+ id TEXT PRIMARY KEY,
532
+ team_id TEXT NOT NULL,
533
+ principal_id TEXT NOT NULL UNIQUE REFERENCES principals(id) ON DELETE RESTRICT,
534
+ node_id TEXT NOT NULL,
535
+ agent_id TEXT NOT NULL,
536
+ external_chat_id TEXT NOT NULL CHECK(length(trim(external_chat_id)) BETWEEN 1 AND 240),
537
+ display_name TEXT NOT NULL CHECK(length(trim(display_name)) BETWEEN 1 AND 240),
538
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived', 'deleted')),
539
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
540
+ updated_at INTEGER NOT NULL CHECK(updated_at >= created_at),
541
+ FOREIGN KEY(team_id, node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
542
+ FOREIGN KEY(team_id, node_id, agent_id)
543
+ REFERENCES agents(team_id, node_id, id) ON DELETE RESTRICT,
544
+ UNIQUE(node_id, external_chat_id),
545
+ UNIQUE(team_id, id),
546
+ UNIQUE(team_id, node_id, id),
547
+ UNIQUE(team_id, node_id, agent_id, id),
548
+ UNIQUE(team_id, principal_id)
549
+ );
550
+
551
+ CREATE TRIGGER chats_identity_is_immutable
552
+ BEFORE UPDATE OF id, team_id, principal_id, node_id, agent_id, external_chat_id, created_at
553
+ ON chats
554
+ BEGIN
555
+ SELECT RAISE(ABORT, 'chat identity is immutable');
556
+ END;
557
+
558
+ CREATE TRIGGER chat_deletion_is_terminal
559
+ BEFORE UPDATE OF status ON chats
560
+ FOR EACH ROW WHEN OLD.status = 'deleted' AND NEW.status <> 'deleted'
561
+ BEGIN
562
+ SELECT RAISE(ABORT, 'chat deletion is terminal');
563
+ END;
564
+
565
+ CREATE TRIGGER chats_require_scoped_chat_principal
566
+ BEFORE INSERT ON chats
567
+ FOR EACH ROW WHEN NOT EXISTS (
568
+ SELECT 1 FROM principals
569
+ WHERE id = NEW.principal_id AND kind = 'chat' AND scope_team_id = NEW.team_id
570
+ )
571
+ BEGIN
572
+ SELECT RAISE(ABORT, 'chat requires a chat principal scoped to the same team');
573
+ END;
574
+
575
+ CREATE TABLE runs (
576
+ id TEXT PRIMARY KEY,
577
+ team_id TEXT NOT NULL,
578
+ node_id TEXT NOT NULL,
579
+ agent_id TEXT NOT NULL,
580
+ chat_id TEXT NOT NULL,
581
+ acting_human_principal_id TEXT,
582
+ external_run_id TEXT NOT NULL CHECK(length(trim(external_run_id)) BETWEEN 1 AND 240),
583
+ status TEXT NOT NULL CHECK(status IN ('registered', 'running', 'succeeded', 'failed', 'cancelled')),
584
+ started_at INTEGER NOT NULL CHECK(started_at >= 0),
585
+ finished_at INTEGER CHECK(finished_at IS NULL OR finished_at >= started_at),
586
+ FOREIGN KEY(team_id, node_id) REFERENCES nodes(team_id, id) ON DELETE RESTRICT,
587
+ FOREIGN KEY(team_id, node_id, agent_id)
588
+ REFERENCES agents(team_id, node_id, id) ON DELETE RESTRICT,
589
+ FOREIGN KEY(team_id, node_id, chat_id)
590
+ REFERENCES chats(team_id, node_id, id) ON DELETE RESTRICT,
591
+ FOREIGN KEY(team_id, node_id, agent_id, chat_id)
592
+ REFERENCES chats(team_id, node_id, agent_id, id) ON DELETE RESTRICT,
593
+ FOREIGN KEY(team_id, acting_human_principal_id)
594
+ REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
595
+ UNIQUE(node_id, external_run_id),
596
+ UNIQUE(team_id, id)
597
+ );
598
+
599
+ CREATE TRIGGER runs_require_active_human_actor
600
+ BEFORE INSERT ON runs
601
+ FOR EACH ROW WHEN NEW.acting_human_principal_id IS NOT NULL AND NOT EXISTS (
602
+ SELECT 1
603
+ FROM memberships AS m
604
+ JOIN principals AS p ON p.id = m.principal_id
605
+ WHERE m.team_id = NEW.team_id
606
+ AND m.principal_id = NEW.acting_human_principal_id
607
+ AND m.status = 'active'
608
+ AND p.kind = 'human'
609
+ AND p.status = 'active'
610
+ )
611
+ BEGIN
612
+ SELECT RAISE(ABORT, 'run acting user must be an active human member');
613
+ END;
614
+
615
+ CREATE TRIGGER runs_provenance_is_immutable
616
+ BEFORE UPDATE OF id, team_id, node_id, agent_id, chat_id,
617
+ acting_human_principal_id, external_run_id, started_at
618
+ ON runs
619
+ BEGIN
620
+ SELECT RAISE(ABORT, 'run provenance is immutable');
621
+ END;
622
+
623
+ CREATE TABLE turn_capabilities (
624
+ id TEXT PRIMARY KEY,
625
+ team_id TEXT NOT NULL,
626
+ issued_to_run_id TEXT NOT NULL,
627
+ issued_by_principal_id TEXT NOT NULL,
628
+ token_hash BLOB NOT NULL UNIQUE
629
+ CHECK(typeof(token_hash) = 'blob' AND length(token_hash) = 32),
630
+ action TEXT NOT NULL CHECK(length(trim(action)) BETWEEN 1 AND 120),
631
+ resource_type TEXT NOT NULL CHECK(length(trim(resource_type)) BETWEEN 1 AND 80),
632
+ resource_id TEXT NOT NULL CHECK(length(trim(resource_id)) BETWEEN 1 AND 240),
633
+ nonce_hash BLOB NOT NULL UNIQUE
634
+ CHECK(typeof(nonce_hash) = 'blob' AND length(nonce_hash) = 32),
635
+ max_uses INTEGER NOT NULL CHECK(max_uses BETWEEN 1 AND 16),
636
+ used_count INTEGER NOT NULL DEFAULT 0 CHECK(used_count BETWEEN 0 AND max_uses),
637
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
638
+ expires_at INTEGER NOT NULL CHECK(expires_at > created_at),
639
+ revoked_at INTEGER CHECK(revoked_at IS NULL OR revoked_at >= created_at),
640
+ FOREIGN KEY(team_id, issued_to_run_id) REFERENCES runs(team_id, id) ON DELETE CASCADE,
641
+ FOREIGN KEY(team_id, issued_by_principal_id)
642
+ REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
643
+ UNIQUE(team_id, id)
644
+ );
645
+
646
+ CREATE TRIGGER turn_capabilities_require_active_human_issuer
647
+ BEFORE INSERT ON turn_capabilities
648
+ FOR EACH ROW WHEN NOT EXISTS (
649
+ SELECT 1
650
+ FROM memberships AS m
651
+ JOIN principals AS p ON p.id = m.principal_id
652
+ WHERE m.team_id = NEW.team_id
653
+ AND m.principal_id = NEW.issued_by_principal_id
654
+ AND m.status = 'active'
655
+ AND p.kind = 'human'
656
+ AND p.status = 'active'
657
+ )
658
+ BEGIN
659
+ SELECT RAISE(ABORT, 'turn capability issuer must be an active human member');
660
+ END;
661
+
662
+ CREATE TRIGGER turn_capability_authority_is_immutable
663
+ BEFORE UPDATE OF id, team_id, issued_to_run_id, issued_by_principal_id,
664
+ token_hash, action, resource_type, resource_id, nonce_hash, max_uses,
665
+ created_at, expires_at
666
+ ON turn_capabilities
667
+ BEGIN
668
+ SELECT RAISE(ABORT, 'turn capability authority is immutable');
669
+ END;
670
+
671
+ CREATE TRIGGER turn_capability_use_and_revocation_are_monotonic
672
+ BEFORE UPDATE ON turn_capabilities
673
+ FOR EACH ROW WHEN
674
+ NEW.used_count < OLD.used_count
675
+ OR NEW.used_count > OLD.used_count + 1
676
+ OR (NEW.revoked_at IS NOT NULL AND NEW.used_count <> OLD.used_count)
677
+ OR (OLD.revoked_at IS NOT NULL AND NEW.revoked_at IS NOT OLD.revoked_at)
678
+ BEGIN
679
+ SELECT RAISE(ABORT, 'turn capability use and revocation are monotonic');
680
+ END;
681
+
682
+ CREATE TRIGGER turn_capabilities_cannot_be_deleted
683
+ BEFORE DELETE ON turn_capabilities
684
+ BEGIN
685
+ SELECT RAISE(ABORT, 'turn capability ledger rows cannot be deleted');
686
+ END;
687
+
688
+ CREATE INDEX turn_capabilities_live_lookup
689
+ ON turn_capabilities(token_hash, expires_at)
690
+ WHERE revoked_at IS NULL;