@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,361 @@
1
+ -- Runnable Team Hub V1 security and service ledgers. This migration tightens
2
+ -- the pre-service foundation without changing its public identity keys.
3
+
4
+ UPDATE invitations
5
+ SET revoked_at = created_at
6
+ WHERE invitee_email_normalized IS NULL
7
+ AND redeemed_at IS NULL
8
+ AND revoked_at IS NULL;
9
+
10
+ CREATE TRIGGER invitations_require_bound_recipient
11
+ BEFORE INSERT ON invitations
12
+ FOR EACH ROW WHEN NEW.invitee_email_normalized IS NULL
13
+ BEGIN
14
+ SELECT RAISE(ABORT, 'invitations require a bound recipient email');
15
+ END;
16
+
17
+ CREATE TRIGGER active_owner_cannot_be_removed
18
+ BEFORE UPDATE OF role, status ON memberships
19
+ FOR EACH ROW WHEN OLD.role = 'owner' AND OLD.status = 'active'
20
+ AND (NEW.role <> 'owner' OR NEW.status <> 'active')
21
+ BEGIN
22
+ SELECT RAISE(ABORT, 'an active team owner cannot be removed');
23
+ END;
24
+
25
+ CREATE TRIGGER active_owner_principal_cannot_be_disabled
26
+ BEFORE UPDATE OF status ON principals
27
+ FOR EACH ROW WHEN NEW.status <> 'active' AND EXISTS (
28
+ SELECT 1 FROM memberships
29
+ WHERE principal_id = OLD.id AND role = 'owner' AND status = 'active'
30
+ )
31
+ BEGIN
32
+ SELECT RAISE(ABORT, 'an active team owner principal cannot be disabled');
33
+ END;
34
+
35
+ CREATE TRIGGER message_recipient_state_is_monotonic
36
+ BEFORE UPDATE OF state ON message_recipients
37
+ FOR EACH ROW WHEN
38
+ (OLD.state = 'available' AND NEW.state NOT IN ('available', 'delivered', 'expired'))
39
+ OR (OLD.state = 'delivered' AND NEW.state <> 'delivered')
40
+ OR (OLD.state = 'expired' AND NEW.state <> 'expired')
41
+ BEGIN
42
+ SELECT RAISE(ABORT, 'message recipient delivery state is monotonic');
43
+ END;
44
+
45
+ CREATE TRIGGER message_receipt_times_are_monotonic
46
+ BEFORE UPDATE ON message_receipts
47
+ FOR EACH ROW WHEN
48
+ (OLD.delivered_at IS NOT NULL AND NEW.delivered_at IS NOT OLD.delivered_at)
49
+ OR (OLD.read_at IS NOT NULL AND NEW.read_at IS NOT OLD.read_at)
50
+ OR (OLD.acknowledged_at IS NOT NULL AND NEW.acknowledged_at IS NOT OLD.acknowledged_at)
51
+ BEGIN
52
+ SELECT RAISE(ABORT, 'message receipt timestamps are monotonic');
53
+ END;
54
+
55
+ CREATE TRIGGER outbox_state_is_forward_only
56
+ BEFORE UPDATE OF state ON outbox_events
57
+ FOR EACH ROW WHEN
58
+ (OLD.state = 'delivered' AND NEW.state <> 'delivered')
59
+ OR (OLD.state = 'dead_letter' AND NEW.state <> 'dead_letter')
60
+ OR (OLD.state = 'pending' AND NEW.state NOT IN ('pending', 'leased', 'dead_letter'))
61
+ OR (OLD.state = 'leased' AND NEW.state NOT IN ('leased', 'pending', 'delivered', 'dead_letter'))
62
+ BEGIN
63
+ SELECT RAISE(ABORT, 'outbox state transition is invalid');
64
+ END;
65
+
66
+ CREATE TABLE bootstrap_claims (
67
+ id TEXT PRIMARY KEY,
68
+ token_hash BLOB NOT NULL UNIQUE
69
+ CHECK(typeof(token_hash) = 'blob' AND length(token_hash) = 32),
70
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
71
+ expires_at INTEGER NOT NULL CHECK(expires_at > created_at),
72
+ consumed_at INTEGER CHECK(consumed_at IS NULL OR consumed_at >= created_at),
73
+ consumed_by_principal_id TEXT REFERENCES human_accounts(principal_id) ON DELETE RESTRICT,
74
+ revoked_at INTEGER CHECK(revoked_at IS NULL OR revoked_at >= created_at),
75
+ CHECK((consumed_at IS NULL) = (consumed_by_principal_id IS NULL)),
76
+ CHECK(consumed_at IS NULL OR revoked_at IS NULL)
77
+ );
78
+
79
+ CREATE TRIGGER bootstrap_claim_authority_is_immutable
80
+ BEFORE UPDATE OF id, token_hash, created_at, expires_at ON bootstrap_claims
81
+ BEGIN
82
+ SELECT RAISE(ABORT, 'bootstrap claim authority is immutable');
83
+ END;
84
+
85
+ CREATE TRIGGER bootstrap_claim_consumption_is_one_way
86
+ BEFORE UPDATE ON bootstrap_claims
87
+ FOR EACH ROW WHEN
88
+ (OLD.consumed_at IS NOT NULL AND (
89
+ NEW.consumed_at IS NOT OLD.consumed_at
90
+ OR NEW.consumed_by_principal_id IS NOT OLD.consumed_by_principal_id
91
+ ))
92
+ OR (OLD.revoked_at IS NOT NULL AND NEW.revoked_at IS NOT OLD.revoked_at)
93
+ BEGIN
94
+ SELECT RAISE(ABORT, 'bootstrap claim consumption is one-way');
95
+ END;
96
+
97
+ CREATE TRIGGER bootstrap_claims_cannot_be_deleted
98
+ BEFORE DELETE ON bootstrap_claims
99
+ BEGIN
100
+ SELECT RAISE(ABORT, 'bootstrap claim ledger rows cannot be deleted');
101
+ END;
102
+
103
+ CREATE TABLE node_enrollment_bindings (
104
+ grant_id TEXT PRIMARY KEY REFERENCES node_enrollment_grants(id) ON DELETE RESTRICT,
105
+ team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE RESTRICT,
106
+ expected_server_identity TEXT NOT NULL
107
+ CHECK(length(trim(expected_server_identity)) BETWEEN 8 AND 240),
108
+ expected_display_name TEXT NOT NULL
109
+ CHECK(length(trim(expected_display_name)) BETWEEN 1 AND 160),
110
+ expected_public_material TEXT NOT NULL
111
+ CHECK(length(expected_public_material) BETWEEN 32 AND 16384),
112
+ expected_public_key_fingerprint BLOB NOT NULL
113
+ CHECK(typeof(expected_public_key_fingerprint) = 'blob'
114
+ AND length(expected_public_key_fingerprint) = 32),
115
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
116
+ UNIQUE(team_id, grant_id)
117
+ );
118
+
119
+ CREATE TRIGGER node_enrollment_binding_is_immutable
120
+ BEFORE UPDATE ON node_enrollment_bindings
121
+ BEGIN
122
+ SELECT RAISE(ABORT, 'node enrollment binding is immutable');
123
+ END;
124
+
125
+ CREATE TRIGGER node_enrollment_binding_requires_same_team
126
+ BEFORE INSERT ON node_enrollment_bindings
127
+ FOR EACH ROW WHEN NOT EXISTS (
128
+ SELECT 1 FROM node_enrollment_grants
129
+ WHERE id = NEW.grant_id AND team_id = NEW.team_id
130
+ )
131
+ BEGIN
132
+ SELECT RAISE(ABORT, 'node enrollment binding must match its grant team');
133
+ END;
134
+
135
+ CREATE TRIGGER node_enrollment_bindings_cannot_be_deleted
136
+ BEFORE DELETE ON node_enrollment_bindings
137
+ BEGIN
138
+ SELECT RAISE(ABORT, 'node enrollment binding rows cannot be deleted');
139
+ END;
140
+
141
+ CREATE TABLE node_enrollment_challenges (
142
+ id TEXT PRIMARY KEY,
143
+ grant_id TEXT NOT NULL UNIQUE REFERENCES node_enrollment_grants(id) ON DELETE RESTRICT,
144
+ team_id TEXT NOT NULL,
145
+ public_material TEXT NOT NULL CHECK(length(public_material) BETWEEN 32 AND 16384),
146
+ public_key_fingerprint BLOB NOT NULL
147
+ CHECK(typeof(public_key_fingerprint) = 'blob' AND length(public_key_fingerprint) = 32),
148
+ nonce_hash BLOB NOT NULL UNIQUE
149
+ CHECK(typeof(nonce_hash) = 'blob' AND length(nonce_hash) = 32),
150
+ signing_payload TEXT NOT NULL CHECK(length(signing_payload) BETWEEN 64 AND 4096),
151
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
152
+ expires_at INTEGER NOT NULL CHECK(expires_at > created_at),
153
+ consumed_at INTEGER CHECK(consumed_at IS NULL OR consumed_at >= created_at),
154
+ revoked_at INTEGER CHECK(revoked_at IS NULL OR revoked_at >= created_at),
155
+ FOREIGN KEY(team_id, grant_id)
156
+ REFERENCES node_enrollment_bindings(team_id, grant_id) ON DELETE RESTRICT,
157
+ CHECK(consumed_at IS NULL OR revoked_at IS NULL)
158
+ );
159
+
160
+ CREATE TRIGGER node_enrollment_challenge_authority_is_immutable
161
+ BEFORE UPDATE OF id, grant_id, team_id, public_material,
162
+ public_key_fingerprint, nonce_hash, signing_payload, created_at, expires_at
163
+ ON node_enrollment_challenges
164
+ BEGIN
165
+ SELECT RAISE(ABORT, 'node enrollment challenge authority is immutable');
166
+ END;
167
+
168
+ CREATE TRIGGER node_enrollment_challenge_terminal_state_is_one_way
169
+ BEFORE UPDATE ON node_enrollment_challenges
170
+ FOR EACH ROW WHEN
171
+ (OLD.consumed_at IS NOT NULL AND NEW.consumed_at IS NOT OLD.consumed_at)
172
+ OR (OLD.revoked_at IS NOT NULL AND NEW.revoked_at IS NOT OLD.revoked_at)
173
+ BEGIN
174
+ SELECT RAISE(ABORT, 'node enrollment challenge terminal state is one-way');
175
+ END;
176
+
177
+ CREATE TRIGGER node_enrollment_challenges_cannot_be_deleted
178
+ BEFORE DELETE ON node_enrollment_challenges
179
+ BEGIN
180
+ SELECT RAISE(ABORT, 'node enrollment challenge rows cannot be deleted');
181
+ END;
182
+
183
+ CREATE TABLE request_idempotency (
184
+ id TEXT PRIMARY KEY,
185
+ team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE RESTRICT,
186
+ principal_id TEXT NOT NULL REFERENCES principals(id) ON DELETE RESTRICT,
187
+ operation TEXT NOT NULL CHECK(length(operation) BETWEEN 1 AND 120),
188
+ key_hash BLOB NOT NULL CHECK(typeof(key_hash) = 'blob' AND length(key_hash) = 32),
189
+ request_fingerprint BLOB NOT NULL
190
+ CHECK(typeof(request_fingerprint) = 'blob' AND length(request_fingerprint) = 32),
191
+ resource_type TEXT NOT NULL CHECK(length(resource_type) BETWEEN 1 AND 80),
192
+ resource_id TEXT NOT NULL CHECK(length(resource_id) BETWEEN 1 AND 240),
193
+ response_json TEXT NOT NULL CHECK(json_valid(response_json) AND json_type(response_json) = 'object'),
194
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
195
+ UNIQUE(team_id, principal_id, operation, key_hash)
196
+ );
197
+
198
+ CREATE TRIGGER request_idempotency_is_immutable_update
199
+ BEFORE UPDATE ON request_idempotency
200
+ BEGIN
201
+ SELECT RAISE(ABORT, 'idempotency records are immutable');
202
+ END;
203
+
204
+ CREATE TRIGGER request_idempotency_is_immutable_delete
205
+ BEFORE DELETE ON request_idempotency
206
+ BEGIN
207
+ SELECT RAISE(ABORT, 'idempotency records are immutable');
208
+ END;
209
+
210
+ CREATE TRIGGER request_idempotency_requires_team_principal
211
+ BEFORE INSERT ON request_idempotency
212
+ FOR EACH ROW WHEN NOT EXISTS (
213
+ SELECT 1 FROM principals AS p
214
+ WHERE p.id = NEW.principal_id
215
+ AND p.status = 'active'
216
+ AND (
217
+ p.scope_team_id = NEW.team_id
218
+ OR EXISTS (
219
+ SELECT 1 FROM memberships AS m
220
+ WHERE m.team_id = NEW.team_id
221
+ AND m.principal_id = p.id
222
+ AND m.status = 'active'
223
+ )
224
+ )
225
+ )
226
+ BEGIN
227
+ SELECT RAISE(ABORT, 'idempotency principal does not belong to its team');
228
+ END;
229
+
230
+ CREATE TRIGGER message_receipt_times_are_ordered_insert
231
+ BEFORE INSERT ON message_receipts
232
+ FOR EACH ROW WHEN
233
+ (NEW.read_at IS NOT NULL AND NEW.read_at < NEW.delivered_at)
234
+ OR (NEW.acknowledged_at IS NOT NULL AND NEW.acknowledged_at < NEW.delivered_at)
235
+ BEGIN
236
+ SELECT RAISE(ABORT, 'message receipt timestamps are out of order');
237
+ END;
238
+
239
+ CREATE TRIGGER message_receipt_times_are_ordered_update
240
+ BEFORE UPDATE ON message_receipts
241
+ FOR EACH ROW WHEN
242
+ (NEW.read_at IS NOT NULL AND NEW.read_at < NEW.delivered_at)
243
+ OR (NEW.acknowledged_at IS NOT NULL AND NEW.acknowledged_at < NEW.delivered_at)
244
+ BEGIN
245
+ SELECT RAISE(ABORT, 'message receipt timestamps are out of order');
246
+ END;
247
+
248
+ CREATE TRIGGER outbox_row_shape_is_valid_insert
249
+ BEFORE INSERT ON outbox_events
250
+ FOR EACH ROW WHEN
251
+ (NEW.state <> 'leased' AND (NEW.lease_owner IS NOT NULL OR NEW.lease_expires_at IS NOT NULL))
252
+ OR (NEW.state = 'delivered' AND NEW.delivered_at IS NULL)
253
+ OR (NEW.state <> 'delivered' AND NEW.delivered_at IS NOT NULL)
254
+ BEGIN
255
+ SELECT RAISE(ABORT, 'outbox row shape is invalid');
256
+ END;
257
+
258
+ CREATE TRIGGER outbox_row_shape_is_valid_update
259
+ BEFORE UPDATE ON outbox_events
260
+ FOR EACH ROW WHEN
261
+ (NEW.state <> 'leased' AND (NEW.lease_owner IS NOT NULL OR NEW.lease_expires_at IS NOT NULL))
262
+ OR (NEW.state = 'delivered' AND NEW.delivered_at IS NULL)
263
+ OR (NEW.state <> 'delivered' AND NEW.delivered_at IS NOT NULL)
264
+ BEGIN
265
+ SELECT RAISE(ABORT, 'outbox row shape is invalid');
266
+ END;
267
+
268
+ CREATE TABLE hub_metadata (
269
+ singleton INTEGER PRIMARY KEY CHECK(singleton = 1),
270
+ hub_id TEXT NOT NULL UNIQUE CHECK(length(hub_id) BETWEEN 12 AND 80),
271
+ created_at INTEGER NOT NULL CHECK(created_at >= 0)
272
+ );
273
+
274
+ CREATE TRIGGER hub_metadata_is_immutable_update
275
+ BEFORE UPDATE ON hub_metadata
276
+ BEGIN
277
+ SELECT RAISE(ABORT, 'Hub identity is immutable');
278
+ END;
279
+
280
+ CREATE TRIGGER hub_metadata_is_immutable_delete
281
+ BEFORE DELETE ON hub_metadata
282
+ BEGIN
283
+ SELECT RAISE(ABORT, 'Hub identity is immutable');
284
+ END;
285
+
286
+ CREATE TABLE owner_recovery_claims (
287
+ id TEXT PRIMARY KEY,
288
+ team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE RESTRICT,
289
+ owner_principal_id TEXT NOT NULL,
290
+ token_hash BLOB NOT NULL UNIQUE
291
+ CHECK(typeof(token_hash) = 'blob' AND length(token_hash) = 32),
292
+ device_label TEXT NOT NULL CHECK(length(trim(device_label)) BETWEEN 1 AND 160),
293
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
294
+ expires_at INTEGER NOT NULL CHECK(expires_at > created_at),
295
+ consumed_at INTEGER CHECK(consumed_at IS NULL OR consumed_at >= created_at),
296
+ consumed_by_session_id TEXT REFERENCES device_sessions(id) ON DELETE RESTRICT,
297
+ revoked_at INTEGER CHECK(revoked_at IS NULL OR revoked_at >= created_at),
298
+ FOREIGN KEY(team_id, owner_principal_id)
299
+ REFERENCES memberships(team_id, principal_id) ON DELETE RESTRICT,
300
+ CHECK((consumed_at IS NULL) = (consumed_by_session_id IS NULL)),
301
+ CHECK(consumed_at IS NULL OR revoked_at IS NULL)
302
+ );
303
+
304
+ CREATE TRIGGER owner_recovery_claim_authority_is_immutable
305
+ BEFORE UPDATE OF id, team_id, owner_principal_id, token_hash,
306
+ device_label, created_at, expires_at ON owner_recovery_claims
307
+ BEGIN
308
+ SELECT RAISE(ABORT, 'owner recovery claim authority is immutable');
309
+ END;
310
+
311
+ CREATE TRIGGER owner_recovery_claim_terminal_state_is_one_way
312
+ BEFORE UPDATE ON owner_recovery_claims
313
+ FOR EACH ROW WHEN
314
+ (OLD.consumed_at IS NOT NULL AND (
315
+ NEW.consumed_at IS NOT OLD.consumed_at
316
+ OR NEW.consumed_by_session_id IS NOT OLD.consumed_by_session_id
317
+ ))
318
+ OR (OLD.revoked_at IS NOT NULL AND NEW.revoked_at IS NOT OLD.revoked_at)
319
+ BEGIN
320
+ SELECT RAISE(ABORT, 'owner recovery claim terminal state is one-way');
321
+ END;
322
+
323
+ CREATE TRIGGER owner_recovery_claims_cannot_be_deleted
324
+ BEFORE DELETE ON owner_recovery_claims
325
+ BEGIN
326
+ SELECT RAISE(ABORT, 'owner recovery claim ledger rows cannot be deleted');
327
+ END;
328
+
329
+ CREATE TABLE audit_chain_heads (
330
+ team_id TEXT PRIMARY KEY REFERENCES teams(id) ON DELETE RESTRICT,
331
+ event_id TEXT NOT NULL REFERENCES audit_events(id) ON DELETE RESTRICT,
332
+ event_hash BLOB NOT NULL
333
+ CHECK(typeof(event_hash) = 'blob' AND length(event_hash) = 32),
334
+ sequence INTEGER NOT NULL CHECK(sequence >= 1),
335
+ updated_at INTEGER NOT NULL CHECK(updated_at >= 0)
336
+ );
337
+
338
+ CREATE TRIGGER audit_chain_head_requires_team_event_insert
339
+ BEFORE INSERT ON audit_chain_heads
340
+ FOR EACH ROW WHEN NOT EXISTS (
341
+ SELECT 1 FROM audit_events WHERE id = NEW.event_id AND team_id = NEW.team_id
342
+ )
343
+ BEGIN
344
+ SELECT RAISE(ABORT, 'audit chain head event must belong to its team');
345
+ END;
346
+
347
+ CREATE TRIGGER audit_chain_head_requires_team_event_update
348
+ BEFORE UPDATE OF event_id ON audit_chain_heads
349
+ FOR EACH ROW WHEN NOT EXISTS (
350
+ SELECT 1 FROM audit_events WHERE id = NEW.event_id AND team_id = NEW.team_id
351
+ )
352
+ BEGIN
353
+ SELECT RAISE(ABORT, 'audit chain head event must belong to its team');
354
+ END;
355
+
356
+ CREATE TRIGGER audit_chain_head_is_monotonic
357
+ BEFORE UPDATE ON audit_chain_heads
358
+ FOR EACH ROW WHEN NEW.sequence <> OLD.sequence + 1 OR NEW.updated_at < OLD.updated_at
359
+ BEGIN
360
+ SELECT RAISE(ABORT, 'audit chain head must advance exactly once');
361
+ END;
@@ -0,0 +1,22 @@
1
+ -- Bind an embedded Hub database to exactly one stable AgentsServer identity.
2
+ -- Standalone Hubs remain unbound until their first managed adoption.
3
+
4
+ CREATE TABLE managed_host_bindings (
5
+ singleton INTEGER PRIMARY KEY CHECK(singleton = 1),
6
+ hub_id TEXT NOT NULL UNIQUE REFERENCES hub_metadata(hub_id) ON DELETE RESTRICT,
7
+ server_identity TEXT NOT NULL UNIQUE
8
+ CHECK(length(trim(server_identity)) BETWEEN 8 AND 240),
9
+ created_at INTEGER NOT NULL CHECK(created_at >= 0)
10
+ );
11
+
12
+ CREATE TRIGGER managed_host_binding_is_immutable_update
13
+ BEFORE UPDATE ON managed_host_bindings
14
+ BEGIN
15
+ SELECT RAISE(ABORT, 'managed Hub host binding is immutable');
16
+ END;
17
+
18
+ CREATE TRIGGER managed_host_binding_is_immutable_delete
19
+ BEFORE DELETE ON managed_host_bindings
20
+ BEGIN
21
+ SELECT RAISE(ABORT, 'managed Hub host binding is immutable');
22
+ END;
@@ -0,0 +1,63 @@
1
+ -- Bind first-owner bootstrap proofs issued through the parent AgentsServer to
2
+ -- one verified Tailscale Serve identity and one live managed-server instance.
3
+ -- The proof itself is never stored; bootstrap_claims retains only its digest.
4
+
5
+ CREATE TABLE bootstrap_delegations (
6
+ bootstrap_claim_id TEXT PRIMARY KEY
7
+ REFERENCES bootstrap_claims(id) ON DELETE RESTRICT,
8
+ request_id TEXT NOT NULL UNIQUE
9
+ CHECK(length(request_id) = 36),
10
+ request_fingerprint BLOB NOT NULL
11
+ CHECK(typeof(request_fingerprint) = 'blob'
12
+ AND length(request_fingerprint) = 32),
13
+ server_identity TEXT NOT NULL
14
+ CHECK(length(trim(server_identity)) BETWEEN 8 AND 240),
15
+ server_instance_id TEXT NOT NULL
16
+ CHECK(length(trim(server_instance_id)) BETWEEN 8 AND 240),
17
+ hub_id TEXT NOT NULL REFERENCES hub_metadata(hub_id) ON DELETE RESTRICT,
18
+ hub_url TEXT NOT NULL
19
+ CHECK(length(hub_url) BETWEEN 16 AND 2048),
20
+ tailnet_login_normalized TEXT NOT NULL
21
+ CHECK(length(tailnet_login_normalized) BETWEEN 3 AND 320),
22
+ recipient_email_normalized TEXT NOT NULL
23
+ CHECK(length(recipient_email_normalized) BETWEEN 3 AND 320),
24
+ display_name TEXT NOT NULL
25
+ CHECK(length(trim(display_name)) BETWEEN 1 AND 160),
26
+ device_label TEXT NOT NULL
27
+ CHECK(length(trim(device_label)) BETWEEN 1 AND 160),
28
+ created_at INTEGER NOT NULL CHECK(created_at >= 0),
29
+ expires_at INTEGER NOT NULL CHECK(expires_at > created_at)
30
+ );
31
+
32
+ CREATE TRIGGER bootstrap_delegation_is_immutable
33
+ BEFORE UPDATE ON bootstrap_delegations
34
+ BEGIN
35
+ SELECT RAISE(ABORT, 'bootstrap delegation authority is immutable');
36
+ END;
37
+
38
+ CREATE TRIGGER bootstrap_delegations_cannot_be_deleted
39
+ BEFORE DELETE ON bootstrap_delegations
40
+ BEGIN
41
+ SELECT RAISE(ABORT, 'bootstrap delegation ledger rows cannot be deleted');
42
+ END;
43
+
44
+ CREATE TRIGGER bootstrap_delegation_matches_claim_expiry
45
+ BEFORE INSERT ON bootstrap_delegations
46
+ FOR EACH ROW WHEN NOT EXISTS (
47
+ SELECT 1 FROM bootstrap_claims
48
+ WHERE id = NEW.bootstrap_claim_id
49
+ AND created_at = NEW.created_at
50
+ AND expires_at = NEW.expires_at
51
+ )
52
+ BEGIN
53
+ SELECT RAISE(ABORT, 'bootstrap delegation must match its claim lifetime');
54
+ END;
55
+
56
+ CREATE TRIGGER bootstrap_delegation_matches_hub
57
+ BEFORE INSERT ON bootstrap_delegations
58
+ FOR EACH ROW WHEN NOT EXISTS (
59
+ SELECT 1 FROM hub_metadata WHERE hub_id = NEW.hub_id
60
+ )
61
+ BEGIN
62
+ SELECT RAISE(ABORT, 'bootstrap delegation must match its Hub');
63
+ END;