patchr 0.1.0__py3-none-any.whl

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 (116) hide show
  1. apps/__init__.py +2 -0
  2. apps/api/__init__.py +2 -0
  3. apps/api/main.py +652 -0
  4. apps/benchmarks/__init__.py +1 -0
  5. apps/benchmarks/main.py +20 -0
  6. apps/sandbox/__init__.py +1 -0
  7. apps/sandbox/main.py +20 -0
  8. apps/worker/__init__.py +2 -0
  9. apps/worker/main.py +15 -0
  10. apps/worker/verify.py +14 -0
  11. patchr/__init__.py +12 -0
  12. patchr/sdk/__init__.py +20 -0
  13. patchr/sdk/client.py +12 -0
  14. patchr-0.1.0.dist-info/METADATA +137 -0
  15. patchr-0.1.0.dist-info/RECORD +116 -0
  16. patchr-0.1.0.dist-info/WHEEL +5 -0
  17. patchr-0.1.0.dist-info/entry_points.txt +5 -0
  18. patchr-0.1.0.dist-info/licenses/LICENSE +17 -0
  19. patchr-0.1.0.dist-info/top_level.txt +3 -0
  20. picux/__init__.py +6 -0
  21. picux/agents/__init__.py +5 -0
  22. picux/agents/registry.py +204 -0
  23. picux/api/__init__.py +5 -0
  24. picux/api/service.py +5075 -0
  25. picux/audit/__init__.py +31 -0
  26. picux/audit/activity.py +97 -0
  27. picux/audit/observability.py +55 -0
  28. picux/audit/verification/__init__.py +21 -0
  29. picux/audit/verification/ledger.py +633 -0
  30. picux/benchmarks/__init__.py +5 -0
  31. picux/benchmarks/local.py +286 -0
  32. picux/config.py +140 -0
  33. picux/contracts/__init__.py +22 -0
  34. picux/contracts/handshake.py +122 -0
  35. picux/contracts/integration.py +385 -0
  36. picux/contracts/openapi.py +187 -0
  37. picux/contracts/protocol_map.py +152 -0
  38. picux/contracts/routes.py +980 -0
  39. picux/contracts/schema_catalog.py +125 -0
  40. picux/core/__init__.py +17 -0
  41. picux/core/models.py +148 -0
  42. picux/core/router.py +131 -0
  43. picux/core/runtime.py +42 -0
  44. picux/core/state_machine.py +38 -0
  45. picux/domains/__init__.py +2 -0
  46. picux/domains/bridge/HostRun.py +1104 -0
  47. picux/domains/bridge/__init__.py +6 -0
  48. picux/domains/bridge/engine.py +345 -0
  49. picux/domains/hunt/__init__.py +6 -0
  50. picux/domains/hunt/engine.py +307 -0
  51. picux/domains/hunt/models.py +88 -0
  52. picux/domains/pay/__init__.py +16 -0
  53. picux/domains/pay/adapters.py +607 -0
  54. picux/domains/pay/engine.py +950 -0
  55. picux/domains/pay/models.py +95 -0
  56. picux/domains/proxy/__init__.py +5 -0
  57. picux/domains/proxy/engine.py +466 -0
  58. picux/domains/resolve/__init__.py +5 -0
  59. picux/domains/resolve/engine.py +546 -0
  60. picux/orchestrator/__init__.py +3 -0
  61. picux/orchestrator/engine.py +2840 -0
  62. picux/portals/__init__.py +17 -0
  63. picux/portals/templates.py +272 -0
  64. picux/protocols/__init__.py +1 -0
  65. picux/protocols/a2a/__init__.py +6 -0
  66. picux/protocols/a2a/client.py +51 -0
  67. picux/protocols/a2a/envelope.py +132 -0
  68. picux/protocols/mcp/__init__.py +7 -0
  69. picux/protocols/mcp/client.py +69 -0
  70. picux/protocols/mcp/contract.py +67 -0
  71. picux/protocols/mcp/server.py +76 -0
  72. picux/sandbox/__init__.py +6 -0
  73. picux/sandbox/midnight_arbitrage.py +215 -0
  74. picux/sandbox/models.py +90 -0
  75. picux/sdk/__init__.py +13 -0
  76. picux/sdk/client.py +768 -0
  77. picux/sdk/external.py +245 -0
  78. picux/security/__init__.py +18 -0
  79. picux/security/auth.py +86 -0
  80. picux/security/config_validator.py +58 -0
  81. picux/security/policy.py +158 -0
  82. picux/security/secrets.py +144 -0
  83. picux/signals/__init__.py +1 -0
  84. picux/signals/community/__init__.py +24 -0
  85. picux/signals/community/adapters/__init__.py +7 -0
  86. picux/signals/community/adapters/reddit.py +37 -0
  87. picux/signals/community/adapters/shopify.py +23 -0
  88. picux/signals/community/adapters/web.py +23 -0
  89. picux/signals/community/disambiguation.py +51 -0
  90. picux/signals/community/intake.py +227 -0
  91. picux/signals/community/models.py +102 -0
  92. picux/signals/community/rules.py +91 -0
  93. picux/signals/community/scoring.py +64 -0
  94. picux/storage/__init__.py +41 -0
  95. picux/storage/agents.py +50 -0
  96. picux/storage/cases.py +440 -0
  97. picux/storage/channels.py +476 -0
  98. picux/storage/connectors.py +411 -0
  99. picux/storage/envelopes.py +137 -0
  100. picux/storage/escrows.py +168 -0
  101. picux/storage/events.py +989 -0
  102. picux/storage/keyspace.py +60 -0
  103. picux/storage/mandates.py +107 -0
  104. picux/storage/portals.py +222 -0
  105. picux/storage/postgres.py +2049 -0
  106. picux/storage/providers.py +148 -0
  107. picux/storage/proxy.py +231 -0
  108. picux/storage/receipts.py +131 -0
  109. picux/storage/signals.py +147 -0
  110. picux/storage/tasks.py +179 -0
  111. picux/tools/__init__.py +11 -0
  112. picux/tools/shared.py +2048 -0
  113. picux/verification/__init__.py +5 -0
  114. picux/verification/rollout.py +183 -0
  115. picux/workflows/__init__.py +5 -0
  116. picux/workflows/templates.py +74 -0
