@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,1236 @@
1
+ """Durable local admission ledger for secure-peer chat deliveries.
2
+
3
+ The authoritative exchange and six-leg counter live on the designated Hub.
4
+ This ledger bridges that remote transaction to one local durable chat turn. A
5
+ delivery is never acknowledged remotely until its exact target route has been
6
+ resolved to a private local chat and this intent has been fsynced. If the
7
+ process dies between the remote CAS and local queue admission, startup resumes
8
+ the saved intent instead of re-running or losing it.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from contextlib import contextmanager
14
+ import hashlib
15
+ import json
16
+ import os
17
+ from pathlib import Path
18
+ import sqlite3
19
+ import stat
20
+ import threading
21
+ import time
22
+ from typing import Any, Iterator, Mapping
23
+
24
+ from agentsdock_team_hub.security import canonical_json, ensure_private_directory
25
+
26
+
27
+ DELIVERY_STATES = frozenset({
28
+ "prepared",
29
+ "authorized",
30
+ "queued",
31
+ "running",
32
+ "completed",
33
+ "failed",
34
+ })
35
+ TERMINAL_DELIVERY_STATES = frozenset({"completed", "failed"})
36
+ MAX_NONTERMINAL_DELIVERIES = 2_048
37
+
38
+
39
+ class SecurePeerDeliveryLedger:
40
+ """Owner-private, crash-safe mapping from a relay envelope to one turn."""
41
+
42
+ def __init__(self, path: Path) -> None:
43
+ self.path = Path(path)
44
+ ensure_private_directory(self.path.parent)
45
+ self._lock = threading.RLock()
46
+ self._initialize()
47
+
48
+ def _connect(self) -> sqlite3.Connection:
49
+ if self.path.is_symlink():
50
+ raise PermissionError("secure peer delivery ledger must not be a symlink")
51
+ connection = sqlite3.connect(self.path, timeout=30)
52
+ connection.row_factory = sqlite3.Row
53
+ connection.execute("PRAGMA journal_mode=WAL")
54
+ connection.execute("PRAGMA synchronous=FULL")
55
+ connection.execute("PRAGMA foreign_keys=ON")
56
+ os.chmod(self.path, 0o600)
57
+ info = os.stat(self.path, follow_symlinks=False)
58
+ if (
59
+ not stat.S_ISREG(info.st_mode)
60
+ or info.st_nlink != 1
61
+ or info.st_uid != os.getuid()
62
+ or stat.S_IMODE(info.st_mode) != 0o600
63
+ ):
64
+ connection.close()
65
+ raise PermissionError("secure peer delivery ledger is unsafe")
66
+ return connection
67
+
68
+ @contextmanager
69
+ def _transaction(self) -> Iterator[sqlite3.Connection]:
70
+ with self._lock:
71
+ connection = self._connect()
72
+ try:
73
+ connection.execute("BEGIN IMMEDIATE")
74
+ yield connection
75
+ connection.execute("COMMIT")
76
+ except BaseException:
77
+ if connection.in_transaction:
78
+ connection.execute("ROLLBACK")
79
+ raise
80
+ finally:
81
+ connection.close()
82
+
83
+ def _initialize(self) -> None:
84
+ with self._lock:
85
+ connection = self._connect()
86
+ try:
87
+ connection.executescript(
88
+ """
89
+ CREATE TABLE IF NOT EXISTS secure_peer_deliveries (
90
+ envelope_id TEXT PRIMARY KEY,
91
+ request_id TEXT NOT NULL,
92
+ claim_digest TEXT NOT NULL,
93
+ transport_role TEXT NOT NULL CHECK(transport_role IN ('host','client')),
94
+ connection_id TEXT NOT NULL,
95
+ lease_token TEXT NOT NULL,
96
+ team_id TEXT NOT NULL,
97
+ source_peer_id TEXT,
98
+ source_server_identity TEXT NOT NULL,
99
+ source_route_id TEXT NOT NULL,
100
+ source_route_revision TEXT NOT NULL,
101
+ target_peer_id TEXT,
102
+ target_server_identity TEXT NOT NULL,
103
+ target_route_id TEXT NOT NULL,
104
+ target_route_revision TEXT NOT NULL,
105
+ target_chat_id TEXT NOT NULL,
106
+ action TEXT NOT NULL CHECK(action IN ('instruction','request_reply')),
107
+ kind TEXT NOT NULL CHECK(kind IN ('instruction','request_reply','response')),
108
+ exchange_id TEXT NOT NULL,
109
+ parent_envelope_id TEXT,
110
+ parent_leg INTEGER,
111
+ used_legs INTEGER NOT NULL CHECK(used_legs BETWEEN 1 AND 6),
112
+ max_legs INTEGER NOT NULL CHECK(max_legs=6),
113
+ expires_at INTEGER NOT NULL,
114
+ body_json TEXT NOT NULL,
115
+ state TEXT NOT NULL CHECK(state IN (
116
+ 'prepared','authorized','queued','running','completed','failed'
117
+ )),
118
+ queued_id TEXT,
119
+ run_id TEXT,
120
+ admission_retry_at INTEGER,
121
+ admission_attempts INTEGER NOT NULL DEFAULT 0,
122
+ admission_last_error TEXT,
123
+ response_committed INTEGER NOT NULL DEFAULT 0
124
+ CHECK(response_committed IN (0,1)),
125
+ response_request_id TEXT,
126
+ response_request_digest TEXT,
127
+ response_body TEXT,
128
+ response_request_response INTEGER CHECK(
129
+ response_request_response IS NULL
130
+ OR response_request_response IN (0,1)
131
+ ),
132
+ response_retry_at INTEGER,
133
+ response_attempts INTEGER NOT NULL DEFAULT 0,
134
+ response_last_error TEXT,
135
+ result_hash TEXT,
136
+ error TEXT,
137
+ created_at INTEGER NOT NULL,
138
+ updated_at INTEGER NOT NULL
139
+ );
140
+ CREATE INDEX IF NOT EXISTS secure_peer_delivery_state
141
+ ON secure_peer_deliveries(state,updated_at);
142
+ CREATE INDEX IF NOT EXISTS secure_peer_delivery_run
143
+ ON secure_peer_deliveries(run_id,state);
144
+ CREATE UNIQUE INDEX IF NOT EXISTS secure_peer_delivery_queued_owner
145
+ ON secure_peer_deliveries(queued_id) WHERE queued_id IS NOT NULL;
146
+ CREATE UNIQUE INDEX IF NOT EXISTS secure_peer_delivery_run_owner
147
+ ON secure_peer_deliveries(run_id) WHERE run_id IS NOT NULL;
148
+ CREATE TABLE IF NOT EXISTS secure_peer_outbound_intents (
149
+ request_id TEXT PRIMARY KEY,
150
+ intent_digest TEXT NOT NULL,
151
+ source_session_id TEXT NOT NULL,
152
+ source_run_id TEXT NOT NULL,
153
+ connection_id TEXT NOT NULL,
154
+ source_route_id TEXT NOT NULL,
155
+ source_route_revision TEXT NOT NULL,
156
+ snapshot_json TEXT NOT NULL,
157
+ body TEXT NOT NULL,
158
+ action TEXT NOT NULL CHECK(action IN ('instruction','request_reply')),
159
+ expires_at INTEGER NOT NULL,
160
+ state TEXT NOT NULL CHECK(state IN ('pending','committed','failed')),
161
+ response_json TEXT,
162
+ retry_at INTEGER,
163
+ attempts INTEGER NOT NULL DEFAULT 0,
164
+ last_error TEXT,
165
+ created_at INTEGER NOT NULL,
166
+ updated_at INTEGER NOT NULL
167
+ );
168
+ CREATE INDEX IF NOT EXISTS secure_peer_outbound_pending
169
+ ON secure_peer_outbound_intents(state,retry_at,created_at);
170
+ """
171
+ )
172
+ # Development snapshots created before the full immutable
173
+ # claim landed are upgraded additively. No released build has
174
+ # written these rows, but keeping the migration safe avoids a
175
+ # local optional-feature boot failure while iterating.
176
+ columns = {
177
+ str(row["name"])
178
+ for row in connection.execute(
179
+ "PRAGMA table_info(secure_peer_deliveries)"
180
+ ).fetchall()
181
+ }
182
+ for name, declaration in (
183
+ ("request_id", "TEXT"),
184
+ ("lease_token", "TEXT"),
185
+ ("team_id", "TEXT"),
186
+ ("source_peer_id", "TEXT"),
187
+ ("target_peer_id", "TEXT"),
188
+ ("parent_leg", "INTEGER"),
189
+ ("response_request_id", "TEXT"),
190
+ ("response_request_digest", "TEXT"),
191
+ ("response_body", "TEXT"),
192
+ ("response_request_response", "INTEGER"),
193
+ ("response_retry_at", "INTEGER"),
194
+ ("response_attempts", "INTEGER NOT NULL DEFAULT 0"),
195
+ ("response_last_error", "TEXT"),
196
+ ("admission_retry_at", "INTEGER"),
197
+ ("admission_attempts", "INTEGER NOT NULL DEFAULT 0"),
198
+ ("admission_last_error", "TEXT"),
199
+ ):
200
+ if name not in columns:
201
+ connection.execute(
202
+ f"ALTER TABLE secure_peer_deliveries ADD COLUMN {name} {declaration}"
203
+ )
204
+ outbound_columns = {
205
+ str(row["name"])
206
+ for row in connection.execute(
207
+ "PRAGMA table_info(secure_peer_outbound_intents)"
208
+ ).fetchall()
209
+ }
210
+ for name in (
211
+ "connection_id",
212
+ "source_route_id",
213
+ "source_route_revision",
214
+ ):
215
+ if name not in outbound_columns:
216
+ connection.execute(
217
+ f"ALTER TABLE secure_peer_outbound_intents ADD COLUMN {name} TEXT"
218
+ )
219
+ # Unreleased development ledgers predate the indexed route
220
+ # projection. Recover it only from the already-digested
221
+ # immutable snapshot; malformed rows fail closed.
222
+ legacy_rows = connection.execute(
223
+ """SELECT request_id,snapshot_json FROM secure_peer_outbound_intents
224
+ WHERE connection_id IS NULL OR source_route_id IS NULL
225
+ OR source_route_revision IS NULL"""
226
+ ).fetchall()
227
+ for row in legacy_rows:
228
+ try:
229
+ snapshot = json.loads(row["snapshot_json"])
230
+ connection_id = str(snapshot.get("connection_id") or "")
231
+ source_route_id = str(snapshot.get("source_route_id") or "")
232
+ source_route_revision = str(
233
+ snapshot.get("source_route_revision") or ""
234
+ )
235
+ if not all(
236
+ (connection_id, source_route_id, source_route_revision)
237
+ ):
238
+ raise ValueError("missing outbound route identity")
239
+ except Exception:
240
+ connection.execute(
241
+ """UPDATE secure_peer_outbound_intents SET
242
+ state='failed',retry_at=NULL,
243
+ last_error='invalid legacy secure peer route snapshot',
244
+ connection_id='',source_route_id='',source_route_revision=''
245
+ WHERE request_id=?""",
246
+ (row["request_id"],),
247
+ )
248
+ else:
249
+ connection.execute(
250
+ """UPDATE secure_peer_outbound_intents SET
251
+ connection_id=?,source_route_id=?,source_route_revision=?
252
+ WHERE request_id=?""",
253
+ (
254
+ connection_id,
255
+ source_route_id,
256
+ source_route_revision,
257
+ row["request_id"],
258
+ ),
259
+ )
260
+ connection.execute(
261
+ """CREATE INDEX IF NOT EXISTS secure_peer_outbound_source
262
+ ON secure_peer_outbound_intents(state,source_session_id,created_at)"""
263
+ )
264
+ connection.execute(
265
+ """CREATE INDEX IF NOT EXISTS secure_peer_outbound_connection
266
+ ON secure_peer_outbound_intents(state,connection_id,created_at)"""
267
+ )
268
+ finally:
269
+ connection.close()
270
+
271
+ @staticmethod
272
+ def _public(row: sqlite3.Row | None) -> dict[str, Any] | None:
273
+ if row is None:
274
+ return None
275
+ item = dict(row)
276
+ item["body"] = json.loads(item.pop("body_json"))
277
+ return item
278
+
279
+ @staticmethod
280
+ def _claim(
281
+ envelope: Mapping[str, Any],
282
+ *,
283
+ transport_role: str,
284
+ connection_id: str,
285
+ lease_token: str,
286
+ target_chat_id: str,
287
+ ) -> tuple[dict[str, Any], str]:
288
+ if transport_role not in {"host", "client"}:
289
+ raise ValueError("secure peer delivery role is invalid")
290
+ action = str(envelope.get("action") or "")
291
+ kind = str(envelope.get("kind") or "")
292
+ if action not in {"instruction", "request_reply"}:
293
+ raise ValueError("secure peer delivery action is invalid")
294
+ if kind not in {"instruction", "request_reply", "response"}:
295
+ raise ValueError("secure peer delivery kind is invalid")
296
+ body = envelope.get("body")
297
+ if (
298
+ not isinstance(body, dict)
299
+ or set(body) != {"message"}
300
+ or not isinstance(body.get("message"), str)
301
+ or not body["message"].strip()
302
+ or len(body["message"]) > 100_000
303
+ ):
304
+ raise ValueError("secure peer delivery body is invalid")
305
+ exact = {
306
+ "envelope_id": str(envelope.get("envelope_id") or ""),
307
+ "request_id": str(envelope.get("request_id") or ""),
308
+ "transport_role": transport_role,
309
+ "connection_id": str(connection_id),
310
+ "lease_token": str(lease_token),
311
+ "team_id": str(envelope.get("team_id") or ""),
312
+ "source_peer_id": (
313
+ str(envelope.get("source_peer_id"))
314
+ if envelope.get("source_peer_id") is not None
315
+ else None
316
+ ),
317
+ "source_server_identity": str(envelope.get("source_server_identity") or ""),
318
+ "source_route_id": str(envelope.get("source_route_id") or ""),
319
+ "source_route_revision": str(envelope.get("source_route_revision") or ""),
320
+ "target_peer_id": (
321
+ str(envelope.get("target_peer_id"))
322
+ if envelope.get("target_peer_id") is not None
323
+ else None
324
+ ),
325
+ "target_server_identity": str(envelope.get("target_server_identity") or ""),
326
+ "target_route_id": str(envelope.get("target_route_id") or ""),
327
+ "target_route_revision": str(envelope.get("target_route_revision") or ""),
328
+ "target_chat_id": str(target_chat_id),
329
+ "action": action,
330
+ "kind": kind,
331
+ "exchange_id": str(envelope.get("exchange_id") or ""),
332
+ "parent_envelope_id": (
333
+ str(envelope.get("parent_envelope_id"))
334
+ if envelope.get("parent_envelope_id") is not None
335
+ else None
336
+ ),
337
+ "parent_leg": (
338
+ int(envelope.get("parent_leg"))
339
+ if envelope.get("parent_leg") is not None
340
+ else None
341
+ ),
342
+ "used_legs": int(envelope.get("used_legs") or 0),
343
+ "max_legs": int(envelope.get("max_legs") or 0),
344
+ "expires_at": int(envelope.get("expires_at") or 0),
345
+ "body": body,
346
+ }
347
+ required = (
348
+ "envelope_id",
349
+ "request_id",
350
+ "connection_id",
351
+ "lease_token",
352
+ "team_id",
353
+ "source_server_identity",
354
+ "source_route_id",
355
+ "source_route_revision",
356
+ "target_server_identity",
357
+ "target_route_id",
358
+ "target_route_revision",
359
+ "target_chat_id",
360
+ "exchange_id",
361
+ )
362
+ if any(not exact[key] for key in required):
363
+ raise ValueError("secure peer delivery identity is incomplete")
364
+ if exact["max_legs"] != 6 or not 1 <= exact["used_legs"] <= 6:
365
+ raise ValueError("secure peer delivery leg budget is invalid")
366
+ # The lease token belongs to a delivery *claim*, not the immutable
367
+ # envelope. A crash past the lease deadline legitimately redelivers
368
+ # the same envelope with a new token.
369
+ immutable = {
370
+ key: value for key, value in exact.items() if key != "lease_token"
371
+ }
372
+ digest = hashlib.sha256(canonical_json(immutable)).hexdigest()
373
+ return exact, digest
374
+
375
+ def prepare(
376
+ self,
377
+ envelope: Mapping[str, Any],
378
+ *,
379
+ transport_role: str,
380
+ connection_id: str,
381
+ lease_token: str,
382
+ target_chat_id: str,
383
+ ) -> tuple[dict[str, Any], bool]:
384
+ exact, digest = self._claim(
385
+ envelope,
386
+ transport_role=transport_role,
387
+ connection_id=connection_id,
388
+ lease_token=lease_token,
389
+ target_chat_id=target_chat_id,
390
+ )
391
+ timestamp = int(time.time())
392
+ with self._transaction() as connection:
393
+ row = connection.execute(
394
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
395
+ (exact["envelope_id"],),
396
+ ).fetchone()
397
+ if row is not None:
398
+ if row["claim_digest"] != digest:
399
+ raise RuntimeError("secure peer envelope identity changed")
400
+ if (
401
+ str(row["state"]) == "prepared"
402
+ and str(row["lease_token"] or "") != exact["lease_token"]
403
+ ):
404
+ connection.execute(
405
+ """UPDATE secure_peer_deliveries
406
+ SET lease_token=?,updated_at=?
407
+ WHERE envelope_id=? AND state='prepared' AND claim_digest=?""",
408
+ (
409
+ exact["lease_token"],
410
+ timestamp,
411
+ exact["envelope_id"],
412
+ digest,
413
+ ),
414
+ )
415
+ row = connection.execute(
416
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
417
+ (exact["envelope_id"],),
418
+ ).fetchone()
419
+ public = self._public(row)
420
+ assert public is not None
421
+ return public, False
422
+ connection.execute(
423
+ """UPDATE secure_peer_deliveries SET state='failed',
424
+ error='secure peer exchange expired',updated_at=?
425
+ WHERE state='prepared' AND expires_at<=?""",
426
+ (timestamp, timestamp),
427
+ )
428
+ nonterminal_count = int(
429
+ connection.execute(
430
+ """SELECT COUNT(*) AS count FROM secure_peer_deliveries
431
+ WHERE state IN ('prepared','authorized','queued','running')"""
432
+ ).fetchone()["count"]
433
+ )
434
+ if nonterminal_count >= MAX_NONTERMINAL_DELIVERIES:
435
+ raise RuntimeError("secure peer delivery capacity is full")
436
+ connection.execute(
437
+ """INSERT INTO secure_peer_deliveries(
438
+ envelope_id,request_id,claim_digest,transport_role,connection_id,lease_token,
439
+ team_id,source_peer_id,
440
+ source_server_identity,source_route_id,source_route_revision,
441
+ target_peer_id,
442
+ target_server_identity,target_route_id,target_route_revision,
443
+ target_chat_id,action,kind,exchange_id,parent_envelope_id,parent_leg,
444
+ used_legs,max_legs,expires_at,body_json,state,created_at,updated_at
445
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'prepared',?,?)""",
446
+ (
447
+ exact["envelope_id"],
448
+ exact["request_id"],
449
+ digest,
450
+ exact["transport_role"],
451
+ exact["connection_id"],
452
+ exact["lease_token"],
453
+ exact["team_id"],
454
+ exact["source_peer_id"],
455
+ exact["source_server_identity"],
456
+ exact["source_route_id"],
457
+ exact["source_route_revision"],
458
+ exact["target_peer_id"],
459
+ exact["target_server_identity"],
460
+ exact["target_route_id"],
461
+ exact["target_route_revision"],
462
+ exact["target_chat_id"],
463
+ exact["action"],
464
+ exact["kind"],
465
+ exact["exchange_id"],
466
+ exact["parent_envelope_id"],
467
+ exact["parent_leg"],
468
+ exact["used_legs"],
469
+ exact["max_legs"],
470
+ exact["expires_at"],
471
+ canonical_json(exact["body"]).decode("utf-8"),
472
+ timestamp,
473
+ timestamp,
474
+ ),
475
+ )
476
+ row = connection.execute(
477
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
478
+ (exact["envelope_id"],),
479
+ ).fetchone()
480
+ public = self._public(row)
481
+ assert public is not None
482
+ return public, True
483
+
484
+ def get(self, envelope_id: str) -> dict[str, Any] | None:
485
+ with self._lock:
486
+ connection = self._connect()
487
+ try:
488
+ return self._public(connection.execute(
489
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
490
+ (envelope_id,),
491
+ ).fetchone())
492
+ finally:
493
+ connection.close()
494
+
495
+ def _transition(
496
+ self,
497
+ envelope_id: str,
498
+ *,
499
+ expected: set[str],
500
+ state: str,
501
+ **values: Any,
502
+ ) -> dict[str, Any] | None:
503
+ if state not in DELIVERY_STATES:
504
+ raise ValueError("secure peer delivery state is invalid")
505
+ allowed = {"queued_id", "run_id", "result_hash", "error"}
506
+ patch = {key: value for key, value in values.items() if key in allowed}
507
+ patch.update({"state": state, "updated_at": int(time.time())})
508
+ assignments = ",".join(f"{key}=?" for key in patch)
509
+ placeholders = ",".join("?" for _ in expected)
510
+ arguments = [*patch.values(), envelope_id, *sorted(expected)]
511
+ with self._transaction() as connection:
512
+ changed = connection.execute(
513
+ f"UPDATE secure_peer_deliveries SET {assignments} "
514
+ f"WHERE envelope_id=? AND state IN ({placeholders})",
515
+ arguments,
516
+ ).rowcount
517
+ if changed != 1:
518
+ return None
519
+ return self._public(connection.execute(
520
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
521
+ (envelope_id,),
522
+ ).fetchone())
523
+
524
+ def authorize(self, envelope_id: str) -> dict[str, Any] | None:
525
+ return self._transition(
526
+ envelope_id,
527
+ expected={"prepared", "authorized"},
528
+ state="authorized",
529
+ )
530
+
531
+ def fail_ownerless_authorized(
532
+ self,
533
+ envelope_id: str,
534
+ *,
535
+ error: str,
536
+ ) -> dict[str, Any] | None:
537
+ """Fail only a receipt-accepted delivery that has no queued/run owner."""
538
+
539
+ timestamp = int(time.time())
540
+ with self._transaction() as connection:
541
+ connection.execute(
542
+ """UPDATE secure_peer_deliveries SET
543
+ state='failed',error=?,admission_retry_at=NULL,
544
+ admission_last_error=NULL,updated_at=?
545
+ WHERE envelope_id=? AND state='authorized'
546
+ AND queued_id IS NULL AND run_id IS NULL""",
547
+ (str(error)[:400], timestamp, envelope_id),
548
+ )
549
+ return self._public(connection.execute(
550
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
551
+ (envelope_id,),
552
+ ).fetchone())
553
+
554
+ def bind_owner(
555
+ self,
556
+ envelope_id: str,
557
+ *,
558
+ queued_id: str | None,
559
+ run_id: str | None,
560
+ ) -> dict[str, Any] | None:
561
+ if queued_id is None and run_id is None:
562
+ raise ValueError("secure peer delivery owner is empty")
563
+ timestamp = int(time.time())
564
+ with self._transaction() as connection:
565
+ row = connection.execute(
566
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
567
+ (envelope_id,),
568
+ ).fetchone()
569
+ if row is None:
570
+ return None
571
+ state = str(row["state"])
572
+ current_queued = row["queued_id"]
573
+ current_run = row["run_id"]
574
+ if state == "authorized":
575
+ next_state = "running" if run_id is not None else "queued"
576
+ connection.execute(
577
+ """UPDATE secure_peer_deliveries
578
+ SET state=?,queued_id=?,run_id=?,admission_retry_at=NULL,
579
+ admission_last_error=NULL,updated_at=?
580
+ WHERE envelope_id=? AND state='authorized'
581
+ AND queued_id IS NULL AND run_id IS NULL""",
582
+ (next_state, queued_id, run_id, timestamp, envelope_id),
583
+ )
584
+ elif state == "queued" and run_id is not None:
585
+ if current_queued != queued_id or current_run is not None:
586
+ return None
587
+ connection.execute(
588
+ """UPDATE secure_peer_deliveries SET state='running',run_id=?,
589
+ admission_retry_at=NULL,admission_last_error=NULL,updated_at=?
590
+ WHERE envelope_id=? AND state='queued' AND queued_id=? AND run_id IS NULL""",
591
+ (run_id, timestamp, envelope_id, queued_id),
592
+ )
593
+ elif state == "queued":
594
+ if current_queued != queued_id or current_run is not None:
595
+ return None
596
+ elif state == "running":
597
+ if current_queued != queued_id or current_run != run_id:
598
+ return None
599
+ else:
600
+ return None
601
+ return self._public(connection.execute(
602
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
603
+ (envelope_id,),
604
+ ).fetchone())
605
+
606
+ def defer_admission(
607
+ self,
608
+ envelope_id: str,
609
+ *,
610
+ error: str,
611
+ ) -> dict[str, Any] | None:
612
+ now = int(time.time())
613
+ with self._transaction() as connection:
614
+ row = connection.execute(
615
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
616
+ (envelope_id,),
617
+ ).fetchone()
618
+ if (
619
+ row is None
620
+ or row["state"] != "authorized"
621
+ or row["queued_id"] is not None
622
+ or row["run_id"] is not None
623
+ ):
624
+ return self._public(row)
625
+ attempts = int(row["admission_attempts"] or 0) + 1
626
+ delay = min(300, 2 ** min(attempts, 8))
627
+ connection.execute(
628
+ """UPDATE secure_peer_deliveries SET
629
+ admission_attempts=?,admission_retry_at=?,admission_last_error=?,updated_at=?
630
+ WHERE envelope_id=? AND state='authorized'
631
+ AND queued_id IS NULL AND run_id IS NULL""",
632
+ (attempts, now + delay, str(error)[:400], now, envelope_id),
633
+ )
634
+ return self._public(connection.execute(
635
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
636
+ (envelope_id,),
637
+ ).fetchone())
638
+
639
+ def pending_admissions(
640
+ self,
641
+ *,
642
+ limit: int = 20,
643
+ now: int | None = None,
644
+ ) -> list[dict[str, Any]]:
645
+ if type(limit) is not int or not 1 <= limit <= 50:
646
+ raise ValueError("secure peer admission retry limit is invalid")
647
+ timestamp = int(time.time()) if now is None else int(now)
648
+ with self._lock:
649
+ connection = self._connect()
650
+ try:
651
+ rows = connection.execute(
652
+ """SELECT * FROM secure_peer_deliveries
653
+ WHERE state='authorized' AND queued_id IS NULL AND run_id IS NULL
654
+ AND COALESCE(admission_retry_at,0)<=?
655
+ ORDER BY COALESCE(admission_retry_at,0),created_at,envelope_id
656
+ LIMIT ?""",
657
+ (timestamp, limit),
658
+ ).fetchall()
659
+ return [self._public(row) for row in rows if row is not None]
660
+ finally:
661
+ connection.close()
662
+
663
+ def nonterminal_for_chat(self, chat_id: str) -> list[dict[str, Any]]:
664
+ with self._lock:
665
+ connection = self._connect()
666
+ try:
667
+ rows = connection.execute(
668
+ """SELECT * FROM secure_peer_deliveries
669
+ WHERE target_chat_id=?
670
+ AND state IN ('prepared','authorized','queued','running')
671
+ ORDER BY created_at,envelope_id""",
672
+ (chat_id,),
673
+ ).fetchall()
674
+ return [self._public(row) for row in rows if row is not None]
675
+ finally:
676
+ connection.close()
677
+
678
+ def finish(
679
+ self,
680
+ envelope_id: str,
681
+ *,
682
+ succeeded: bool,
683
+ result_text: str = "",
684
+ error: str | None = None,
685
+ ) -> dict[str, Any] | None:
686
+ return self._transition(
687
+ envelope_id,
688
+ expected=(
689
+ {"authorized", "queued", "running"}
690
+ if succeeded
691
+ else {"prepared", "authorized", "queued", "running"}
692
+ ),
693
+ state="completed" if succeeded else "failed",
694
+ result_hash=(
695
+ hashlib.sha256(result_text.encode("utf-8")).hexdigest()
696
+ if result_text
697
+ else None
698
+ ),
699
+ error=error,
700
+ )
701
+
702
+ def prepare_response(
703
+ self,
704
+ envelope_id: str,
705
+ *,
706
+ request_id: str,
707
+ body: str,
708
+ request_response: bool,
709
+ ) -> dict[str, Any] | None:
710
+ if (
711
+ not request_id
712
+ or not isinstance(body, str)
713
+ or not body.strip()
714
+ or len(body) > 100_000
715
+ or type(request_response) is not bool
716
+ ):
717
+ raise ValueError("secure peer response intent is invalid")
718
+ intent = {
719
+ "request_id": request_id,
720
+ "body": body,
721
+ "request_response": request_response,
722
+ }
723
+ digest = hashlib.sha256(canonical_json(intent)).hexdigest()
724
+ timestamp = int(time.time())
725
+ with self._transaction() as connection:
726
+ row = connection.execute(
727
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
728
+ (envelope_id,),
729
+ ).fetchone()
730
+ if (
731
+ row is None
732
+ or row["state"] not in {"authorized", "queued", "running"}
733
+ or row["kind"] == "instruction"
734
+ ):
735
+ return None
736
+ existing = row["response_request_digest"]
737
+ if existing is not None:
738
+ if existing != digest:
739
+ raise RuntimeError("secure peer response intent changed")
740
+ return self._public(row)
741
+ connection.execute(
742
+ """UPDATE secure_peer_deliveries SET
743
+ response_request_id=?,response_request_digest=?,response_body=?,
744
+ response_request_response=?,response_retry_at=?,
745
+ response_attempts=0,response_last_error=NULL,updated_at=?
746
+ WHERE envelope_id=? AND response_request_digest IS NULL
747
+ AND response_committed=0
748
+ AND state IN ('authorized','queued','running')""",
749
+ (
750
+ request_id,
751
+ digest,
752
+ body,
753
+ int(request_response),
754
+ timestamp,
755
+ timestamp,
756
+ envelope_id,
757
+ ),
758
+ )
759
+ return self._public(connection.execute(
760
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
761
+ (envelope_id,),
762
+ ).fetchone())
763
+
764
+ def mark_response_committed(
765
+ self,
766
+ envelope_id: str,
767
+ *,
768
+ request_id: str | None = None,
769
+ ) -> dict[str, Any] | None:
770
+ with self._transaction() as connection:
771
+ changed = connection.execute(
772
+ """UPDATE secure_peer_deliveries
773
+ SET response_committed=1,response_retry_at=NULL,
774
+ response_last_error=NULL,updated_at=?
775
+ WHERE envelope_id=? AND state IN ('authorized','queued','running')
776
+ AND response_committed=0
777
+ AND (? IS NULL OR response_request_id=?)""",
778
+ (int(time.time()), envelope_id, request_id, request_id),
779
+ ).rowcount
780
+ if changed not in {0, 1}:
781
+ raise RuntimeError("secure peer response state is ambiguous")
782
+ return self._public(connection.execute(
783
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
784
+ (envelope_id,),
785
+ ).fetchone())
786
+
787
+ def clear_response_intent(
788
+ self,
789
+ envelope_id: str,
790
+ *,
791
+ request_id: str,
792
+ ) -> dict[str, Any] | None:
793
+ with self._transaction() as connection:
794
+ connection.execute(
795
+ """UPDATE secure_peer_deliveries SET
796
+ response_request_id=NULL,response_request_digest=NULL,
797
+ response_body=NULL,response_request_response=NULL,
798
+ response_retry_at=NULL,response_attempts=0,
799
+ response_last_error=NULL,updated_at=?
800
+ WHERE envelope_id=? AND response_request_id=?
801
+ AND response_committed=0
802
+ AND state IN ('authorized','queued','running')""",
803
+ (int(time.time()), envelope_id, request_id),
804
+ )
805
+ return self._public(connection.execute(
806
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
807
+ (envelope_id,),
808
+ ).fetchone())
809
+
810
+ def defer_response(
811
+ self,
812
+ envelope_id: str,
813
+ *,
814
+ request_id: str,
815
+ error: str,
816
+ ) -> dict[str, Any] | None:
817
+ """Persist bounded exponential retry state for one response intent."""
818
+
819
+ now = int(time.time())
820
+ with self._transaction() as connection:
821
+ row = connection.execute(
822
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
823
+ (envelope_id,),
824
+ ).fetchone()
825
+ if (
826
+ row is None
827
+ or row["state"] not in {"authorized", "queued", "running"}
828
+ or row["response_request_id"] != request_id
829
+ or int(row["response_committed"] or 0) != 0
830
+ ):
831
+ return self._public(row)
832
+ attempts = int(row["response_attempts"] or 0) + 1
833
+ delay = min(300, 2 ** min(attempts, 8))
834
+ connection.execute(
835
+ """UPDATE secure_peer_deliveries SET
836
+ response_attempts=?,response_retry_at=?,response_last_error=?,updated_at=?
837
+ WHERE envelope_id=? AND response_request_id=?
838
+ AND response_committed=0
839
+ AND state IN ('authorized','queued','running')""",
840
+ (
841
+ attempts,
842
+ now + delay,
843
+ str(error)[:400],
844
+ now,
845
+ envelope_id,
846
+ request_id,
847
+ ),
848
+ )
849
+ return self._public(connection.execute(
850
+ "SELECT * FROM secure_peer_deliveries WHERE envelope_id=?",
851
+ (envelope_id,),
852
+ ).fetchone())
853
+
854
+ def pending_responses(
855
+ self,
856
+ *,
857
+ limit: int = 8,
858
+ now: int | None = None,
859
+ ) -> list[dict[str, Any]]:
860
+ if (
861
+ type(limit) is not int
862
+ or not 1 <= limit <= MAX_NONTERMINAL_DELIVERIES
863
+ ):
864
+ raise ValueError("secure peer response retry limit is invalid")
865
+ timestamp = int(time.time()) if now is None else int(now)
866
+ with self._lock:
867
+ connection = self._connect()
868
+ try:
869
+ rows = connection.execute(
870
+ """SELECT * FROM secure_peer_deliveries
871
+ WHERE state IN ('authorized','queued','running')
872
+ AND response_request_id IS NOT NULL
873
+ AND response_committed=0
874
+ AND COALESCE(response_retry_at,0)<=?
875
+ ORDER BY COALESCE(response_retry_at,0),created_at,envelope_id
876
+ LIMIT ?""",
877
+ (timestamp, limit),
878
+ ).fetchall()
879
+ return [self._public(row) for row in rows if row is not None]
880
+ finally:
881
+ connection.close()
882
+
883
+ def recoverable(self) -> list[dict[str, Any]]:
884
+ with self._lock:
885
+ connection = self._connect()
886
+ try:
887
+ rows = connection.execute(
888
+ """SELECT * FROM secure_peer_deliveries
889
+ WHERE state IN ('prepared','authorized','queued','running')
890
+ ORDER BY created_at,envelope_id"""
891
+ ).fetchall()
892
+ return [self._public(row) for row in rows if row is not None]
893
+ finally:
894
+ connection.close()
895
+
896
+ def for_run(self, run_id: str) -> dict[str, Any] | None:
897
+ with self._lock:
898
+ connection = self._connect()
899
+ try:
900
+ return self._public(connection.execute(
901
+ "SELECT * FROM secure_peer_deliveries WHERE run_id=?",
902
+ (run_id,),
903
+ ).fetchone())
904
+ finally:
905
+ connection.close()
906
+
907
+ @staticmethod
908
+ def _public_outbound(row: sqlite3.Row | None) -> dict[str, Any] | None:
909
+ if row is None:
910
+ return None
911
+ item = dict(row)
912
+ item["snapshot"] = json.loads(item.pop("snapshot_json"))
913
+ response = item.pop("response_json")
914
+ item["response"] = json.loads(response) if response else None
915
+ return item
916
+
917
+ def prepare_outbound(
918
+ self,
919
+ *,
920
+ request_id: str,
921
+ source_session_id: str,
922
+ source_run_id: str,
923
+ snapshot: Mapping[str, Any],
924
+ body: str,
925
+ action: str,
926
+ expires_at: int,
927
+ ) -> tuple[dict[str, Any], bool]:
928
+ connection_id = str(snapshot.get("connection_id") or "")
929
+ source_route_id = str(snapshot.get("source_route_id") or "")
930
+ source_route_revision = str(snapshot.get("source_route_revision") or "")
931
+ if (
932
+ not request_id
933
+ or not source_session_id
934
+ or not source_run_id
935
+ or not connection_id
936
+ or not source_route_id
937
+ or not source_route_revision
938
+ or str(snapshot.get("source_chat_id") or "") != source_session_id
939
+ or str(snapshot.get("action") or "") != action
940
+ or action not in {"instruction", "request_reply"}
941
+ or not isinstance(body, str)
942
+ or not body.strip()
943
+ or len(body) > 100_000
944
+ or not isinstance(snapshot, Mapping)
945
+ ):
946
+ raise ValueError("secure peer outbound intent is invalid")
947
+ exact = {
948
+ "request_id": request_id,
949
+ "source_session_id": source_session_id,
950
+ "source_run_id": source_run_id,
951
+ "snapshot": dict(snapshot),
952
+ "body": body,
953
+ "action": action,
954
+ "expires_at": int(expires_at),
955
+ }
956
+ digest = hashlib.sha256(canonical_json(exact)).hexdigest()
957
+ now = int(time.time())
958
+ with self._transaction() as connection:
959
+ row = connection.execute(
960
+ "SELECT * FROM secure_peer_outbound_intents WHERE request_id=?",
961
+ (request_id,),
962
+ ).fetchone()
963
+ if row is not None:
964
+ if row["intent_digest"] != digest:
965
+ raise RuntimeError("secure peer outbound intent changed")
966
+ public = self._public_outbound(row)
967
+ assert public is not None
968
+ return public, False
969
+ connection.execute(
970
+ """INSERT INTO secure_peer_outbound_intents(
971
+ request_id,intent_digest,source_session_id,source_run_id,
972
+ connection_id,source_route_id,source_route_revision,
973
+ snapshot_json,body,action,expires_at,state,retry_at,
974
+ created_at,updated_at
975
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,'pending',?,?,?)""",
976
+ (
977
+ request_id,
978
+ digest,
979
+ source_session_id,
980
+ source_run_id,
981
+ connection_id,
982
+ source_route_id,
983
+ source_route_revision,
984
+ canonical_json(dict(snapshot)).decode("utf-8"),
985
+ body,
986
+ action,
987
+ int(expires_at),
988
+ now,
989
+ now,
990
+ now,
991
+ ),
992
+ )
993
+ public = self._public_outbound(connection.execute(
994
+ "SELECT * FROM secure_peer_outbound_intents WHERE request_id=?",
995
+ (request_id,),
996
+ ).fetchone())
997
+ assert public is not None
998
+ return public, True
999
+
1000
+ @staticmethod
1001
+ def _expire_pending_outbound(
1002
+ connection: sqlite3.Connection,
1003
+ timestamp: int,
1004
+ ) -> None:
1005
+ connection.execute(
1006
+ """UPDATE secure_peer_outbound_intents SET
1007
+ state='failed',retry_at=NULL,last_error='secure peer exchange expired',
1008
+ updated_at=? WHERE state='pending' AND expires_at<=?""",
1009
+ (timestamp, timestamp),
1010
+ )
1011
+
1012
+ def pending_outbound_for_chat(
1013
+ self,
1014
+ source_session_id: str,
1015
+ *,
1016
+ now: int | None = None,
1017
+ ) -> list[dict[str, Any]]:
1018
+ timestamp = int(time.time()) if now is None else int(now)
1019
+ with self._transaction() as connection:
1020
+ self._expire_pending_outbound(connection, timestamp)
1021
+ rows = connection.execute(
1022
+ """SELECT * FROM secure_peer_outbound_intents
1023
+ WHERE state='pending' AND source_session_id=?
1024
+ ORDER BY created_at,request_id""",
1025
+ (str(source_session_id),),
1026
+ ).fetchall()
1027
+ return [
1028
+ item
1029
+ for row in rows
1030
+ if (item := self._public_outbound(row)) is not None
1031
+ ]
1032
+
1033
+ def pending_outbound_for_route(
1034
+ self,
1035
+ connection_id: str,
1036
+ source_route_id: str,
1037
+ source_route_revision: str,
1038
+ *,
1039
+ now: int | None = None,
1040
+ ) -> list[dict[str, Any]]:
1041
+ timestamp = int(time.time()) if now is None else int(now)
1042
+ with self._transaction() as connection:
1043
+ self._expire_pending_outbound(connection, timestamp)
1044
+ rows = connection.execute(
1045
+ """SELECT * FROM secure_peer_outbound_intents
1046
+ WHERE state='pending' AND connection_id=? AND source_route_id=?
1047
+ AND source_route_revision=? ORDER BY created_at,request_id""",
1048
+ (connection_id, source_route_id, source_route_revision),
1049
+ ).fetchall()
1050
+ return [
1051
+ item
1052
+ for row in rows
1053
+ if (item := self._public_outbound(row)) is not None
1054
+ ]
1055
+
1056
+ def pending_outbound_for_connection(
1057
+ self,
1058
+ connection_id: str,
1059
+ *,
1060
+ now: int | None = None,
1061
+ ) -> list[dict[str, Any]]:
1062
+ timestamp = int(time.time()) if now is None else int(now)
1063
+ with self._transaction() as connection:
1064
+ self._expire_pending_outbound(connection, timestamp)
1065
+ rows = connection.execute(
1066
+ """SELECT * FROM secure_peer_outbound_intents
1067
+ WHERE state='pending' AND connection_id=?
1068
+ ORDER BY created_at,request_id""",
1069
+ (connection_id,),
1070
+ ).fetchall()
1071
+ return [
1072
+ item
1073
+ for row in rows
1074
+ if (item := self._public_outbound(row)) is not None
1075
+ ]
1076
+
1077
+ def nonterminal_for_connection(
1078
+ self,
1079
+ connection_id: str,
1080
+ ) -> list[dict[str, Any]]:
1081
+ with self._lock:
1082
+ connection = self._connect()
1083
+ try:
1084
+ rows = connection.execute(
1085
+ """SELECT * FROM secure_peer_deliveries
1086
+ WHERE connection_id=?
1087
+ AND state IN ('prepared','authorized','queued','running')
1088
+ ORDER BY created_at,envelope_id""",
1089
+ (connection_id,),
1090
+ ).fetchall()
1091
+ return [
1092
+ item
1093
+ for row in rows
1094
+ if (item := self._public(row)) is not None
1095
+ ]
1096
+ finally:
1097
+ connection.close()
1098
+
1099
+ def outbound(self, request_id: str) -> dict[str, Any] | None:
1100
+ with self._lock:
1101
+ connection = self._connect()
1102
+ try:
1103
+ return self._public_outbound(connection.execute(
1104
+ "SELECT * FROM secure_peer_outbound_intents WHERE request_id=?",
1105
+ (request_id,),
1106
+ ).fetchone())
1107
+ finally:
1108
+ connection.close()
1109
+
1110
+ def commit_outbound(
1111
+ self,
1112
+ request_id: str,
1113
+ response: Mapping[str, Any],
1114
+ ) -> dict[str, Any] | None:
1115
+ response_text = canonical_json(dict(response)).decode("utf-8")
1116
+ now = int(time.time())
1117
+ with self._transaction() as connection:
1118
+ row = connection.execute(
1119
+ "SELECT * FROM secure_peer_outbound_intents WHERE request_id=?",
1120
+ (request_id,),
1121
+ ).fetchone()
1122
+ if row is None:
1123
+ return None
1124
+ if row["state"] == "committed":
1125
+ if str(row["response_json"] or "") != response_text:
1126
+ raise RuntimeError("secure peer outbound result changed")
1127
+ return self._public_outbound(row)
1128
+ if row["state"] != "pending":
1129
+ return self._public_outbound(row)
1130
+ connection.execute(
1131
+ """UPDATE secure_peer_outbound_intents SET
1132
+ state='committed',response_json=?,retry_at=NULL,last_error=NULL,updated_at=?
1133
+ WHERE request_id=? AND state='pending'""",
1134
+ (response_text, now, request_id),
1135
+ )
1136
+ return self._public_outbound(connection.execute(
1137
+ "SELECT * FROM secure_peer_outbound_intents WHERE request_id=?",
1138
+ (request_id,),
1139
+ ).fetchone())
1140
+
1141
+ def defer_outbound(self, request_id: str, error: str) -> dict[str, Any] | None:
1142
+ now = int(time.time())
1143
+ with self._transaction() as connection:
1144
+ row = connection.execute(
1145
+ "SELECT * FROM secure_peer_outbound_intents WHERE request_id=?",
1146
+ (request_id,),
1147
+ ).fetchone()
1148
+ if row is None or row["state"] != "pending":
1149
+ return self._public_outbound(row)
1150
+ attempts = int(row["attempts"] or 0) + 1
1151
+ delay = min(300, 2 ** min(attempts, 8))
1152
+ connection.execute(
1153
+ """UPDATE secure_peer_outbound_intents SET
1154
+ attempts=?,retry_at=?,last_error=?,updated_at=?
1155
+ WHERE request_id=? AND state='pending'""",
1156
+ (attempts, now + delay, str(error)[:400], now, request_id),
1157
+ )
1158
+ return self._public_outbound(connection.execute(
1159
+ "SELECT * FROM secure_peer_outbound_intents WHERE request_id=?",
1160
+ (request_id,),
1161
+ ).fetchone())
1162
+
1163
+ def fail_outbound(self, request_id: str, error: str) -> dict[str, Any] | None:
1164
+ now = int(time.time())
1165
+ with self._transaction() as connection:
1166
+ connection.execute(
1167
+ """UPDATE secure_peer_outbound_intents SET
1168
+ state='failed',retry_at=NULL,last_error=?,updated_at=?
1169
+ WHERE request_id=? AND state='pending'""",
1170
+ (str(error)[:400], now, request_id),
1171
+ )
1172
+ return self._public_outbound(connection.execute(
1173
+ "SELECT * FROM secure_peer_outbound_intents WHERE request_id=?",
1174
+ (request_id,),
1175
+ ).fetchone())
1176
+
1177
+ def pending_outbound(
1178
+ self,
1179
+ *,
1180
+ limit: int = 8,
1181
+ now: int | None = None,
1182
+ ) -> list[dict[str, Any]]:
1183
+ if type(limit) is not int or not 1 <= limit <= 50:
1184
+ raise ValueError("secure peer outbound retry limit is invalid")
1185
+ timestamp = int(time.time()) if now is None else int(now)
1186
+ with self._lock:
1187
+ connection = self._connect()
1188
+ try:
1189
+ rows = connection.execute(
1190
+ """SELECT * FROM secure_peer_outbound_intents
1191
+ WHERE state='pending' AND COALESCE(retry_at,0)<=?
1192
+ ORDER BY COALESCE(retry_at,0),created_at,request_id LIMIT ?""",
1193
+ (timestamp, limit),
1194
+ ).fetchall()
1195
+ return [
1196
+ self._public_outbound(row)
1197
+ for row in rows
1198
+ if row is not None
1199
+ ]
1200
+ finally:
1201
+ connection.close()
1202
+
1203
+ def recoverable_outbound(self, *, limit: int = 50) -> list[dict[str, Any]]:
1204
+ """Return every nonterminal outbound intent, including future retries."""
1205
+
1206
+ if type(limit) is not int or not 1 <= limit <= 50:
1207
+ raise ValueError("secure peer outbound recovery limit is invalid")
1208
+ with self._lock:
1209
+ connection = self._connect()
1210
+ try:
1211
+ rows = connection.execute(
1212
+ """SELECT * FROM secure_peer_outbound_intents
1213
+ WHERE state='pending'
1214
+ ORDER BY created_at,request_id LIMIT ?""",
1215
+ (limit,),
1216
+ ).fetchall()
1217
+ return [
1218
+ self._public_outbound(row)
1219
+ for row in rows
1220
+ if row is not None
1221
+ ]
1222
+ finally:
1223
+ connection.close()
1224
+
1225
+ def prune(self, *, retain_seconds: int = 30 * 24 * 60 * 60) -> int:
1226
+ cutoff = int(time.time()) - retain_seconds
1227
+ with self._transaction() as connection:
1228
+ deleted = int(connection.execute(
1229
+ "DELETE FROM secure_peer_deliveries WHERE state IN ('completed','failed') AND updated_at<?",
1230
+ (cutoff,),
1231
+ ).rowcount or 0)
1232
+ deleted += int(connection.execute(
1233
+ "DELETE FROM secure_peer_outbound_intents WHERE state IN ('committed','failed') AND updated_at<?",
1234
+ (cutoff,),
1235
+ ).rowcount or 0)
1236
+ return deleted