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
@@ -21,15 +21,30 @@ from typing import Any, Iterator, Protocol
21
21
 
22
22
 
23
23
  SCHEMA_VERSION = 1
24
- APPROVAL_SCHEMA = "blun.website-localization-approval.v3"
25
- RECEIPT_BINDING_SCHEMA = "blun.localization-quality-receipt-binding.v2"
24
+ APPROVAL_SCHEMA = "blun.website-localization-approval.v10"
25
+ RECEIPT_BINDING_SCHEMA = "blun.localization-quality-receipt-binding.v9"
26
26
  INDEPENDENT_MODEL_REVIEW_SCHEMA = "blun.independent-model-review.v1"
27
- PUBLICATION_EVIDENCE_SCHEMA = "blun.website-localization-release-evidence.v1"
27
+ PUBLICATION_EVIDENCE_SCHEMA = "blun.website-localization-release-evidence.v14"
28
+ PUBLICATION_EVIDENCE_CAPABILITIES_SCHEMA = (
29
+ "blun.website-localization-release-evidence-capabilities.v8"
30
+ )
31
+ PUBLICATION_EVIDENCE_FIELDS = (
32
+ "schema", "release_evidence_contract_sha256", "job_id", "target_locale",
33
+ "target_sha256", "approval_id",
34
+ "content_type", "result_sha256", "approval_sha256",
35
+ "quality_receipt_sha256", "evidence_request_id", "evidence_revision",
36
+ "quality_profile",
37
+ "commercial_profile", "commercial_quality_profile", "commercial_review",
38
+ "commercial_review_routing_contract_sha256",
39
+ "commercial_review_resolution_contract_sha256",
40
+ "commercial_review_resolution",
41
+ )
28
42
  MAX_TEXT_BYTES = 2_000_000
29
43
  MAX_RECEIPT_LENGTH = 16_384
30
44
  MAX_TTL_SECONDS = 31_536_000.0
31
45
  HEX64 = re.compile(r"^[0-9a-f]{64}$")
32
46
  TOKEN = re.compile(r"^[A-Za-z0-9_.:-]{1,256}$")
47
+ EVIDENCE_REQUEST_ID = re.compile(r"^blun-l10n-evidence-[0-9a-f]{64}$")
33
48
  _COLUMNS = (
34
49
  "approval_id", "job_id", "target_locale", "target_sha256",
35
50
  "result_json", "result_sha256", "approval_json", "approval_sha256",
@@ -56,6 +71,9 @@ _WORKER = _load_module(
56
71
  "blun_website_localization_release_worker",
57
72
  _ROOT / "integrations" / "website_localization_worker.py",
58
73
  )
74
+ COMMERCIAL_REVIEW_RESOLUTION_SCHEMA = (
75
+ _WORKER._COMMERCIAL.REVIEW_RESOLUTION_SCHEMA
76
+ )
59
77
 
60
78
 
61
79
  class LocalizationReleaseBlocked(RuntimeError):
@@ -122,17 +140,23 @@ class WebsiteReadiness:
122
140
  blocked: tuple[tuple[str, str], ...]
123
141
 
124
142
 
125
- def validate_publication_evidence(value: Any) -> dict[str, Any]:
143
+ def validate_publication_evidence(
144
+ value: Any,
145
+ *,
146
+ require_current_locale_quality: bool = True,
147
+ ) -> dict[str, Any]:
126
148
  """Validate the content-free release proof carried to a CMS."""
127
149
 
128
- if not isinstance(value, dict) or set(value) != {
129
- "schema", "job_id", "target_locale", "target_sha256", "approval_id",
130
- "content_type", "result_sha256", "approval_sha256",
131
- "quality_receipt_sha256", "commercial_profile", "commercial_review",
132
- }:
150
+ if (
151
+ not isinstance(require_current_locale_quality, bool)
152
+ or not isinstance(value, dict)
153
+ or set(value) != set(PUBLICATION_EVIDENCE_FIELDS)
154
+ ):
133
155
  raise LocalizationReleaseBlocked("publication.evidence.invalid")
134
156
  if (
135
157
  value.get("schema") != PUBLICATION_EVIDENCE_SCHEMA
158
+ or value.get("release_evidence_contract_sha256")
159
+ != publication_evidence_contract()["sha256"]
136
160
  or any(
137
161
  not isinstance(value.get(field), str)
138
162
  or TOKEN.fullmatch(value[field]) is None
@@ -145,18 +169,111 @@ def validate_publication_evidence(value: Any) -> dict[str, Any]:
145
169
  "quality_receipt_sha256",
146
170
  )
147
171
  )
172
+ or not isinstance(value.get("evidence_request_id"), str)
173
+ or EVIDENCE_REQUEST_ID.fullmatch(value["evidence_request_id"]) is None
174
+ or not isinstance(value.get("evidence_revision"), str)
175
+ or TOKEN.fullmatch(value["evidence_revision"]) is None
148
176
  ):
