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,918 @@
1
+ #!/usr/bin/env python3
2
+ """Rotatable HMAC authentication for the source-delivery sidecar.
3
+
4
+ The client signer and server verifier bind the exact request, tenant identity,
5
+ idempotency fields, and both deployed capability contracts. A durable nonce
6
+ store rejects replay before the sidecar parses or persists website content.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import hmac
13
+ import importlib.util
14
+ import json
15
+ import math
16
+ import os
17
+ import re
18
+ import secrets
19
+ import sqlite3
20
+ import sys
21
+ import threading
22
+ import time
23
+ import unicodedata
24
+ from dataclasses import dataclass, field
25
+ from pathlib import Path
26
+ from typing import Any, Callable, Iterable, Mapping
27
+
28
+
29
+ PROOF_SCHEMA = "blun.cms-source-delivery-sidecar-hmac-proof.v1"
30
+ REPLAY_SCHEMA_VERSION = 1
31
+ ALGORITHM = "hmac-sha256"
32
+ MAX_SECRET_BYTES = 4_096
33
+ MAX_CLOCK_SKEW_SECONDS = 300
34
+ TOKEN = re.compile(r"^[A-Za-z0-9_.:-]{1,256}$")
35
+ NONCE = re.compile(r"^[A-Za-z0-9_-]{32,128}$")
36
+ SHA256 = re.compile(r"^[a-f0-9]{64}$")
37
+ SIGNATURE = re.compile(r"^[a-f0-9]{64}$")
38
+
39
+ HEADER_CREDENTIAL_ID = "X-Localization-Auth-Credential-Id"
40
+ HEADER_CREDENTIAL_VERSION = "X-Localization-Auth-Credential-Version"
41
+ HEADER_TIMESTAMP = "X-Localization-Auth-Timestamp"
42
+ HEADER_NONCE = "X-Localization-Auth-Nonce"
43
+ HEADER_SIGNATURE = "X-Localization-Auth-Signature"
44
+ HEADER_SITE_ID = "X-Localization-Auth-Site-Id"
45
+ HEADER_EVENT_ID = "X-Localization-Auth-Event-Id"
46
+ HEADER_REQUEST_ID = "X-Localization-Auth-Request-Id"
47
+ HEADER_PAYLOAD_SHA256 = "X-Localization-Auth-Payload-Sha256"
48
+
49
+ _COMMON_HEADERS = {
50
+ HEADER_CREDENTIAL_ID.lower(),
51
+ HEADER_CREDENTIAL_VERSION.lower(),
52
+ HEADER_TIMESTAMP.lower(),
53
+ HEADER_NONCE.lower(),
54
+ HEADER_SIGNATURE.lower(),
55
+ }
56
+ _TENANT_HEADERS = {
57
+ HEADER_SITE_ID.lower(),
58
+ HEADER_EVENT_ID.lower(),
59
+ HEADER_REQUEST_ID.lower(),
60
+ HEADER_PAYLOAD_SHA256.lower(),
61
+ }
62
+ _REPLAY_META_COLUMNS = ("singleton", "schema_version")
63
+ _REPLAY_COLUMNS = (
64
+ "credential_id", "credential_version", "nonce", "proof_sha256",
65
+ "issued_at", "expires_at",
66
+ )
67
+
68
+
69
+ def _load_module(name: str, path: Path):
70
+ spec = importlib.util.spec_from_file_location(name, path)
71
+ if spec is None or spec.loader is None:
72
+ raise RuntimeError(f"cannot load HMAC authentication dependency: {path.name}")
73
+ module = importlib.util.module_from_spec(spec)
74
+ sys.modules[spec.name] = module
75
+ spec.loader.exec_module(module)
76
+ return module
77
+
78
+
79
+ _ROOT = Path(__file__).resolve().parents[1]
80
+ _HTTP = _load_module(
81
+ "blun_website_localization_cms_source_delivery_auth_http",
82
+ _ROOT / "integrations" / "website_localization_cms_source_delivery_http.py",
83
+ )
84
+ _CLIENT = _load_module(
85
+ "blun_website_localization_cms_source_delivery_auth_client",
86
+ _ROOT / "integrations" / "website_localization_cms_source_delivery_client.py",
87
+ )
88
+
89
+
90
+ class SourceDeliveryHMACAuthenticationUnavailable(RuntimeError):
91
+ """Stable content-free authentication infrastructure failure."""
92
+
93
+ def __init__(self, code: str):
94
+ super().__init__(code)
95
+ self.code = code
96
+
97
+
98
+ def _unavailable(code: str) -> SourceDeliveryHMACAuthenticationUnavailable:
99
+ return SourceDeliveryHMACAuthenticationUnavailable(
100
+ "source_delivery_hmac." + code,
101
+ )
102
+
103
+
104
+ def _token(value: Any) -> bool:
105
+ return (
106
+ isinstance(value, str)
107
+ and TOKEN.fullmatch(value) is not None
108
+ and unicodedata.is_normalized("NFC", value)
109
+ )
110
+
111
+
112
+ def _sha256(value: Any) -> bool:
113
+ return isinstance(value, str) and SHA256.fullmatch(value) is not None
114
+
115
+
116
+ def _timestamp(value: Any) -> float:
117
+ if (
118
+ isinstance(value, bool)
119
+ or not isinstance(value, (int, float))
120
+ or not math.isfinite(float(value))
121
+ or float(value) < 0
122
+ ):
123
+ raise ValueError("timestamp is invalid")
124
+ return float(value)
125
+
126
+
127
+ def _canonical(value: Mapping[str, Any]) -> bytes:
128
+ try:
129
+ return json.dumps(
130
+ dict(value), ensure_ascii=False, allow_nan=False, sort_keys=True,
131
+ separators=(",", ":"),
132
+ ).encode("utf-8")
133
+ except (TypeError, ValueError, RecursionError):
134
+ raise ValueError("proof is invalid") from None
135
+
136
+
137
+ @dataclass(frozen=True, repr=False)
138
+ class HMACCredential:
139
+ """One explicit credential generation; the secret is never represented."""
140
+
141
+ principal_id: str
142
+ credential_id: str
143
+ credential_version: str
144
+ secret: bytes = field(repr=False, compare=False)
145
+ scopes: tuple[str, ...]
146
+ site_id: str | None = None
147
+
148
+ def __post_init__(self) -> None:
149
+ if not all(_token(value) for value in (
150
+ self.principal_id, self.credential_id, self.credential_version,
151
+ )):
152
+ raise ValueError("credential identity is invalid")
153
+ if (
154
+ not isinstance(self.secret, bytes)
155
+ or not 32 <= len(self.secret) <= MAX_SECRET_BYTES
156
+ ):
157
+ raise ValueError("credential secret must contain 32 to 4096 bytes")
158
+ if (
159
+ not isinstance(self.scopes, tuple)
160
+ or not self.scopes
161
+ or tuple(sorted(set(self.scopes))) != self.scopes
162
+ or any(scope not in _HTTP.SCOPES.values() for scope in self.scopes)
163
+ ):
164
+ raise ValueError("credential scopes are invalid")
165
+ tenant_scopes = {
166
+ _HTTP.SCOPES[path] for path in _HTTP.TENANT_PATHS
167
+ }
168
+ if self.site_id is not None and not _token(self.site_id):
169
+ raise ValueError("credential tenant is invalid")
170
+ if tenant_scopes.intersection(self.scopes) and self.site_id is None:
171
+ raise ValueError("tenant scopes require one site")
172
+
173
+ def __repr__(self) -> str:
174
+ return (
175
+ "HMACCredential(principal_id={!r}, credential_id={!r}, "
176
+ "credential_version={!r}, scopes={!r}, site_id={!r}, "
177
+ "secret=<redacted>)"
178
+ ).format(
179
+ self.principal_id, self.credential_id, self.credential_version,
180
+ self.scopes, self.site_id,
181
+ )
182
+
183
+
184
+ class DurableHMACReplayStore:
185
+ """Atomically consume proof nonces in caller-owned durable SQLite."""
186
+
187
+ def __init__(self, connection: sqlite3.Connection):
188
+ if not isinstance(connection, sqlite3.Connection):
189
+ raise TypeError("connection must be sqlite3.Connection")
190
+ self.connection = connection
191
+ self.connection.row_factory = sqlite3.Row
192
+ self._lock = threading.RLock()
193
+ self._initialize()
194
+
195
+ def _initialize(self) -> None:
196
+ with self._lock:
197
+ try:
198
+ self.connection.execute("""
199
+ CREATE TABLE IF NOT EXISTS cms_source_delivery_hmac_meta (
200
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
201
+ schema_version INTEGER NOT NULL CHECK (schema_version = 1)
202
+ )
203
+ """)
204
+ self.connection.execute("""
205
+ INSERT OR IGNORE INTO cms_source_delivery_hmac_meta
206
+ VALUES (1, 1)
207
+ """)
208
+ self.connection.execute("""
209
+ CREATE TABLE IF NOT EXISTS cms_source_delivery_hmac_nonces (
210
+ credential_id TEXT NOT NULL,
211
+ credential_version TEXT NOT NULL,
212
+ nonce TEXT NOT NULL,
213
+ proof_sha256 TEXT NOT NULL,
214
+ issued_at REAL NOT NULL,
215
+ expires_at REAL NOT NULL,
216
+ PRIMARY KEY (credential_id, credential_version, nonce)
217
+ )
218
+ """)
219
+ self.connection.commit()
220
+ self._validate()
221
+ except SourceDeliveryHMACAuthenticationUnavailable:
222
+ raise
223
+ except sqlite3.Error:
224
+ if self.connection.in_transaction:
225
+ self.connection.rollback()
226
+ raise _unavailable("replay_store_unavailable") from None
227
+
228
+ def _validate(self) -> None:
229
+ meta_columns = tuple(
230
+ row["name"] for row in self.connection.execute(
231
+ "PRAGMA table_info(cms_source_delivery_hmac_meta)"
232
+ ).fetchall()
233
+ )
234
+ nonce_columns = tuple(
235
+ row["name"] for row in self.connection.execute(
236
+ "PRAGMA table_info(cms_source_delivery_hmac_nonces)"
237
+ ).fetchall()
238
+ )
239
+ meta = self.connection.execute(
240
+ "SELECT singleton, schema_version FROM cms_source_delivery_hmac_meta"
241
+ ).fetchall()
242
+ if (
243
+ meta_columns != _REPLAY_META_COLUMNS
244
+ or nonce_columns != _REPLAY_COLUMNS
245
+ or len(meta) != 1
246
+ or tuple(meta[0]) != (1, REPLAY_SCHEMA_VERSION)
247
+ ):
248
+ raise _unavailable("replay_store_integrity")
249
+ for row in self.connection.execute(
250
+ "SELECT * FROM cms_source_delivery_hmac_nonces"
251
+ ).fetchall():
252
+ try:
253
+ valid = (
254
+ _token(row["credential_id"])
255
+ and _token(row["credential_version"])
256
+ and isinstance(row["nonce"], str)
257
+ and NONCE.fullmatch(row["nonce"]) is not None
258
+ and _sha256(row["proof_sha256"])
259
+ and _timestamp(row["issued_at"])
260
+ <= _timestamp(row["expires_at"])
261
+ )
262
+ except (TypeError, ValueError):
263
+ valid = False
264
+ if not valid:
265
+ raise _unavailable("replay_store_integrity")
266
+
267
+ def consume(
268
+ self,
269
+ credential_id: str,
270
+ credential_version: str,
271
+ nonce: str,
272
+ proof_sha256: str,
273
+ *,
274
+ issued_at: float,
275
+ expires_at: float,
276
+ now: float,
277
+ ) -> bool:
278
+ """Return true exactly once for one valid proof identity."""
279
+
280
+ if (
281
+ not _token(credential_id)
282
+ or not _token(credential_version)
283
+ or not isinstance(nonce, str)
284
+ or NONCE.fullmatch(nonce) is None
285
+ or not _sha256(proof_sha256)
286
+ ):
287
+ raise _unavailable("replay_store_request_invalid")
288
+ try:
289
+ issued = _timestamp(issued_at)
290
+ expiry = _timestamp(expires_at)
291
+ current = _timestamp(now)
292
+ except ValueError:
293
+ raise _unavailable("replay_store_request_invalid") from None
294
+ if issued > expiry or current > expiry:
295
+ raise _unavailable("replay_store_request_invalid")
296
+ with self._lock:
297
+ if self.connection.in_transaction:
298
+ raise _unavailable("replay_store_transaction_nested")
299
+ try:
300
+ self.connection.execute("BEGIN IMMEDIATE")
301
+ self._validate()
302
+ self.connection.execute(
303
+ "DELETE FROM cms_source_delivery_hmac_nonces "
304
+ "WHERE expires_at < ?",
305
+ (current,),
306
+ )
307
+ inserted = self.connection.execute("""
308
+ INSERT OR IGNORE INTO cms_source_delivery_hmac_nonces (
309
+ credential_id, credential_version, nonce, proof_sha256,
310
+ issued_at, expires_at
311
+ ) VALUES (?, ?, ?, ?, ?, ?)
312
+ """, (
313
+ credential_id, credential_version, nonce, proof_sha256,
314
+ issued, expiry,
315
+ )).rowcount == 1
316
+ self.connection.commit()
317
+ return inserted
318
+ except SourceDeliveryHMACAuthenticationUnavailable:
319
+ if self.connection.in_transaction:
320
+ self.connection.rollback()
321
+ raise
322
+ except sqlite3.Error:
323
+ if self.connection.in_transaction:
324
+ self.connection.rollback()
325
+ raise _unavailable("replay_store_unavailable") from None
326
+
327
+ def health(self) -> Mapping[str, Any]:
328
+ with self._lock:
329
+ try:
330
+ self._validate()
331
+ count = self.connection.execute(
332
+ "SELECT COUNT(*) FROM cms_source_delivery_hmac_nonces"
333
+ ).fetchone()[0]
334
+ except SourceDeliveryHMACAuthenticationUnavailable:
335
+ raise
336
+ except sqlite3.Error:
337
+ raise _unavailable("replay_store_unavailable") from None
338
+ return {
339
+ "schema": "blun.cms-source-delivery-hmac-replay-health.v1",
340
+ "status": "ok",
341
+ "consumed_nonces": int(count),
342
+ }
343
+
344
+
345
+ def _clock(clock: Callable[[], float | int]) -> float:
346
+ try:
347
+ return _timestamp(clock())
348
+ except Exception:
349
+ raise _unavailable("clock_unavailable") from None
350
+
351
+
352
+ def _nonce(factory: Callable[[], str]) -> str:
353
+ try:
354
+ value = factory()
355
+ except Exception:
356
+ raise _unavailable("nonce_unavailable") from None
357
+ if not isinstance(value, str) or NONCE.fullmatch(value) is None:
358
+ raise _unavailable("nonce_unavailable")
359
+ return value
360
+
361
+
362
+ def _binding(
363
+ credential: HMACCredential,
364
+ *,
365
+ origin: str,
366
+ sidecar_capabilities_sha256: str,
367
+ remote_capabilities_sha256: str,
368
+ method: str,
369
+ path: str,
370
+ scope: str,
371
+ body_sha256: str,
372
+ issued_at: int,
373
+ nonce: str,
374
+ site_id: str | None,
375
+ event_id: str | None,
376
+ request_id: str | None,
377
+ payload_sha256: str | None,
378
+ idempotency_key: str | None,
379
+ source_payload_sha256: str | None,
380
+ ) -> dict[str, Any]:
381
+ return {
382
+ "schema": PROOF_SCHEMA,
383
+ "algorithm": ALGORITHM,
384
+ "origin": origin,
385
+ "sidecar_capabilities_sha256": sidecar_capabilities_sha256,
386
+ "remote_capabilities_sha256": remote_capabilities_sha256,
387
+ "method": method,
388
+ "path": path,
389
+ "scope": scope,
390
+ "body_sha256": body_sha256,
391
+ "principal_id": credential.principal_id,
392
+ "credential_id": credential.credential_id,
393
+ "credential_version": credential.credential_version,
394
+ "issued_at": issued_at,
395
+ "nonce": nonce,
396
+ "site_id": site_id,
397
+ "event_id": event_id,
398
+ "request_id": request_id,
399
+ "payload_sha256": payload_sha256,
400
+ "idempotency_key": idempotency_key,
401
+ "source_payload_sha256": source_payload_sha256,
402
+ }
403
+
404
+
405
+ class SourceDeliveryHMACSigner:
406
+ """Produce request-bound authentication headers for the reference client."""
407
+
408
+ def __init__(
409
+ self,
410
+ credential: HMACCredential,
411
+ *,
412
+ origin: str,
413
+ sidecar_capabilities_sha256: str,
414
+ remote_capabilities_sha256: str,
415
+ clock: Callable[[], float | int] = time.time,
416
+ nonce_factory: Callable[[], str] = lambda: secrets.token_urlsafe(24),
417
+ allow_loopback_http: bool = False,
418
+ ):
419
+ if not isinstance(credential, HMACCredential):
420
+ raise TypeError("credential must be HMACCredential")
421
+ if not callable(clock) or not callable(nonce_factory):
422
+ raise TypeError("clock and nonce factory must be callable")
423
+ self.credential = credential
424
+ self.origin = _CLIENT._endpoint(origin, allow_loopback_http)
425
+ if not _sha256(sidecar_capabilities_sha256):
426
+ raise ValueError("sidecar capability digest is invalid")
427
+ if not _sha256(remote_capabilities_sha256):
428
+ raise ValueError("remote capability digest is invalid")
429
+ self.sidecar_capabilities_sha256 = sidecar_capabilities_sha256
430
+ self.remote_capabilities_sha256 = remote_capabilities_sha256
431
+ self.clock = clock
432
+ self.nonce_factory = nonce_factory
433
+
434
+ def __repr__(self) -> str:
435
+ return "SourceDeliveryHMACSigner(configured=True, secret=<redacted>)"
436
+
437
+ def __call__(self, context: Mapping[str, Any]) -> Mapping[str, str]:
438
+ base_fields = {
439
+ "schema", "method", "origin", "path", "scope", "body_sha256",
440
+ }
441
+ if not isinstance(context, Mapping):
442
+ raise _unavailable("signing_context_invalid")
443
+ path = context.get("path")
444
+ tenant = path in _HTTP.TENANT_PATHS
445
+ expected_fields = base_fields | (
446
+ {"site_id", "event_id", "request_id", "payload_sha256"}
447
+ if tenant else set()
448
+ )
449
+ if set(context) != expected_fields:
450
+ raise _unavailable("signing_context_invalid")
451
+ try:
452
+ method = _HTTP.METHODS[path]
453
+ scope = _HTTP.SCOPES[path]
454
+ except (KeyError, TypeError):
455
+ raise _unavailable("signing_context_invalid") from None
456
+ if (
457
+ context.get("schema") != _CLIENT.AUTH_CONTEXT_SCHEMA
458
+ or context.get("method") != method
459
+ or context.get("origin") != self.origin
460
+ or context.get("scope") != scope
461
+ or not _sha256(context.get("body_sha256"))
462
+ or scope not in self.credential.scopes
463
+ ):
464
+ raise _unavailable("signing_context_invalid")
465
+ site_id = event_id = request_id = payload_sha256 = None
466
+ if tenant:
467
+ site_id = context["site_id"]
468
+ event_id = context["event_id"]
469
+ request_id = context["request_id"]
470
+ payload_sha256 = context["payload_sha256"]
471
+ if (
472
+ not all(_token(value) for value in (
473
+ site_id, event_id, request_id,
474
+ ))
475
+ or not _sha256(payload_sha256)
476
+ or site_id != self.credential.site_id
477
+ ):
478
+ raise _unavailable("signing_context_invalid")
479
+ issued_at = int(_clock(self.clock))
480
+ nonce = _nonce(self.nonce_factory)
481
+ write = path in {_HTTP.CHANGE_PATH, _HTTP.REMOVAL_PATH}
482
+ binding = _binding(
483
+ self.credential,
484
+ origin=self.origin,
485
+ sidecar_capabilities_sha256=self.sidecar_capabilities_sha256,
486
+ remote_capabilities_sha256=self.remote_capabilities_sha256,
487
+ method=method,
488
+ path=path,
489
+ scope=scope,
490
+ body_sha256=context["body_sha256"],
491
+ issued_at=issued_at,
492
+ nonce=nonce,
493
+ site_id=site_id,
494
+ event_id=event_id,
495
+ request_id=request_id,
496
+ payload_sha256=payload_sha256,
497
+ idempotency_key=request_id if write else None,
498
+ source_payload_sha256=payload_sha256 if write else None,
499
+ )
500
+ signature = hmac.new(
501
+ self.credential.secret, _canonical(binding), hashlib.sha256,
502
+ ).hexdigest()
503
+ headers = {
504
+ HEADER_CREDENTIAL_ID: self.credential.credential_id,
505
+ HEADER_CREDENTIAL_VERSION: self.credential.credential_version,
506
+ HEADER_TIMESTAMP: str(issued_at),
507
+ HEADER_NONCE: nonce,
508
+ HEADER_SIGNATURE: signature,
509
+ }
510
+ if tenant:
511
+ headers.update({
512
+ HEADER_SITE_ID: site_id,
513
+ HEADER_EVENT_ID: event_id,
514
+ HEADER_REQUEST_ID: request_id,
515
+ HEADER_PAYLOAD_SHA256: payload_sha256,
516
+ })
517
+ return headers
518
+
519
+
520
+ class RotatingSourceDeliveryHMACSigner:
521
+ """Atomically replace one client credential under a fixed proof contract."""
522
+
523
+ def __init__(
524
+ self,
525
+ credential: HMACCredential,
526
+ *,
527
+ origin: str,
528
+ sidecar_capabilities_sha256: str,
529
+ remote_capabilities_sha256: str,
530
+ clock: Callable[[], float | int] = time.time,
531
+ nonce_factory: Callable[[], str] = lambda: secrets.token_urlsafe(24),
532
+ allow_loopback_http: bool = False,
533
+ ):
534
+ self._configuration = {
535
+ "origin": origin,
536
+ "sidecar_capabilities_sha256": sidecar_capabilities_sha256,
537
+ "remote_capabilities_sha256": remote_capabilities_sha256,
538
+ "clock": clock,
539
+ "nonce_factory": nonce_factory,
540
+ "allow_loopback_http": allow_loopback_http,
541
+ }
542
+ try:
543
+ self._signer = SourceDeliveryHMACSigner(
544
+ credential, **self._configuration,
545
+ )
546
+ except Exception:
547
+ raise _unavailable("signer_configuration_invalid") from None
548
+ self._lock = threading.RLock()
549
+ self._owner_pid = os.getpid()
550
+
551
+ def __repr__(self) -> str:
552
+ return "RotatingSourceDeliveryHMACSigner(configured=True, secret=<redacted>)"
553
+
554
+ def _assert_owner(self) -> None:
555
+ if os.getpid() != self._owner_pid:
556
+ raise _unavailable("signer_foreign_process")
557
+
558
+ def __call__(self, context: Mapping[str, Any]) -> Mapping[str, str]:
559
+ self._assert_owner()
560
+ with self._lock:
561
+ return self._signer(context)
562
+
563
+ def replace_credential(self, credential: HMACCredential) -> None:
564
+ """Replace the signer only after the complete new credential validates."""
565
+
566
+ self._assert_owner()
567
+ with self._lock:
568
+ try:
569
+ replacement = SourceDeliveryHMACSigner(
570
+ credential, **self._configuration,
571
+ )
572
+ except Exception:
573
+ raise _unavailable("signer_configuration_invalid") from None
574
+ self._signer = replacement
575
+
576
+
577
+ class RotatingHMACCMSSourceDeliveryClient:
578
+ """Bind one rotatable signer and HTTP client to the same fixed contract."""
579
+
580
+ def __init__(
581
+ self,
582
+ credential: HMACCredential,
583
+ *,
584
+ origin: str,
585
+ sidecar_capabilities_sha256: str,
586
+ remote_capabilities_sha256: str,
587
+ clock: Callable[[], float | int] = time.time,
588
+ nonce_factory: Callable[[], str] = lambda: secrets.token_urlsafe(24),
589
+ transport: Any = None,
590
+ timeout: float | int = 30,
591
+ allow_loopback_http: bool = False,
592
+ ):
593
+ self._owner_pid = os.getpid()
594
+ try:
595
+ signer = RotatingSourceDeliveryHMACSigner(
596
+ credential,
597
+ origin=origin,
598
+ sidecar_capabilities_sha256=sidecar_capabilities_sha256,
599
+ remote_capabilities_sha256=remote_capabilities_sha256,
600
+ clock=clock,
601
+ nonce_factory=nonce_factory,
602
+ allow_loopback_http=allow_loopback_http,
603
+ )
604
+ client_options = {
605
+ "timeout": timeout,
606
+ "allow_loopback_http": allow_loopback_http,
607
+ }
608
+ if transport is not None:
609
+ client_options["transport"] = transport
610
+ client = _CLIENT.CMSSourceDeliverySidecarHTTPClient(
611
+ origin,
612
+ sidecar_capabilities_sha256,
613
+ remote_capabilities_sha256,
614
+ signer,
615
+ **client_options,
616
+ )
617
+ except Exception:
618
+ raise _unavailable("client_configuration_invalid") from None
619
+ self._signer = signer
620
+ self._client = client
621
+ # Preserve the immutable public contract required by durable client
622
+ # adapters without exposing the signer or credential.
623
+ self.origin = client.origin
624
+ self.expected_capabilities_sha256 = (
625
+ client.expected_capabilities_sha256
626
+ )
627
+ self.expected_remote_capabilities_sha256 = (
628
+ client.expected_remote_capabilities_sha256
629
+ )
630
+ self.timeout = client.timeout
631
+
632
+ def __repr__(self) -> str:
633
+ return f"RotatingHMACCMSSourceDeliveryClient(state={self.state!r})"
634
+
635
+ def _assert_owner(self) -> None:
636
+ if os.getpid() != self._owner_pid:
637
+ raise _unavailable("client_foreign_process")
638
+
639
+ def _call(self, name: str, *args: Any, **kwargs: Any) -> Mapping[str, Any]:
640
+ self._assert_owner()
641
+ return getattr(self._client, name)(*args, **kwargs)
642
+
643
+ def replace_credential(self, credential: HMACCredential) -> None:
644
+ self._assert_owner()
645
+ self._signer.replace_credential(credential)
646
+
647
+ def capabilities(self) -> Mapping[str, Any]:
648
+ return self._call("capabilities")
649
+
650
+ def submit_change(
651
+ self,
652
+ change: Mapping[str, Any],
653
+ *,
654
+ source_max_attempts: int = 5,
655
+ delivery_max_attempts: int = 5,
656
+ ) -> Mapping[str, Any]:
657
+ return self._call(
658
+ "submit_change",
659
+ change,
660
+ source_max_attempts=source_max_attempts,
661
+ delivery_max_attempts=delivery_max_attempts,
662
+ )
663
+
664
+ def submit_removal(
665
+ self,
666
+ removal: Mapping[str, Any],
667
+ *,
668
+ source_max_attempts: int = 5,
669
+ delivery_max_attempts: int = 5,
670
+ ) -> Mapping[str, Any]:
671
+ return self._call(
672
+ "submit_removal",
673
+ removal,
674
+ source_max_attempts=source_max_attempts,
675
+ delivery_max_attempts=delivery_max_attempts,
676
+ )
677
+
678
+ def status(
679
+ self,
680
+ operation: str,
681
+ request_id: str,
682
+ event_id: str,
683
+ site_id: str,
684
+ payload_sha256: str,
685
+ ) -> Mapping[str, Any]:
686
+ return self._call(
687
+ "status", operation, request_id, event_id, site_id, payload_sha256,
688
+ )
689
+
690
+ def source_status(
691
+ self,
692
+ event_id: str,
693
+ site_id: str,
694
+ payload_sha256: str,
695
+ ) -> Mapping[str, Any]:
696
+ return self._call(
697
+ "source_status", event_id, site_id, payload_sha256,
698
+ )
699
+
700
+ def source_readiness(self) -> Mapping[str, Any]:
701
+ return self._call("source_readiness")
702
+
703
+ def source_health(self) -> Mapping[str, Any]:
704
+ return self._call("source_health")
705
+
706
+ def health(self) -> Mapping[str, Any]:
707
+ return self._call("health")
708
+
709
+ def readiness(self) -> Mapping[str, Any]:
710
+ return self._call("readiness")
711
+
712
+ @property
713
+ def state(self) -> str:
714
+ if os.getpid() != self._owner_pid:
715
+ return "foreign-process"
716
+ return "open"
717
+
718
+
719
+ class SourceDeliveryHMACVerifier:
720
+ """Authenticate exact sidecar requests and consume each proof once."""
721
+
722
+ def __init__(
723
+ self,
724
+ credentials: Iterable[HMACCredential],
725
+ replay_store: DurableHMACReplayStore,
726
+ *,
727
+ origin: str,
728
+ sidecar_capabilities_sha256: str,
729
+ remote_capabilities_sha256: str,
730
+ clock: Callable[[], float | int] = time.time,
731
+ max_age_seconds: float | int = 60,
732
+ future_skew_seconds: float | int = 5,
733
+ allow_loopback_http: bool = False,
734
+ ):
735
+ try:
736
+ values = tuple(credentials)
737
+ except TypeError:
738
+ raise TypeError("credentials must be iterable") from None
739
+ if not values or any(not isinstance(item, HMACCredential) for item in values):
740
+ raise TypeError("credentials must contain HMACCredential values")
741
+ keyed = {
742
+ (item.credential_id, item.credential_version): item
743
+ for item in values
744
+ }
745
+ if len(keyed) != len(values):
746
+ raise ValueError("credential generation is duplicated")
747
+ if not isinstance(replay_store, DurableHMACReplayStore):
748
+ raise TypeError("replay_store must be DurableHMACReplayStore")
749
+ if not callable(clock):
750
+ raise TypeError("clock must be callable")
751
+ self.origin = _CLIENT._endpoint(origin, allow_loopback_http)
752
+ if not _sha256(sidecar_capabilities_sha256):
753
+ raise ValueError("sidecar capability digest is invalid")
754
+ if not _sha256(remote_capabilities_sha256):
755
+ raise ValueError("remote capability digest is invalid")
756
+ try:
757
+ age = _timestamp(max_age_seconds)
758
+ skew = _timestamp(future_skew_seconds)
759
+ except ValueError:
760
+ raise ValueError("authentication time window is invalid") from None
761
+ if not 0 < age <= MAX_CLOCK_SKEW_SECONDS or skew > age:
762
+ raise ValueError("authentication time window is invalid")
763
+ self.credentials = keyed
764
+ self.replay_store = replay_store
765
+ self.sidecar_capabilities_sha256 = sidecar_capabilities_sha256
766
+ self.remote_capabilities_sha256 = remote_capabilities_sha256
767
+ self.clock = clock
768
+ self.max_age_seconds = age
769
+ self.future_skew_seconds = skew
770
+
771
+ def __repr__(self) -> str:
772
+ return "SourceDeliveryHMACVerifier(configured=True, secrets=<redacted>)"
773
+
774
+ @staticmethod
775
+ def _headers(request: Mapping[str, Any]) -> dict[str, str] | None:
776
+ items = request.get("headers")
777
+ if not isinstance(items, list):
778
+ return None
779
+ result: dict[str, str] = {}
780
+ for item in items:
781
+ if (
782
+ not isinstance(item, list)
783
+ or len(item) != 2
784
+ or not all(isinstance(part, str) for part in item)
785
+ ):
786
+ return None
787
+ name, value = item[0].lower(), item[1]
788
+ if name in result:
789
+ return None
790
+ result[name] = value
791
+ return result
792
+
793
+ def __call__(self, request: Mapping[str, Any]) -> Mapping[str, str] | None:
794
+ if (
795
+ not isinstance(request, Mapping)
796
+ or set(request) != {
797
+ "schema", "method", "path", "headers", "body_sha256",
798
+ }
799
+ or request.get("schema") != _HTTP.AUTH_REQUEST_SCHEMA
800
+ ):
801
+ return None
802
+ path = request.get("path")
803
+ try:
804
+ method = _HTTP.METHODS[path]
805
+ scope = _HTTP.SCOPES[path]
806
+ except (KeyError, TypeError):
807
+ return None
808
+ if request.get("method") != method or not _sha256(request.get("body_sha256")):
809
+ return None
810
+ headers = self._headers(request)
811
+ if headers is None or not _COMMON_HEADERS.issubset(headers):
812
+ return None
813
+ tenant = path in _HTTP.TENANT_PATHS
814
+ if tenant != _TENANT_HEADERS.issubset(headers):
815
+ return None
816
+ if not tenant and _TENANT_HEADERS.intersection(headers):
817
+ return None
818
+ credential = self.credentials.get((
819
+ headers[HEADER_CREDENTIAL_ID.lower()],
820
+ headers[HEADER_CREDENTIAL_VERSION.lower()],
821
+ ))
822
+ if credential is None or scope not in credential.scopes:
823
+ return None
824
+ try:
825
+ issued_text = headers[HEADER_TIMESTAMP.lower()]
826
+ if not issued_text.isascii() or not issued_text.isdecimal():
827
+ return None
828
+ issued_at = int(issued_text)
829
+ nonce = headers[HEADER_NONCE.lower()]
830
+ signature = headers[HEADER_SIGNATURE.lower()]
831
+ if (
832
+ NONCE.fullmatch(nonce) is None
833
+ or SIGNATURE.fullmatch(signature) is None
834
+ ):
835
+ return None
836
+ except (KeyError, TypeError):
837
+ return None
838
+ current = _clock(self.clock)
839
+ if (
840
+ issued_at < current - self.max_age_seconds
841
+ or issued_at > current + self.future_skew_seconds
842
+ ):
843
+ return None
844
+ site_id = event_id = request_id = payload_sha256 = None
845
+ if tenant:
846
+ site_id = headers[HEADER_SITE_ID.lower()]
847
+ event_id = headers[HEADER_EVENT_ID.lower()]
848
+ request_id = headers[HEADER_REQUEST_ID.lower()]
849
+ payload_sha256 = headers[HEADER_PAYLOAD_SHA256.lower()]
850
+ if (
851
+ not all(_token(value) for value in (
852
+ site_id, event_id, request_id,
853
+ ))
854
+ or not _sha256(payload_sha256)
855
+ or site_id != credential.site_id
856
+ ):
857
+ return None
858
+ write = path in {_HTTP.CHANGE_PATH, _HTTP.REMOVAL_PATH}
859
+ idempotency_key = headers.get("idempotency-key")
860
+ source_payload_sha256 = headers.get(
861
+ "x-localization-source-payload-sha256"
862
+ )
863
+ if write:
864
+ if (
865
+ idempotency_key != request_id
866
+ or source_payload_sha256 != payload_sha256
867
+ ):
868
+ return None
869
+ elif idempotency_key is not None or source_payload_sha256 is not None:
870
+ return None
871
+ binding = _binding(
872
+ credential,
873
+ origin=self.origin,
874
+ sidecar_capabilities_sha256=self.sidecar_capabilities_sha256,
875
+ remote_capabilities_sha256=self.remote_capabilities_sha256,
876
+ method=method,
877
+ path=path,
878
+ scope=scope,
879
+ body_sha256=request["body_sha256"],
880
+ issued_at=issued_at,
881
+ nonce=nonce,
882
+ site_id=site_id,
883
+ event_id=event_id,
884
+ request_id=request_id,
885
+ payload_sha256=payload_sha256,
886
+ idempotency_key=idempotency_key,
887
+ source_payload_sha256=source_payload_sha256,
888
+ )
889
+ proof = _canonical(binding)
890
+ expected = hmac.new(
891
+ credential.secret, proof, hashlib.sha256,
892
+ ).hexdigest()
893
+ if not hmac.compare_digest(signature, expected):
894
+ return None
895
+ proof_sha256 = hashlib.sha256(proof).hexdigest()
896
+ if not self.replay_store.consume(
897
+ credential.credential_id,
898
+ credential.credential_version,
899
+ nonce,
900
+ proof_sha256,
901
+ issued_at=float(issued_at),
902
+ expires_at=float(issued_at) + self.max_age_seconds,
903
+ now=current,
904
+ ):
905
+ return None
906
+ principal = {
907
+ "schema": (
908
+ _HTTP.TENANT_PRINCIPAL_SCHEMA
909
+ if tenant else _HTTP.PRINCIPAL_SCHEMA
910
+ ),
911
+ "principal_id": credential.principal_id,
912
+ "credential_id": credential.credential_id,
913
+ "credential_version": credential.credential_version,
914
+ "scope": scope,
915
+ }
916
+ if tenant:
917
+ principal["site_id"] = site_id
918
+ return principal