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
@@ -29,11 +29,13 @@ Neither credentials nor remote response prose appears in adapter errors.
29
29
  ## Request
30
30
 
31
31
  The adapter accepts only the coordinator's exact immutable
32
- `blun.localization-quality-evidence-request.v5` object. It validates the
33
- complete field set, request ID, SHA-256 values, locale profile, model identity,
34
- confidence decisions, optional commercial profile and its exact content-free
35
- targeted-review summary, UTF-8 size, Unicode NFC, and the source and target text
36
- hashes before authentication or transport.
32
+ `blun.localization-quality-evidence-request.v14` object. It validates the
33
+ complete field set, derives the request ID, and checks SHA-256 values, locale
34
+ profile, model identity,
35
+ confidence decisions, and, for commercial content, the exact locale-specific
36
+ commercial quality-profile binding plus its content-free targeted-review
37
+ summary, UTF-8 size, Unicode NFC, and the source and target text hashes before
38
+ authentication or transport.
37
39
 
38
40
  It sends one canonical UTF-8 JSON document:
39
41
 
@@ -43,7 +45,7 @@ It sends one canonical UTF-8 JSON document:
43
45
  "request_id": "blun-l10n-evidence-<64 lowercase hexadecimal characters>",
44
46
  "request_sha256": "<SHA-256 of the canonical inner request>",