149
177
  raise LocalizationReleaseBlocked("publication.evidence.invalid")
178
+ quality_profile = value.get("quality_profile")
179
+ if (
180
+ not isinstance(quality_profile, dict)
181
+ or set(quality_profile) != {"locale", "version", "sha256"}
182
+ or quality_profile.get("locale") != value["target_locale"]
183
+ or not isinstance(quality_profile.get("version"), str)
184
+ or TOKEN.fullmatch(quality_profile["version"]) is None
185
+ or HEX64.fullmatch(str(quality_profile.get("sha256"))) is None
186
+ ):
187
+ raise LocalizationReleaseBlocked("publication.evidence.invalid")
188
+ if require_current_locale_quality:
189
+ try:
190
+ current_quality = _WORKER._PLANNER.quality_profile_for(
191
+ value["target_locale"],
192
+ )
193
+ expected_quality_profile = {
194
+ "locale": current_quality["locale"],
195
+ "version": current_quality["version"],
196
+ "sha256": current_quality["sha256"],
197
+ }
198
+ except Exception:
199
+ raise LocalizationReleaseBlocked(
200
+ "publication.evidence.invalid"
201
+ ) from None
202
+ if quality_profile != expected_quality_profile:
203
+ raise LocalizationReleaseBlocked("publication.evidence.invalid")
150
204
  profile = value.get("commercial_profile")
205
+ commercial_quality_profile = value.get("commercial_quality_profile")
151
206
  review = value.get("commercial_review")
207
+ routing_contract_sha256 = value.get(
208
+ "commercial_review_routing_contract_sha256"
209
+ )
210
+ resolution_contract_sha256 = value.get(
211
+ "commercial_review_resolution_contract_sha256"
212
+ )
213
+ resolution = value.get("commercial_review_resolution")
152
214
  if (
153
- (profile is None) != (review is None)
215
+ len({
216
+ profile is None,
217
+ commercial_quality_profile is None,
218
+ review is None,
219
+ }) != 1
220
+ or (profile is None) != (routing_contract_sha256 is None)
221
+ or (profile is None) != (resolution_contract_sha256 is None)
154
222
  or (value["content_type"] == "commercial") != (profile is not None)
155
223
  ):
156
224
  raise LocalizationReleaseBlocked("publication.evidence.invalid")
157
225
  if profile is not None:
158
226
  if not isinstance(profile, str) or TOKEN.fullmatch(profile) is None:
159
227
  raise LocalizationReleaseBlocked("publication.evidence.invalid")
