blun-king-cli 9.1.597 → 9.1.600

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 (106) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +17 -1
  3. package/agent-spine-plugin/CHANGELOG.md +1 -1
  4. package/agent-spine-plugin/docs/host-integration.md +1 -15
  5. package/agent-spine-plugin/docs/preflight-recall.md +1 -1
  6. package/agent-spine-plugin/scripts/check-install.js +1 -6
  7. package/agent-spine-plugin/scripts/hermetic-process.js +8 -0
  8. package/agent-spine-plugin/scripts/release-check.js +2 -3
  9. package/agent-spine-plugin/scripts/run-tests-hermetic.js +8 -11
  10. package/agent-spine-plugin/src/hook.js +40 -40
  11. package/agent-spine-plugin/src/lib/briefing.js +6 -3
  12. package/agent-spine-plugin/src/lib/hook-context.js +63 -64
  13. package/agent-spine-plugin/src/lib/hook-output.js +115 -94
  14. package/agent-spine-plugin/src/lib/hook-timeline.js +20 -11
  15. package/agent-spine-plugin/src/lib/host-instruction-budget.js +8 -14
  16. package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +0 -7
  17. package/agent-spine-plugin/src/lib/pre-answer-timeline-recall.js +104 -0
  18. package/agent-spine-plugin/src/lib/session-timeline-codex.js +0 -4
  19. package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +0 -3
  20. package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +0 -4
  21. package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +0 -2
  22. package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +0 -22
  23. package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +0 -4
  24. package/agent-spine-plugin/src/lib/session-timeline-prior.js +42 -50
  25. package/agent-spine-plugin/src/lib/session-timeline-provider.js +0 -3
  26. package/agent-spine-plugin/src/lib/session-timeline-query.js +0 -1
  27. package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +0 -4
  28. package/agent-spine-plugin/src/lib/session-timeline-source.js +0 -3
  29. package/agent-spine-plugin/src/lib/session-timeline-transport.js +0 -8
  30. package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +0 -3
  31. package/agent-spine-plugin/src/lib/session-timeline.js +370 -408
  32. package/agent-spine-plugin/src/lib/source-roots.js +26 -16
  33. package/agent-spine-plugin/src/lib/timeline-user-feedback.js +0 -1
  34. package/bin/core-bootstrap.js +18 -10
  35. package/bin/launcher-mode.js +5 -0
  36. package/bin/launcher-runtime.js +27 -0
  37. package/bin/verify-bundled-agent-sources.cjs +28 -2
  38. package/blun.mjs +798 -143
  39. package/bundled-agent-sources.json +192 -155
  40. package/package.json +1 -1
  41. package/standard-skills/translate-native/README.md +1334 -1
  42. package/standard-skills/translate-native/SKILL.md +1 -1
  43. package/standard-skills/translate-native/VERSION +1 -1
  44. package/standard-skills/translate-native/docs/PREMORTEM.md +901 -0
  45. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION.md +208 -41
  46. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_API.md +1319 -77
  47. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_EVIDENCE_HTTP.md +66 -11
  48. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_HTTP_PROVIDER.md +19 -1
  49. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_RECEIPT_VERIFIER_HTTP.md +24 -6
  50. package/standard-skills/translate-native/integrations/commercial_localization_profile.py +23 -0
  51. package/standard-skills/translate-native/integrations/website_localization.py +234 -10
  52. package/standard-skills/translate-native/integrations/website_localization_benchmark.py +39 -3
  53. package/standard-skills/translate-native/integrations/website_localization_cms.py +512 -10
  54. package/standard-skills/translate-native/integrations/website_localization_cms_client.py +116 -0
  55. package/standard-skills/translate-native/integrations/website_localization_cms_receiver.py +101 -11
  56. package/standard-skills/translate-native/integrations/website_localization_cms_receiver_runtime.py +12 -1
  57. package/standard-skills/translate-native/integrations/website_localization_cms_receiver_store.py +186 -24
  58. package/standard-skills/translate-native/integrations/website_localization_cms_source_client.py +64 -1
  59. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery.py +528 -5
  60. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth.py +918 -0
  61. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth_runtime.py +609 -0
  62. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_client.py +885 -0
  63. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_http.py +1244 -0
  64. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_runtime.py +220 -0
  65. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_sidecar_adapter.py +331 -0
  66. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_capabilities_http.py +444 -0
  67. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_client.py +673 -0
  68. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch.py +1317 -0
  69. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_client.py +826 -0
  70. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_http.py +1152 -0
  71. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_openapi.py +896 -0
  72. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_runtime.py +621 -0
  73. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_http.py +1180 -0
  74. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_runtime.py +1479 -0
  75. package/standard-skills/translate-native/integrations/website_localization_cms_source_http.py +133 -17
  76. package/standard-skills/translate-native/integrations/website_localization_cms_source_runtime.py +319 -4
  77. package/standard-skills/translate-native/integrations/website_localization_cms_source_service.py +72 -2
  78. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_http.py +13 -0
  79. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver.py +56 -13
  80. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_openapi.py +335 -0
  81. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_runtime.py +66 -1
  82. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_processing_monitor.py +73 -7
  83. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_receiver_client.py +93 -9
  84. package/standard-skills/translate-native/integrations/website_localization_evidence_http.py +221 -11
  85. package/standard-skills/translate-native/integrations/website_localization_health.py +6 -0
  86. package/standard-skills/translate-native/integrations/website_localization_http_provider.py +1 -1
  87. package/standard-skills/translate-native/integrations/website_localization_quality_profiles.py +350 -0
  88. package/standard-skills/translate-native/integrations/website_localization_queue.py +126 -56
  89. package/standard-skills/translate-native/integrations/website_localization_receipt_verifier_http.py +166 -13
  90. package/standard-skills/translate-native/integrations/website_localization_release.py +423 -17
  91. package/standard-skills/translate-native/integrations/website_localization_release_coordinator.py +168 -13
  92. package/standard-skills/translate-native/integrations/website_localization_runner.py +10 -0
  93. package/standard-skills/translate-native/integrations/website_localization_worker.py +213 -9
  94. package/standard-skills/translate-native/provenance.json +2 -2
  95. package/standard-skills/translate-native/references/commercial-localization.md +96 -17
  96. package/standard-skills/translate-native/scripts/blun_language_guard.py +3 -1
  97. package/standard-skills/translate-native/scripts/check_commercial_review.py +69 -9
  98. package/standard-skills/translate-native/scripts/commercial_localization_profile.py +829 -31
  99. package/standard-tools/language-guard/VERSION +1 -1
  100. package/standard-tools/language-guard/blun_language_guard.py +3 -1
  101. package/standard-tools/language-guard/check_commercial_review.py +69 -9
  102. package/standard-tools/language-guard/commercial_localization_profile.py +829 -31
  103. package/standard-tools/language-guard/provenance.json +2 -2
  104. package/standard-tools/manifest.json +1 -1
  105. package/worker-host.mjs +215 -82
  106. package/agent-spine-plugin/scripts/check-install-king.js +0 -37