45
47
  "request": {
46
- "schema": "blun.localization-quality-evidence-request.v5",
48
+ "schema": "blun.localization-quality-evidence-request.v14",
47
49
  "request_id": "<same request ID>",
48
50
  "source_locale": "en-IE",
49
51
  "target_locale": "fi-FI",
@@ -53,6 +55,11 @@ It sends one canonical UTF-8 JSON document:
53
55
  "source_sha256": "<source-text hash>",
54
56
  "target_sha256": "<target-text hash>",
55
57
  "content_type": "headline",
58
+ "commercial_review_routing": null,
59
+ "commercial_review_routing_contract_sha256": null,
60
+ "commercial_review_routing_contract": null,
61
+ "commercial_review_resolution_contract_sha256": null,
62
+ "commercial_review_resolution_contract": null,
56
63
  "quality_profile": {
57
64
  "locale": "fi-FI",
58
65
  "version": "<locale profile version>",
@@ -63,13 +70,57 @@ It sends one canonical UTF-8 JSON document:
63
70
  ```
64
71
 
65
72
  The abbreviated example omits other required inner fields for readability.
66
- Production requests always contain exactly the full v5 field set. Commercial
67
- requests include `commercial_profile` and the matching `commercial_review`
68
- summary; non-commercial requests require both fields to be `null`. Source and
73
+ Production requests always contain exactly the full v14 field set. Commercial
74
+ requests include `commercial_profile`, the matching `commercial_review`
75
+ summary, and `quality_profile.commercial` with the same profile identifier plus
76
+ its exact locale-specific version and SHA-256 digest. The summary binds the
77
+ exact advertised commercial review-evidence-contract SHA-256, so a prior
78
+ structurally valid report cannot be reinterpreted under a changed contract. An unresolved commercial
79
+ summary additionally carries the canonical advertised resolution-contract
80
+ SHA-256 and the complete content-free resolution contract. Both fields are
81
+ part of the deterministic request ID, so a reviewer can resolve the targeted
82
+ uncertainty without separate capability discovery. The coordinator and HTTP
83
+ adapter independently reconstruct the current contract and reject a missing,
84
+ stale, substituted, or merely self-rehashed object before provider access.
85
+ Verified commercial results and non-commercial requests require both fields
86
+ to be `null`.
87
+ Every commercial request also carries
88
+ `commercial_review_routing_contract_sha256`, even when the commercial review
89
+ is already verified and no private offer route is needed. This lets the
90
+ evidence provider issue the receipt against the same exact public contract
91
+ digest that the receipt verifier, signed approval, and CMS evidence later
92
+ require. Non-commercial requests require this field to be `null`.
93
+ An unresolved commercial request also carries a private
94
+ `commercial_review_routing` object. It maps every zero-based opaque offer index
95
+ to its ordered, non-overlapping source and target Unicode code-point spans and
96
+ binds both complete text lengths. The object contains no configured offer ID,
97
+ text, price, brand, or reviewer prose. It also carries the exact SHA-256 of the
98
+ separately advertised machine-readable routing contract. The sibling
99
+ `commercial_review_routing_contract` field contains that complete content-free
100
+ contract, so a remote reviewer does not depend on separate capability
101
+ discovery. The adapter reconstructs the canonical object and validates the
102
+ route against the exact source, target, summary, and profile before
103
+ authentication or transport. Route, digest, and full contract are part of the
104
+ deterministic request ID. Verified commercial results and non-commercial
105
+ requests require both route and routing contract to be `null`.
106
+ Non-commercial requests require every commercial field to be `null` and forbid
107
+ the nested commercial quality profile.
108
+ Source and
69
109
  target text are intentionally present because this external step verifies the
70
110
  existing source-blind native review and the separate source-aware fidelity
71
111
  review. The service must preserve their confidentiality.
72
112
 
113
+ The suffix of `request_id` is the SHA-256 of canonical UTF-8 JSON containing
114
+ exactly `schema`, `evidence_revision`, `event_id`, `plan_id`, `job_id`,
115
+ `result_sha256`, both text hashes, both locales, `content_type`, glossary and
116
+ policy versions, provider/model identity, software version, both confidence
117
+ decisions, the complete locale quality profile, commercial profile and review,
118
+ the routing-contract digest, the conditional commercial review-routing object,
119
+ its complete routing contract, the resolution-contract hash, and both escalation
120
+ flags. The full texts and `request_id` itself are excluded from that identity;
121
+ their exact bytes are already bound by `source_sha256` and `target_sha256`.
122
+ The durable store and HTTP adapter independently recompute this same identity.
123
+
73
124
  The adapter adds:
74
125
 
75
126
  ```text
@@ -109,11 +160,15 @@ numbers, a UTF-8 byte-order mark, wrong bindings, ambiguous content types,
109
160
  incorrect lengths, and oversized bodies. The release coordinator then applies
110
161
  its existing independent receipt checks. Each opaque receipt must verify
111
162
  against the complete canonical
112
- `blun.localization-quality-receipt-binding.v2` object supplied by the release
163
+ `blun.localization-quality-receipt-binding.v9` object supplied by the release
113
164
  coordinator, including the review purpose, job and result hashes, both texts
114
165
  and locales, content type, glossary and policy versions, provider/model and
115
166
  software identities, locale quality and commercial profiles, the exact
116
- commercial review scope, confidence, and escalation requirements. Reuse across
167
+ commercial review scope, canonical resolution-contract SHA-256, confidence,
168
+ the exact public offer-routing-contract SHA-256, the private offer-routing
169
+ context, escalation requirements, and the exact
170
+ evidence request ID and revision from
171
+ this response. Reuse across
117
172
  a changed field or between quality,
118
173
  qualified-human, and independent-model review purposes must fail closed.
119
174
  Legal content still requires a
@@ -80,7 +80,7 @@ The JSON envelope has exactly four fields:
80
80
  "request_id": "<deterministic phase request ID>",
81
81
  "request_sha256": "<SHA-256 of canonical request JSON>",
82
82
  "request": {
83
- "schema": "blun.website-localization-worker.v4",
83
+ "schema": "blun.website-localization-worker.v9",
84
84
  "request_id": "<same deterministic phase request ID>",
85
85
  "phase": "transcreation",
86
86
  "provider_id": "customer-llm",
@@ -98,6 +98,24 @@ the `target_native` input intentionally excludes the source text and
98
98
  source-language glossary terms; reconstructing or fetching that source would
99
99
  invalidate the independent, source-blind review.
100
100
 
101
+ For commercial content, only `source_fidelity` receives
102
+ `commercial_review_evidence_contract`. This is the complete content-free,
103
+ SHA-256-bound public contract for the required offer registry, Unicode spans,
104
+ ten checks, exact per-offer verdict coverage, deterministic aggregate-status
105
+ derivation, limits, and fail-closed trust boundary. The
106
+ worker verifies it against the installed public commercial profile before the
107
+ first provider call. Transcreation and source-blind native review do not receive
108
+ this contract. The provider still returns the private evidence only under
109
+ `response_schema.commercial_review`; the public contract itself contains no
110
+ project price, brand, source, target, span, or reviewer prose.
111
+
112
+ Commercial plan v4 and job v4 payloads also bind the exact public
113
+ offer-routing-contract SHA-256. Worker v8 reconstructs that contract together
114
+ with the review-evidence contract before any provider request, so queue health,
115
+ lease validation, retries, and provider invocation reject obsolete routing
116
+ semantics under the same deterministic job identity. Non-commercial jobs omit
117
+ both commercial contract digests.
118
+
101
119
  `request_sha256` is the lowercase hexadecimal SHA-256 of `request` encoded as
102
120
  UTF-8 JSON with keys sorted, no insignificant whitespace, native Unicode
103
121
  characters unescaped, and non-finite numbers forbidden. The bundled adapter
@@ -25,11 +25,18 @@ retry and its attempt limit.
25
25
  ## Request
26
26
 
27
27
  The adapter canonicalizes the complete
28
- `blun.localization-quality-receipt-binding.v2` object and validates its native
28
+ `blun.localization-quality-receipt-binding.v9` object and validates its native
29
29
  Unicode text, hashes, locales, content type, glossary and policy versions,
30
30
  provider/model identities, software version, two-pass confidence, locale
31
- quality profile, optional commercial profile and exact targeted-review summary,
32
- escalation requirements, and review purpose. It then sends HTTP POST with JSON content type and these
31
+ quality profile, exact locale-specific commercial profile when applicable,
32
+ matching targeted-review summary and its exact review-evidence-contract
33
+ SHA-256, the exact current offer-routing-contract SHA-256, the canonical
34
+ resolution-contract SHA-256
35
+ and the private, text-free mapping from opaque offer indexes to exact source
36
+ and target Unicode code-point spans for unresolved commercial review,
37
+ escalation requirements, review purpose,
38
+ canonical quality-evidence request ID, and evidence revision.
39
+ It then sends HTTP POST with JSON content type and these
33
40
  protected headers:
34
41
 
35
42
  - `Idempotency-Key`;
@@ -44,15 +51,24 @@ The body is exactly:
44
51
  "request_id": "blun-l10n-receipt-<sha256>",
45
52
  "binding_sha256": "<canonical binding hash>",
46
53
  "receipt_sha256": "<opaque receipt hash>",
47
- "binding": {"schema": "blun.localization-quality-receipt-binding.v2"},
54
+ "binding": {"schema": "blun.localization-quality-receipt-binding.v9"},
48
55
  "receipt": "<opaque receipt>"
49
56
  }
50
57
  ```
51
58
 
52
59
  The deterministic request ID is derived from both hashes. Identical retries
53
60
  therefore address the same verification operation, while any changed receipt
54
- or binding dimension receives a different identity. The remote service must
61
+ or binding dimension—including the evidence request ID or revision—receives a
62
+ different identity. The remote service must
55
63
  treat an idempotency-key collision with different bytes as a terminal error.
64
+ The adapter recomputes the commercial resolution-contract digest from the
65
+ installed profile before authentication or transport. Verified commercial and
66
+ non-commercial bindings require both that digest and the routing object to be
67
+ `null`. For unresolved review, it validates span order, bounds, text lengths,
68
+ offer count, and exact opaque index order before any credential callback or
69
+ network access. It also reconstructs and requires the exact digest of the
70
+ separately advertised machine-readable routing contract. The routing object
71
+ never contains configured offer IDs, text, prices, brands, or reviewer prose.
56
72
 
57
73
  ## Response
58
74
 
@@ -83,4 +99,6 @@ statuses, binding mismatches, and explicit negative verdicts are terminal.
83
99
 
84
100
  The same adapter can be configured separately for quality, qualified-human,
85
101
  and independent-model receipts. Purpose is part of the signed binding, so a
86
- receipt accepted for one route cannot satisfy another route.
102
+ receipt accepted for one route cannot satisfy another route. Evidence context
103
+ is also part of the binding, so a receipt cannot be relabelled beneath a newer
104
+ provider response envelope.
@@ -27,11 +27,32 @@ _SPEC.loader.exec_module(_IMPL)
27
27
 
28
28
  hashlib = _IMPL.hashlib
29
29
  PUBLIC_PROFILE_SCHEMA = _IMPL.PUBLIC_PROFILE_SCHEMA
30
+ REVIEW_EVIDENCE_CAPABILITIES_SCHEMA = (
31
+ _IMPL.REVIEW_EVIDENCE_CAPABILITIES_SCHEMA
32
+ )
30
33
  REVIEW_SUMMARY_CAPABILITIES_SCHEMA = _IMPL.REVIEW_SUMMARY_CAPABILITIES_SCHEMA
31
34
  REVIEW_SUMMARY_SCHEMA = _IMPL.REVIEW_SUMMARY_SCHEMA
35
+ REVIEW_ROUTING_CAPABILITIES_SCHEMA = (
36
+ _IMPL.REVIEW_ROUTING_CAPABILITIES_SCHEMA
37
+ )
38
+ REVIEW_ROUTING_SCHEMA = _IMPL.REVIEW_ROUTING_SCHEMA
39
+ EVIDENCE_BINDING_SCHEMA = _IMPL.EVIDENCE_BINDING_SCHEMA
40
+ REVIEW_RESOLUTION_CAPABILITIES_SCHEMA = (
41
+ _IMPL.REVIEW_RESOLUTION_CAPABILITIES_SCHEMA
42
+ )
43
+ REVIEW_RESOLUTION_SCHEMA = _IMPL.REVIEW_RESOLUTION_SCHEMA
44
+ COMMERCIAL_LOCALE_PROFILE_SCHEMA = _IMPL.COMMERCIAL_LOCALE_PROFILE_SCHEMA
45
+ COMMERCIAL_RENDERING_REFERENCE_SCHEMA = (
46
+ _IMPL.COMMERCIAL_RENDERING_REFERENCE_SCHEMA
47
+ )
32
48
  DIMENSIONS = _IMPL.DIMENSIONS
33
49
  _canonical_json = _IMPL._canonical_json
50
+ evidence_sha256 = _IMPL.evidence_sha256
51
+ public_review_evidence_contract = _IMPL.public_review_evidence_contract
34
52
  public_review_summary_contract = _IMPL.public_review_summary_contract
53
+ public_review_routing_contract = _IMPL.public_review_routing_contract
54
+ public_review_resolution_contract = _IMPL.public_review_resolution_contract
55
+ validate_review_resolution = _IMPL.validate_review_resolution
35
56
  public_profile = _IMPL.public_profile
36
57
  CREATION_GUIDANCE = _IMPL.CREATION_GUIDANCE
37
58
  NATIVE_GUIDANCE = _IMPL.NATIVE_GUIDANCE
@@ -40,3 +61,5 @@ CommercialReviewBlocked = _IMPL.CommercialReviewBlocked
40
61
  review_contract = _IMPL.review_contract
41
62
  validate_review = _IMPL.validate_review
42
63
  validate_summary = _IMPL.validate_summary
64
+ validate_review_routing_context = _IMPL.validate_review_routing_context
65
+ review_routing_context = _IMPL.review_routing_context
@@ -21,8 +21,8 @@ from pathlib import Path
21
21
  from typing import Any, Iterable, Mapping, Sequence
22
22
 
23
23
 
24
- SCHEMA = "blun.website-localization-plan.v2"
25
- JOB_SCHEMA = "blun.website-localization-job.v2"
24
+ SCHEMA = "blun.website-localization-plan.v5"
25
+ JOB_SCHEMA = "blun.website-localization-job.v5"
26
26
  EU_LANGUAGE_SOURCE = (
27
27
  "https://european-union.europa.eu/principles-countries-history/languages_en"
28
28
  )
@@ -42,7 +42,7 @@ CONTENT_TYPES = frozenset({
42
42
  "commercial",
43
43
  })
44
44
  QUALITY_PASSES = ("target_native", "source_fidelity")
45
- COMMERCIAL_PROFILE = "translate-native.commercial.v2"
45
+ COMMERCIAL_PROFILE = "translate-native.commercial.v13"
46
46
 
47
47
 
48
48
  class LocalizationPlanBlocked(ValueError):
@@ -78,6 +78,25 @@ def _load_quality_profiles():
78
78
 
79
79
 
80
80
  _QUALITY_PROFILES = _load_quality_profiles()
81
+ COMMERCIAL_RENDERING_REGISTRY_SCHEMA = (
82
+ _QUALITY_PROFILES.COMMERCIAL_RENDERING_REGISTRY_SCHEMA
83
+ )
84
+
85
+
86
+ def _load_commercial_profile():
87
+ path = Path(__file__).with_name("commercial_localization_profile.py")
88
+ spec = importlib.util.spec_from_file_location(
89
+ "blun_website_planner_commercial_profile", path,
90
+ )
91
+ if spec is None or spec.loader is None:
92
+ raise RuntimeError("cannot load commercial localization profile")
93
+ module = importlib.util.module_from_spec(spec)
94
+ sys.modules[spec.name] = module
95
+ spec.loader.exec_module(module)
96
+ return module
97
+
98
+
99
+ _COMMERCIAL = _load_commercial_profile()
81
100
 
82
101
 
83
102
  def _locale(locale: str, eu_code: str, language: str, native_name: str, script: str) -> LocaleProfile:
@@ -123,6 +142,126 @@ def quality_profile_for(locale: str) -> dict[str, Any]:
123
142
  return _QUALITY_PROFILES.quality_profile_for(locale)
124
143
 
125
144
 
145
+ def commercial_quality_profile_for(locale: str) -> dict[str, Any]:
146
+ return _QUALITY_PROFILES.commercial_quality_profile_for(
147
+ locale,
148
+ COMMERCIAL_PROFILE,
149
+ )
150
+
151
+
152
+ def commercial_rendering_registry() -> dict[str, Any]:
153
+ return _QUALITY_PROFILES.commercial_rendering_registry(COMMERCIAL_PROFILE)
154
+
155
+
156
+ def commercial_review_evidence_contract_sha256() -> str:
157
+ """Return the exact current evidence-contract generation or fail closed."""
158
+ try:
159
+ contract = _COMMERCIAL.public_review_evidence_contract(
160
+ COMMERCIAL_PROFILE,
161
+ )
162
+ public_contract = _COMMERCIAL.public_profile(COMMERCIAL_PROFILE)[
163
+ "review_evidence_contract"
164
+ ]
165
+ if not isinstance(contract, dict) or not isinstance(
166
+ public_contract, dict
167
+ ):
168
+ raise TypeError
169
+ unsigned = dict(contract)
170
+ digest = unsigned.pop("sha256")
171
+ computed_digest = _digest(unsigned)
172
+ except (KeyError, TypeError, ValueError) as error:
173
+ raise LocalizationPlanBlocked(
174
+ "commercial review-evidence contract is invalid"
175
+ ) from error
176
+ if (
177
+ contract != public_contract
178
+ or contract.get("schema")
179
+ != _COMMERCIAL.REVIEW_EVIDENCE_CAPABILITIES_SCHEMA
180
+ or contract.get("result_schema") != COMMERCIAL_PROFILE
181
+ or contract.get("profile") != COMMERCIAL_PROFILE
182
+ or not isinstance(digest, str)
183
+ or re.fullmatch(r"[0-9a-f]{64}", digest) is None
184
+ or digest != computed_digest
185
+ ):
186
+ raise LocalizationPlanBlocked(
187
+ "commercial review-evidence contract is invalid"
188
+ )
189
+ return digest
190
+
191
+
192
+ def commercial_review_routing_contract_sha256() -> str:
193
+ """Return the exact current private-routing contract or fail closed."""
194
+ try:
195
+ contract = _COMMERCIAL.public_review_routing_contract(
196
+ COMMERCIAL_PROFILE,
197
+ )
198
+ public_contract = _COMMERCIAL.public_profile(COMMERCIAL_PROFILE)[
199
+ "review_routing_contract"
200
+ ]
201
+ if not isinstance(contract, dict) or not isinstance(
202
+ public_contract, dict
203
+ ):
204
+ raise TypeError
205
+ unsigned = dict(contract)
206
+ digest = unsigned.pop("sha256")
207
+ computed_digest = _digest(unsigned)
208
+ except (KeyError, TypeError, ValueError) as error:
209
+ raise LocalizationPlanBlocked(
210
+ "commercial review-routing contract is invalid"
211
+ ) from error
212
+ if (
213
+ contract != public_contract
214
+ or contract.get("schema")
215
+ != _COMMERCIAL.REVIEW_ROUTING_CAPABILITIES_SCHEMA
216
+ or contract.get("result_schema") != _COMMERCIAL.REVIEW_ROUTING_SCHEMA
217
+ or contract.get("profile") != COMMERCIAL_PROFILE
218
+ or not isinstance(digest, str)
219
+ or re.fullmatch(r"[0-9a-f]{64}", digest) is None
220
+ or digest != computed_digest
221
+ ):
222
+ raise LocalizationPlanBlocked(
223
+ "commercial review-routing contract is invalid"
224
+ )
225
+ return digest
226
+
227
+
228
+ def commercial_review_resolution_contract_sha256() -> str:
229
+ """Return the exact current review-resolution contract or fail closed."""
230
+ try:
231
+ contract = _COMMERCIAL.public_review_resolution_contract(
232
+ COMMERCIAL_PROFILE,
233
+ )
234
+ public_contract = _COMMERCIAL.public_profile(COMMERCIAL_PROFILE)[
235
+ "review_resolution_contract"
236
+ ]
237
+ if not isinstance(contract, dict) or not isinstance(
238
+ public_contract, dict
239
+ ):
240
+ raise TypeError
241
+ unsigned = dict(contract)
242
+ digest = unsigned.pop("sha256")
243
+ computed_digest = _digest(unsigned)
244
+ except (KeyError, TypeError, ValueError) as error:
245
+ raise LocalizationPlanBlocked(
246
+ "commercial review-resolution contract is invalid"
247
+ ) from error
248
+ if (
249
+ contract != public_contract
250
+ or contract.get("schema")
251
+ != _COMMERCIAL.REVIEW_RESOLUTION_CAPABILITIES_SCHEMA
252
+ or contract.get("result_schema")
253
+ != _COMMERCIAL.REVIEW_RESOLUTION_SCHEMA
254
+ or contract.get("profile") != COMMERCIAL_PROFILE
255
+ or not isinstance(digest, str)
256
+ or re.fullmatch(r"[0-9a-f]{64}", digest) is None
257
+ or digest != computed_digest
258
+ ):
259
+ raise LocalizationPlanBlocked(
260
+ "commercial review-resolution contract is invalid"
261
+ )
262
+ return digest
263
+
264
+
126
265
  @dataclass(frozen=True)
127
266
  class LocalizationJob:
128
267
  job_id: str
@@ -139,9 +278,43 @@ class LocalizationJob:
139
278
  model_id: str
140
279
  model_version: str
141
280
  software_version: str
281
+ commercial_review_evidence_contract_sha256: str | None = None
282
+ commercial_review_routing_contract_sha256: str | None = None
283
+ commercial_review_resolution_contract_sha256: str | None = None
142
284
 
143
285
  def as_payload(self) -> dict[str, Any]:
144
286
  """Return the provider-neutral queue payload for one target locale."""
287
+ commercial = (
288
+ commercial_quality_profile_for(self.target.locale)
289
+ if self.content_type == "commercial"
290
+ else None
291
+ )
292
+ commercial_evidence_sha256 = (
293
+ commercial_review_evidence_contract_sha256()
294
+ if commercial is not None
295
+ else None
296
+ )
297
+ commercial_routing_sha256 = (
298
+ commercial_review_routing_contract_sha256()
299
+ if commercial is not None
300
+ else None
301
+ )
302
+ commercial_resolution_sha256 = (
303
+ commercial_review_resolution_contract_sha256()
304
+ if commercial is not None
305
+ else None
306
+ )
307
+ if (
308
+ self.commercial_review_evidence_contract_sha256
309
+ != commercial_evidence_sha256
310
+ or self.commercial_review_routing_contract_sha256
311
+ != commercial_routing_sha256
312
+ or self.commercial_review_resolution_contract_sha256
313
+ != commercial_resolution_sha256
314
+ ):
315
+ raise LocalizationPlanBlocked(
316
+ "commercial review contract binding is stale"
317
+ )
145
318
  return {
146
319
  "schema": JOB_SCHEMA,
147
320
  "job_id": self.job_id,
@@ -165,7 +338,23 @@ class LocalizationJob:
165
338
  "software_version": self.software_version,
166
339
  "quality_passes": list(QUALITY_PASSES),
167
340
  "release_required": True,
168
- **({"commercial_profile": COMMERCIAL_PROFILE} if self.content_type == "commercial" else {}),
341
+ **(
342
+ {
343
+ "commercial_profile": COMMERCIAL_PROFILE,
344
+ "commercial_quality_profile": commercial,
345
+ "commercial_review_evidence_contract_sha256": (
346
+ commercial_evidence_sha256
347
+ ),
348
+ "commercial_review_routing_contract_sha256": (
349
+ commercial_routing_sha256
350
+ ),
351
+ "commercial_review_resolution_contract_sha256": (
352
+ commercial_resolution_sha256
353
+ ),
354
+ }
355
+ if commercial is not None
356
+ else {}
357
+ ),
169
358
  }
170
359
 
171
360
 
@@ -319,11 +508,38 @@ def plan_website_localization(
319
508
  "software_version": software_version,
320
509
  "quality_passes": QUALITY_PASSES,
321
510
  }
511
+ commercial_evidence_sha256 = None
512
+ commercial_routing_sha256 = None
513
+ commercial_resolution_sha256 = None
322
514
  if content_type == "commercial":
323
515
  common["commercial_profile"] = COMMERCIAL_PROFILE
324
- jobs = tuple(
325
- LocalizationJob(
326
- job_id="blun-l10n-" + _digest({**common, "target": asdict(profile)}),
516
+ commercial_evidence_sha256 = (
517
+ commercial_review_evidence_contract_sha256()
518
+ )
519
+ common["commercial_review_evidence_contract_sha256"] = (
520
+ commercial_evidence_sha256
521
+ )
522
+ commercial_routing_sha256 = (
523
+ commercial_review_routing_contract_sha256()
524
+ )
525
+ common["commercial_review_routing_contract_sha256"] = (
526
+ commercial_routing_sha256
527
+ )
528
+ commercial_resolution_sha256 = (
529
+ commercial_review_resolution_contract_sha256()
530
+ )
531
+ common["commercial_review_resolution_contract_sha256"] = (
532
+ commercial_resolution_sha256
533
+ )
534
+ jobs_list = []
535
+ for profile in profiles:
536
+ job_binding = {**common, "target": asdict(profile)}
537
+ if content_type == "commercial":
538
+ job_binding["commercial_quality_profile"] = (
539
+ commercial_quality_profile_for(profile.locale)
540
+ )
541
+ jobs_list.append(LocalizationJob(
542
+ job_id="blun-l10n-" + _digest(job_binding),
327
543
  source_id=source_id,
328
544
  source_revision=source_revision,
329
545
  source_text=source_text,
@@ -337,9 +553,17 @@ def plan_website_localization(
337
553
  model_id=model_id,
338
554
  model_version=model_version,
339
555
  software_version=software_version,
340
- )
341
- for profile in profiles
342
- )
556
+ commercial_review_evidence_contract_sha256=(
557
+ commercial_evidence_sha256
558
+ ),
559
+ commercial_review_routing_contract_sha256=(
560
+ commercial_routing_sha256
561
+ ),
562
+ commercial_review_resolution_contract_sha256=(
563
+ commercial_resolution_sha256
564
+ ),
565
+ ))
566
+ jobs = tuple(jobs_list)
343
567
  plan_binding = {
344
568
  **common,
345
569
  "target_locales": [job.target.locale for job in jobs],
@@ -734,7 +734,10 @@ def _validate_worker_result(job: dict[str, Any], result: Any) -> dict[str, Any]:
734
734
  "source_locale", "target_locale", "content_type", "glossary_version",
735
735
  "policy_version", "provider", "software_version", "candidate",
736
736
  "quality_passes", "integrity", "review_confidence",
737
- "quality_profile", "commercial_review", "human_review_required",
737
+ "quality_profile", "commercial_review", "commercial_review_routing",
738
+ "commercial_review_routing_contract_sha256",
739
+ "commercial_review_resolution_contract_sha256",
740
+ "human_review_required",
738
741
  "independent_review_required", "release_required",
739
742
  }
740
743
  if not isinstance(result, dict) or set(result) != expected_keys:
@@ -771,16 +774,49 @@ def _validate_worker_result(job: dict[str, Any], result: Any) -> dict[str, Any]:
771
774
  job["content_type"] != "legal" and "low" in review_confidence.values()
772
775
  )
773
776
  commercial_review = result["commercial_review"]
777
+ commercial_review_routing = result["commercial_review_routing"]
778
+ commercial_routing_contract_sha256 = result[
779
+ "commercial_review_routing_contract_sha256"
780
+ ]
781
+ commercial_resolution_contract_sha256 = result[
782
+ "commercial_review_resolution_contract_sha256"
783
+ ]
774
784
  if job["content_type"] == "commercial":
785
+ if (
786
+ commercial_routing_contract_sha256
787
+ != job["commercial_review_routing_contract_sha256"]
788
+ or commercial_resolution_contract_sha256
789
+ != job["commercial_review_resolution_contract_sha256"]
790
+ ):
791
+ raise BenchmarkBlocked(
792
+ "benchmark.candidate.commercial_review_invalid"
793
+ )
775
794
  try:
776
- _WORKER._COMMERCIAL.validate_summary(
795
+ commercial_review = _WORKER._COMMERCIAL.validate_summary(
777
796
  commercial_review,
778
797
  job["commercial_profile"],
779
798
  review_required=expected_independent_review,
780
799
  )
800
+ if commercial_review["status"] == "review_required":
801
+ _WORKER._COMMERCIAL.validate_review_routing_context(
802
+ commercial_review_routing,
803
+ job["source"]["text"],
804
+ candidate,
805
+ commercial_review,
806
+ job["commercial_profile"],
807
+ )
808
+ elif commercial_review_routing is not None:
809
+ raise _WORKER._COMMERCIAL.CommercialReviewBlocked(
810
+ "review.commercial.routing_invalid",
811
+ )
781
812
  except _WORKER._COMMERCIAL.CommercialReviewBlocked:
782
813
  raise BenchmarkBlocked("benchmark.candidate.commercial_review_invalid") from None
783
- elif commercial_review is not None:
814
+ elif any(value is not None for value in (
815
+ commercial_review,
816
+ commercial_review_routing,
817
+ commercial_routing_contract_sha256,
818
+ commercial_resolution_contract_sha256,
819
+ )):
784
820
  raise BenchmarkBlocked("benchmark.candidate.commercial_review_invalid")
785
821
  expected_quality_profile = {
786
822
  "locale": job["target"]["locale"],