228
+ expected_commercial_quality = None
229
+ if require_current_locale_quality:
230
+ try:
231
+ current_commercial_quality = (
232
+ _WORKER._PLANNER.commercial_quality_profile_for(
233
+ value["target_locale"],
234
+ )
235
+ )
236
+ expected_commercial_quality = {
237
+ "profile": current_commercial_quality[
238
+ "commercial_profile"
239
+ ],
240
+ "version": current_commercial_quality["version"],
241
+ "sha256": current_commercial_quality["sha256"],
242
+ }
243
+ except Exception:
244
+ raise LocalizationReleaseBlocked(
245
+ "publication.evidence.invalid"
246
+ ) from None
247
+ if (
248
+ profile != _WORKER._PLANNER.COMMERCIAL_PROFILE
249
+ or (
250
+ require_current_locale_quality
251
+ and commercial_quality_profile != expected_commercial_quality
252
+ )
253
+ or HEX64.fullmatch(str(routing_contract_sha256)) is None
254
+ or routing_contract_sha256
255
+ != _WORKER._COMMERCIAL.public_review_routing_contract(profile)[
256
+ "sha256"
257
+ ]
258
+ or HEX64.fullmatch(str(resolution_contract_sha256)) is None
259
+ or resolution_contract_sha256
260
+ != _WORKER._COMMERCIAL.public_review_resolution_contract(profile)[
261
+ "sha256"
262
+ ]
263
+ ):
264
+ raise LocalizationReleaseBlocked("publication.evidence.invalid")
265
+ if (
266
+ not isinstance(commercial_quality_profile, dict)
267
+ or set(commercial_quality_profile)
268
+ != {"profile", "version", "sha256"}
269
+ or commercial_quality_profile.get("profile") != profile
270
+ or not isinstance(commercial_quality_profile.get("version"), str)
271
+ or TOKEN.fullmatch(commercial_quality_profile["version"]) is None
272
+ or HEX64.fullmatch(
273
+ str(commercial_quality_profile.get("sha256"))
274
+ ) is None
275
+ ):
276
+ raise LocalizationReleaseBlocked("publication.evidence.invalid")
160
277
  try:
161
278
  review = _WORKER._COMMERCIAL.validate_summary(
162
279
  review,
@@ -168,7 +285,64 @@ def validate_publication_evidence(value: Any) -> dict[str, Any]:
168
285
  )
169
286
  except _WORKER._COMMERCIAL.CommercialReviewBlocked:
170
287
  raise LocalizationReleaseBlocked("publication.evidence.invalid") from None
171
- return json.loads(_canonical_json({**value, "commercial_review": review}))
288
+ if review["status"] == "verified":
289
+ if resolution is not None:
290
+ raise LocalizationReleaseBlocked("publication.evidence.invalid")
291
+ else:
292
+ try:
293
+ resolution = _WORKER._COMMERCIAL.validate_review_resolution(
294
+ resolution, review, profile,
295
+ )
296
+ except _WORKER._COMMERCIAL.CommercialReviewBlocked:
297
+ raise LocalizationReleaseBlocked("publication.evidence.invalid")
298
+ elif resolution is not None:
299
+ raise LocalizationReleaseBlocked("publication.evidence.invalid")
300
+ return json.loads(_canonical_json({
301
+ **value,
302
+ "commercial_review": review,
303
+ "commercial_review_resolution": resolution,
304
+ }))
305
+
306
+
307
+ def _commercial_review_resolution(
308
+ result: dict[str, Any],
309
+ approval: dict[str, Any],
310
+ ) -> dict[str, Any] | None:
311
+ review = result["commercial_review"]
312
+ if (
313
+ result["content_type"] != "commercial"
314
+ or review["status"] == "verified"
315
+ ):
316
+ return None
317
+ human_receipt_sha256 = approval["human_review_receipt_sha256"]
318
+ independent = approval["independent_model_review"]
319
+ if human_receipt_sha256 is not None:
320
+ method = "qualified_human"
321
+ receipt_sha256 = human_receipt_sha256
322
+ provider = None
323
+ else:
324
+ method = "independent_model"
325
+ receipt_sha256 = independent["receipt_sha256"]
326
+ provider = json.loads(_canonical_json(independent["provider"]))
327
+ return {
328
+ "schema": COMMERCIAL_REVIEW_RESOLUTION_SCHEMA,
329
+ "profile": review["profile"],
330
+ "contract_sha256": approval[
331
+ "commercial_review_resolution_contract_sha256"
332
+ ],
333
+ "status": "resolved",
334
+ "reviewed_dimensions": json.loads(_canonical_json(
335
+ review["review_required_dimensions"]
336
+ )),
337
+ "reviewed_offer_count": review["offer_count"],
338
+ "reviewed_offers": json.loads(_canonical_json(
339
+ review["review_required_offers"]
340
+ )),
341
+ "method": method,
342
+ "receipt_sha256": receipt_sha256,
343
+ "primary_provider": json.loads(_canonical_json(result["provider"])),
344
+ "provider": provider,
345
+ }
172
346
 
173
347
 
174
348
  @dataclass(frozen=True)
