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,609 @@
1
+ #!/usr/bin/env python3
2
+ """Protected runtime and hosted composition for source-delivery HMAC auth."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import importlib.util
7
+ import math
8
+ import os
9
+ import sqlite3
10
+ import stat
11
+ import sys
12
+ import threading
13
+ import time
14
+ from pathlib import Path
15
+ from typing import Any, Callable, Iterable, Mapping
16
+
17
+
18
+ def _load_module(name: str, path: Path):
19
+ spec = importlib.util.spec_from_file_location(name, path)
20
+ if spec is None or spec.loader is None:
21
+ raise RuntimeError(
22
+ f"cannot load source-delivery HMAC runtime dependency: {path.name}"
23
+ )
24
+ module = importlib.util.module_from_spec(spec)
25
+ sys.modules[spec.name] = module
26
+ spec.loader.exec_module(module)
27
+ return module
28
+
29
+
30
+ _ROOT = Path(__file__).resolve().parents[1]
31
+ _AUTH = _load_module(
32
+ "blun_website_localization_cms_source_delivery_auth_runtime_auth",
33
+ _ROOT / "integrations" / "website_localization_cms_source_delivery_auth.py",
34
+ )
35
+ _DELIVERY_RUNTIME = _load_module(
36
+ "blun_website_localization_cms_source_delivery_auth_runtime_delivery",
37
+ _ROOT / "integrations" / "website_localization_cms_source_delivery_runtime.py",
38
+ )
39
+ HMACCredential = _AUTH.HMACCredential
40
+ RotatingSourceDeliveryHMACSigner = _AUTH.RotatingSourceDeliveryHMACSigner
41
+ RotatingHMACCMSSourceDeliveryClient = (
42
+ _AUTH.RotatingHMACCMSSourceDeliveryClient
43
+ )
44
+
45
+
46
+ class SourceDeliveryHMACRuntimeBlocked(RuntimeError):
47
+ """Stable runtime failure without paths, secrets, or private exceptions."""
48
+
49
+ def __init__(self, code: str):
50
+ super().__init__(code)
51
+ self.code = code
52
+
53
+
54
+ def _blocked(code: str) -> SourceDeliveryHMACRuntimeBlocked:
55
+ return SourceDeliveryHMACRuntimeBlocked("source_delivery_hmac_runtime." + code)
56
+
57
+
58
+ def _database_path(value: Any) -> str:
59
+ try:
60
+ result = os.fspath(value)
61
+ except TypeError:
62
+ raise _blocked("database_path_invalid") from None
63
+ if isinstance(result, bytes):
64
+ raise _blocked("database_path_invalid")
65
+ if (
66
+ not isinstance(result, str)
67
+ or not result
68
+ or "\x00" in result
69
+ or result.startswith("file:")
70
+ ):
71
+ raise _blocked("database_path_invalid")
72
+ if result == ":memory:":
73
+ return result
74
+ if (
75
+ os.name != "posix"
76
+ or not os.path.isabs(result)
77
+ or os.path.normpath(result) != result
78
+ ):
79
+ raise _blocked("database_path_invalid")
80
+ return result
81
+
82
+
83
+ def _sqlite_timeout(value: Any) -> float:
84
+ if (
85
+ isinstance(value, bool)
86
+ or not isinstance(value, (int, float))
87
+ or not math.isfinite(float(value))
88
+ or not 0 < float(value) <= 60
89
+ ):
90
+ raise _blocked("sqlite_timeout_invalid")
91
+ return float(value)
92
+
93
+
94
+ def _validate_parent(database_path: str) -> None:
95
+ current = os.path.dirname(database_path)
96
+ first = True
97
+ while True:
98
+ try:
99
+ current_stat = os.lstat(current)
100
+ except OSError:
101
+ raise _blocked("database_parent_unavailable") from None
102
+ mode = stat.S_IMODE(current_stat.st_mode)
103
+ sticky_root_directory = (
104
+ current_stat.st_uid == 0
105
+ and bool(mode & stat.S_ISVTX)
106
+ and bool(mode & 0o002)
107
+ )
108
+ if (
109
+ not stat.S_ISDIR(current_stat.st_mode)
110
+ or current_stat.st_uid not in {0, os.geteuid()}
111
+ or (first and current_stat.st_uid != os.geteuid())
112
+ or (mode & 0o022 and not sticky_root_directory)
113
+ ):
114
+ raise _blocked("database_parent_unsafe")
115
+ parent = os.path.dirname(current)
116
+ if parent == current:
117
+ break
118
+ current = parent
119
+ first = False
120
+
121
+
122
+ def _validate_file(
123
+ database_path: str,
124
+ expected_identity: tuple[int, int] | None = None,
125
+ ) -> tuple[int, int]:
126
+ try:
127
+ database_stat = os.lstat(database_path)
128
+ except FileNotFoundError:
129
+ raise
130
+ except OSError:
131
+ raise _blocked("database_unavailable") from None
132
+ identity = (database_stat.st_dev, database_stat.st_ino)
133
+ if (
134
+ not stat.S_ISREG(database_stat.st_mode)
135
+ or database_stat.st_uid != os.geteuid()
136
+ or database_stat.st_nlink != 1
137
+ or stat.S_IMODE(database_stat.st_mode) != 0o600
138
+ or (expected_identity is not None and identity != expected_identity)
139
+ ):
140
+ raise _blocked("database_unsafe")
141
+ return identity
142
+
143
+
144
+ def _prepare_database_file(database_path: str) -> Callable[[], None]:
145
+ if database_path == ":memory:":
146
+ return lambda: None
147
+ _validate_parent(database_path)
148
+ try:
149
+ identity = _validate_file(database_path)
150
+ except FileNotFoundError:
151
+ flags = os.O_CREAT | os.O_EXCL | os.O_RDWR
152
+ flags |= getattr(os, "O_CLOEXEC", 0)
153
+ flags |= getattr(os, "O_NOFOLLOW", 0)
154
+ try:
155
+ descriptor = os.open(database_path, flags, 0o600)
156
+ except FileExistsError:
157
+ identity = _validate_file(database_path)
158
+ except OSError:
159
+ raise _blocked("database_create_failed") from None
160
+ else:
161
+ try:
162
+ os.fchmod(descriptor, 0o600)
163
+ created_stat = os.fstat(descriptor)
164
+ identity = (created_stat.st_dev, created_stat.st_ino)
165
+ if (
166
+ not stat.S_ISREG(created_stat.st_mode)
167
+ or created_stat.st_uid != os.geteuid()
168
+ or created_stat.st_nlink != 1
169
+ or stat.S_IMODE(created_stat.st_mode) != 0o600
170
+ ):
171
+ raise _blocked("database_create_failed")
172
+ finally:
173
+ os.close(descriptor)
174
+
175
+ def guard() -> None:
176
+ _validate_parent(database_path)
177
+ try:
178
+ _validate_file(database_path, identity)
179
+ except FileNotFoundError:
180
+ raise _blocked("database_unavailable") from None
181
+
182
+ guard()
183
+ return guard
184
+
185
+
186
+ def _same_database_identity(first_path: str, second_path: str) -> bool:
187
+ if ":memory:" in {first_path, second_path}:
188
+ return False
189
+ try:
190
+ first = os.lstat(first_path)
191
+ second = os.lstat(second_path)
192
+ except OSError:
193
+ raise _blocked("database_identity_unavailable") from None
194
+ return (first.st_dev, first.st_ino) == (second.st_dev, second.st_ino)
195
+
196
+
197
+ def _credential_values(
198
+ credentials: Iterable[Any],
199
+ ) -> tuple[Any, ...]:
200
+ try:
201
+ values = tuple(credentials)
202
+ except Exception:
203
+ raise _blocked("configuration_invalid") from None
204
+ if not values:
205
+ raise _blocked("configuration_invalid")
206
+ return values
207
+
208
+
209
+ def _preflight_authentication(
210
+ credentials: tuple[Any, ...],
211
+ *,
212
+ origin: str,
213
+ sidecar_capabilities_sha256: str,
214
+ remote_capabilities_sha256: str,
215
+ clock: Callable[[], float | int],
216
+ max_age_seconds: float | int,
217
+ future_skew_seconds: float | int,
218
+ allow_loopback_http: bool,
219
+ ) -> None:
220
+ connection = sqlite3.connect(":memory:", check_same_thread=False)
221
+ try:
222
+ store = _AUTH.DurableHMACReplayStore(connection)
223
+ _AUTH.SourceDeliveryHMACVerifier(
224
+ credentials,
225
+ store,
226
+ origin=origin,
227
+ sidecar_capabilities_sha256=sidecar_capabilities_sha256,
228
+ remote_capabilities_sha256=remote_capabilities_sha256,
229
+ clock=clock,
230
+ max_age_seconds=max_age_seconds,
231
+ future_skew_seconds=future_skew_seconds,
232
+ allow_loopback_http=allow_loopback_http,
233
+ )
234
+ except Exception as error:
235
+ raise _blocked("configuration_invalid") from error
236
+ finally:
237
+ connection.close()
238
+
239
+
240
+ class DurableSourceDeliveryHMACRuntime:
241
+ """Own one guarded replay ledger and its request verifier."""
242
+
243
+ def __init__(
244
+ self,
245
+ connection: sqlite3.Connection,
246
+ guard: Callable[[], None],
247
+ store: Any,
248
+ verifier: Any,
249
+ verifier_configuration: Mapping[str, Any],
250
+ ):
251
+ self._connection = connection
252
+ self._guard = guard
253
+ self._store = store
254
+ self._verifier = verifier
255
+ self._verifier_configuration = dict(verifier_configuration)
256
+ self._lock = threading.RLock()
257
+ self._owner_pid = os.getpid()
258
+ self._closed = False
259
+
260
+ def __repr__(self) -> str:
261
+ return f"DurableSourceDeliveryHMACRuntime(state={self.state!r})"
262
+
263
+ def __enter__(self) -> "DurableSourceDeliveryHMACRuntime":
264
+ self._assert_owner()
265
+ if self._closed:
266
+ raise _blocked("closed")
267
+ return self
268
+
269
+ def __exit__(self, *_exc: Any) -> None:
270
+ self.close()
271
+
272
+ def _assert_owner(self) -> None:
273
+ if os.getpid() != self._owner_pid:
274
+ raise _blocked("foreign_process")
275
+
276
+ def _call(self, callback: Callable[[], Any]) -> Any:
277
+ self._assert_owner()
278
+ with self._lock:
279
+ if self._closed:
280
+ raise _blocked("closed")
281
+ self._guard()
282
+ try:
283
+ return callback()
284
+ finally:
285
+ self._guard()
286
+
287
+ def __call__(self, request: Mapping[str, Any]) -> Mapping[str, str] | None:
288
+ return self._call(lambda: self._verifier(request))
289
+
290
+ def replace_credentials(self, credentials: Iterable[Any]) -> None:
291
+ """Atomically replace accepted generations without resetting replay state."""
292
+
293
+ self._assert_owner()
294
+ values = _credential_values(credentials)
295
+ with self._lock:
296
+ if self._closed:
297
+ raise _blocked("closed")
298
+ self._guard()
299
+ try:
300
+ replacement = _AUTH.SourceDeliveryHMACVerifier(
301
+ values,
302
+ self._store,
303
+ **self._verifier_configuration,
304
+ )
305
+ except Exception as error:
306
+ raise _blocked("configuration_invalid") from error
307
+ self._guard()
308
+ self._verifier = replacement
309
+
310
+ def health(self) -> Mapping[str, Any]:
311
+ try:
312
+ store_health = self._call(self._store.health)
313
+ if (
314
+ not isinstance(store_health, Mapping)
315
+ or store_health.get("status") != "ok"
316
+ or not isinstance(store_health.get("consumed_nonces"), int)
317
+ or isinstance(store_health.get("consumed_nonces"), bool)
318
+ or store_health["consumed_nonces"] < 0
319
+ ):
320
+ raise _blocked("replay_store_blocked")
321
+ return {
322
+ "schema": "blun.cms-source-delivery-hmac-runtime-health.v1",
323
+ "status": "ok",
324
+ "runtime_state": "open",
325
+ "consumed_nonces": store_health["consumed_nonces"],
326
+ "error_code": None,
327
+ }
328
+ except Exception as error:
329
+ code = getattr(error, "code", None)
330
+ if not (
331
+ isinstance(code, str)
332
+ and code.startswith((
333
+ "source_delivery_hmac_runtime.",
334
+ "source_delivery_hmac.",
335
+ ))
336
+ ):
337
+ code = "source_delivery_hmac_runtime.health_unavailable"
338
+ return {
339
+ "schema": "blun.cms-source-delivery-hmac-runtime-health.v1",
340
+ "status": "blocked",
341
+ "runtime_state": self.state,
342
+ "consumed_nonces": None,
343
+ "error_code": code,
344
+ }
345
+
346
+ def close(self) -> None:
347
+ self._assert_owner()
348
+ with self._lock:
349
+ if self._closed:
350
+ return
351
+ try:
352
+ self._connection.close()
353
+ except sqlite3.Error as error:
354
+ raise _blocked("close_failed") from error
355
+ self._closed = True
356
+
357
+ @property
358
+ def state(self) -> str:
359
+ if os.getpid() != self._owner_pid:
360
+ return "foreign-process"
361
+ with self._lock:
362
+ return "closed" if self._closed else "open"
363
+
364
+
365
+ def open_durable_source_delivery_hmac_runtime(
366
+ replay_database: str | os.PathLike[str],
367
+ credentials: Iterable[Any],
368
+ *,
369
+ origin: str,
370
+ sidecar_capabilities_sha256: str,
371
+ remote_capabilities_sha256: str,
372
+ clock: Callable[[], float | int] = time.time,
373
+ max_age_seconds: float | int = 60,
374
+ future_skew_seconds: float | int = 5,
375
+ sqlite_timeout_seconds: float | int = 5,
376
+ allow_loopback_http: bool = False,
377
+ ) -> DurableSourceDeliveryHMACRuntime:
378
+ """Preflight configuration, then open one private replay ledger."""
379
+
380
+ if not isinstance(allow_loopback_http, bool):
381
+ raise _blocked("configuration_invalid")
382
+ path = _database_path(replay_database)
383
+ timeout = _sqlite_timeout(sqlite_timeout_seconds)
384
+ values = _credential_values(credentials)
385
+ _preflight_authentication(
386
+ values,
387
+ origin=origin,
388
+ sidecar_capabilities_sha256=sidecar_capabilities_sha256,
389
+ remote_capabilities_sha256=remote_capabilities_sha256,
390
+ clock=clock,
391
+ max_age_seconds=max_age_seconds,
392
+ future_skew_seconds=future_skew_seconds,
393
+ allow_loopback_http=allow_loopback_http,
394
+ )
395
+ guard = _prepare_database_file(path)
396
+ connection = None
397
+ try:
398
+ connection = sqlite3.connect(
399
+ path,
400
+ timeout=timeout,
401
+ isolation_level=None,
402
+ check_same_thread=False,
403
+ )
404
+ guard()
405
+ store = _AUTH.DurableHMACReplayStore(connection)
406
+ verifier_configuration = {
407
+ "origin": origin,
408
+ "sidecar_capabilities_sha256": sidecar_capabilities_sha256,
409
+ "remote_capabilities_sha256": remote_capabilities_sha256,
410
+ "clock": clock,
411
+ "max_age_seconds": max_age_seconds,
412
+ "future_skew_seconds": future_skew_seconds,
413
+ "allow_loopback_http": allow_loopback_http,
414
+ }
415
+ verifier = _AUTH.SourceDeliveryHMACVerifier(
416
+ values, store, **verifier_configuration,
417
+ )
418
+ guard()
419
+ except Exception as error:
420
+ if connection is not None:
421
+ try:
422
+ connection.close()
423
+ except sqlite3.Error:
424
+ pass
425
+ if isinstance(error, SourceDeliveryHMACRuntimeBlocked):
426
+ raise
427
+ raise _blocked("initialization_failed") from error
428
+ return DurableSourceDeliveryHMACRuntime(
429
+ connection, guard, store, verifier, verifier_configuration,
430
+ )
431
+
432
+
433
+ class HostedHMACAuthenticatedCMSSourceDelivery:
434
+ """Own the hosted delivery runtime and its HMAC replay runtime."""
435
+
436
+ def __init__(self, delivery: Any, authentication: Any):
437
+ self.delivery = delivery
438
+ self.authentication = authentication
439
+ self.http = delivery.http
440
+
441
+ def __repr__(self) -> str:
442
+ return (
443
+ "HostedHMACAuthenticatedCMSSourceDelivery("
444
+ f"state={self.state!r}, worker_state={self.worker_state!r})"
445
+ )
446
+
447
+ def __enter__(self) -> "HostedHMACAuthenticatedCMSSourceDelivery":
448
+ if self.state != "open":
449
+ raise _blocked("closed")
450
+ return self
451
+
452
+ def __exit__(self, *_exc: Any) -> None:
453
+ self.close()
454
+
455
+ def enqueue_change(self, *args: Any, **kwargs: Any) -> Any:
456
+ return self.delivery.enqueue_change(*args, **kwargs)
457
+
458
+ def enqueue_removal(self, *args: Any, **kwargs: Any) -> Any:
459
+ return self.delivery.enqueue_removal(*args, **kwargs)
460
+
461
+ def status(self, *args: Any, **kwargs: Any) -> Any:
462
+ return self.delivery.status(*args, **kwargs)
463
+
464
+ def health(self) -> Any:
465
+ return self.delivery.health()
466
+
467
+ def worker_readiness(self) -> Mapping[str, Any]:
468
+ return self.delivery.worker_readiness()
469
+
470
+ def authentication_health(self) -> Mapping[str, Any]:
471
+ return self.authentication.health()
472
+
473
+ def replace_credentials(self, credentials: Iterable[Any]) -> None:
474
+ self.authentication.replace_credentials(credentials)
475
+
476
+ def close(self, *, worker_timeout_seconds: float | int = 30) -> None:
477
+ self.delivery.close(worker_timeout_seconds=worker_timeout_seconds)
478
+ self.authentication.close()
479
+
480
+ @property
481
+ def state(self) -> str:
482
+ states = {self.delivery.state, self.authentication.state}
483
+ if "foreign-process" in states:
484
+ return "foreign-process"
485
+ if states == {"closed"}:
486
+ return "closed"
487
+ if states == {"open"}:
488
+ return "open"
489
+ return "blocked"
490
+
491
+ @property
492
+ def worker_state(self) -> str:
493
+ return self.delivery.worker_state
494
+
495
+
496
+ def open_hosted_hmac_authenticated_cms_source_delivery(
497
+ outbox_database: str | os.PathLike[str],
498
+ replay_database: str | os.PathLike[str],
499
+ client: Any,
500
+ credentials: Iterable[Any],
501
+ *,
502
+ worker_id: str,
503
+ origin: str,
504
+ sidecar_capabilities_sha256: str,
505
+ remote_capabilities_sha256: str,
506
+ clock: Callable[[], float | int] = time.time,
507
+ sqlite_timeout_seconds: float | int = 5,
508
+ replay_sqlite_timeout_seconds: float | int = 5,
509
+ lease_seconds: float | int = 600,
510
+ base_delay_seconds: float | int = 5,
511
+ max_delay_seconds: float | int = 300,
512
+ max_age_seconds: float | int = 60,
513
+ future_skew_seconds: float | int = 5,
514
+ active_delay_seconds: float | int = 0.05,
515
+ idle_delay_seconds: float | int = 1,
516
+ blocked_delay_seconds: float | int = 5,
517
+ allow_loopback_http: bool = False,
518
+ ) -> HostedHMACAuthenticatedCMSSourceDelivery:
519
+ """Open one hosted sidecar with a separately guarded replay ledger."""
520
+
521
+ if not isinstance(allow_loopback_http, bool):
522
+ raise _blocked("configuration_invalid")
523
+ try:
524
+ outbox_path = _DELIVERY_RUNTIME._database_path(outbox_database)
525
+ except Exception as error:
526
+ raise _blocked("configuration_invalid") from error
527
+ replay_path = _database_path(replay_database)
528
+ if outbox_path == replay_path:
529
+ raise _blocked("database_paths_conflict")
530
+ values = _credential_values(credentials)
531
+ _sqlite_timeout(replay_sqlite_timeout_seconds)
532
+ _preflight_authentication(
533
+ values,
534
+ origin=origin,
535
+ sidecar_capabilities_sha256=sidecar_capabilities_sha256,
536
+ remote_capabilities_sha256=remote_capabilities_sha256,
537
+ clock=clock,
538
+ max_age_seconds=max_age_seconds,
539
+ future_skew_seconds=future_skew_seconds,
540
+ allow_loopback_http=allow_loopback_http,
541
+ )
542
+ try:
543
+ _DELIVERY_RUNTIME._sqlite_timeout(sqlite_timeout_seconds)
544
+ _DELIVERY_RUNTIME.DurableCMSSourceDeliveryRuntime._loop_delays(
545
+ active_delay_seconds,
546
+ idle_delay_seconds,
547
+ blocked_delay_seconds,
548
+ )
549
+ _DELIVERY_RUNTIME._preflight(
550
+ client,
551
+ worker_id,
552
+ lease_seconds,
553
+ {
554
+ "clock": clock,
555
+ "base_delay_seconds": base_delay_seconds,
556
+ "max_delay_seconds": max_delay_seconds,
557
+ },
558
+ )
559
+ except Exception as error:
560
+ raise _blocked("configuration_invalid") from error
561
+
562
+ authentication = open_durable_source_delivery_hmac_runtime(
563
+ replay_path,
564
+ values,
565
+ origin=origin,
566
+ sidecar_capabilities_sha256=sidecar_capabilities_sha256,
567
+ remote_capabilities_sha256=remote_capabilities_sha256,
568
+ clock=clock,
569
+ max_age_seconds=max_age_seconds,
570
+ future_skew_seconds=future_skew_seconds,
571
+ sqlite_timeout_seconds=replay_sqlite_timeout_seconds,
572
+ allow_loopback_http=allow_loopback_http,
573
+ )
574
+ delivery = None
575
+ try:
576
+ delivery = _DELIVERY_RUNTIME.open_durable_cms_source_delivery(
577
+ outbox_path,
578
+ client,
579
+ worker_id=worker_id,
580
+ clock=clock,
581
+ sqlite_timeout_seconds=sqlite_timeout_seconds,
582
+ lease_seconds=lease_seconds,
583
+ base_delay_seconds=base_delay_seconds,
584
+ max_delay_seconds=max_delay_seconds,
585
+ http_authenticator=authentication,
586
+ )
587
+ if _same_database_identity(outbox_path, replay_path):
588
+ delivery.close()
589
+ authentication.close()
590
+ raise _blocked("database_paths_conflict")
591
+ delivery.start_worker(
592
+ active_delay_seconds=active_delay_seconds,
593
+ idle_delay_seconds=idle_delay_seconds,
594
+ blocked_delay_seconds=blocked_delay_seconds,
595
+ )
596
+ except Exception as error:
597
+ if delivery is not None and delivery.state == "open":
598
+ try:
599
+ delivery.close()
600
+ except Exception:
601
+ pass
602
+ if authentication.state == "open":
603
+ authentication.close()
604
+ if isinstance(error, SourceDeliveryHMACRuntimeBlocked):
605
+ raise
606
+ raise _blocked("delivery_initialization_failed") from error
607
+ return HostedHMACAuthenticatedCMSSourceDelivery(
608
+ delivery, authentication,
609
+ )