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,60 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Literal
5
+
6
+
7
+ LegacyKind = Literal["user", "task", "receipt", "metric"]
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class Keyspace:
12
+ prefix: str = "picux:v1"
13
+
14
+ def user(self, userId: str) -> str:
15
+ return f"{self.prefix}:user:{userId}"
16
+
17
+ def task(self, taskId: str) -> str:
18
+ return f"{self.prefix}:task:{taskId}"
19
+
20
+ def taskLog(self, taskId: str) -> str:
21
+ return f"{self.prefix}:audit:task:{taskId}"
22
+
23
+ def userTasks(self, userId: str) -> str:
24
+ return f"{self.prefix}:userTasks:{userId}"
25
+
26
+ def userLog(self, userId: str) -> str:
27
+ return f"{self.prefix}:audit:user:{userId}"
28
+
29
+ def metric(self, name: str) -> str:
30
+ return f"{self.prefix}:metrics:{name}"
31
+
32
+ def receipt(self, receiptId: str) -> str:
33
+ return f"{self.prefix}:receipt:{receiptId}"
34
+
35
+ def legacyCandidates(self, kind: LegacyKind, itemId: str) -> list[str]:
36
+ item = str(itemId or "")
37
+ if kind == "user":
38
+ return [f"swarm:user:{item}", f"swarm:v2:user:{item}"]
39
+ if kind == "task":
40
+ return [f"swarm:task:{item}", f"swarm:v2:task:{item}"]
41
+ if kind == "receipt":
42
+ return [f"swarm:receipt:{item}", f"swarm:v2:receipt:{item}"]
43
+ if kind == "metric":
44
+ return [f"swarm:metrics:{item}", f"swarm:v2:metrics:{item}"]
45
+ return []
46
+
47
+ def migrationReadPlan(self, kind: LegacyKind, itemId: str) -> dict[str, object]:
48
+ current = {
49
+ "user": self.user,
50
+ "task": self.task,
51
+ "receipt": self.receipt,
52
+ "metric": self.metric,
53
+ }[kind](itemId)
54
+ return {
55
+ "kind": kind,
56
+ "id": itemId,
57
+ "writeKey": current,
58
+ "readKeys": [current, *self.legacyCandidates(kind, itemId)],
59
+ "writePolicy": "picuxOnly",
60
+ }
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ import time
5
+ from typing import Any
6
+
7
+
8
+ class MandateBook:
9
+ """Mandate record storage with optional durable backing."""
10
+
11
+ def __init__(self, *, backing: Any | None = None) -> None:
12
+ self.backing = backing
13
+ self._records: dict[str, dict[str, Any]] = {}
14
+
15
+ def saveMandate(self, mandate: dict[str, Any], *, status: str = "pendingVerification") -> dict[str, Any]:
16
+ mandateId = str(mandate.get("mandateId", "") or "")
17
+ if not mandateId:
18
+ return {"ok": False, "error": "missing:mandateId"}
19
+ now = int(time.time())
20
+ current = self.getMandate(mandateId)
21
+ createdAt = current.get("record", {}).get("createdAt", now) if current.get("ok") else now
22
+ record = {
23
+ "mandateId": mandateId,
24
+ "status": status,
25
+ "mandate": copy.deepcopy(mandate),
26
+ "validation": current.get("record", {}).get("validation", {}) if current.get("ok") else {},
27
+ "createdAt": createdAt,
28
+ "updatedAt": now,
29
+ }
30
+ self._saveRecord(record)
31
+ return {"ok": True, "record": copy.deepcopy(record)}
32
+
33
+ def saveValidation(self, mandateId: str, mandate: dict[str, Any], validation: dict[str, Any]) -> dict[str, Any]:
34
+ status = "verified" if validation.get("ok") else "rejected"
35
+ base = self.saveMandate({**mandate, "mandateId": mandateId}, status=status)
36
+ if not base.get("ok"):
37
+ return base
38
+ record = base["record"]
39
+ record["validation"] = copy.deepcopy(validation)
40
+ record["updatedAt"] = int(time.time())
41
+ self._saveRecord(record)
42
+ return {"ok": True, "record": copy.deepcopy(record)}
43
+
44
+ def getMandate(self, mandateId: str) -> dict[str, Any]:
45
+ mandateId = str(mandateId or "")
46
+ record = self._records.get(mandateId)
47
+ if record is None and self._backingEnabled() and hasattr(self.backing, "fetchMandate"):
48
+ record = self.backing.fetchMandate(mandateId)
49
+ if record:
50
+ self._records[mandateId] = copy.deepcopy(record)
51
+ if not record:
52
+ return {"ok": False, "error": "mandateNotFound", "mandateId": mandateId}
53
+ return {"ok": True, "record": copy.deepcopy(record)}
54
+
55
+ def listMandates(self, filters: dict[str, Any] | None = None) -> dict[str, Any]:
56
+ self._loadBacking()
57
+ filters = filters or {}
58
+ status = str(filters.get("status", "") or "")
59
+ issuerId = str(filters.get("issuerId", "") or "")
60
+ records = list(self._records.values())
61
+ if status:
62
+ records = [record for record in records if record.get("status") == status]
63
+ if issuerId:
64
+ records = [
65
+ record
66
+ for record in records
67
+ if str(record.get("mandate", {}).get("issuer", {}).get("entityId", "") or "") == issuerId
68
+ ]
69
+ records.sort(
70
+ key=lambda item: (int(item.get("createdAt", 0) or 0), str(item.get("mandateId", ""))),
71
+ reverse=True,
72
+ )
73
+ limit = _limit(filters.get("limit", 100))
74
+ return {
75
+ "ok": True,
76
+ "mandates": [copy.deepcopy(record) for record in records[:limit]],
77
+ "count": min(len(records), limit),
78
+ }
79
+
80
+ def _saveRecord(self, record: dict[str, Any]) -> None:
81
+ mandateId = str(record.get("mandateId", "") or "")
82
+ if not mandateId:
83
+ return
84
+ self._records[mandateId] = copy.deepcopy(record)
85
+ if self._backingEnabled() and hasattr(self.backing, "upsertMandate"):
86
+ self.backing.upsertMandate(record)
87
+
88
+ def _loadBacking(self) -> None:
89
+ if self.backing is None or not self._backingEnabled() or not hasattr(self.backing, "listMandates"):
90
+ return
91
+ for record in self.backing.listMandates():
92
+ mandateId = str(record.get("mandateId", "") or "")
93
+ if mandateId:
94
+ self._records[mandateId] = copy.deepcopy(record)
95
+
96
+ def _backingEnabled(self) -> bool:
97
+ if self.backing is None:
98
+ return False
99
+ return bool(getattr(self.backing, "enabled", True))
100
+
101
+
102
+ def _limit(value: Any) -> int:
103
+ try:
104
+ parsed = int(value)
105
+ except (TypeError, ValueError):
106
+ return 100
107
+ return max(1, min(parsed, 500))
@@ -0,0 +1,222 @@
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
+ PORTAL_SESSION_STATUSES = {"active", "needsAuth", "expired", "revoked"}
11
+ PORTAL_ACTION_STATUSES = {"queued", "running", "succeeded", "failed", "blocked", "needsInput", "needsProxy"}
12
+
13
+
14
+ class PortalBook:
15
+ """Authenticated portal action state for merchant, carrier, utility, and provider workflows."""
16
+
17
+ def __init__(self, *, backing: Any | None = None) -> None:
18
+ self.backing = backing
19
+ self._sessions: dict[str, dict[str, Any]] = {}
20
+ self._actions: dict[str, dict[str, Any]] = {}
21
+
22
+ def createSession(self, payload: dict[str, Any]) -> dict[str, Any]:
23
+ now = int(time.time())
24
+ sessionId = str(payload.get("portalSessionId", payload.get("sessionId", "")) or _stableId("portalSession", payload))
25
+ current = self.getSession(sessionId)
26
+ createdAt = int(current.get("session", {}).get("createdAt", now) if current.get("ok") else now)
27
+ record = _sessionFromPayload({**payload, "portalSessionId": sessionId}, createdAt=createdAt, updatedAt=now)
28
+ self._saveSession(record)
29
+ return {"ok": True, "session": copy.deepcopy(record)}
30
+
31
+ def getSession(self, sessionId: str) -> dict[str, Any]:
32
+ sessionId = str(sessionId or "")
33
+ record = self._sessions.get(sessionId)
34
+ if record is None and self._backingEnabled() and hasattr(self.backing, "fetchPortalSession"):
35
+ record = self.backing.fetchPortalSession(sessionId)
36
+ if record:
37
+ self._sessions[sessionId] = copy.deepcopy(record)
38
+ if not record:
39
+ return {"ok": False, "error": "portalSessionNotFound", "portalSessionId": sessionId}
40
+ return {"ok": True, "session": copy.deepcopy(record)}
41
+
42
+ def listSessions(self, filters: dict[str, Any] | None = None) -> dict[str, Any]:
43
+ self._loadSessions()
44
+ filters = filters or {}
45
+ records = list(self._sessions.values())
46
+ for key in ("caseId", "conversationId", "connectorId", "provider", "status"):
47
+ value = str(filters.get(key, "") or "")
48
+ if value:
49
+ records = [record for record in records if str(record.get(key, "") or "") == value]
50
+ records.sort(key=lambda item: (int(item.get("updatedAt", 0) or 0), str(item.get("portalSessionId", ""))), reverse=True)
51
+ limit = _limit(filters.get("limit", 100))
52
+ return {"ok": True, "sessions": [copy.deepcopy(record) for record in records[:limit]], "count": min(len(records), limit)}
53
+
54
+ def createAction(self, payload: dict[str, Any]) -> dict[str, Any]:
55
+ now = int(time.time())
56
+ actionId = str(payload.get("portalActionId", payload.get("actionId", "")) or _stableId("portalAction", payload))
57
+ record = _actionFromPayload({**payload, "portalActionId": actionId}, createdAt=now, updatedAt=now)
58
+ self._saveAction(record)
59
+ return {"ok": True, "action": copy.deepcopy(record)}
60
+
61
+ def getAction(self, actionId: str) -> dict[str, Any]:
62
+ actionId = str(actionId or "")
63
+ record = self._actions.get(actionId)
64
+ if record is None and self._backingEnabled() and hasattr(self.backing, "fetchPortalAction"):
65
+ record = self.backing.fetchPortalAction(actionId)
66
+ if record:
67
+ self._actions[actionId] = copy.deepcopy(record)
68
+ if not record:
69
+ return {"ok": False, "error": "portalActionNotFound", "portalActionId": actionId}
70
+ return {"ok": True, "action": copy.deepcopy(record)}
71
+
72
+ def listActions(self, filters: dict[str, Any] | None = None) -> dict[str, Any]:
73
+ self._loadActions()
74
+ filters = filters or {}
75
+ records = list(self._actions.values())
76
+ for key in ("caseId", "conversationId", "portalSessionId", "connectorId", "provider", "status", "kind"):
77
+ value = str(filters.get(key, "") or "")
78
+ if value:
79
+ records = [record for record in records if str(record.get(key, "") or "") == value]
80
+ records.sort(key=lambda item: (int(item.get("updatedAt", 0) or 0), str(item.get("portalActionId", ""))), reverse=True)
81
+ limit = _limit(filters.get("limit", 100))
82
+ return {"ok": True, "actions": [copy.deepcopy(record) for record in records[:limit]], "count": min(len(records), limit)}
83
+
84
+ def updateAction(self, actionId: str, updates: dict[str, Any]) -> dict[str, Any]:
85
+ current = self.getAction(actionId)
86
+ if not current.get("ok"):
87
+ return current
88
+ record = _actionFromPayload(
89
+ {**current["action"], **updates, "portalActionId": actionId},
90
+ createdAt=int(current["action"].get("createdAt", int(time.time())) or int(time.time())),
91
+ updatedAt=int(time.time()),
92
+ )
93
+ self._saveAction(record)
94
+ return {"ok": True, "action": copy.deepcopy(record)}
95
+
96
+ def appendProof(self, actionId: str, proof: dict[str, Any]) -> dict[str, Any]:
97
+ current = self.getAction(actionId)
98
+ if not current.get("ok"):
99
+ return current
100
+ action = current["action"]
101
+ proofs = action.get("proofs", []) if isinstance(action.get("proofs"), list) else []
102
+ updated = self.updateAction(actionId, {"proofs": [*proofs, _proof(proof)]})
103
+ if updated.get("ok"):
104
+ updated["proof"] = updated["action"].get("proofs", [])[-1]
105
+ return updated
106
+
107
+ def _saveSession(self, record: dict[str, Any]) -> None:
108
+ sessionId = str(record.get("portalSessionId", "") or "")
109
+ if not sessionId:
110
+ return
111
+ self._sessions[sessionId] = copy.deepcopy(record)
112
+ if self._backingEnabled() and hasattr(self.backing, "upsertPortalSession"):
113
+ self.backing.upsertPortalSession(record)
114
+
115
+ def _saveAction(self, record: dict[str, Any]) -> None:
116
+ actionId = str(record.get("portalActionId", "") or "")
117
+ if not actionId:
118
+ return
119
+ self._actions[actionId] = copy.deepcopy(record)
120
+ if self._backingEnabled() and hasattr(self.backing, "upsertPortalAction"):
121
+ self.backing.upsertPortalAction(record)
122
+
123
+ def _loadSessions(self) -> None:
124
+ if self._backingEnabled() and hasattr(self.backing, "listPortalSessions"):
125
+ for record in self.backing.listPortalSessions():
126
+ if record.get("portalSessionId"):
127
+ self._sessions[str(record["portalSessionId"])] = copy.deepcopy(record)
128
+
129
+ def _loadActions(self) -> None:
130
+ if self._backingEnabled() and hasattr(self.backing, "listPortalActions"):
131
+ for record in self.backing.listPortalActions():
132
+ if record.get("portalActionId"):
133
+ self._actions[str(record["portalActionId"])] = copy.deepcopy(record)
134
+
135
+ def _backingEnabled(self) -> bool:
136
+ return bool(self.backing is not None and getattr(self.backing, "enabled", True))
137
+
138
+
139
+ def _sessionFromPayload(payload: dict[str, Any], *, createdAt: int, updatedAt: int) -> dict[str, Any]:
140
+ return {
141
+ "portalSessionId": str(payload.get("portalSessionId", payload.get("sessionId", "")) or ""),
142
+ "caseId": str(payload.get("caseId", "") or ""),
143
+ "conversationId": str(payload.get("conversationId", "") or ""),
144
+ "connectorId": str(payload.get("connectorId", "") or ""),
145
+ "provider": str(payload.get("provider", "") or ""),
146
+ "baseUrl": str(payload.get("baseUrl", payload.get("url", "")) or ""),
147
+ "status": _choice(payload.get("status", "active"), PORTAL_SESSION_STATUSES, "active"),
148
+ "credentialRef": str(payload.get("credentialRef", "") or ""),
149
+ "storageStateRef": str(payload.get("storageStateRef", "") or ""),
150
+ "mfa": payload.get("mfa", {}) if isinstance(payload.get("mfa"), dict) else {},
151
+ "meta": payload.get("meta", {}) if isinstance(payload.get("meta"), dict) else {},
152
+ "createdAt": int(createdAt),
153
+ "updatedAt": int(updatedAt),
154
+ }
155
+
156
+
157
+ def _actionFromPayload(payload: dict[str, Any], *, createdAt: int, updatedAt: int) -> dict[str, Any]:
158
+ return {
159
+ "portalActionId": str(payload.get("portalActionId", payload.get("actionId", "")) or ""),
160
+ "portalSessionId": str(payload.get("portalSessionId", payload.get("sessionId", "")) or ""),
161
+ "templateId": str(payload.get("templateId", "") or ""),
162
+ "caseId": str(payload.get("caseId", "") or ""),
163
+ "conversationId": str(payload.get("conversationId", "") or ""),
164
+ "connectorId": str(payload.get("connectorId", "") or ""),
165
+ "provider": str(payload.get("provider", "") or ""),
166
+ "kind": str(payload.get("kind", payload.get("action", "submitForm")) or "submitForm"),
167
+ "targetUrl": str(payload.get("targetUrl", payload.get("url", "")) or ""),
168
+ "status": _choice(payload.get("status", "queued"), PORTAL_ACTION_STATUSES, "queued"),
169
+ "steps": [_step(item) for item in payload.get("steps", []) if isinstance(item, dict)],
170
+ "inputs": payload.get("inputs", {}) if isinstance(payload.get("inputs"), dict) else {},
171
+ "proofs": [_proof(item) for item in payload.get("proofs", []) if isinstance(item, dict)],
172
+ "result": payload.get("result", {}) if isinstance(payload.get("result"), dict) else {},
173
+ "error": str(payload.get("error", "") or ""),
174
+ "requiresProxy": bool(payload.get("requiresProxy", False)),
175
+ "meta": payload.get("meta", {}) if isinstance(payload.get("meta"), dict) else {},
176
+ "createdAt": int(createdAt),
177
+ "updatedAt": int(updatedAt),
178
+ }
179
+
180
+
181
+ def _step(payload: dict[str, Any]) -> dict[str, Any]:
182
+ return {
183
+ "stepId": str(payload.get("stepId", "") or _stableId("portalStep", payload)),
184
+ "label": str(payload.get("label", payload.get("action", "step")) or "step"),
185
+ "action": str(payload.get("action", "") or ""),
186
+ "selector": str(payload.get("selector", "") or ""),
187
+ "valueKey": str(payload.get("valueKey", "") or ""),
188
+ "status": str(payload.get("status", "pending") or "pending"),
189
+ "proofRequired": bool(payload.get("proofRequired", False)),
190
+ "meta": payload.get("meta", {}) if isinstance(payload.get("meta"), dict) else {},
191
+ }
192
+
193
+
194
+ def _proof(payload: dict[str, Any]) -> dict[str, Any]:
195
+ return {
196
+ "proofId": str(payload.get("proofId", "") or _stableId("portalProof", payload)),
197
+ "kind": str(payload.get("kind", "screenshot") or "screenshot"),
198
+ "status": str(payload.get("status", "captured") or "captured"),
199
+ "label": str(payload.get("label", "") or ""),
200
+ "artifactRef": str(payload.get("artifactRef", payload.get("artifactId", "")) or ""),
201
+ "sourceUrl": str(payload.get("sourceUrl", "") or ""),
202
+ "hash": str(payload.get("hash", payload.get("sha256", "")) or ""),
203
+ "createdAt": int(payload.get("createdAt", int(time.time())) or int(time.time())),
204
+ "meta": payload.get("meta", {}) if isinstance(payload.get("meta"), dict) else {},
205
+ }
206
+
207
+
208
+ def _choice(value: Any, allowed: set[str], default: str) -> str:
209
+ clean = str(value or default)
210
+ return clean if clean in allowed else default
211
+
212
+
213
+ def _limit(value: Any) -> int:
214
+ try:
215
+ return max(1, min(int(value or 100), 500))
216
+ except Exception:
217
+ return 100
218
+
219
+
220
+ def _stableId(prefix: str, payload: dict[str, Any]) -> str:
221
+ digest = hashlib.sha256(json.dumps(payload, ensure_ascii=True, sort_keys=True).encode("utf-8")).hexdigest()
222
+ return f"{prefix}_{digest[:24]}"