@@ -204,6 +378,74 @@ def _hash_text(value: str) -> str:
204
378
  return hashlib.sha256(value.encode("utf-8")).hexdigest()
205
379
 
206
380
 
381
+ def _publication_evidence_contract_body() -> dict[str, Any]:
382
+ return {
383
+ "schema": PUBLICATION_EVIDENCE_CAPABILITIES_SCHEMA,
384
+ "release_evidence_schema": PUBLICATION_EVIDENCE_SCHEMA,
385
+ "required_fields": list(PUBLICATION_EVIDENCE_FIELDS),
386
+ "bindings": {
387
+ "sha256_fields": [
388
+ "release_evidence_contract_sha256", "target_sha256",
389
+ "result_sha256", "approval_sha256", "quality_receipt_sha256",
390
+ "commercial_review_routing_contract_sha256",
391
+ "commercial_review_resolution_contract_sha256",
392
+ ],
393
+ "lineage_fields": ["evidence_request_id", "evidence_revision"],
394
+ "target_identity_fields": [
395
+ "job_id", "target_locale", "target_sha256", "approval_id",
396
+ ],
397
+ "quality_profile": "exact-current-target-locale-quality-profile",
398
+ "signed_container": "blun.cms-localization-publication.v3",
399
+ },
400
+ "commercial_scope": {
401
+ "content_type": "commercial",
402
+ "required_non_null": [
403
+ "commercial_profile", "commercial_quality_profile",
404
+ "commercial_review", "commercial_review_routing_contract_sha256",
405
+ "commercial_review_resolution_contract_sha256",
406
+ ],
407
+ "review_evidence_contract_sha256": (
408
+ "exact-current-public-commercial-evidence-contract"
409
+ ),
410
+ "review_resolution_contract_sha256": (
411
+ "exact-current-public-commercial-resolution-contract"
412
+ ),
413
+ "quality_profile": (
414
+ "exact-current-target-locale-commercial-quality-profile"
415
+ ),
416
+ "resolution": "required-only-when-review-required",
417
+ "non_commercial_fields": "all-null",
418
+ },
419
+ "content_policy": {
420
+ "source_text": False,
421
+ "target_text": False,
422
+ "raw_receipt": False,
423
+ "reviewer_identity": False,
424
+ "reviewer_prose": False,
425
+ "commercial_review_routing": False,
426
+ "project_prices": False,
427
+ "project_brands": False,
428
+ "authentication_material": False,
429
+ },
430
+ "tamper_policy": "reject-complete-publication-before-host-commit",
431
+ }
432
+
433
+
434
+ def publication_evidence_contract() -> dict[str, Any]:
435
+ """Return the exact content-free contract advertised to CMS adapters."""
436
+ body = _publication_evidence_contract_body()
437
+ return {**body, "sha256": _hash_text(_canonical_json(body))}
438
+
439
+
440
+ def validate_publication_evidence_contract(value: Any) -> dict[str, Any]:
441
+ """Reject a partial, reordered, altered, or merely rehashed contract."""
442
+ body = _publication_evidence_contract_body()
443
+ expected = {**body, "sha256": _hash_text(_canonical_json(body))}
444
+ if value != expected:
445
+ raise LocalizationReleaseBlocked("publication.evidence_contract.invalid")
446
+ return json.loads(_canonical_json(expected))
447
+
448
+
207
449
  def _text(value: Any, code: str, *, limit: int = 256) -> str:
208
450
  if not isinstance(value, str) or not value or value != value.strip():
209
451
  raise LocalizationReleaseBlocked(code)
@@ -242,12 +484,37 @@ def _independent_model_review(value: Any, primary_provider: dict[str, Any]) -> t
242
484
  return normalized, _receipt(value.get("receipt"), "independent_model_review.receipt.required")
243
485
 
244
486
 