@@ -0,0 +1,989 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ import hashlib
5
+ import json
6
+ import time
7
+ from typing import Any
8
+
9
+
10
+ EVENT_STATUSES = {"queued", "acked", "failed", "canceled"}
11
+ SUBSCRIPTION_STATUSES = {"active", "paused", "disabled"}
12
+ SUBSCRIPTION_TRANSPORTS = {"poll", "webhook", "a2a", "mcp"}
13
+ DELIVERY_STATUSES = {"queued", "claimed", "delivered", "failed", "canceled"}
14
+
15
+
16
+ class EventBook:
17
+ """External integration event outbox with optional durable backing."""
18
+
19
+ def __init__(
20
+ self,
21
+ *,
22
+ backing: Any | None = None,
23
+ subscriptionBook: "EventSubscriptionBook | None" = None,
24
+ deliveryBook: "EventDeliveryBook | None" = None,
25
+ ) -> None:
26
+ self.backing = backing
27
+ self.subscriptionBook = subscriptionBook
28
+ self.deliveryBook = deliveryBook
29
+ self._records: dict[str, dict[str, Any]] = {}
30
+
31
+ def recordEvent(self, payload: dict[str, Any]) -> dict[str, Any]:
32
+ eventType = str(payload.get("type", payload.get("event", "")) or "").strip()
33
+ if not eventType:
34
+ return {"ok": False, "error": "missing:type"}
35
+ now = int(payload.get("createdAt", 0) or time.time())
36
+ eventId = str(payload.get("eventId", "") or _stableId(eventType, payload, now))
37
+ current = self.getEvent(eventId)
38
+ createdAt = current.get("event", {}).get("createdAt", now) if current.get("ok") else now
39
+ event = {
40
+ "type": eventType,
41
+ "subject": str(payload.get("subject", "") or ""),
42
+ "source": str(payload.get("source", "picux") or "picux"),
43
+ "target": str(payload.get("target", "") or ""),
44
+ "payload": payload.get("payload", {}) if isinstance(payload.get("payload"), dict) else {},
45
+ }
46
+ subscriptions = self._matchedSubscriptionRecords(event)
47
+ record = {
48
+ "eventId": eventId,
49
+ "type": eventType,
50
+ "subject": event["subject"],
51
+ "source": event["source"],
52
+ "target": event["target"],
53
+ "status": _status(payload.get("status", "queued")),
54
+ "payload": copy.deepcopy(payload.get("payload", {}) if isinstance(payload.get("payload"), dict) else {}),
55
+ "subscriptions": [str(record.get("subId", "") or "") for record in subscriptions if record.get("subId")],
56
+ "ack": copy.deepcopy(payload.get("ack", {}) if isinstance(payload.get("ack"), dict) else {}),
57
+ "createdAt": int(createdAt or now),
58
+ "updatedAt": int(payload.get("updatedAt", now) or now),
59
+ "ackAt": int(payload.get("ackAt", 0) or 0),
60
+ }
61
+ self._saveRecord(record)
62
+ deliveries = self._recordDeliveries(record, subscriptions)
63
+ result = {"ok": True, "event": copy.deepcopy(record)}
64
+ if deliveries:
65
+ result["deliveries"] = deliveries
66
+ return result
67
+
68
+ def getEvent(self, eventId: str) -> dict[str, Any]:
69
+ eventId = str(eventId or "")
70
+ record = self._records.get(eventId)
71
+ if record is None and self._backingEnabled() and hasattr(self.backing, "fetchIntegrationEvent"):
72
+ record = self.backing.fetchIntegrationEvent(eventId)
73
+ if record:
74
+ self._records[eventId] = copy.deepcopy(record)
75
+ if not record:
76
+ return {"ok": False, "error": "eventNotFound", "eventId": eventId}
77
+ return {"ok": True, "event": copy.deepcopy(record)}
78
+
79
+ def listEvents(self, filters: dict[str, Any] | None = None) -> dict[str, Any]:
80
+ self._loadBacking()
81
+ filters = filters or {}
82
+ eventType = str(filters.get("type", filters.get("event", "")) or "")
83
+ source = str(filters.get("source", "") or "")
84
+ target = str(filters.get("target", "") or "")
85
+ subject = str(filters.get("subject", "") or "")
86
+ status = str(filters.get("status", "") or "")
87
+ records = list(self._records.values())
88
+ if eventType:
89
+ records = [record for record in records if record.get("type") == eventType]
90
+ if source:
91
+ records = [record for record in records if record.get("source") == source]
92
+ if target:
93
+ records = [record for record in records if record.get("target") == target]
94
+ if subject:
95
+ records = [record for record in records if record.get("subject") == subject]
96
+ if status:
97
+ records = [record for record in records if record.get("status") == status]
98
+ records.sort(key=lambda item: (int(item.get("createdAt", 0) or 0), str(item.get("eventId", ""))), reverse=True)
99
+ limit = _limit(filters.get("limit", 100))
100
+ return {
101
+ "ok": True,
102
+ "events": [copy.deepcopy(record) for record in records[:limit]],
103
+ "count": min(len(records), limit),
104
+ }
105
+
106
+ def ackEvent(self, eventId: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
107
+ current = self.getEvent(eventId)
108
+ if not current.get("ok"):
109
+ return current
110
+ now = int(time.time())
111
+ payload = payload or {}
112
+ record = current["event"]
113
+ ack = payload.get("ack", payload) if isinstance(payload.get("ack", payload), dict) else {}
114
+ merged = {
115
+ **record,
116
+ "status": "acked",
117
+ "ack": copy.deepcopy(ack),
118
+ "ackAt": int(payload.get("ackAt", now) or now),
119
+ "updatedAt": int(payload.get("updatedAt", now) or now),
120
+ }
121
+ self._saveRecord(merged)
122
+ return {"ok": True, "event": copy.deepcopy(merged)}
123
+
124
+ def _saveRecord(self, record: dict[str, Any]) -> None:
125
+ eventId = str(record.get("eventId", "") or "")
126
+ if not eventId:
127
+ return
128
+ self._records[eventId] = copy.deepcopy(record)
129
+ if self._backingEnabled() and hasattr(self.backing, "upsertIntegrationEvent"):
130
+ self.backing.upsertIntegrationEvent(record)
131
+
132
+ def _loadBacking(self) -> None:
133
+ if self.backing is None or not self._backingEnabled() or not hasattr(self.backing, "listIntegrationEvents"):
134
+ return
135
+ for record in self.backing.listIntegrationEvents():
136
+ eventId = str(record.get("eventId", "") or "")
137
+ if eventId:
138
+ self._records[eventId] = copy.deepcopy(record)
139
+
140
+ def _backingEnabled(self) -> bool:
141
+ if self.backing is None:
142
+ return False
143
+ return bool(getattr(self.backing, "enabled", True))
144
+
145
+ def _matchedSubscriptionRecords(self, event: dict[str, Any]) -> list[dict[str, Any]]:
146
+ if self.subscriptionBook is None:
147
+ return []
148
+ result = self.subscriptionBook.matchEvent(event)
149
+ if not result.get("ok"):
150
+ return []
151
+ return [
152
+ copy.deepcopy(record)
153
+ for record in result.get("subscriptions", [])
154
+ if isinstance(record, dict) and record.get("subId")
155
+ ]
156
+
157
+ def _recordDeliveries(self, event: dict[str, Any], subscriptions: list[dict[str, Any]]) -> list[dict[str, Any]]:
158
+ if self.deliveryBook is None:
159
+ return []
160
+ deliveries: list[dict[str, Any]] = []
161
+ for subscription in subscriptions:
162
+ result = self.deliveryBook.createForEvent(event, subscription)
163
+ if result.get("ok") and isinstance(result.get("delivery"), dict):
164
+ deliveries.append(copy.deepcopy(result["delivery"]))
165
+ return deliveries
166
+
167
+
168
+ class EventSubscriptionBook:
169
+ """Event interest registry for external apps, services, and agents."""
170
+
171
+ def __init__(self, *, backing: Any | None = None) -> None:
172
+ self.backing = backing
173
+ self._records: dict[str, dict[str, Any]] = {}
174
+
175
+ def createSubscription(self, payload: dict[str, Any]) -> dict[str, Any]:
176
+ clientId = str(payload.get("clientId", payload.get("target", "")) or "").strip()
177
+ eventTypes = _cleanList(payload.get("eventTypes", payload.get("types", [])))
178
+ errors = _subscriptionErrors(clientId=clientId, eventTypes=eventTypes, payload=payload)
179
+ subId = str(payload.get("subId", "") or _subscriptionId(clientId, eventTypes, payload))
180
+ record = self._record(payload, subId=subId, clientId=clientId, eventTypes=eventTypes)
181
+ if errors:
182
+ return {"ok": False, "errors": errors, "subscription": record}
183
+ self._saveRecord(record)
184
+ return {"ok": True, "subscription": copy.deepcopy(record)}
185
+
186
+ def getSubscription(self, subId: str) -> dict[str, Any]:
187
+ subId = str(subId or "")
188
+ record = self._records.get(subId)
189
+ if record is None and self._backingEnabled() and hasattr(self.backing, "fetchEventSubscription"):
190
+ record = self.backing.fetchEventSubscription(subId)
191
+ if record:
192
+ self._records[subId] = copy.deepcopy(record)
193
+ if not record:
194
+ return {"ok": False, "error": "subscriptionNotFound", "subId": subId}
195
+ return {"ok": True, "subscription": copy.deepcopy(record)}
196
+
197
+ def listSubscriptions(self, filters: dict[str, Any] | None = None) -> dict[str, Any]:
198
+ self._loadBacking()
199
+ filters = filters or {}
200
+ clientId = str(filters.get("clientId", "") or "")
201
+ eventType = str(filters.get("eventType", filters.get("type", "")) or "")
202
+ status = str(filters.get("status", "") or "")
203
+ transport = str(filters.get("transport", "") or "")
204
+ records = list(self._records.values())
205
+ if clientId:
206
+ records = [record for record in records if record.get("clientId") == clientId]
207
+ if eventType:
208
+ records = [record for record in records if eventType in record.get("eventTypes", []) or "*" in record.get("eventTypes", [])]
209
+ if status:
210
+ records = [record for record in records if record.get("status") == status]
211
+ if transport:
212
+ records = [record for record in records if record.get("transport") == transport]
213
+ records.sort(key=lambda item: (int(item.get("createdAt", 0) or 0), str(item.get("subId", ""))), reverse=True)
214
+ limit = _limit(filters.get("limit", 100))
215
+ return {
216
+ "ok": True,
217
+ "subscriptions": [copy.deepcopy(record) for record in records[:limit]],
218
+ "count": min(len(records), limit),
219
+ }
220
+
221
+ def updateSubscription(self, subId: str, updates: dict[str, Any]) -> dict[str, Any]:
222
+ current = self.getSubscription(subId)
223
+ if not current.get("ok"):
224
+ return current
225
+ record = current["subscription"]
226
+ eventTypes = _cleanList(updates.get("eventTypes", updates.get("types", record.get("eventTypes", []))))
227
+ merged = self._record(
228
+ {**record, **updates},
229
+ subId=subId,
230
+ clientId=str(updates.get("clientId", record.get("clientId", "")) or ""),
231
+ eventTypes=eventTypes,
232
+ createdAt=int(record.get("createdAt", 0) or 0),
233
+ )
234
+ errors = _subscriptionErrors(clientId=merged["clientId"], eventTypes=eventTypes, payload=merged)
235
+ if errors:
236
+ return {"ok": False, "errors": errors, "subscription": merged}
237
+ self._saveRecord(merged)
238
+ return {"ok": True, "subscription": copy.deepcopy(merged)}
239
+
240
+ def matchEvent(self, event: dict[str, Any]) -> dict[str, Any]:
241
+ self._loadBacking()
242
+ eventType = str(event.get("type", event.get("event", "")) or "")
243
+ target = str(event.get("target", "") or "")
244
+ source = str(event.get("source", "") or "")
245
+ subject = str(event.get("subject", "") or "")
246
+ matches: list[dict[str, Any]] = []
247
+ for record in self._records.values():
248
+ filters = record.get("filters", {}) if isinstance(record.get("filters"), dict) else {}
249
+ if record.get("status") != "active":
250
+ continue
251
+ if eventType not in record.get("eventTypes", []) and "*" not in record.get("eventTypes", []):
252
+ continue
253
+ if record.get("target") and record.get("target") != target:
254
+ continue
255
+ if filters.get("source") and filters.get("source") != source:
256
+ continue
257
+ if filters.get("subject") and filters.get("subject") != subject:
258
+ continue
259
+ matches.append(copy.deepcopy(record))
260
+ matches.sort(key=lambda item: str(item.get("subId", "")))
261
+ return {"ok": True, "subscriptions": matches, "count": len(matches)}
262
+
263
+ def _record(
264
+ self,
265
+ payload: dict[str, Any],
266
+ *,
267
+ subId: str,
268
+ clientId: str,
269
+ eventTypes: list[str],
270
+ createdAt: int | None = None,
271
+ ) -> dict[str, Any]:
272
+ now = int(time.time())
273
+ return {
274
+ "subId": subId,
275
+ "clientId": clientId,
276
+ "eventTypes": eventTypes,
277
+ "target": str(payload.get("target", clientId) or ""),
278
+ "transport": _transport(payload.get("transport", "poll")),
279
+ "endpoint": str(payload.get("endpoint", "") or ""),
280
+ "status": _subStatus(payload.get("status", "active")),
281
+ "filters": copy.deepcopy(payload.get("filters", {}) if isinstance(payload.get("filters"), dict) else {}),
282
+ "meta": copy.deepcopy(payload.get("meta", {}) if isinstance(payload.get("meta"), dict) else {}),
283
+ "createdAt": int(createdAt or payload.get("createdAt", now) or now),
284
+ "updatedAt": int(payload.get("updatedAt", now) or now),
285
+ }
286
+
287
+ def _saveRecord(self, record: dict[str, Any]) -> None:
288
+ subId = str(record.get("subId", "") or "")
289
+ if not subId:
290
+ return
291
+ self._records[subId] = copy.deepcopy(record)
292
+ if self._backingEnabled() and hasattr(self.backing, "upsertEventSubscription"):
293
+ self.backing.upsertEventSubscription(record)
294
+
295
+ def _loadBacking(self) -> None:
296
+ if self.backing is None or not self._backingEnabled() or not hasattr(self.backing, "listEventSubscriptions"):
297
+ return
298
+ for record in self.backing.listEventSubscriptions():
299
+ subId = str(record.get("subId", "") or "")
300
+ if subId:
301
+ self._records[subId] = copy.deepcopy(record)
302
+
303
+ def _backingEnabled(self) -> bool:
304
+ if self.backing is None:
305
+ return False
306
+ return bool(getattr(self.backing, "enabled", True))
307
+
308
+
309
+ class EventDeliveryBook:
310
+ """Delivery attempt ledger for matched event subscriptions."""
311
+
312
+ def __init__(self, *, backing: Any | None = None) -> None:
313
+ self.backing = backing
314
+ self._records: dict[str, dict[str, Any]] = {}
315
+
316
+ def createForEvent(self, event: dict[str, Any], subscription: dict[str, Any]) -> dict[str, Any]:
317
+ eventId = str(event.get("eventId", "") or "")
318
+ subId = str(subscription.get("subId", "") or "")
319
+ if eventId and subId:
320
+ current = self.getDelivery(_deliveryId(eventId, subId, 1))
321
+ if current.get("ok"):
322
+ return current
323
+ return self.createDelivery(
324
+ {
325
+ "eventId": eventId,
326
+ "subId": subId,
327
+ "clientId": subscription.get("clientId", ""),
328
+ "transport": subscription.get("transport", "poll"),
329
+ "endpoint": subscription.get("endpoint", ""),
330
+ "meta": {
331
+ "eventType": event.get("type", ""),
332
+ "subject": event.get("subject", ""),
333
+ "target": event.get("target", ""),
334
+ },
335
+ }
336
+ )
337
+
338
+ def createDelivery(self, payload: dict[str, Any]) -> dict[str, Any]:
339
+ eventId = str(payload.get("eventId", "") or "").strip()
340
+ subId = str(payload.get("subId", "") or "").strip()
341
+ attempt = _attempt(payload.get("attempt", 1))
342
+ errors = _deliveryErrors(eventId=eventId, subId=subId, payload=payload)
343
+ deliveryId = str(payload.get("deliveryId", "") or _deliveryId(eventId, subId, attempt))
344
+ now = int(payload.get("createdAt", 0) or time.time())
345
+ current = self.getDelivery(deliveryId)
346
+ createdAt = current.get("delivery", {}).get("createdAt", now) if current.get("ok") else now
347
+ status = _deliveryStatus(payload.get("status", "queued"))
348
+ record = {
349
+ "deliveryId": deliveryId,
350
+ "eventId": eventId,
351
+ "subId": subId,
352
+ "clientId": str(payload.get("clientId", "") or ""),
353
+ "transport": _transport(payload.get("transport", "poll")),
354
+ "endpoint": str(payload.get("endpoint", "") or ""),
355
+ "status": status,
356
+ "attempt": attempt,
357
+ "claimedBy": str(payload.get("claimedBy", "") or ""),
358
+ "lastError": str(payload.get("lastError", "") or ""),
359
+ "meta": copy.deepcopy(payload.get("meta", {}) if isinstance(payload.get("meta"), dict) else {}),
360
+ "createdAt": int(createdAt or now),
361
+ "updatedAt": int(payload.get("updatedAt", now) or now),
362
+ "nextAttemptAt": _nextAttemptAt(payload, now) if status == "queued" else 0,
363
+ "claimedAt": int(payload.get("claimedAt", 0) or 0),
364
+ "leaseExpiresAt": int(payload.get("leaseExpiresAt", 0) or 0),
365
+ "deliveredAt": int(payload.get("deliveredAt", 0) or 0),
366
+ }
367
+ if errors:
368
+ return {"ok": False, "errors": errors, "delivery": record}
369
+ self._saveRecord(record)
370
+ return {"ok": True, "delivery": copy.deepcopy(record)}
371
+
372
+ def getDelivery(self, deliveryId: str) -> dict[str, Any]:
373
+ deliveryId = str(deliveryId or "")
374
+ record = self._records.get(deliveryId)
375
+ if record is None and self._backingEnabled() and hasattr(self.backing, "fetchEventDelivery"):
376
+ record = self.backing.fetchEventDelivery(deliveryId)
377
+ if record:
378
+ self._records[deliveryId] = copy.deepcopy(record)
379
+ if not record:
380
+ return {"ok": False, "error": "deliveryNotFound", "deliveryId": deliveryId}
381
+ return {"ok": True, "delivery": copy.deepcopy(record)}
382
+
383
+ def listDeliveries(self, filters: dict[str, Any] | None = None) -> dict[str, Any]:
384
+ self._loadBacking()
385
+ filters = filters or {}
386
+ records = self._filtered(filters)
387
+ records.sort(key=lambda item: (int(item.get("createdAt", 0) or 0), str(item.get("deliveryId", ""))), reverse=True)
388
+ limit = _limit(filters.get("limit", 100))
389
+ return {
390
+ "ok": True,
391
+ "deliveries": [copy.deepcopy(record) for record in records[:limit]],
392
+ "count": min(len(records), limit),
393
+ }
394
+
395
+ def deliveryStats(self, filters: dict[str, Any] | None = None) -> dict[str, Any]:
396
+ self._loadBacking()
397
+ filters = filters or {}
398
+ now = int(filters.get("now", 0) or time.time())
399
+ records = self._filtered(filters)
400
+ ready = [record for record in records if record.get("status") == "queued" and _isDue(record, now)]
401
+ scheduled = [record for record in records if record.get("status") == "queued" and not _isDue(record, now)]
402
+ expired = [
403
+ record
404
+ for record in records
405
+ if record.get("status") == "claimed" and 0 < int(record.get("leaseExpiresAt", 0) or 0) <= now
406
+ ]
407
+ activeClaims = [
408
+ record
409
+ for record in records
410
+ if record.get("status") == "claimed"
411
+ and (int(record.get("leaseExpiresAt", 0) or 0) <= 0 or int(record.get("leaseExpiresAt", 0) or 0) > now)
412
+ ]
413
+ return {
414
+ "ok": True,
415
+ "stats": {
416
+ "total": len(records),
417
+ "ready": len(ready),
418
+ "scheduled": len(scheduled),
419
+ "expired": len(expired),
420
+ "activeClaims": len(activeClaims),
421
+ "byStatus": _countBy(records, "status"),
422
+ "byTransport": _countBy(records, "transport"),
423
+ "byClient": _countBy(records, "clientId"),
424
+ },
425
+ "count": len(records),
426
+ }
427
+
428
+ def readyDeliveries(self, filters: dict[str, Any] | None = None) -> dict[str, Any]:
429
+ self._loadBacking()
430
+ filters = filters or {}
431
+ now = int(filters.get("now", 0) or time.time())
432
+ records = self._claimable(self._claimFilters(filters), now)
433
+ records.sort(key=lambda item: (int(item.get("createdAt", 0) or 0), str(item.get("deliveryId", ""))))
434
+ limit = _limit(filters.get("limit", 100))
435
+ return {
436
+ "ok": True,
437
+ "deliveries": [copy.deepcopy(record) for record in records[:limit]],
438
+ "count": min(len(records), limit),
439
+ "total": len(records),
440
+ }
441
+
442
+ def deliveryChain(self, deliveryId: str, filters: dict[str, Any] | None = None) -> dict[str, Any]:
443
+ current = self.getDelivery(deliveryId)
444
+ if not current.get("ok"):
445
+ return current
446
+ self._loadBacking()
447
+ filters = filters or {}
448
+ record = current["delivery"]
449
+ eventId = str(record.get("eventId", "") or "")
450
+ subId = str(record.get("subId", "") or "")
451
+ records = self._filtered({"eventId": eventId, "subId": subId})
452
+ records.sort(key=lambda item: (int(item.get("attempt", 1) or 1), int(item.get("createdAt", 0) or 0), str(item.get("deliveryId", ""))))
453
+ limit = _limit(filters.get("limit", 100))
454
+ root = records[0] if records else {}
455
+ latest = records[-1] if records else {}
456
+ attempts = [copy.deepcopy(item) for item in records[:limit]]
457
+ return {
458
+ "ok": True,
459
+ "deliveryId": str(deliveryId or ""),
460
+ "eventId": eventId,
461
+ "subId": subId,
462
+ "rootDeliveryId": str(root.get("deliveryId", "") or ""),
463
+ "latestDeliveryId": str(latest.get("deliveryId", "") or ""),
464
+ "deliveries": attempts,
465
+ "count": min(len(records), limit),
466
+ "total": len(records),
467
+ }
468
+
469
+ def claimNext(self, payload: dict[str, Any] | None = None) -> dict[str, Any]:
470
+ payload = payload or {}
471
+ filters = self._claimFilters(payload)
472
+ if self._backingEnabled() and hasattr(self.backing, "claimEventDelivery"):
473
+ claimed = self.backing.claimEventDelivery(payload)
474
+ if claimed:
475
+ deliveryId = str(claimed.get("deliveryId", "") or "")
476
+ if deliveryId:
477
+ self._records[deliveryId] = copy.deepcopy(claimed)
478
+ return {"ok": True, "delivery": copy.deepcopy(claimed)}
479
+ return {"ok": False, "error": "deliveryNotFound", "filters": {key: value for key, value in filters.items() if value}}
480
+ else:
481
+ self._loadBacking()
482
+ now = int(time.time())
483
+ records = self._claimable(filters, now)
484
+ records.sort(key=lambda item: (int(item.get("createdAt", 0) or 0), str(item.get("deliveryId", ""))))
485
+ if not records:
486
+ return {"ok": False, "error": "deliveryNotFound", "filters": {key: value for key, value in filters.items() if value}}
487
+ claimedBy = str(payload.get("claimedBy", payload.get("workerId", "")) or "")
488
+ return self.updateDelivery(
489
+ records[0]["deliveryId"],
490
+ {
491
+ "status": "claimed",
492
+ "claimedBy": claimedBy,
493
+ "leaseExpiresAt": _leaseExpiresAt(payload, now),
494
+ },
495
+ )
496
+
497
+ def claimBatch(self, payload: dict[str, Any] | None = None) -> dict[str, Any]:
498
+ payload = payload or {}
499
+ limit = _limit(payload.get("limit", payload.get("count", 10)))
500
+ claimed: list[dict[str, Any]] = []
501
+ if self._backingEnabled() and hasattr(self.backing, "claimEventDelivery"):
502
+ for _ in range(limit):
503
+ result = self.backing.claimEventDelivery(payload)
504
+ if not result:
505
+ break
506
+ deliveryId = str(result.get("deliveryId", "") or "")
507
+ if deliveryId:
508
+ self._records[deliveryId] = copy.deepcopy(result)
509
+ claimed.append(copy.deepcopy(result))
510
+ return {"ok": True, "deliveries": claimed, "count": len(claimed), "total": len(claimed)}
511
+ self._loadBacking()
512
+ now = int(time.time())
513
+ records = self._claimable(self._claimFilters(payload), now)
514
+ records.sort(key=lambda item: (int(item.get("createdAt", 0) or 0), str(item.get("deliveryId", ""))))
515
+ for record in records[:limit]:
516
+ result = self.updateDelivery(
517
+ str(record.get("deliveryId", "") or ""),
518
+ {
519
+ "status": "claimed",
520
+ "claimedBy": str(payload.get("claimedBy", payload.get("workerId", "")) or ""),
521
+ "leaseExpiresAt": _leaseExpiresAt(payload, now),
522
+ },
523
+ )
524
+ if result.get("ok") and isinstance(result.get("delivery"), dict):
525
+ claimed.append(copy.deepcopy(result["delivery"]))
526
+ return {"ok": True, "deliveries": claimed, "count": len(claimed), "total": len(records)}
527
+
528
+ def retryDelivery(self, deliveryId: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
529
+ current = self.getDelivery(deliveryId)
530
+ if not current.get("ok"):
531
+ return current
532
+ payload = payload or {}
533
+ record = current["delivery"]
534
+ if record.get("status") not in {"failed", "canceled"}:
535
+ return {"ok": False, "error": "invalid:retryStatus", "delivery": copy.deepcopy(record)}
536
+ meta = record.get("meta", {}) if isinstance(record.get("meta"), dict) else {}
537
+ if isinstance(payload.get("meta"), dict):
538
+ meta = {**meta, **payload["meta"]}
539
+ meta["parentDeliveryId"] = str(record.get("deliveryId", "") or deliveryId)
540
+ return self.createDelivery(
541
+ {
542
+ "eventId": record.get("eventId", ""),
543
+ "subId": record.get("subId", ""),
544
+ "clientId": payload.get("clientId", record.get("clientId", "")),
545
+ "transport": payload.get("transport", record.get("transport", "poll")),
546
+ "endpoint": payload.get("endpoint", record.get("endpoint", "")),
547
+ "status": "queued",
548
+ "attempt": _attempt(payload.get("attempt", int(record.get("attempt", 1) or 1) + 1)),
549
+ "nextAttemptAt": payload.get("nextAttemptAt", 0),
550
+ "delaySeconds": payload.get("delaySeconds", ""),
551
+ "meta": meta,
552
+ }
553
+ )
554
+
555
+ def failDelivery(self, deliveryId: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
556
+ current = self.getDelivery(deliveryId)
557
+ if not current.get("ok"):
558
+ return current
559
+ payload = payload or {}
560
+ record = current["delivery"]
561
+ if record.get("status") in {"delivered", "canceled"}:
562
+ return {"ok": False, "error": "invalid:failStatus", "delivery": copy.deepcopy(record)}
563
+ workerId = str(payload.get("workerId", payload.get("claimedBy", "")) or "")
564
+ claimedBy = str(record.get("claimedBy", "") or "")
565
+ if record.get("status") == "claimed" and workerId and claimedBy and workerId != claimedBy:
566
+ return {"ok": False, "error": "invalid:claimOwner", "delivery": copy.deepcopy(record)}
567
+ if record.get("status") == "failed":
568
+ failed = copy.deepcopy(record)
569
+ else:
570
+ updates: dict[str, Any] = {"status": "failed"}
571
+ if "lastError" in payload:
572
+ updates["lastError"] = str(payload.get("lastError", "") or "")
573
+ if isinstance(payload.get("meta"), dict):
574
+ updates["meta"] = payload["meta"]
575
+ failedResult = self.updateDelivery(deliveryId, updates)
576
+ if not failedResult.get("ok"):
577
+ return failedResult
578
+ failed = failedResult["delivery"]
579
+ result: dict[str, Any] = {"ok": True, "delivery": copy.deepcopy(failed)}
580
+ if not _retryEnabled(payload.get("retry", True)):
581
+ result["retry"] = {"scheduled": False, "reason": "disabled"}
582
+ return result
583
+ maxAttempts = _maxAttempts(payload.get("maxAttempts", 3))
584
+ if int(failed.get("attempt", 1) or 1) >= maxAttempts:
585
+ result["retry"] = {"scheduled": False, "reason": "maxAttempts", "maxAttempts": maxAttempts}
586
+ return result
587
+ nextAttempt = _attempt(payload.get("attempt", int(failed.get("attempt", 1) or 1) + 1))
588
+ if nextAttempt > maxAttempts:
589
+ result["retry"] = {"scheduled": False, "reason": "maxAttempts", "maxAttempts": maxAttempts}
590
+ return result
591
+ retryPayload = {
592
+ key: payload[key]
593
+ for key in ("clientId", "transport", "endpoint", "attempt", "nextAttemptAt", "delaySeconds")
594
+ if key in payload
595
+ }
596
+ retryMeta = payload.get("retryMeta", {})
597
+ if isinstance(retryMeta, dict):
598
+ retryPayload["meta"] = retryMeta
599
+ retried = self.retryDelivery(deliveryId, retryPayload)
600
+ if not retried.get("ok"):
601
+ return {"ok": False, "error": retried.get("error", "retryFailed"), "delivery": copy.deepcopy(failed), "retry": retried}
602
+ result["retry"] = {"scheduled": True, "delivery": retried["delivery"], "maxAttempts": maxAttempts}
603
+ return result
604
+
605
+ def completeDelivery(self, deliveryId: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
606
+ current = self.getDelivery(deliveryId)
607
+ if not current.get("ok"):
608
+ return current
609
+ payload = payload or {}
610
+ record = current["delivery"]
611
+ if record.get("status") == "delivered":
612
+ return {"ok": True, "delivery": copy.deepcopy(record)}
613
+ if record.get("status") != "claimed":
614
+ return {"ok": False, "error": "invalid:completeStatus", "delivery": copy.deepcopy(record)}
615
+ workerId = str(payload.get("workerId", payload.get("claimedBy", "")) or "")
616
+ claimedBy = str(record.get("claimedBy", "") or "")
617
+ if workerId and claimedBy and workerId != claimedBy:
618
+ return {"ok": False, "error": "invalid:claimOwner", "delivery": copy.deepcopy(record)}
619
+ updates: dict[str, Any] = {"status": "delivered"}
620
+ if "deliveredAt" in payload:
621
+ updates["deliveredAt"] = int(payload.get("deliveredAt", 0) or 0)
622
+ if "lastError" in payload:
623
+ updates["lastError"] = str(payload.get("lastError", "") or "")
624
+ if isinstance(payload.get("meta"), dict):
625
+ updates["meta"] = payload["meta"]
626
+ return self.updateDelivery(deliveryId, updates)
627
+
628
+ def cancelDelivery(self, deliveryId: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
629
+ current = self.getDelivery(deliveryId)
630
+ if not current.get("ok"):
631
+ return current
632
+ payload = payload or {}
633
+ record = current["delivery"]
634
+ if record.get("status") == "canceled":
635
+ return {"ok": True, "delivery": copy.deepcopy(record)}
636
+ if record.get("status") == "delivered":
637
+ return {"ok": False, "error": "invalid:cancelStatus", "delivery": copy.deepcopy(record)}
638
+ workerId = str(payload.get("workerId", payload.get("claimedBy", "")) or "")
639
+ claimedBy = str(record.get("claimedBy", "") or "")
640
+ if record.get("status") == "claimed" and workerId and claimedBy and workerId != claimedBy:
641
+ return {"ok": False, "error": "invalid:claimOwner", "delivery": copy.deepcopy(record)}
642
+ updates: dict[str, Any] = {"status": "canceled"}
643
+ if "lastError" in payload:
644
+ updates["lastError"] = str(payload.get("lastError", "") or "")
645
+ elif "reason" in payload:
646
+ updates["lastError"] = str(payload.get("reason", "") or "")
647
+ if isinstance(payload.get("meta"), dict):
648
+ updates["meta"] = payload["meta"]
649
+ return self.updateDelivery(deliveryId, updates)
650
+
651
+ def renewLease(self, deliveryId: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
652
+ current = self.getDelivery(deliveryId)
653
+ if not current.get("ok"):
654
+ return current
655
+ payload = payload or {}
656
+ record = current["delivery"]
657
+ if record.get("status") != "claimed":
658
+ return {"ok": False, "error": "invalid:leaseStatus", "delivery": copy.deepcopy(record)}
659
+ claimedBy = str(payload.get("claimedBy", payload.get("workerId", "")) or "")
660
+ if claimedBy and claimedBy != str(record.get("claimedBy", "") or ""):
661
+ return {"ok": False, "error": "invalid:leaseOwner", "delivery": copy.deepcopy(record)}
662
+ now = int(time.time())
663
+ return self.updateDelivery(deliveryId, {"leaseExpiresAt": _leaseExpiresAt(payload, now)})
664
+
665
+ def releaseDelivery(self, deliveryId: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
666
+ current = self.getDelivery(deliveryId)
667
+ if not current.get("ok"):
668
+ return current
669
+ payload = payload or {}
670
+ record = current["delivery"]
671
+ if record.get("status") != "claimed":
672
+ return {"ok": False, "error": "invalid:releaseStatus", "delivery": copy.deepcopy(record)}
673
+ updates: dict[str, Any] = {
674
+ "status": "queued",
675
+ "claimedBy": "",
676
+ "claimedAt": 0,
677
+ "leaseExpiresAt": 0,
678
+ }
679
+ if payload.get("lastError"):
680
+ updates["lastError"] = str(payload.get("lastError", "") or "")
681
+ if isinstance(payload.get("meta"), dict):
682
+ updates["meta"] = payload["meta"]
683
+ if payload.get("nextAttemptAt") or payload.get("delaySeconds"):
684
+ updates["nextAttemptAt"] = _nextAttemptAt(payload, int(time.time()))
685
+ return self.updateDelivery(deliveryId, updates)
686
+
687
+ def sweepExpiredLeases(self, payload: dict[str, Any] | None = None) -> dict[str, Any]:
688
+ payload = payload or {}
689
+ if self._backingEnabled() and hasattr(self.backing, "sweepEventDeliveryLeases"):
690
+ records = self.backing.sweepEventDeliveryLeases(payload)
691
+ for record in records:
692
+ deliveryId = str(record.get("deliveryId", "") or "")
693
+ if deliveryId:
694
+ self._records[deliveryId] = copy.deepcopy(record)
695
+ return {"ok": True, "deliveries": [copy.deepcopy(record) for record in records], "count": len(records)}
696
+ self._loadBacking()
697
+ now = int(payload.get("now", 0) or time.time())
698
+ records = self._expiredLeases(self._claimFilters(payload), now)
699
+ limit = _limit(payload.get("limit", 100))
700
+ swept: list[dict[str, Any]] = []
701
+ for record in records[:limit]:
702
+ result = self.releaseDelivery(
703
+ str(record.get("deliveryId", "") or ""),
704
+ {"lastError": str(payload.get("lastError", record.get("lastError", "")) or "")},
705
+ )
706
+ if result.get("ok") and isinstance(result.get("delivery"), dict):
707
+ swept.append(copy.deepcopy(result["delivery"]))
708
+ return {"ok": True, "deliveries": swept, "count": len(swept)}
709
+
710
+ def updateDelivery(self, deliveryId: str, updates: dict[str, Any]) -> dict[str, Any]:
711
+ current = self.getDelivery(deliveryId)
712
+ if not current.get("ok"):
713
+ return current
714
+ now = int(time.time())
715
+ record = current["delivery"]
716
+ status = _deliveryStatus(updates.get("status", record.get("status", "queued")))
717
+ claimedAt = int(updates.get("claimedAt", record.get("claimedAt", 0)) or 0)
718
+ leaseExpiresAt = int(updates.get("leaseExpiresAt", record.get("leaseExpiresAt", 0)) or 0)
719
+ nextAttemptAt = int(updates.get("nextAttemptAt", record.get("nextAttemptAt", 0)) or 0)
720
+ deliveredAt = int(updates.get("deliveredAt", record.get("deliveredAt", 0)) or 0)
721
+ if "delaySeconds" in updates:
722
+ nextAttemptAt = _nextAttemptAt(updates, now)
723
+ if status == "claimed" and not claimedAt:
724
+ claimedAt = now
725
+ if status == "claimed" and not leaseExpiresAt:
726
+ leaseExpiresAt = _leaseExpiresAt(updates, now)
727
+ if status != "claimed":
728
+ leaseExpiresAt = 0
729
+ if status != "queued":
730
+ nextAttemptAt = 0
731
+ if status == "delivered" and not deliveredAt:
732
+ deliveredAt = now
733
+ meta = record.get("meta", {}) if isinstance(record.get("meta"), dict) else {}
734
+ if isinstance(updates.get("meta"), dict):
735
+ meta = {**meta, **updates["meta"]}
736
+ merged = {
737
+ **record,
738
+ "status": status,
739
+ "attempt": _attempt(updates.get("attempt", record.get("attempt", 1))),
740
+ "claimedBy": str(updates.get("claimedBy", record.get("claimedBy", "")) or ""),
741
+ "lastError": str(updates.get("lastError", record.get("lastError", "")) or ""),
742
+ "meta": meta,
743
+ "updatedAt": int(updates.get("updatedAt", now) or now),
744
+ "nextAttemptAt": nextAttemptAt,
745
+ "claimedAt": claimedAt,
746
+ "leaseExpiresAt": leaseExpiresAt,
747
+ "deliveredAt": deliveredAt,
748
+ }
749
+ errors = _deliveryErrors(eventId=merged["eventId"], subId=merged["subId"], payload=merged)
750
+ if errors:
751
+ return {"ok": False, "errors": errors, "delivery": merged}
752
+ self._saveRecord(merged)
753
+ return {"ok": True, "delivery": copy.deepcopy(merged)}
754
+
755
+ def _saveRecord(self, record: dict[str, Any]) -> None:
756
+ deliveryId = str(record.get("deliveryId", "") or "")
757
+ if not deliveryId:
758
+ return
759
+ self._records[deliveryId] = copy.deepcopy(record)
760
+ if self._backingEnabled() and hasattr(self.backing, "upsertEventDelivery"):
761
+ self.backing.upsertEventDelivery(record)
762
+
763
+ def _loadBacking(self) -> None:
764
+ if self.backing is None or not self._backingEnabled() or not hasattr(self.backing, "listEventDeliveries"):
765
+ return
766
+ for record in self.backing.listEventDeliveries():
767
+ deliveryId = str(record.get("deliveryId", "") or "")
768
+ if deliveryId:
769
+ self._records[deliveryId] = copy.deepcopy(record)
770
+
771
+ def _backingEnabled(self) -> bool:
772
+ if self.backing is None:
773
+ return False
774
+ return bool(getattr(self.backing, "enabled", True))
775
+
776
+ def _filtered(self, filters: dict[str, Any]) -> list[dict[str, Any]]:
777
+ eventId = str(filters.get("eventId", "") or "")
778
+ subId = str(filters.get("subId", "") or "")
779
+ clientId = str(filters.get("clientId", "") or "")
780
+ status = str(filters.get("status", "") or "")
781
+ transport = str(filters.get("transport", "") or "")
782
+ records = list(self._records.values())
783
+ if eventId:
784
+ records = [record for record in records if record.get("eventId") == eventId]
785
+ if subId:
786
+ records = [record for record in records if record.get("subId") == subId]
787
+ if clientId:
788
+ records = [record for record in records if record.get("clientId") == clientId]
789
+ if status:
790
+ records = [record for record in records if record.get("status") == status]
791
+ if transport:
792
+ records = [record for record in records if record.get("transport") == transport]
793
+ return [copy.deepcopy(record) for record in records]
794
+
795
+ @staticmethod
796
+ def _claimFilters(payload: dict[str, Any]) -> dict[str, Any]:
797
+ return {
798
+ "eventId": payload.get("eventId", ""),
799
+ "subId": payload.get("subId", ""),
800
+ "clientId": payload.get("clientId", ""),
801
+ "transport": payload.get("transport", ""),
802
+ }
803
+
804
+ def _claimable(self, filters: dict[str, Any], now: int) -> list[dict[str, Any]]:
805
+ records = self._filtered(filters)
806
+ return [
807
+ record
808
+ for record in records
809
+ if (record.get("status") == "queued" and _isDue(record, now))
810
+ or (record.get("status") == "claimed" and 0 < int(record.get("leaseExpiresAt", 0) or 0) <= now)
811
+ ]
812
+
813
+ def _expiredLeases(self, filters: dict[str, Any], now: int) -> list[dict[str, Any]]:
814
+ records = self._filtered(filters)
815
+ records = [
816
+ record
817
+ for record in records
818
+ if record.get("status") == "claimed" and 0 < int(record.get("leaseExpiresAt", 0) or 0) <= now
819
+ ]
820
+ records.sort(key=lambda item: (int(item.get("leaseExpiresAt", 0) or 0), str(item.get("deliveryId", ""))))
821
+ return records
822
+
823
+
824
+ def _stableId(eventType: str, payload: dict[str, Any], createdAt: int) -> str:
825
+ base = {
826
+ "type": eventType,
827
+ "subject": str(payload.get("subject", "") or ""),
828
+ "source": str(payload.get("source", "picux") or "picux"),
829
+ "target": str(payload.get("target", "") or ""),
830
+ "payload": payload.get("payload", {}) if isinstance(payload.get("payload"), dict) else {},
831
+ "createdAt": int(createdAt or 0),
832
+ }
833
+ digest = hashlib.sha256(json.dumps(base, ensure_ascii=True, sort_keys=True).encode("utf-8")).hexdigest()
834
+ return f"evt_{digest[:24]}"
835
+
836
+
837
+ def _status(value: Any) -> str:
838
+ status = str(value or "queued")
839
+ return status if status in EVENT_STATUSES else "queued"
840
+
841
+
842
+ def _subStatus(value: Any) -> str:
843
+ status = str(value or "active")
844
+ return status if status in SUBSCRIPTION_STATUSES else "active"
845
+
846
+
847
+ def _transport(value: Any) -> str:
848
+ transport = str(value or "poll")
849
+ return transport if transport in SUBSCRIPTION_TRANSPORTS else "poll"
850
+
851
+
852
+ def _deliveryStatus(value: Any) -> str:
853
+ status = str(value or "queued")
854
+ return status if status in DELIVERY_STATUSES else "queued"
855
+
856
+
857
+ def _cleanList(value: Any) -> list[str]:
858
+ if isinstance(value, str):
859
+ value = [value]
860
+ if not isinstance(value, (list, tuple, set)):
861
+ return []
862
+ cleaned: list[str] = []
863
+ for item in value:
864
+ text = str(item or "").strip()
865
+ if text and text not in cleaned:
866
+ cleaned.append(text)
867
+ return cleaned
868
+
869
+
870
+ def _subscriptionErrors(*, clientId: str, eventTypes: list[str], payload: dict[str, Any]) -> list[str]:
871
+ errors: list[str] = []
872
+ if not clientId:
873
+ errors.append("missing:clientId")
874
+ if not eventTypes:
875
+ errors.append("missing:eventTypes")
876
+ if str(payload.get("status", "active") or "active") not in SUBSCRIPTION_STATUSES:
877
+ errors.append("invalid:status")
878
+ if str(payload.get("transport", "poll") or "poll") not in SUBSCRIPTION_TRANSPORTS:
879
+ errors.append("invalid:transport")
880
+ return errors
881
+
882
+
883
+ def _subscriptionId(clientId: str, eventTypes: list[str], payload: dict[str, Any]) -> str:
884
+ base = {
885
+ "clientId": clientId,
886
+ "eventTypes": eventTypes,
887
+ "target": str(payload.get("target", clientId) or ""),
888
+ "transport": str(payload.get("transport", "poll") or "poll"),
889
+ "endpoint": str(payload.get("endpoint", "") or ""),
890
+ }
891
+ digest = hashlib.sha256(json.dumps(base, ensure_ascii=True, sort_keys=True).encode("utf-8")).hexdigest()
892
+ return f"sub_{digest[:24]}"
893
+
894
+
895
+ def _deliveryId(eventId: str, subId: str, attempt: int) -> str:
896
+ base = {"eventId": eventId, "subId": subId, "attempt": int(attempt or 1)}
897
+ digest = hashlib.sha256(json.dumps(base, ensure_ascii=True, sort_keys=True).encode("utf-8")).hexdigest()
898
+ return f"delv_{digest[:24]}"
899
+
900
+
901
+ def _deliveryErrors(*, eventId: str, subId: str, payload: dict[str, Any]) -> list[str]:
902
+ errors: list[str] = []
903
+ if not eventId:
904
+ errors.append("missing:eventId")
905
+ if not subId:
906
+ errors.append("missing:subId")
907
+ if str(payload.get("status", "queued") or "queued") not in DELIVERY_STATUSES:
908
+ errors.append("invalid:status")
909
+ if str(payload.get("transport", "poll") or "poll") not in SUBSCRIPTION_TRANSPORTS:
910
+ errors.append("invalid:transport")
911
+ if _attempt(payload.get("attempt", 1)) < 1:
912
+ errors.append("invalid:attempt")
913
+ return errors
914
+
915
+
916
+ def _attempt(value: Any) -> int:
917
+ try:
918
+ parsed = int(value)
919
+ except (TypeError, ValueError):
920
+ return 1
921
+ return max(1, parsed)
922
+
923
+
924
+ def _leaseSeconds(value: Any) -> int:
925
+ try:
926
+ parsed = int(value)
927
+ except (TypeError, ValueError):
928
+ return 300
929
+ return max(1, min(parsed, 86400))
930
+
931
+
932
+ def _leaseExpiresAt(payload: dict[str, Any], now: int) -> int:
933
+ explicit = int(payload.get("leaseExpiresAt", 0) or 0)
934
+ if explicit:
935
+ return explicit
936
+ return now + _leaseSeconds(payload.get("leaseSeconds", 300))
937
+
938
+
939
+ def _delaySeconds(value: Any) -> int:
940
+ try:
941
+ parsed = int(value)
942
+ except (TypeError, ValueError):
943
+ return 0
944
+ return max(0, min(parsed, 2592000))
945
+
946
+
947
+ def _retryEnabled(value: Any) -> bool:
948
+ if isinstance(value, bool):
949
+ return value
950
+ return str(value).strip().lower() not in {"0", "false", "no", "off", "disabled"}
951
+
952
+
953
+ def _maxAttempts(value: Any) -> int:
954
+ try:
955
+ parsed = int(value)
956
+ except (TypeError, ValueError):
957
+ return 3
958
+ return max(1, min(parsed, 100))
959
+
960
+
961
+ def _nextAttemptAt(payload: dict[str, Any], now: int) -> int:
962
+ explicit = int(payload.get("nextAttemptAt", 0) or 0)
963
+ if explicit:
964
+ return explicit
965
+ delay = _delaySeconds(payload.get("delaySeconds", 0))
966
+ return now + delay if delay else 0
967
+
968
+
969
+ def _isDue(record: dict[str, Any], now: int) -> bool:
970
+ nextAttemptAt = int(record.get("nextAttemptAt", 0) or 0)
971
+ return nextAttemptAt <= 0 or nextAttemptAt <= now
972
+
973
+
974
+ def _countBy(records: list[dict[str, Any]], key: str) -> dict[str, int]:
975
+ counts: dict[str, int] = {}
976
+ for record in records:
977
+ value = str(record.get(key, "") or "")
978
+ if not value:
979
+ continue
980
+ counts[value] = counts.get(value, 0) + 1
981
+ return counts
982
+
983
+
984
+ def _limit(value: Any) -> int:
985
+ try:
986
+ parsed = int(value)
987
+ except (TypeError, ValueError):
988
+ return 100
989
+ return max(1, min(parsed, 500))