@@ -0,0 +1,1479 @@
1
+ #!/usr/bin/env python3
2
+ """Owned website runtime for durable HMAC-authenticated sidecar submission."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import copy
7
+ import hashlib
8
+ import importlib.util
9
+ import json
10
+ import os
11
+ import secrets
12
+ import sys
13
+ import time
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+ from typing import Any, Callable, Mapping
17
+
18
+
19
+ def _load_module(name: str, path: Path):
20
+ spec = importlib.util.spec_from_file_location(name, path)
21
+ if spec is None or spec.loader is None:
22
+ raise RuntimeError(
23
+ f"cannot load source-delivery submission dependency: {path.name}"
24
+ )
25
+ module = importlib.util.module_from_spec(spec)
26
+ sys.modules[spec.name] = module
27
+ spec.loader.exec_module(module)
28
+ return module
29
+
30
+
31
+ _ROOT = Path(__file__).resolve().parents[1]
32
+ _AUTH = _load_module(
33
+ "blun_website_localization_cms_source_delivery_submission_auth",
34
+ _ROOT / "integrations" / "website_localization_cms_source_delivery_auth.py",
35
+ )
36
+ _ADAPTER = _load_module(
37
+ "blun_website_localization_cms_source_delivery_submission_adapter",
38
+ _ROOT
39
+ / "integrations"
40
+ / "website_localization_cms_source_delivery_sidecar_adapter.py",
41
+ )
42
+ _RUNTIME = _load_module(
43
+ "blun_website_localization_cms_source_delivery_submission_outbox",
44
+ _ROOT / "integrations" / "website_localization_cms_source_delivery_runtime.py",
45
+ )
46
+
47
+ HMACCredential = _AUTH.HMACCredential
48
+
49
+ CAPABILITIES_SCHEMA = (
50
+ "blun.cms-source-delivery-submission-capabilities.v7"
51
+ )
52
+ CAPABILITIES_HTTP_PATH = (
53
+ "/v1/localization/source-delivery/submission-capabilities"
54
+ )
55
+ CAPABILITIES_HTTP_SCOPE = (
56
+ "source-delivery-submission-capabilities:read"
57
+ )
58
+ CAPABILITIES_HTTP_AUTH_REQUEST_SCHEMA = (
59
+ "blun.cms-source-delivery-submission-capabilities-auth-request.v1"
60
+ )
61
+ CAPABILITIES_HTTP_PRINCIPAL_SCHEMA = (
62
+ "blun.cms-source-delivery-submission-capabilities-principal.v1"
63
+ )
64
+ CAPABILITIES_HTTP_RESPONSE_SCHEMA = (
65
+ "blun.cms-source-delivery-submission-capabilities-response.v1"
66
+ )
67
+ CHANGE_HTTP_PATH = (
68
+ "/v1/localization/source-delivery/submissions/changes"
69
+ )
70
+ REMOVAL_HTTP_PATH = (
71
+ "/v1/localization/source-delivery/submissions/removals"
72
+ )
73
+ CHANGE_HTTP_SCOPE = "source-delivery-submission-change:write"
74
+ REMOVAL_HTTP_SCOPE = "source-delivery-submission-removal:write"
75
+ STATUS_HTTP_PATH = (
76
+ "/v1/localization/source-delivery/submissions/status"
77
+ )
78
+ LIFECYCLE_HTTP_PATH = (
79
+ "/v1/localization/source-delivery/submissions/lifecycle"
80
+ )
81
+ STATUS_HTTP_SCOPE = "source-delivery-submission-status:read"
82
+ LIFECYCLE_HTTP_SCOPE = "source-delivery-submission-lifecycle:read"
83
+ PIPELINE_HEALTH_HTTP_PATH = (
84
+ "/v1/localization/source-delivery/submissions/pipeline-health"
85
+ )
86
+ PIPELINE_READINESS_HTTP_PATH = (
87
+ "/v1/localization/source-delivery/submissions/pipeline-readiness"
88
+ )
89
+ PIPELINE_HEALTH_HTTP_SCOPE = (
90
+ "source-delivery-submission-pipeline-health:read"
91
+ )
92
+ PIPELINE_READINESS_HTTP_SCOPE = (
93
+ "source-delivery-submission-pipeline-readiness:read"
94
+ )
95
+ OPERATOR_HTTP_AUTH_REQUEST_SCHEMA = (
96
+ "blun.cms-source-delivery-submission-operator-auth-request.v1"
97
+ )
98
+ OPERATOR_HTTP_PRINCIPAL_SCHEMA = (
99
+ "blun.cms-source-delivery-submission-operator-principal.v1"
100
+ )
101
+ PIPELINE_HEALTH_HTTP_RESPONSE_SCHEMA = (
102
+ "blun.cms-source-delivery-submission-pipeline-health-response.v1"
103
+ )
104
+ PIPELINE_READINESS_HTTP_RESPONSE_SCHEMA = (
105
+ "blun.cms-source-delivery-submission-pipeline-readiness-response.v1"
106
+ )
107
+ WRITE_HTTP_AUTH_REQUEST_SCHEMA = (
108
+ "blun.cms-source-delivery-submission-http-auth-request.v1"
109
+ )
110
+ WRITE_HTTP_PRINCIPAL_SCHEMA = (
111
+ "blun.cms-source-delivery-submission-write-principal.v1"
112
+ )
113
+ READ_HTTP_AUTH_REQUEST_SCHEMA = (
114
+ "blun.cms-source-delivery-submission-read-auth-request.v1"
115
+ )
116
+ READ_HTTP_PRINCIPAL_SCHEMA = (
117
+ "blun.cms-source-delivery-submission-read-principal.v1"
118
+ )
119
+ CHANGE_HTTP_REQUEST_SCHEMA = (
120
+ "blun.cms-source-delivery-submission-change-request.v1"
121
+ )
122
+ REMOVAL_HTTP_REQUEST_SCHEMA = (
123
+ "blun.cms-source-delivery-submission-removal-request.v1"
124
+ )
125
+ CHANGE_HTTP_RESPONSE_SCHEMA = (
126
+ "blun.cms-source-delivery-submission-change-response.v1"
127
+ )
128
+ REMOVAL_HTTP_RESPONSE_SCHEMA = (
129
+ "blun.cms-source-delivery-submission-removal-response.v1"
130
+ )
131
+ STATUS_HTTP_REQUEST_SCHEMA = (
132
+ "blun.cms-source-delivery-submission-status-request.v1"
133
+ )
134
+ LIFECYCLE_HTTP_REQUEST_SCHEMA = (
135
+ "blun.cms-source-delivery-submission-lifecycle-request.v1"
136
+ )
137
+ STATUS_HTTP_RESPONSE_SCHEMA = (
138
+ "blun.cms-source-delivery-submission-status-response.v1"
139
+ )
140
+ LIFECYCLE_HTTP_RESPONSE_SCHEMA = (
141
+ "blun.cms-source-delivery-submission-lifecycle-response.v1"
142
+ )
143
+ STATUS_SCHEMA = "blun.cms-source-delivery-submission-status.v2"
144
+ LIFECYCLE_SCHEMA = "blun.cms-source-delivery-submission-lifecycle.v4"
145
+ HEALTH_SCHEMA = "blun.cms-source-delivery-submission-health.v2"
146
+ PIPELINE_HEALTH_SCHEMA = (
147
+ "blun.cms-source-delivery-submission-pipeline-health.v3"
148
+ )
149
+ READINESS_SCHEMA = "blun.cms-source-delivery-submission-readiness.v2"
150
+ PIPELINE_READINESS_SCHEMA = (
151
+ "blun.cms-source-delivery-submission-pipeline-readiness.v3"
152
+ )
153
+
154
+
155
+ def _canonical(value: Any) -> bytes:
156
+ return json.dumps(
157
+ value,
158
+ ensure_ascii=False,
159
+ allow_nan=False,
160
+ sort_keys=True,
161
+ separators=(",", ":"),
162
+ ).encode("utf-8")
163
+
164
+
165
+ class HMACCMSSourceDeliverySubmissionRuntimeBlocked(RuntimeError):
166
+ """Stable composition failure without credentials or website content."""
167
+
168
+ def __init__(self, code: str):
169
+ super().__init__(code)
170
+ self.code = code
171
+
172
+
173
+ def _blocked(code: str) -> HMACCMSSourceDeliverySubmissionRuntimeBlocked:
174
+ return HMACCMSSourceDeliverySubmissionRuntimeBlocked(
175
+ "source_delivery_submission_runtime." + code
176
+ )
177
+
178
+
179
+ @dataclass(frozen=True)
180
+ class HMACCMSSourceDeliverySubmissionCapabilities:
181
+ """Canonical content-free contract for the complete website edge."""
182
+
183
+ schema: str
184
+ operations: Mapping[str, Any]
185
+ retry_budgets: Mapping[str, Any]
186
+ semantics: Mapping[str, Any]
187
+ sidecar_capabilities_sha256: str
188
+ source_capabilities_sha256: str
189
+ website_capability_binding: Mapping[str, Any]
190
+ sha256: str
191
+
192
+ def as_payload(self) -> dict[str, Any]:
193
+ return copy.deepcopy({
194
+ "schema": self.schema,
195
+ "operations": dict(self.operations),
196
+ "retry_budgets": dict(self.retry_budgets),
197
+ "semantics": dict(self.semantics),
198
+ "sidecar_capabilities_sha256": (
199
+ self.sidecar_capabilities_sha256
200
+ ),
201
+ "source_capabilities_sha256": (
202
+ self.source_capabilities_sha256
203
+ ),
204
+ "website_capability_binding": dict(
205
+ self.website_capability_binding
206
+ ),
207
+ "sha256": self.sha256,
208
+ })
209
+
210
+
211
+ @dataclass(frozen=True)
212
+ class HMACCMSSourceDeliverySubmissionStatus:
213
+ """Content-free progress across website and sidecar acceptance queues."""
214
+
215
+ schema: str
216
+ operation: str
217
+ request_id: str
218
+ event_id: str
219
+ site_id: str
220
+ payload_sha256: str
221
+ status: str
222
+ stage: str
223
+ website_status: str
224
+ website_attempts: int
225
+ website_delivery_max_attempts: int
226
+ sidecar_status: str | None
227
+ sidecar_attempts: int | None
228
+ sidecar_delivery_max_attempts: int
229
+ source_max_attempts: int
230
+ next_attempt_at: float
231
+ lease_expired: bool
232
+ error_code: str | None
233
+ website_capability_binding: Mapping[str, Any]
234
+
235
+ def as_payload(self) -> dict[str, Any]:
236
+ return {
237
+ "schema": self.schema,
238
+ "operation": self.operation,
239
+ "request_id": self.request_id,
240
+ "event_id": self.event_id,
241
+ "site_id": self.site_id,
242
+ "payload_sha256": self.payload_sha256,
243
+ "status": self.status,
244
+ "stage": self.stage,
245
+ "website_status": self.website_status,
246
+ "website_attempts": self.website_attempts,
247
+ "website_delivery_max_attempts": (
248
+ self.website_delivery_max_attempts
249
+ ),
250
+ "sidecar_status": self.sidecar_status,
251
+ "sidecar_attempts": self.sidecar_attempts,
252
+ "sidecar_delivery_max_attempts": (
253
+ self.sidecar_delivery_max_attempts
254
+ ),
255
+ "source_max_attempts": self.source_max_attempts,
256
+ "next_attempt_at": self.next_attempt_at,
257
+ "lease_expired": self.lease_expired,
258
+ "error_code": self.error_code,
259
+ "website_capability_binding": copy.deepcopy(
260
+ dict(self.website_capability_binding)
261
+ ),
262
+ }
263
+
264
+
265
+ @dataclass(frozen=True)
266
+ class HMACCMSSourceDeliverySubmissionReadiness:
267
+ """Content-free readiness across both durable submission workers."""
268
+
269
+ schema: str
270
+ status: str
271
+ website_status: str
272
+ website_worker_state: str
273
+ website_outbox_status: str | None
274
+ website_error_code: str | None
275
+ sidecar_status: str | None
276
+ sidecar_worker_state: str | None
277
+ sidecar_outbox_status: str | None
278
+ sidecar_error_code: str | None
279
+ sidecar_capabilities_sha256: str
280
+ source_capabilities_sha256: str
281
+ website_capability_binding: Mapping[str, Any]
282
+
283
+ def as_payload(self) -> dict[str, Any]:
284
+ return {
285
+ "schema": self.schema,
286
+ "status": self.status,
287
+ "website_status": self.website_status,
288
+ "website_worker_state": self.website_worker_state,
289
+ "website_outbox_status": self.website_outbox_status,
290
+ "website_error_code": self.website_error_code,
291
+ "sidecar_status": self.sidecar_status,
292
+ "sidecar_worker_state": self.sidecar_worker_state,
293
+ "sidecar_outbox_status": self.sidecar_outbox_status,
294
+ "sidecar_error_code": self.sidecar_error_code,
295
+ "sidecar_capabilities_sha256": self.sidecar_capabilities_sha256,
296
+ "source_capabilities_sha256": self.source_capabilities_sha256,
297
+ "website_capability_binding": copy.deepcopy(
298
+ dict(self.website_capability_binding)
299
+ ),
300
+ }
301
+
302
+
303
+ @dataclass(frozen=True)
304
+ class HMACCMSSourceDeliverySubmissionPipelineReadiness:
305
+ """Content-free intake and source-processing readiness kept separate."""
306
+
307
+ schema: str
308
+ status: str
309
+ intake_readiness: Mapping[str, Any]
310
+ source_readiness: Mapping[str, Any] | None
311
+ source_capability_binding: Mapping[str, Any] | None
312
+ website_capability_binding: Mapping[str, Any]
313
+ sidecar_capabilities_sha256: str
314
+ source_capabilities_sha256: str
315
+
316
+ def as_payload(self) -> dict[str, Any]:
317
+ return {
318
+ "schema": self.schema,
319
+ "status": self.status,
320
+ "intake_readiness": copy.deepcopy(dict(self.intake_readiness)),
321
+ "source_readiness": (
322
+ None
323
+ if self.source_readiness is None
324
+ else copy.deepcopy(dict(self.source_readiness))
325
+ ),
326
+ "source_capability_binding": (
327
+ None
328
+ if self.source_capability_binding is None
329
+ else copy.deepcopy(dict(self.source_capability_binding))
330
+ ),
331
+ "website_capability_binding": copy.deepcopy(
332
+ dict(self.website_capability_binding)
333
+ ),
334
+ "sidecar_capabilities_sha256": (
335
+ self.sidecar_capabilities_sha256
336
+ ),
337
+ "source_capabilities_sha256": (
338
+ self.source_capabilities_sha256
339
+ ),
340
+ }
341
+
342
+
343
+ @dataclass(frozen=True)
344
+ class HMACCMSSourceDeliverySubmissionHealth:
345
+ """Content-free health across both durable submission outboxes."""
346
+
347
+ schema: str
348
+ status: str
349
+ website_health: Mapping[str, Any]
350
+ sidecar_health: Mapping[str, Any]
351
+ sidecar_capabilities_sha256: str
352
+ source_capabilities_sha256: str
353
+ website_capability_binding: Mapping[str, Any]
354
+
355
+ @staticmethod
356
+ def _copy_health(value: Mapping[str, Any]) -> dict[str, Any]:
357
+ result = dict(value)
358
+ result["counts"] = dict(value["counts"])
359
+ result["operations"] = dict(value["operations"])
360
+ return result
361
+
362
+ def as_payload(self) -> dict[str, Any]:
363
+ return {
364
+ "schema": self.schema,
365
+ "status": self.status,
366
+ "website_health": self._copy_health(self.website_health),
367
+ "sidecar_health": self._copy_health(self.sidecar_health),
368
+ "sidecar_capabilities_sha256": self.sidecar_capabilities_sha256,
369
+ "source_capabilities_sha256": self.source_capabilities_sha256,
370
+ "website_capability_binding": copy.deepcopy(
371
+ dict(self.website_capability_binding)
372
+ ),
373
+ }
374
+
375
+
376
+ @dataclass(frozen=True)
377
+ class HMACCMSSourceDeliverySubmissionPipelineHealth:
378
+ """Content-free intake and source queue health kept separate."""
379
+
380
+ schema: str
381
+ status: str
382
+ intake_health: Mapping[str, Any]
383
+ source_health: Mapping[str, Any] | None
384
+ source_capability_binding: Mapping[str, Any] | None
385
+ website_capability_binding: Mapping[str, Any]
386
+ sidecar_capabilities_sha256: str
387
+ source_capabilities_sha256: str
388
+
389
+ def as_payload(self) -> dict[str, Any]:
390
+ return {
391
+ "schema": self.schema,
392
+ "status": self.status,
393
+ "intake_health": copy.deepcopy(dict(self.intake_health)),
394
+ "source_health": (
395
+ None
396
+ if self.source_health is None
397
+ else copy.deepcopy(dict(self.source_health))
398
+ ),
399
+ "source_capability_binding": (
400
+ None
401
+ if self.source_capability_binding is None
402
+ else copy.deepcopy(dict(self.source_capability_binding))
403
+ ),
404
+ "website_capability_binding": copy.deepcopy(
405
+ dict(self.website_capability_binding)
406
+ ),
407
+ "sidecar_capabilities_sha256": (
408
+ self.sidecar_capabilities_sha256
409
+ ),
410
+ "source_capabilities_sha256": (
411
+ self.source_capabilities_sha256
412
+ ),
413
+ }
414
+
415
+
416
+ @dataclass(frozen=True)
417
+ class HMACCMSSourceDeliverySubmissionLifecycle:
418
+ """Content-free acceptance and source localization state kept separate."""
419
+
420
+ schema: str
421
+ status: str
422
+ stage: str
423
+ submission: Mapping[str, Any]
424
+ source_status: Mapping[str, Any] | None
425
+ source_capability_binding: Mapping[str, Any] | None
426
+ website_capability_binding: Mapping[str, Any]
427
+ sidecar_capabilities_sha256: str
428
+ source_capabilities_sha256: str
429
+
430
+ def as_payload(self) -> dict[str, Any]:
431
+ return {
432
+ "schema": self.schema,
433
+ "status": self.status,
434
+ "stage": self.stage,
435
+ "submission": copy.deepcopy(dict(self.submission)),
436
+ "source_status": (
437
+ None
438
+ if self.source_status is None
439
+ else copy.deepcopy(dict(self.source_status))
440
+ ),
441
+ "source_capability_binding": (
442
+ None
443
+ if self.source_capability_binding is None
444
+ else copy.deepcopy(dict(self.source_capability_binding))
445
+ ),
446
+ "website_capability_binding": copy.deepcopy(
447
+ dict(self.website_capability_binding)
448
+ ),
449
+ "sidecar_capabilities_sha256": (
450
+ self.sidecar_capabilities_sha256
451
+ ),
452
+ "source_capabilities_sha256": (
453
+ self.source_capabilities_sha256
454
+ ),
455
+ }
456
+
457
+
458
+ class HMACCMSSourceDeliverySubmissionRuntime:
459
+ """Own one signer, sidecar client, adapter, outbox, and optional worker."""
460
+
461
+ def __init__(self, client: Any, adapter: Any, delivery: Any):
462
+ self._owner_pid = os.getpid()
463
+ self._client = client
464
+ self._adapter = adapter
465
+ self._delivery = delivery
466
+
467
+ def __repr__(self) -> str:
468
+ return (
469
+ "HMACCMSSourceDeliverySubmissionRuntime"
470
+ f"(state={self.state!r}, worker_state={self.worker_state!r})"
471
+ )
472
+
473
+ def __enter__(self) -> "HMACCMSSourceDeliverySubmissionRuntime":
474
+ self._assert_open()
475
+ return self
476
+
477
+ def __exit__(self, *_exc: Any) -> None:
478
+ self.close()
479
+
480
+ def _assert_owner(self) -> None:
481
+ if os.getpid() != self._owner_pid:
482
+ raise _blocked("foreign_process")
483
+
484
+ def _assert_open(self) -> None:
485
+ self._assert_owner()
486
+ if self._delivery.state != "open":
487
+ raise _blocked("closed")
488
+
489
+ def website_capability_binding(self) -> dict[str, Any]:
490
+ """Return the exact content-free generation bound to the website DB."""
491
+
492
+ self._assert_open()
493
+ try:
494
+ row = _RUNTIME._DELIVERY._capability_binding_row(
495
+ self._adapter.expected_capabilities_sha256,
496
+ self._adapter.expected_runtime_capabilities_sha256,
497
+ self._adapter.expected_commercial_rendering_registry_sha256,
498
+ self._adapter.expected_terminal_receiver_capabilities_sha256,
499
+ )
500
+ expected = {
501
+ "schema": row[1],
502
+ "status": "verified",
503
+ "database_role": row[2],
504
+ "delivery_capabilities_sha256": row[3],
505
+ "runtime_capabilities_sha256": row[4],
506
+ "commercial_rendering_registry_sha256": row[5],
507
+ "terminal_receiver_capabilities_sha256": row[6],
508
+ "binding_sha256": row[7],
509
+ }
510
+ binding = self._delivery.capability_binding()
511
+ if not isinstance(binding, Mapping) or binding != expected:
512
+ raise ValueError
513
+ except Exception:
514
+ raise _blocked("website_capability_binding_invalid") from None
515
+ return copy.deepcopy(expected)
516
+
517
+ def submission_capabilities(
518
+ self,
519
+ ) -> HMACCMSSourceDeliverySubmissionCapabilities:
520
+ """Discover and hash the exact live end-to-end submission contract."""
521
+
522
+ self._assert_open()
523
+ website_capability_binding = self.website_capability_binding()
524
+ try:
525
+ response = self._client.capabilities()
526
+ expected_sidecar = _AUTH._HTTP._capabilities_payload()
527
+ if (
528
+ not isinstance(response, Mapping)
529
+ or set(response) != {"schema", "capabilities"}
530
+ or response.get("schema")
531
+ != _AUTH._HTTP.CAPABILITIES_RESPONSE_SCHEMA
532
+ or response.get("capabilities") != expected_sidecar
533
+ or expected_sidecar.get("sha256")
534
+ != self._client.expected_capabilities_sha256
535
+ or self._client.expected_capabilities_sha256
536
+ != self._adapter.sidecar_capabilities_sha256
537
+ or self._client.expected_remote_capabilities_sha256
538
+ != self._adapter.remote_capabilities_sha256
539
+ ):
540
+ raise ValueError
541
+ except Exception:
542
+ raise _blocked("capabilities_invalid") from None
543
+
544
+ operations = {
545
+ "capabilities_http": {
546
+ "kind": "read",
547
+ "method": "GET",
548
+ "path": CAPABILITIES_HTTP_PATH,
549
+ "scope": CAPABILITIES_HTTP_SCOPE,
550
+ "principal_schema": CAPABILITIES_HTTP_PRINCIPAL_SCHEMA,
551
+ "request_schema": None,
552
+ "response_schema": CAPABILITIES_HTTP_RESPONSE_SCHEMA,
553
+ },
554
+ "enqueue_change": {
555
+ "kind": "write",
556
+ "method": "POST",
557
+ "path": CHANGE_HTTP_PATH,
558
+ "scope": CHANGE_HTTP_SCOPE,
559
+ "principal_schema": WRITE_HTTP_PRINCIPAL_SCHEMA,
560
+ "request_schema": CHANGE_HTTP_REQUEST_SCHEMA,
561
+ "payload_schemas": [_ADAPTER.CHANGE_SCHEMA],
562
+ "response_schema": CHANGE_HTTP_RESPONSE_SCHEMA,
563
+ },
564
+ "enqueue_removal": {
565
+ "kind": "write",
566
+ "method": "POST",
567
+ "path": REMOVAL_HTTP_PATH,
568
+ "scope": REMOVAL_HTTP_SCOPE,
569
+ "principal_schema": WRITE_HTTP_PRINCIPAL_SCHEMA,
570
+ "request_schema": REMOVAL_HTTP_REQUEST_SCHEMA,
571
+ "payload_schemas": [
572
+ _ADAPTER.CANCELLATION_SCHEMA,
573
+ _ADAPTER.TOMBSTONE_SCHEMA,
574
+ ],
575
+ "response_schema": REMOVAL_HTTP_RESPONSE_SCHEMA,
576
+ },
577
+ "submission_status": {
578
+ "kind": "read",
579
+ "method": "POST",
580
+ "path": STATUS_HTTP_PATH,
581
+ "scope": STATUS_HTTP_SCOPE,
582
+ "principal_schema": READ_HTTP_PRINCIPAL_SCHEMA,
583
+ "request_schema": STATUS_HTTP_REQUEST_SCHEMA,
584
+ "result_schema": STATUS_SCHEMA,
585
+ "response_schema": STATUS_HTTP_RESPONSE_SCHEMA,
586
+ },
587
+ "submission_lifecycle": {
588
+ "kind": "read",
589
+ "method": "POST",
590
+ "path": LIFECYCLE_HTTP_PATH,
591
+ "scope": LIFECYCLE_HTTP_SCOPE,
592
+ "principal_schema": READ_HTTP_PRINCIPAL_SCHEMA,
593
+ "request_schema": LIFECYCLE_HTTP_REQUEST_SCHEMA,
594
+ "result_schema": LIFECYCLE_SCHEMA,
595
+ "response_schema": LIFECYCLE_HTTP_RESPONSE_SCHEMA,
596
+ },
597
+ "submission_health": {
598
+ "kind": "read",
599
+ "response_schema": HEALTH_SCHEMA,
600
+ },
601
+ "submission_pipeline_health": {
602
+ "kind": "read",
603
+ "method": "GET",
604
+ "path": PIPELINE_HEALTH_HTTP_PATH,
605
+ "scope": PIPELINE_HEALTH_HTTP_SCOPE,
606
+ "principal_schema": OPERATOR_HTTP_PRINCIPAL_SCHEMA,
607
+ "request_schema": None,
608
+ "result_schema": PIPELINE_HEALTH_SCHEMA,
609
+ "response_schema": PIPELINE_HEALTH_HTTP_RESPONSE_SCHEMA,
610
+ },
611
+ "submission_readiness": {
612
+ "kind": "read",
613
+ "response_schema": READINESS_SCHEMA,
614
+ },
615
+ "submission_pipeline_readiness": {
616
+ "kind": "read",
617
+ "method": "GET",
618
+ "path": PIPELINE_READINESS_HTTP_PATH,
619
+ "scope": PIPELINE_READINESS_HTTP_SCOPE,
620
+ "principal_schema": OPERATOR_HTTP_PRINCIPAL_SCHEMA,
621
+ "request_schema": None,
622
+ "result_schema": PIPELINE_READINESS_SCHEMA,
623
+ "response_schema": PIPELINE_READINESS_HTTP_RESPONSE_SCHEMA,
624
+ },
625
+ }
626
+ retry_budgets = {
627
+ "website_to_sidecar": {
628
+ "configured_per_request": True,
629
+ "minimum": 1,
630
+ "maximum": 20,
631
+ },
632
+ "sidecar_to_source": {
633
+ "configured_per_runtime": True,
634
+ "maximum_attempts": (
635
+ self._adapter.sidecar_delivery_max_attempts
636
+ ),
637
+ },
638
+ "source_processing": {
639
+ "configured_per_request": True,
640
+ "minimum": 1,
641
+ "maximum": 20,
642
+ },
643
+ }
644
+ semantics = {
645
+ "content_free": True,
646
+ "accepted_means": "durable_website_outbox_acceptance",
647
+ "accepted_implies_publication": False,
648
+ "translation_generation": False,
649
+ "publication_authority": False,
650
+ }
651
+ body = {
652
+ "schema": CAPABILITIES_SCHEMA,
653
+ "operations": operations,
654
+ "retry_budgets": retry_budgets,
655
+ "semantics": semantics,
656
+ "sidecar_capabilities_sha256": (
657
+ self._client.expected_capabilities_sha256
658
+ ),
659
+ "source_capabilities_sha256": (
660
+ self._client.expected_remote_capabilities_sha256
661
+ ),
662
+ "website_capability_binding": website_capability_binding,
663
+ }
664
+ try:
665
+ digest = hashlib.sha256(_canonical(body)).hexdigest()
666
+ except Exception:
667
+ raise _blocked("capabilities_invalid") from None
668
+ return HMACCMSSourceDeliverySubmissionCapabilities(
669
+ **copy.deepcopy(body),
670
+ sha256=digest,
671
+ )
672
+
673
+ def replace_credential(self, credential: HMACCredential) -> None:
674
+ """Atomically replace the signer used by every sidecar operation."""
675
+
676
+ self._assert_open()
677
+ self._client.replace_credential(credential)
678
+
679
+ def enqueue_change(
680
+ self,
681
+ change: Mapping[str, Any],
682
+ *,
683
+ source_max_attempts: int = 5,
684
+ delivery_max_attempts: int = 5,
685
+ ) -> Any:
686
+ self._assert_open()
687
+ return self._delivery.enqueue_change(
688
+ change,
689
+ source_max_attempts=source_max_attempts,
690
+ delivery_max_attempts=delivery_max_attempts,
691
+ )
692
+
693
+ def enqueue_removal(
694
+ self,
695
+ removal: Mapping[str, Any],
696
+ *,
697
+ source_max_attempts: int = 5,
698
+ delivery_max_attempts: int = 5,
699
+ ) -> Any:
700
+ self._assert_open()
701
+ return self._delivery.enqueue_removal(
702
+ removal,
703
+ source_max_attempts=source_max_attempts,
704
+ delivery_max_attempts=delivery_max_attempts,
705
+ )
706
+
707
+ def status(self, operation: str, request_id: str) -> Any:
708
+ """Return only the local website-to-sidecar acceptance state."""
709
+
710
+ self._assert_open()
711
+ return self._delivery.status(operation, request_id)
712
+
713
+ def submission_status(
714
+ self, operation: str, request_id: str,
715
+ ) -> HMACCMSSourceDeliverySubmissionStatus:
716
+ """Project exact progress through both durable acceptance queues."""
717
+
718
+ self._assert_open()
719
+ website_capability_binding = self.website_capability_binding()
720
+ local = self._delivery.status(operation, request_id)
721
+ if local.capabilities_sha256 != self.expected_capabilities_sha256:
722
+ raise _blocked("status_invalid")
723
+ if local.status != "succeeded":
724
+ failed = local.status == "failed"
725
+ return HMACCMSSourceDeliverySubmissionStatus(
726
+ schema=STATUS_SCHEMA,
727
+ operation=local.operation,
728
+ request_id=local.request_id,
729
+ event_id=local.event_id,
730
+ site_id=local.site_id,
731
+ payload_sha256=local.payload_sha256,
732
+ status="failed" if failed else "pending",
733
+ stage="website_acceptance",
734
+ website_status=local.status,
735
+ website_attempts=local.attempts,
736
+ website_delivery_max_attempts=local.delivery_max_attempts,
737
+ sidecar_status=None,
738
+ sidecar_attempts=None,
739
+ sidecar_delivery_max_attempts=(
740
+ self._adapter.sidecar_delivery_max_attempts
741
+ ),
742
+ source_max_attempts=local.source_max_attempts,
743
+ next_attempt_at=local.next_attempt_at,
744
+ lease_expired=local.lease_expired,
745
+ error_code=local.last_error_code,
746
+ website_capability_binding=website_capability_binding,
747
+ )
748
+
749
+ response = self._client.status(
750
+ local.operation,
751
+ local.request_id,
752
+ local.event_id,
753
+ local.site_id,
754
+ local.payload_sha256,
755
+ )
756
+ try:
757
+ if (
758
+ not isinstance(response, Mapping)
759
+ or set(response)
760
+ != {"schema", "status", "capabilities_sha256"}
761
+ or response.get("schema")
762
+ != _AUTH._HTTP.STATUS_RESPONSE_SCHEMA
763
+ or response.get("capabilities_sha256")
764
+ != self._client.expected_capabilities_sha256
765
+ ):
766
+ raise ValueError
767
+ sidecar = _AUTH._CLIENT._status_payload(
768
+ response["status"],
769
+ expected_operation=local.operation,
770
+ expected_request_id=local.request_id,
771
+ expected_event_id=local.event_id,
772
+ expected_site_id=local.site_id,
773
+ expected_payload_sha256=local.payload_sha256,
774
+ expected_remote_capabilities_sha256=(
775
+ self._client.expected_remote_capabilities_sha256
776
+ ),
777
+ )
778
+ if (
779
+ sidecar["delivery_max_attempts"]
780
+ != self._adapter.sidecar_delivery_max_attempts
781
+ or sidecar["source_max_attempts"]
782
+ != local.source_max_attempts
783
+ ):
784
+ raise ValueError
785
+ except Exception:
786
+ raise _blocked("status_invalid") from None
787
+
788
+ accepted = sidecar["status"] == "succeeded"
789
+ failed = sidecar["status"] == "failed"
790
+ return HMACCMSSourceDeliverySubmissionStatus(
791
+ schema=STATUS_SCHEMA,
792
+ operation=local.operation,
793
+ request_id=local.request_id,
794
+ event_id=local.event_id,
795
+ site_id=local.site_id,
796
+ payload_sha256=local.payload_sha256,
797
+ status="accepted" if accepted else "failed" if failed else "pending",
798
+ stage="source_acceptance" if accepted else "sidecar_delivery",
799
+ website_status=local.status,
800
+ website_attempts=local.attempts,
801
+ website_delivery_max_attempts=local.delivery_max_attempts,
802
+ sidecar_status=sidecar["status"],
803
+ sidecar_attempts=sidecar["attempts"],
804
+ sidecar_delivery_max_attempts=sidecar["delivery_max_attempts"],
805
+ source_max_attempts=sidecar["source_max_attempts"],
806
+ next_attempt_at=float(sidecar["next_attempt_at"]),
807
+ lease_expired=sidecar["lease_expired"],
808
+ error_code=sidecar["last_error_code"],
809
+ website_capability_binding=website_capability_binding,
810
+ )
811
+
812
+ def submission_lifecycle(
813
+ self, operation: str, request_id: str,
814
+ ) -> HMACCMSSourceDeliverySubmissionLifecycle:
815
+ """Read acceptance first, then the complete source localization state."""
816
+
817
+ self._assert_open()
818
+ submission = self.submission_status(operation, request_id)
819
+ submission_payload = submission.as_payload()
820
+ website_capability_binding = submission.website_capability_binding
821
+ if submission.status != "accepted":
822
+ return HMACCMSSourceDeliverySubmissionLifecycle(
823
+ schema=LIFECYCLE_SCHEMA,
824
+ status=submission.status,
825
+ stage=submission.stage,
826
+ submission=submission_payload,
827
+ source_status=None,
828
+ source_capability_binding=None,
829
+ website_capability_binding=website_capability_binding,
830
+ sidecar_capabilities_sha256=(
831
+ self._client.expected_capabilities_sha256
832
+ ),
833
+ source_capabilities_sha256=(
834
+ self._client.expected_remote_capabilities_sha256
835
+ ),
836
+ )
837
+
838
+ try:
839
+ response = self._client.source_status(
840
+ submission.event_id,
841
+ submission.site_id,
842
+ submission.payload_sha256,
843
+ )
844
+ except Exception:
845
+ raise _blocked("lifecycle_unavailable") from None
846
+ try:
847
+ if (
848
+ not isinstance(response, Mapping)
849
+ or set(response) != {
850
+ "schema", "source_status",
851
+ "source_capabilities_sha256", "capabilities_sha256",
852
+ "source_capability_binding",
853
+ }
854
+ or response.get("schema")
855
+ != _AUTH._HTTP.SOURCE_STATUS_RESPONSE_SCHEMA
856
+ or response.get("capabilities_sha256")
857
+ != self._client.expected_capabilities_sha256
858
+ or response.get("source_capabilities_sha256")
859
+ != self._client.expected_remote_capabilities_sha256
860
+ ):
861
+ raise ValueError
862
+ source_status = _AUTH._HTTP._source_status_response(
863
+ {
864
+ "schema": _AUTH._HTTP._SOURCE_HTTP.STATUS_RESPONSE_SCHEMA,
865
+ "status": response["source_status"],
866
+ "capability_binding": response[
867
+ "source_capability_binding"
868
+ ],
869
+ "capabilities_sha256": (
870
+ response["source_capabilities_sha256"]
871
+ ),
872
+ },
873
+ expected_event_id=submission.event_id,
874
+ expected_site_id=submission.site_id,
875
+ expected_capabilities_sha256=(
876
+ self._client.expected_remote_capabilities_sha256
877
+ ),
878
+ )
879
+ if (
880
+ source_status["status"] != response["source_status"]
881
+ or source_status["capability_binding"]
882
+ != response["source_capability_binding"]
883
+ ):
884
+ raise ValueError
885
+ except Exception:
886
+ raise _blocked("lifecycle_invalid") from None
887
+
888
+ source_capability_binding = source_status["capability_binding"]
889
+ source_status = source_status["status"]
890
+ remote_status = source_status["remote_status"]
891
+ if remote_status is not None:
892
+ status = remote_status
893
+ stage = "localization_lifecycle"
894
+ elif source_status["dispatch_status"] == "failed":
895
+ status = "failed"
896
+ stage = "source_processing"
897
+ else:
898
+ status = "accepted"
899
+ stage = "source_processing"
900
+ return HMACCMSSourceDeliverySubmissionLifecycle(
901
+ schema=LIFECYCLE_SCHEMA,
902
+ status=status,
903
+ stage=stage,
904
+ submission=submission_payload,
905
+ source_status=source_status,
906
+ source_capability_binding=source_capability_binding,
907
+ website_capability_binding=website_capability_binding,
908
+ sidecar_capabilities_sha256=(
909
+ self._client.expected_capabilities_sha256
910
+ ),
911
+ source_capabilities_sha256=(
912
+ self._client.expected_remote_capabilities_sha256
913
+ ),
914
+ )
915
+
916
+ def health(self) -> Any:
917
+ self._assert_open()
918
+ return self._delivery.health()
919
+
920
+ def submission_health(self) -> HMACCMSSourceDeliverySubmissionHealth:
921
+ """Project exact health across both durable acceptance outboxes."""
922
+
923
+ self._assert_open()
924
+ website_capability_binding = self.website_capability_binding()
925
+ try:
926
+ website = _AUTH._HTTP._health_payload(self._delivery.health())
927
+ except Exception:
928
+ raise _blocked("health_invalid") from None
929
+
930
+ response = self._client.health()
931
+ try:
932
+ if (
933
+ not isinstance(response, Mapping)
934
+ or set(response)
935
+ != {"schema", "health", "capabilities_sha256"}
936
+ or response.get("schema") != _AUTH._HTTP.HEALTH_RESPONSE_SCHEMA
937
+ or response.get("capabilities_sha256")
938
+ != self._client.expected_capabilities_sha256
939
+ ):
940
+ raise ValueError
941
+ sidecar = _AUTH._HTTP._health_payload(
942
+ _AUTH._CLIENT._PayloadView(response["health"]),
943
+ )
944
+ if sidecar != response["health"]:
945
+ raise ValueError
946
+ except Exception:
947
+ raise _blocked("health_invalid") from None
948
+
949
+ return HMACCMSSourceDeliverySubmissionHealth(
950
+ schema=HEALTH_SCHEMA,
951
+ status=(
952
+ "ok"
953
+ if website["status"] == sidecar["status"] == "ok"
954
+ else "blocked"
955
+ ),
956
+ website_health=website,
957
+ sidecar_health=sidecar,
958
+ sidecar_capabilities_sha256=(
959
+ self._client.expected_capabilities_sha256
960
+ ),
961
+ source_capabilities_sha256=(
962
+ self._client.expected_remote_capabilities_sha256
963
+ ),
964
+ website_capability_binding=website_capability_binding,
965
+ )
966
+
967
+ def submission_pipeline_health(
968
+ self,
969
+ ) -> HMACCMSSourceDeliverySubmissionPipelineHealth:
970
+ """Project exact health through intake and source processing."""
971
+
972
+ self._assert_open()
973
+ intake = self.submission_health()
974
+ intake_payload = intake.as_payload()
975
+ website_capability_binding = intake.website_capability_binding
976
+ if intake.status != "ok":
977
+ return HMACCMSSourceDeliverySubmissionPipelineHealth(
978
+ schema=PIPELINE_HEALTH_SCHEMA,
979
+ status="blocked",
980
+ intake_health=intake_payload,
981
+ source_health=None,
982
+ source_capability_binding=None,
983
+ website_capability_binding=website_capability_binding,
984
+ sidecar_capabilities_sha256=(
985
+ self._client.expected_capabilities_sha256
986
+ ),
987
+ source_capabilities_sha256=(
988
+ self._client.expected_remote_capabilities_sha256
989
+ ),
990
+ )
991
+
992
+ try:
993
+ response = self._client.source_health()
994
+ except Exception:
995
+ raise _blocked("pipeline_health_unavailable") from None
996
+ try:
997
+ if (
998
+ not isinstance(response, Mapping)
999
+ or set(response) != {
1000
+ "schema", "source_health",
1001
+ "source_capabilities_sha256", "capabilities_sha256",
1002
+ "source_capability_binding",
1003
+ }
1004
+ or response.get("schema")
1005
+ != _AUTH._HTTP.SOURCE_HEALTH_RESPONSE_SCHEMA
1006
+ or response.get("capabilities_sha256")
1007
+ != self._client.expected_capabilities_sha256
1008
+ or response.get("source_capabilities_sha256")
1009
+ != self._client.expected_remote_capabilities_sha256
1010
+ ):
1011
+ raise ValueError
1012
+ source_health = _AUTH._HTTP._source_health_response(
1013
+ {
1014
+ "schema": _AUTH._HTTP._SOURCE_HTTP.HEALTH_RESPONSE_SCHEMA,
1015
+ "health": response["source_health"],
1016
+ "capability_binding": response[
1017
+ "source_capability_binding"
1018
+ ],
1019
+ "capabilities_sha256": (
1020
+ response["source_capabilities_sha256"]
1021
+ ),
1022
+ },
1023
+ expected_capabilities_sha256=(
1024
+ self._client.expected_remote_capabilities_sha256
1025
+ ),
1026
+ )
1027
+ if (
1028
+ source_health["health"] != response["source_health"]
1029
+ or source_health["capability_binding"]
1030
+ != response["source_capability_binding"]
1031
+ ):
1032
+ raise ValueError
1033
+ except Exception:
1034
+ raise _blocked("pipeline_health_invalid") from None
1035
+
1036
+ return HMACCMSSourceDeliverySubmissionPipelineHealth(
1037
+ schema=PIPELINE_HEALTH_SCHEMA,
1038
+ status=source_health["health"]["status"],
1039
+ intake_health=intake_payload,
1040
+ source_health=source_health["health"],
1041
+ source_capability_binding=source_health["capability_binding"],
1042
+ website_capability_binding=website_capability_binding,
1043
+ sidecar_capabilities_sha256=(
1044
+ self._client.expected_capabilities_sha256
1045
+ ),
1046
+ source_capabilities_sha256=(
1047
+ self._client.expected_remote_capabilities_sha256
1048
+ ),
1049
+ )
1050
+
1051
+ def submission_readiness(
1052
+ self,
1053
+ ) -> HMACCMSSourceDeliverySubmissionReadiness:
1054
+ """Project readiness without confusing durable intake with delivery."""
1055
+
1056
+ self._assert_open()
1057
+ website_capability_binding = self.website_capability_binding()
1058
+ local = self._delivery.worker_readiness()
1059
+ try:
1060
+ website = _AUTH._HTTP._readiness_payload(local)
1061
+ if website != local:
1062
+ raise ValueError
1063
+ except Exception:
1064
+ raise _blocked("readiness_invalid") from None
1065
+
1066
+ if website["status"] != "ready":
1067
+ return HMACCMSSourceDeliverySubmissionReadiness(
1068
+ schema=READINESS_SCHEMA,
1069
+ status="not_ready",
1070
+ website_status=website["status"],
1071
+ website_worker_state=website["worker_state"],
1072
+ website_outbox_status=website["outbox_status"],
1073
+ website_error_code=website["error_code"],
1074
+ sidecar_status=None,
1075
+ sidecar_worker_state=None,
1076
+ sidecar_outbox_status=None,
1077
+ sidecar_error_code=None,
1078
+ sidecar_capabilities_sha256=(
1079
+ self._client.expected_capabilities_sha256
1080
+ ),
1081
+ source_capabilities_sha256=(
1082
+ self._client.expected_remote_capabilities_sha256
1083
+ ),
1084
+ website_capability_binding=website_capability_binding,
1085
+ )
1086
+
1087
+ response = self._client.readiness()
1088
+ try:
1089
+ if (
1090
+ not isinstance(response, Mapping)
1091
+ or set(response)
1092
+ != {"schema", "readiness", "capabilities_sha256"}
1093
+ or response.get("schema")
1094
+ != _AUTH._HTTP.READINESS_RESPONSE_SCHEMA
1095
+ or response.get("capabilities_sha256")
1096
+ != self._client.expected_capabilities_sha256
1097
+ ):
1098
+ raise ValueError
1099
+ sidecar = _AUTH._HTTP._readiness_payload(response["readiness"])
1100
+ if sidecar != response["readiness"]:
1101
+ raise ValueError
1102
+ except Exception:
1103
+ raise _blocked("readiness_invalid") from None
1104
+
1105
+ ready = sidecar["status"] == "ready"
1106
+ return HMACCMSSourceDeliverySubmissionReadiness(
1107
+ schema=READINESS_SCHEMA,
1108
+ status="ready" if ready else "not_ready",
1109
+ website_status=website["status"],
1110
+ website_worker_state=website["worker_state"],
1111
+ website_outbox_status=website["outbox_status"],
1112
+ website_error_code=website["error_code"],
1113
+ sidecar_status=sidecar["status"],
1114
+ sidecar_worker_state=sidecar["worker_state"],
1115
+ sidecar_outbox_status=sidecar["outbox_status"],
1116
+ sidecar_error_code=sidecar["error_code"],
1117
+ sidecar_capabilities_sha256=(
1118
+ self._client.expected_capabilities_sha256
1119
+ ),
1120
+ source_capabilities_sha256=(
1121
+ self._client.expected_remote_capabilities_sha256
1122
+ ),
1123
+ website_capability_binding=website_capability_binding,
1124
+ )
1125
+
1126
+ def submission_pipeline_readiness(
1127
+ self,
1128
+ ) -> HMACCMSSourceDeliverySubmissionPipelineReadiness:
1129
+ """Require website, sidecar, and source processing to be ready."""
1130
+
1131
+ self._assert_open()
1132
+ intake = self.submission_readiness()
1133
+ intake_payload = intake.as_payload()
1134
+ website_capability_binding = intake.website_capability_binding
1135
+ if intake.status != "ready":
1136
+ return HMACCMSSourceDeliverySubmissionPipelineReadiness(
1137
+ schema=PIPELINE_READINESS_SCHEMA,
1138
+ status="not_ready",
1139
+ intake_readiness=intake_payload,
1140
+ source_readiness=None,
1141
+ source_capability_binding=None,
1142
+ website_capability_binding=website_capability_binding,
1143
+ sidecar_capabilities_sha256=(
1144
+ self._client.expected_capabilities_sha256
1145
+ ),
1146
+ source_capabilities_sha256=(
1147
+ self._client.expected_remote_capabilities_sha256
1148
+ ),
1149
+ )
1150
+
1151
+ try:
1152
+ response = self._client.source_readiness()
1153
+ except Exception:
1154
+ raise _blocked("pipeline_readiness_unavailable") from None
1155
+ try:
1156
+ if (
1157
+ not isinstance(response, Mapping)
1158
+ or set(response) != {
1159
+ "schema", "source_readiness",
1160
+ "source_capabilities_sha256", "capabilities_sha256",
1161
+ "source_capability_binding",
1162
+ }
1163
+ or response.get("schema")
1164
+ != _AUTH._HTTP.SOURCE_READINESS_RESPONSE_SCHEMA
1165
+ or response.get("capabilities_sha256")
1166
+ != self._client.expected_capabilities_sha256
1167
+ or response.get("source_capabilities_sha256")
1168
+ != self._client.expected_remote_capabilities_sha256
1169
+ ):
1170
+ raise ValueError
1171
+ source_readiness = _AUTH._HTTP._source_readiness_response(
1172
+ {
1173
+ "schema": (
1174
+ _AUTH._HTTP._SOURCE_HTTP.READINESS_RESPONSE_SCHEMA
1175
+ ),
1176
+ "readiness": response["source_readiness"],
1177
+ "capability_binding": response[
1178
+ "source_capability_binding"
1179
+ ],
1180
+ "capabilities_sha256": (
1181
+ response["source_capabilities_sha256"]
1182
+ ),
1183
+ },
1184
+ expected_capabilities_sha256=(
1185
+ self._client.expected_remote_capabilities_sha256
1186
+ ),
1187
+ )
1188
+ if (
1189
+ source_readiness["readiness"]
1190
+ != response["source_readiness"]
1191
+ or source_readiness["capability_binding"]
1192
+ != response["source_capability_binding"]
1193
+ ):
1194
+ raise ValueError
1195
+ except Exception:
1196
+ raise _blocked("pipeline_readiness_invalid") from None
1197
+
1198
+ return HMACCMSSourceDeliverySubmissionPipelineReadiness(
1199
+ schema=PIPELINE_READINESS_SCHEMA,
1200
+ status=(
1201
+ "ready"
1202
+ if source_readiness["readiness"]["status"] == "ready"
1203
+ else "not_ready"
1204
+ ),
1205
+ intake_readiness=intake_payload,
1206
+ source_readiness=source_readiness["readiness"],
1207
+ source_capability_binding=(
1208
+ source_readiness["capability_binding"]
1209
+ ),
1210
+ website_capability_binding=website_capability_binding,
1211
+ sidecar_capabilities_sha256=(
1212
+ self._client.expected_capabilities_sha256
1213
+ ),
1214
+ source_capabilities_sha256=(
1215
+ self._client.expected_remote_capabilities_sha256
1216
+ ),
1217
+ )
1218
+
1219
+ def run_once(self) -> Any:
1220
+ self._assert_open()
1221
+ return self._delivery.run_once()
1222
+
1223
+ def start_worker(self, **kwargs: Any) -> None:
1224
+ self._assert_open()
1225
+ self._delivery.start_worker(**kwargs)
1226
+
1227
+ def stop_worker(self, **kwargs: Any) -> None:
1228
+ self._assert_open()
1229
+ self._delivery.stop_worker(**kwargs)
1230
+
1231
+ def require_worker_ready(self) -> None:
1232
+ self._assert_open()
1233
+ self._delivery.require_worker_ready()
1234
+
1235
+ def worker_readiness(self) -> Mapping[str, Any]:
1236
+ self._assert_open()
1237
+ return self._delivery.worker_readiness()
1238
+
1239
+ def run_forever(self, stop: Callable[[], bool], **kwargs: Any) -> None:
1240
+ self._assert_open()
1241
+ self._delivery.run_forever(stop, **kwargs)
1242
+
1243
+ def sidecar_capabilities(self) -> Mapping[str, Any]:
1244
+ self._assert_open()
1245
+ return self._client.capabilities()
1246
+
1247
+ def sidecar_status(
1248
+ self,
1249
+ operation: str,
1250
+ request_id: str,
1251
+ event_id: str,
1252
+ site_id: str,
1253
+ payload_sha256: str,
1254
+ ) -> Mapping[str, Any]:
1255
+ """Read the separately durable sidecar-to-source processing state."""
1256
+
1257
+ self._assert_open()
1258
+ return self._client.status(
1259
+ operation, request_id, event_id, site_id, payload_sha256,
1260
+ )
1261
+
1262
+ def sidecar_source_status(
1263
+ self, event_id: str, site_id: str, payload_sha256: str,
1264
+ ) -> Mapping[str, Any]:
1265
+ """Read source lifecycle through the owned authenticated sidecar."""
1266
+
1267
+ self._assert_open()
1268
+ return self._client.source_status(
1269
+ event_id, site_id, payload_sha256,
1270
+ )
1271
+
1272
+ def sidecar_source_readiness(self) -> Mapping[str, Any]:
1273
+ """Read source processing readiness through the owned sidecar."""
1274
+
1275
+ self._assert_open()
1276
+ return self._client.source_readiness()
1277
+
1278
+ def sidecar_source_health(self) -> Mapping[str, Any]:
1279
+ """Read source queue health through the owned sidecar."""
1280
+
1281
+ self._assert_open()
1282
+ return self._client.source_health()
1283
+
1284
+ def sidecar_health(self) -> Mapping[str, Any]:
1285
+ self._assert_open()
1286
+ return self._client.health()
1287
+
1288
+ def sidecar_readiness(self) -> Mapping[str, Any]:
1289
+ self._assert_open()
1290
+ return self._client.readiness()
1291
+
1292
+ def close(self, *, worker_timeout_seconds: float | int = 30) -> None:
1293
+ self._assert_owner()
1294
+ self._delivery.close(worker_timeout_seconds=worker_timeout_seconds)
1295
+
1296
+ @property
1297
+ def state(self) -> str:
1298
+ if os.getpid() != self._owner_pid:
1299
+ return "foreign-process"
1300
+ return self._delivery.state
1301
+
1302
+ @property
1303
+ def worker_state(self) -> str:
1304
+ if os.getpid() != self._owner_pid:
1305
+ return "foreign-process"
1306
+ return self._delivery.worker_state
1307
+
1308
+ @property
1309
+ def expected_capabilities_sha256(self) -> str:
1310
+ """Return the synthetic contract pinned into every local outbox row."""
1311
+
1312
+ return self._adapter.expected_capabilities_sha256
1313
+
1314
+
1315
+ def _client_and_adapter(
1316
+ credential: HMACCredential,
1317
+ *,
1318
+ origin: str,
1319
+ sidecar_capabilities_sha256: str,
1320
+ remote_capabilities_sha256: str,
1321
+ runtime_capabilities_sha256: str,
1322
+ commercial_rendering_registry_sha256: str,
1323
+ terminal_receiver_capabilities_sha256: str,
1324
+ sidecar_delivery_max_attempts: int,
1325
+ clock: Callable[[], float | int],
1326
+ nonce_factory: Callable[[], str] = lambda: secrets.token_urlsafe(24),
1327
+ transport: Any,
1328
+ timeout: float | int,
1329
+ allow_loopback_http: bool,
1330
+ local_preflight: Callable[[Any], None] | None = None,
1331
+ ) -> tuple[Any, Any]:
1332
+ client = _AUTH.RotatingHMACCMSSourceDeliveryClient(
1333
+ credential,
1334
+ origin=origin,
1335
+ sidecar_capabilities_sha256=sidecar_capabilities_sha256,
1336
+ remote_capabilities_sha256=remote_capabilities_sha256,
1337
+ clock=clock,
1338
+ nonce_factory=nonce_factory,
1339
+ transport=transport,
1340
+ timeout=timeout,
1341
+ allow_loopback_http=allow_loopback_http,
1342
+ )
1343
+ adapter = _ADAPTER.CMSSourceDeliverySidecarOutboxAdapter(
1344
+ client,
1345
+ sidecar_delivery_max_attempts=sidecar_delivery_max_attempts,
1346
+ runtime_capabilities_sha256=runtime_capabilities_sha256,
1347
+ commercial_rendering_registry_sha256=(
1348
+ commercial_rendering_registry_sha256
1349
+ ),
1350
+ terminal_receiver_capabilities_sha256=(
1351
+ terminal_receiver_capabilities_sha256
1352
+ ),
1353
+ )
1354
+ if local_preflight is not None:
1355
+ try:
1356
+ local_preflight(adapter)
1357
+ except Exception as error:
1358
+ code = getattr(error, "code", "")
1359
+ if (
1360
+ isinstance(code, str)
1361
+ and code.startswith("source_delivery_runtime.")
1362
+ ):
1363
+ raise _blocked("local_" + code.split(".", 1)[1]) from None
1364
+ raise _blocked("local_database_preflight_failed") from None
1365
+ try:
1366
+ response = client.source_readiness()
1367
+ except Exception:
1368
+ raise _blocked("capability_preflight_unavailable") from None
1369
+ binding = (
1370
+ response.get("source_capability_binding")
1371
+ if isinstance(response, Mapping)
1372
+ else None
1373
+ )
1374
+ if (
1375
+ not isinstance(binding, Mapping)
1376
+ or set(binding) != {
1377
+ "schema", "status", "capabilities_sha256",
1378
+ "commercial_rendering_registry_sha256", "database_roles",
1379
+ }
1380
+ or binding.get("schema") != "blun.cms-source-capability-binding.v2"
1381
+ or binding.get("status") != "verified"
1382
+ or binding.get("capabilities_sha256")
1383
+ != runtime_capabilities_sha256
1384
+ or binding.get("commercial_rendering_registry_sha256")
1385
+ != commercial_rendering_registry_sha256
1386
+ or binding.get("database_roles")
1387
+ != ["changes", "removals", "lifecycle"]
1388
+ ):
1389
+ raise _blocked("capability_preflight_mismatch")
1390
+ return client, adapter
1391
+
1392
+
1393
+ def open_durable_hmac_cms_source_delivery_submission(
1394
+ database: str | os.PathLike[str],
1395
+ credential: HMACCredential,
1396
+ *,
1397
+ worker_id: str,
1398
+ origin: str,
1399
+ sidecar_capabilities_sha256: str,
1400
+ remote_capabilities_sha256: str,
1401
+ runtime_capabilities_sha256: str,
1402
+ commercial_rendering_registry_sha256: str,
1403
+ terminal_receiver_capabilities_sha256: str,
1404
+ sidecar_delivery_max_attempts: int = 5,
1405
+ clock: Callable[[], float | int] = time.time,
1406
+ nonce_factory: Callable[[], str],
1407
+ transport: Any = None,
1408
+ timeout: float | int = 30,
1409
+ allow_loopback_http: bool = False,
1410
+ sqlite_timeout_seconds: float | int = 5,
1411
+ lease_seconds: float | int = 600,
1412
+ base_delay_seconds: float | int = 5,
1413
+ max_delay_seconds: float | int = 300,
1414
+ ) -> HMACCMSSourceDeliverySubmissionRuntime:
1415
+ """Preflight and open one durable website-to-sidecar submission runtime."""
1416
+
1417
+ client, adapter = _client_and_adapter(
1418
+ credential,
1419
+ origin=origin,
1420
+ sidecar_capabilities_sha256=sidecar_capabilities_sha256,
1421
+ remote_capabilities_sha256=remote_capabilities_sha256,
1422
+ runtime_capabilities_sha256=runtime_capabilities_sha256,
1423
+ commercial_rendering_registry_sha256=(
1424
+ commercial_rendering_registry_sha256
1425
+ ),
1426
+ terminal_receiver_capabilities_sha256=(
1427
+ terminal_receiver_capabilities_sha256
1428
+ ),
1429
+ sidecar_delivery_max_attempts=sidecar_delivery_max_attempts,
1430
+ clock=clock,
1431
+ nonce_factory=nonce_factory,
1432
+ transport=transport,
1433
+ timeout=timeout,
1434
+ allow_loopback_http=allow_loopback_http,
1435
+ local_preflight=lambda adapter: (
1436
+ _RUNTIME.preflight_existing_durable_cms_source_delivery(
1437
+ database, adapter,
1438
+ )
1439
+ ),
1440
+ )
1441
+ delivery = _RUNTIME.open_durable_cms_source_delivery(
1442
+ database,
1443
+ adapter,
1444
+ worker_id=worker_id,
1445
+ clock=clock,
1446
+ sqlite_timeout_seconds=sqlite_timeout_seconds,
1447
+ lease_seconds=lease_seconds,
1448
+ base_delay_seconds=base_delay_seconds,
1449
+ max_delay_seconds=max_delay_seconds,
1450
+ )
1451
+ return HMACCMSSourceDeliverySubmissionRuntime(client, adapter, delivery)
1452
+
1453
+
1454
+ def open_hosted_hmac_cms_source_delivery_submission(
1455
+ *args: Any,
1456
+ active_delay_seconds: float | int = 0.05,
1457
+ idle_delay_seconds: float | int = 1,
1458
+ blocked_delay_seconds: float | int = 5,
1459
+ **kwargs: Any,
1460
+ ) -> HMACCMSSourceDeliverySubmissionRuntime:
1461
+ """Open the complete composition and start its supervised worker."""
1462
+
1463
+ # Validate worker delays before the durable factory can create its file.
1464
+ delays = _RUNTIME.DurableCMSSourceDeliveryRuntime._loop_delays(
1465
+ active_delay_seconds,
1466
+ idle_delay_seconds,
1467
+ blocked_delay_seconds,
1468
+ )
1469
+ runtime = open_durable_hmac_cms_source_delivery_submission(*args, **kwargs)
1470
+ try:
1471
+ runtime.start_worker(
1472
+ active_delay_seconds=delays[0],
1473
+ idle_delay_seconds=delays[1],
1474
+ blocked_delay_seconds=delays[2],
1475
+ )
1476
+ except Exception:
1477
+ runtime.close()
1478
+ raise
1479
+ return runtime