487
+ def _commercial_resolution_contract_sha256(
488
+ job: dict[str, Any],
489
+ result: dict[str, Any],
490
+ ) -> str | None:
491
+ commercial_profile = job.get("commercial_profile")
492
+ commercial_review = result["commercial_review"]
493
+ if commercial_profile is None or commercial_review["status"] != "review_required":
494
+ return None
495
+ try:
496
+ expected = _WORKER._COMMERCIAL.public_review_resolution_contract(
497
+ commercial_profile,
498
+ )["sha256"]
499
+ except (KeyError, TypeError, ValueError):
500
+ raise LocalizationReleaseBlocked("approval.binding_mismatch") from None
501
+ if (
502
+ job.get("commercial_review_resolution_contract_sha256") != expected
503
+ or result.get("commercial_review_resolution_contract_sha256")
504
+ != expected
505
+ ):
506
+ raise LocalizationReleaseBlocked("approval.binding_mismatch")
507
+ return expected
508
+
509
+
245
510
  def _receipt_binding(
246
511
  job_id: str,
247
512
  job: dict[str, Any],
248
513
  result: dict[str, Any],
249
514
  result_sha256: str,
250
515
  *,
516
+ evidence_request_id: str,
517
+ evidence_revision: str,
251
518
  review_kind: str,
252
519
  review_provider: dict[str, str] | None = None,
253
520
  ) -> dict[str, Any]:
@@ -257,9 +524,23 @@ def _receipt_binding(
257
524
  raise LocalizationReleaseBlocked("review.binding.invalid")
258
525
  if (review_kind == "independent_model") != (review_provider is not None):
259
526
  raise LocalizationReleaseBlocked("review.binding.invalid")
527
+ if (
528
+ not isinstance(evidence_request_id, str)
529
+ or EVIDENCE_REQUEST_ID.fullmatch(evidence_request_id) is None
530
+ or not isinstance(evidence_revision, str)
531
+ or TOKEN.fullmatch(evidence_revision) is None
532
+ ):
533
+ raise LocalizationReleaseBlocked("review.evidence_context.invalid")
534
+ commercial_profile = job.get("commercial_profile")
535
+ commercial_review = result["commercial_review"]
536
+ resolution_contract_sha256 = _commercial_resolution_contract_sha256(
537
+ job, result,
538
+ )
260
539
  binding = {
261
540
  "schema": RECEIPT_BINDING_SCHEMA,
262
541
  "review_kind": review_kind,
542
+ "evidence_request_id": evidence_request_id,
543
+ "evidence_revision": evidence_revision,
263
544
  "job_id": job_id,
264
545
  "result_sha256": result_sha256,
265
546
  "source_text": job["source"]["text"],
@@ -276,8 +557,15 @@ def _receipt_binding(
276
557
  "software_version": result["software_version"],
277
558
  "review_confidence": result["review_confidence"],
278
559
  "quality_profile": result["quality_profile"],
279
- "commercial_profile": job.get("commercial_profile"),
280
- "commercial_review": result["commercial_review"],
560
+ "commercial_profile": commercial_profile,
561
+ "commercial_review": commercial_review,
562
+ "commercial_review_routing": result["commercial_review_routing"],
563
+ "commercial_review_routing_contract_sha256": result[
564
+ "commercial_review_routing_contract_sha256"
565
+ ],
566
+ "commercial_review_resolution_contract_sha256": (
567
+ resolution_contract_sha256
568
+ ),
281
569
  "human_review_required": result["human_review_required"],
282
570
  "independent_review_required": result["independent_review_required"],
283
571
  }
@@ -351,7 +639,10 @@ def _validate_result(job: dict[str, Any], result: Any) -> dict[str, Any]:
351
639
  "source_locale", "target_locale", "content_type", "glossary_version",
352
640
  "policy_version", "provider", "software_version", "candidate",
353
641
  "quality_passes", "integrity", "review_confidence",
354
- "quality_profile", "commercial_review", "human_review_required",
642
+ "quality_profile", "commercial_review", "commercial_review_routing",
643
+ "commercial_review_routing_contract_sha256",
644
+ "commercial_review_resolution_contract_sha256",
645
+ "human_review_required",
355
646
  "independent_review_required", "release_required",
356
647
  }
357
648
  if set(result) != expected:
@@ -379,6 +670,12 @@ def _validate_result(job: dict[str, Any], result: Any) -> dict[str, Any]:
379
670
  "version": job["target"]["quality_profile_version"],
380
671
  "sha256": job["target"]["quality_profile_sha256"],
381
672
  }
673
+ if job["content_type"] == "commercial":
674
+ expected_quality_profile["commercial"] = {
675
+ "profile": job["commercial_profile"],
676
+ "version": job["commercial_quality_profile"]["version"],
677
+ "sha256": job["commercial_quality_profile"]["sha256"],
678
+ }
382
679
  if result.get("quality_profile") != expected_quality_profile:
383
680
  raise LocalizationReleaseBlocked("result.quality_profile.invalid")
384
681
  if result.get("schema") != _WORKER.RESULT_SCHEMA or result.get("worker_schema") != _WORKER.WORKER_SCHEMA:
@@ -420,16 +717,48 @@ def _validate_result(job: dict[str, Any], result: Any) -> dict[str, Any]:
420
717
  ):
421
718
  raise LocalizationReleaseBlocked("result.independent_review.invalid")
422
719
  commercial_review = result.get("commercial_review")
720
+ commercial_review_routing = result.get("commercial_review_routing")
721
+ commercial_routing_contract_sha256 = result.get(
722
+ "commercial_review_routing_contract_sha256"
723
+ )
724
+ commercial_resolution_contract_sha256 = result.get(
725
+ "commercial_review_resolution_contract_sha256"
726
+ )
423
727
  if job["content_type"] == "commercial":
728
+ expected_routing_contract_sha256 = job[
729
+ "commercial_review_routing_contract_sha256"
730
+ ]
731
+ if commercial_routing_contract_sha256 != expected_routing_contract_sha256:
732
+ raise LocalizationReleaseBlocked("result.commercial_review.invalid")
733
+ if commercial_resolution_contract_sha256 != job[
734
+ "commercial_review_resolution_contract_sha256"
735
+ ]:
736
+ raise LocalizationReleaseBlocked("result.commercial_review.invalid")
424
737
  try:
425
- _WORKER._COMMERCIAL.validate_summary(
738
+ commercial_review = _WORKER._COMMERCIAL.validate_summary(
426
739
  commercial_review,
427
740
  job["commercial_profile"],
428
741
  review_required=independent_review_required,
429
742
  )
743
+ if commercial_review["status"] == "review_required":
744
+ _WORKER._COMMERCIAL.validate_review_routing_context(
745
+ commercial_review_routing,
746
+ job["source"]["text"],
747
+ candidate,
748
+ commercial_review,
749
+ job["commercial_profile"],
750
+ )
751
+ elif commercial_review_routing is not None:
752
+ raise _WORKER._COMMERCIAL.CommercialReviewBlocked(
753
+ "review.commercial.routing_invalid",
754
+ )
430
755
  except _WORKER._COMMERCIAL.CommercialReviewBlocked:
431
756
  raise LocalizationReleaseBlocked("result.commercial_review.invalid") from None
432
- elif commercial_review is not None:
757
+ elif any(value is not None for value in (
758
+ commercial_review, commercial_review_routing,
759
+ commercial_routing_contract_sha256,
760
+ commercial_resolution_contract_sha256,
761
+ )):
433
762
  raise LocalizationReleaseBlocked("result.commercial_review.invalid")
434
763
  return result
435
764
 
@@ -491,6 +820,8 @@ class LocalizationReleaseStore:
491
820
  quality_verifier: QualityReceiptVerifier,
492
821
  authority: ApprovalAuthority,
493
822
  *,
823
+ evidence_request_id: str,
824
+ evidence_revision: str,
494
825
  now: float | int,
495
826
  ttl_seconds: float | int = 2_592_000,
496
827
  human_review_receipt: str | None = None,
@@ -508,6 +839,13 @@ class LocalizationReleaseStore:
508
839
  result_json = _canonical_json(result)
509
840
  result_hash = _hash_text(result_json)
510
841
  quality_receipt = _receipt(quality_receipt, "quality.receipt.invalid")
842
+ if (
843
+ not isinstance(evidence_request_id, str)
844
+ or EVIDENCE_REQUEST_ID.fullmatch(evidence_request_id) is None
845
+ or not isinstance(evidence_revision, str)
846
+ or TOKEN.fullmatch(evidence_revision) is None
847
+ ):
848
+ raise LocalizationReleaseBlocked("review.evidence_context.invalid")
511
849
  verifier = getattr(quality_verifier, "verify", None)
512
850
  if not callable(verifier):
513
851
  raise LocalizationReleaseBlocked("quality.verifier.invalid")
@@ -518,6 +856,8 @@ class LocalizationReleaseStore:
518
856
  job,
519
857
  result,
520
858
  result_hash,
859
+ evidence_request_id=evidence_request_id,
860
+ evidence_revision=evidence_revision,
521
861
  review_kind="quality",
522
862
  ),
523
863
  receipt=quality_receipt,
@@ -544,6 +884,8 @@ class LocalizationReleaseStore:
544
884
  job,
545
885
  result,
546
886
  result_hash,
887
+ evidence_request_id=evidence_request_id,
888
+ evidence_revision=evidence_revision,
547
889
  review_kind="qualified_human",
548
890
  ),
549
891
  receipt=human_review_receipt,
@@ -571,6 +913,8 @@ class LocalizationReleaseStore:
571
913
  job,
572
914
  result,
573
915
  result_hash,
916
+ evidence_request_id=evidence_request_id,
917
+ evidence_revision=evidence_revision,
574
918
  review_kind="independent_model",
575
919
  review_provider=provider,
576
920
  ),
@@ -598,6 +942,8 @@ class LocalizationReleaseStore:
598
942
  job,
599
943
  result,
600
944
  result_hash,
945
+ evidence_request_id=evidence_request_id,
946
+ evidence_revision=evidence_revision,
601
947
  review_kind="qualified_human",
602
948
  ),
603
949
  receipt=human_review_receipt,
@@ -616,6 +962,11 @@ class LocalizationReleaseStore:
616
962
 
617
963
  immutable = {
618
964
  "schema": APPROVAL_SCHEMA,
965
+ "release_evidence_contract_sha256": (
966
+ publication_evidence_contract()["sha256"]
967
+ ),
968
+ "evidence_request_id": evidence_request_id,
969
+ "evidence_revision": evidence_revision,
619
970
  "job_id": job_id,
620
971
  "source_sha256": result["source_sha256"],
621
972
  "target_sha256": result["target_sha256"],
@@ -629,10 +980,16 @@ class LocalizationReleaseStore:
629
980
  "worker_schema": result["worker_schema"],
630
981
  "review_confidence": result["review_confidence"],
631
982
  "quality_profile": result["quality_profile"],
983
+ "commercial_review_routing_contract_sha256": result[
984
+ "commercial_review_routing_contract_sha256"
985
+ ],
632
986
  "result_sha256": result_hash,
633
987
  "quality_receipt_sha256": _hash_text(quality_receipt),
634
988
  "human_review_receipt_sha256": human_hash,
635
989
  "independent_model_review": independent_binding,
990
+ "commercial_review_resolution_contract_sha256": (
991
+ _commercial_resolution_contract_sha256(job, result)
992
+ ),
636
993
  }
637
994
  approval_id = "blun-l10n-approval-" + _hash_text(_canonical_json(immutable))
638
995
  payload = {
@@ -715,19 +1072,28 @@ class LocalizationReleaseStore:
715
1072
  raise LocalizationReleaseBlocked("translation_memory.json.invalid") from None
716
1073
  result = _validate_result(job, result)
717
1074
  expected_keys = {
718
- "schema", "job_id", "source_sha256", "target_sha256", "source_locale",
1075
+ "schema", "release_evidence_contract_sha256",
1076
+ "evidence_request_id", "evidence_revision", "job_id",
1077
+ "source_sha256", "target_sha256", "source_locale",
719
1078
  "target_locale", "content_type", "glossary_version", "policy_version",
720
1079
  "provider", "software_version", "worker_schema", "result_sha256",
721
1080
  "review_confidence",
722
1081
  "quality_profile",
1082
+ "commercial_review_routing_contract_sha256",
723
1083
  "quality_receipt_sha256", "human_review_receipt_sha256",
724
1084
  "independent_model_review", "approval_id",
1085
+ "commercial_review_resolution_contract_sha256",
725
1086
  "approved_at", "expires_at",
726
1087
  }
727
1088
  if set(payload) != expected_keys:
728
1089
  raise LocalizationReleaseBlocked("approval.binding_mismatch")
729
1090
  binding = {
730
1091
  "schema": APPROVAL_SCHEMA,
1092
+ "release_evidence_contract_sha256": (
1093
+ publication_evidence_contract()["sha256"]
1094
+ ),
1095
+ "evidence_request_id": payload.get("evidence_request_id"),
1096
+ "evidence_revision": payload.get("evidence_revision"),
731
1097
  "job_id": row["job_id"],
732
1098
  "source_sha256": result["source_sha256"],
733
1099
  "target_sha256": result["target_sha256"],
@@ -741,12 +1107,26 @@ class LocalizationReleaseStore:
741
1107
  "worker_schema": result["worker_schema"],
742
1108
  "review_confidence": result["review_confidence"],
743
1109
  "quality_profile": result["quality_profile"],
1110
+ "commercial_review_routing_contract_sha256": result[
1111
+ "commercial_review_routing_contract_sha256"
1112
+ ],
744
1113
  "result_sha256": row["result_sha256"],
1114
+ "commercial_review_resolution_contract_sha256": (
1115
+ _commercial_resolution_contract_sha256(job, result)
1116
+ ),
745
1117
  "approved_at": row["approved_at"],
746
1118
  "expires_at": row["expires_at"],
747
1119
  }
748
1120
  if any(payload.get(name) != value for name, value in binding.items()):
749
1121
  raise LocalizationReleaseBlocked("approval.binding_mismatch")
1122
+ if (
1123
+ EVIDENCE_REQUEST_ID.fullmatch(
1124
+ str(payload.get("evidence_request_id"))
1125
+ ) is None
1126
+ or not isinstance(payload.get("evidence_revision"), str)
1127
+ or TOKEN.fullmatch(payload["evidence_revision"]) is None
1128
+ ):
1129
+ raise LocalizationReleaseBlocked("approval.binding_mismatch")
750
1130
  if payload.get("approval_id") != row["approval_id"]:
751
1131
  raise LocalizationReleaseBlocked("approval.binding_mismatch")
752
1132
  if HEX64.fullmatch(str(payload.get("quality_receipt_sha256"))) is None:
@@ -811,6 +1191,9 @@ class LocalizationReleaseStore:
811
1191
  candidate=result["candidate"],
812
1192
  release_evidence=validate_publication_evidence({
813
1193
  "schema": PUBLICATION_EVIDENCE_SCHEMA,
1194
+ "release_evidence_contract_sha256": payload[
1195
+ "release_evidence_contract_sha256"
1196
+ ],
814
1197
  "job_id": row["job_id"],
815
1198
  "target_locale": row["target_locale"],
816
1199
  "target_sha256": row["target_sha256"],
@@ -819,10 +1202,33 @@ class LocalizationReleaseStore:
819
1202
  "result_sha256": row["result_sha256"],
820
1203
  "approval_sha256": row["approval_sha256"],
821
1204
  "quality_receipt_sha256": payload["quality_receipt_sha256"],
1205
+ "evidence_request_id": payload["evidence_request_id"],
1206
+ "evidence_revision": payload["evidence_revision"],
1207
+ "quality_profile": {
1208
+ "locale": result["quality_profile"]["locale"],
1209
+ "version": result["quality_profile"]["version"],
1210
+ "sha256": result["quality_profile"]["sha256"],
1211
+ },
822
1212
  "commercial_profile": job.get("commercial_profile"),
1213
+ "commercial_quality_profile": (
1214
+ json.loads(_canonical_json(
1215
+ result["quality_profile"].get("commercial")
1216
+ ))
1217
+ if result["content_type"] == "commercial"
1218
+ else None
1219
+ ),
823
1220
  "commercial_review": json.loads(
824
1221
  _canonical_json(result["commercial_review"])
825
1222
  ),
1223
+ "commercial_review_routing_contract_sha256": result[
1224
+ "commercial_review_routing_contract_sha256"
1225
+ ],
1226
+ "commercial_review_resolution_contract_sha256": result[
1227
+ "commercial_review_resolution_contract_sha256"
1228
+ ],
1229
+ "commercial_review_resolution": _commercial_review_resolution(
1230
+ result, payload,
1231
+ ),
826
1232
  }),
827
1233
  approved_at=float(row["approved_at"]),
828
1234
  expires_at=float(row["expires_at"]),