blun-king-cli 9.1.587 → 9.1.588

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 (436) hide show
  1. package/CHANGELOG.md +11 -185
  2. package/LIESMICH.txt +51 -13
  3. package/README.md +44 -47
  4. package/agent-spine-plugin/.codex-plugin/plugin.json +16 -4
  5. package/agent-spine-plugin/CHANGELOG.md +37 -5
  6. package/agent-spine-plugin/README.md +3 -3
  7. package/agent-spine-plugin/blun.plugin.json +45 -10
  8. package/agent-spine-plugin/docs/artifact-evaluation.md +93 -0
  9. package/agent-spine-plugin/docs/host-integration.md +42 -27
  10. package/agent-spine-plugin/docs/preflight-recall.md +4 -2
  11. package/agent-spine-plugin/docs/session-timeline.md +97 -236
  12. package/agent-spine-plugin/docs/world-model.md +25 -0
  13. package/agent-spine-plugin/hooks/codex.json +1 -1
  14. package/agent-spine-plugin/hooks/hooks.json +1 -1
  15. package/agent-spine-plugin/package.json +1 -3
  16. package/agent-spine-plugin/scripts/check-hosts.js +3 -3
  17. package/agent-spine-plugin/scripts/release-check.js +10 -5
  18. package/agent-spine-plugin/scripts/run-checks.js +4 -1
  19. package/agent-spine-plugin/scripts/run-tests-hermetic.js +32 -6
  20. package/agent-spine-plugin/src/cli-learning.js +15 -0
  21. package/agent-spine-plugin/src/cli.js +2 -0
  22. package/agent-spine-plugin/src/hook.js +32 -32
  23. package/agent-spine-plugin/src/lib/action-lesson-recall.js +73 -8
  24. package/agent-spine-plugin/src/lib/briefing.js +146 -36
  25. package/agent-spine-plugin/src/lib/channel-continuity.js +19 -0
  26. package/agent-spine-plugin/src/lib/delivery-agent-usage.js +14 -7
  27. package/agent-spine-plugin/src/lib/gateway-group-response.js +128 -0
  28. package/agent-spine-plugin/src/lib/gateway-runs.js +24 -15
  29. package/agent-spine-plugin/src/lib/hook-briefing-use.js +13 -3
  30. package/agent-spine-plugin/src/lib/hook-context.js +16 -3
  31. package/agent-spine-plugin/src/lib/hook-output.js +129 -5
  32. package/agent-spine-plugin/src/lib/hook-timeline.js +5 -3
  33. package/agent-spine-plugin/src/lib/indexed-memory.js +2 -2
  34. package/agent-spine-plugin/src/lib/learning-artifact-evaluator.js +114 -0
  35. package/agent-spine-plugin/src/lib/learning-context.js +11 -4
  36. package/agent-spine-plugin/src/lib/learning-measurements.js +2 -2
  37. package/agent-spine-plugin/src/lib/mcp-runtime.js +89 -3
  38. package/agent-spine-plugin/src/lib/mcp-source-context.js +12 -2
  39. package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +91 -8
  40. package/agent-spine-plugin/src/lib/mcp-world-tools.js +2 -2
  41. package/agent-spine-plugin/src/lib/owned-file-lock.js +20 -1
  42. package/agent-spine-plugin/src/lib/persona-runtime.js +2 -2
  43. package/agent-spine-plugin/src/lib/preflight-delivery-id.js +27 -0
  44. package/agent-spine-plugin/src/lib/preflight.js +4 -4
  45. package/agent-spine-plugin/src/lib/session-timeline-codex.js +15 -0
  46. package/agent-spine-plugin/src/lib/session-timeline-contract.js +12 -4
  47. package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +36 -7
  48. package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +13 -10
  49. package/agent-spine-plugin/src/lib/session-timeline-invocation.js +1 -1
  50. package/agent-spine-plugin/src/lib/session-timeline-king.js +14 -0
  51. package/agent-spine-plugin/src/lib/session-timeline-prior.js +18 -12
  52. package/agent-spine-plugin/src/lib/session-timeline-provider.js +5 -0
  53. package/agent-spine-plugin/src/lib/session-timeline-query.js +2 -0
  54. package/agent-spine-plugin/src/lib/session-timeline-results.js +35 -10
  55. package/agent-spine-plugin/src/lib/session-timeline-source-open.js +30 -0
  56. package/agent-spine-plugin/src/lib/session-timeline.js +122 -75
  57. package/agent-spine-plugin/src/lib/source-roots.js +3 -2
  58. package/agent-spine-plugin/src/lib/task-knowledge-context.js +22 -1
  59. package/agent-spine-plugin/src/lib/timeline-continuation-update.js +100 -0
  60. package/agent-spine-plugin/src/lib/timeline-tool-guard.js +30 -7
  61. package/agent-spine-plugin/src/lib/timeline-user-feedback.js +217 -0
  62. package/agent-spine-plugin/src/lib/timeline-world-capture.js +233 -0
  63. package/agent-spine-plugin/src/lib/world-knowledge.js +59 -2
  64. package/agent-spine-plugin/src/lib/world-model.js +64 -9
  65. package/agent-spine-plugin/src/worker.js +13 -1
  66. package/bin/blun.js +43 -28
  67. package/bin/core-bootstrap.js +5 -4
  68. package/bin/king.js +43 -28
  69. package/bin/launcher-mode.js +1 -10
  70. package/bin/launcher-runtime.js +128 -295
  71. package/bin/managed-node.js +0 -0
  72. package/bin/managed-plugin-selection.cjs +0 -1
  73. package/bin/native-module-repair.js +0 -0
  74. package/bin/node-runtime.js +0 -0
  75. package/bin/node-version.js +0 -0
  76. package/bin/plugin-bootstrap.js +56 -120
  77. package/bin/private-paths.js +11 -34
  78. package/bin/standard-tools-bootstrap.js +34 -114
  79. package/bin/turn-thinking-policy.cjs +3 -11
  80. package/bin/update-copy.js +200 -0
  81. package/bin/update-lease.js +0 -0
  82. package/bin/update-notice.js +136 -289
  83. package/bin/verify-agent-behavior.cjs +122 -0
  84. package/bin/verify-agent-components.cjs +104 -0
  85. package/bin/verify-bundled-agent-sources.cjs +57 -0
  86. package/blun.mjs +143076 -135288
  87. package/bundled-agent-sources.json +701 -0
  88. package/package.json +12 -15
  89. package/standard-skills/translate-native/README.md +1293 -0
  90. package/standard-skills/translate-native/SKILL.md +172 -22
  91. package/standard-skills/translate-native/VERSION +1 -1
  92. package/standard-skills/translate-native/agents/openai.yaml +18 -0
  93. package/standard-skills/translate-native/assets/icon.svg +8 -0
  94. package/standard-skills/translate-native/docs/BLUN_CODE_INTEGRATION.md +76 -0
  95. package/standard-skills/translate-native/docs/PREMORTEM.md +489 -0
  96. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION.md +2035 -0
  97. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_API.md +1302 -0
  98. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_EVIDENCE_HTTP.md +136 -0
  99. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_HEALTH_HTTP.md +130 -0
  100. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_HTTP_PROVIDER.md +175 -0
  101. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_RECEIPT_VERIFIER_HTTP.md +86 -0
  102. package/standard-skills/translate-native/integrations/AGENT_RULES.md +32 -0
  103. package/standard-skills/translate-native/integrations/adapters/blun-code-language-guard.js +514 -0
  104. package/standard-skills/translate-native/integrations/adapters/node-language-guard.js +230 -0
  105. package/standard-skills/translate-native/integrations/audit_log.py +327 -0
  106. package/standard-skills/translate-native/integrations/claude_language_hook.js +1536 -0
  107. package/standard-skills/translate-native/integrations/commercial_localization_profile.py +42 -0
  108. package/standard-skills/translate-native/integrations/delivery-policy.example.json +28 -0
  109. package/standard-skills/translate-native/integrations/enforced_delivery.py +543 -0
  110. package/standard-skills/translate-native/integrations/guard_service.py +435 -0
  111. package/standard-skills/translate-native/integrations/language_gateway.py +67 -0
  112. package/standard-skills/translate-native/integrations/mcp_auth_headers.py +198 -0
  113. package/standard-skills/translate-native/integrations/mcp_http_gateway.py +429 -0
  114. package/standard-skills/translate-native/integrations/non_language_html_entities.js +1485 -0
  115. package/standard-skills/translate-native/integrations/pre_output_guard.py +65 -0
  116. package/standard-skills/translate-native/integrations/task_router.py +101 -0
  117. package/standard-skills/translate-native/integrations/website_localization.py +401 -0
  118. package/standard-skills/translate-native/integrations/website_localization_api.py +581 -0
  119. package/standard-skills/translate-native/integrations/website_localization_benchmark.py +1885 -0
  120. package/standard-skills/translate-native/integrations/website_localization_benchmark_campaign.py +1772 -0
  121. package/standard-skills/translate-native/integrations/website_localization_benchmark_candidate.py +506 -0
  122. package/standard-skills/translate-native/integrations/website_localization_benchmark_http.py +400 -0
  123. package/standard-skills/translate-native/integrations/website_localization_benchmark_review_store.py +781 -0
  124. package/standard-skills/translate-native/integrations/website_localization_benchmark_reviewer_http.py +500 -0
  125. package/standard-skills/translate-native/integrations/website_localization_benchmark_runtime.py +1107 -0
  126. package/standard-skills/translate-native/integrations/website_localization_benchmark_suite.py +463 -0
  127. package/standard-skills/translate-native/integrations/website_localization_cms.py +2835 -0
  128. package/standard-skills/translate-native/integrations/website_localization_cms_client.py +875 -0
  129. package/standard-skills/translate-native/integrations/website_localization_cms_dispatch.py +805 -0
  130. package/standard-skills/translate-native/integrations/website_localization_cms_http.py +588 -0
  131. package/standard-skills/translate-native/integrations/website_localization_cms_lifecycle_monitor.py +991 -0
  132. package/standard-skills/translate-native/integrations/website_localization_cms_receiver.py +1441 -0
  133. package/standard-skills/translate-native/integrations/website_localization_cms_receiver_runtime.py +414 -0
  134. package/standard-skills/translate-native/integrations/website_localization_cms_receiver_store.py +1073 -0
  135. package/standard-skills/translate-native/integrations/website_localization_cms_removal_dispatch.py +865 -0
  136. package/standard-skills/translate-native/integrations/website_localization_cms_source_client.py +583 -0
  137. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery.py +964 -0
  138. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_runtime.py +665 -0
  139. package/standard-skills/translate-native/integrations/website_localization_cms_source_http.py +1153 -0
  140. package/standard-skills/translate-native/integrations/website_localization_cms_source_runtime.py +675 -0
  141. package/standard-skills/translate-native/integrations/website_localization_cms_source_service.py +1125 -0
  142. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification.py +674 -0
  143. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_http.py +444 -0
  144. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver.py +1469 -0
  145. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_runtime.py +1142 -0
  146. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_processing_monitor.py +634 -0
  147. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_receiver_client.py +804 -0
  148. package/standard-skills/translate-native/integrations/website_localization_deepl_baseline.py +922 -0
  149. package/standard-skills/translate-native/integrations/website_localization_evidence_http.py +482 -0
  150. package/standard-skills/translate-native/integrations/website_localization_health.py +1541 -0
  151. package/standard-skills/translate-native/integrations/website_localization_health_http.py +372 -0
  152. package/standard-skills/translate-native/integrations/website_localization_http_provider.py +297 -0
  153. package/standard-skills/translate-native/integrations/website_localization_native_reference_http.py +479 -0
  154. package/standard-skills/translate-native/integrations/website_localization_native_reference_intake.py +363 -0
  155. package/standard-skills/translate-native/integrations/website_localization_native_reference_queue.py +1449 -0
  156. package/standard-skills/translate-native/integrations/website_localization_native_reference_store.py +420 -0
  157. package/standard-skills/translate-native/integrations/website_localization_quality_profiles.py +235 -0
  158. package/standard-skills/translate-native/integrations/website_localization_queue.py +671 -0
  159. package/standard-skills/translate-native/integrations/website_localization_receipt_verifier_http.py +516 -0
  160. package/standard-skills/translate-native/integrations/website_localization_release.py +928 -0
  161. package/standard-skills/translate-native/integrations/website_localization_release_coordinator.py +1008 -0
  162. package/standard-skills/translate-native/integrations/website_localization_runner.py +276 -0
  163. package/standard-skills/translate-native/integrations/website_localization_runtime.py +862 -0
  164. package/standard-skills/translate-native/integrations/website_localization_service.py +350 -0
  165. package/standard-skills/translate-native/integrations/website_localization_supervisor.py +511 -0
  166. package/standard-skills/translate-native/integrations/website_localization_worker.py +663 -0
  167. package/standard-skills/translate-native/provenance.json +3 -4
  168. package/standard-skills/translate-native/references/commercial-localization.md +177 -0
  169. package/standard-skills/translate-native/scripts/blun_language_guard.py +7 -1
  170. package/standard-skills/translate-native/scripts/check_commercial_review.py +80 -0
  171. package/standard-skills/translate-native/scripts/commercial_localization_profile.py +333 -0
  172. package/standard-tools/language-guard/LICENSE +21 -0
  173. package/standard-tools/language-guard/VERSION +1 -0
  174. package/standard-tools/language-guard/blun_language_guard.py +7 -1
  175. package/standard-tools/language-guard/check_commercial_review.py +80 -0
  176. package/standard-tools/language-guard/commercial_localization_profile.py +333 -0
  177. package/standard-tools/language-guard/language_gateway.py +62 -0
  178. package/standard-tools/language-guard/pre_output_guard.py +64 -0
  179. package/standard-tools/language-guard/provenance.json +4 -11
  180. package/standard-tools/manifest.json +34 -11
  181. package/telegram-plugin/commands/access.md +2 -10
  182. package/telegram-plugin/dist/bridge.mjs +64041 -687
  183. package/telegram-plugin/dist/mcp-server.mjs +72810 -9027
  184. package/telegram-plugin/dist/noise.mjs +28 -63511
  185. package/agent-spine-plugin/CONTRIBUTING.md +0 -52
  186. package/agent-spine-plugin/SECURITY.md +0 -47
  187. package/agent-spine-plugin/docs/assignment-continuation.md +0 -48
  188. package/agent-spine-plugin/docs/releasing.md +0 -85
  189. package/agent-spine-plugin/docs/structured-completion.md +0 -67
  190. package/bin/abort-listener-policy.cjs +0 -43
  191. package/bin/active-steer-priority-policy.cjs +0 -24
  192. package/bin/agent-api-http-adapter.mjs +0 -446
  193. package/bin/agent-api-private-http-server.mjs +0 -288
  194. package/bin/agent-api-runtime.mjs +0 -252
  195. package/bin/agent-api-service-environment.mjs +0 -236
  196. package/bin/agent-api-service-host.mjs +0 -209
  197. package/bin/agent-api-service-process.mjs +0 -171
  198. package/bin/agent-api-session-registry.mjs +0 -428
  199. package/bin/agent-api-tool-broker.cjs +0 -248
  200. package/bin/agent-api-turn-controller.mjs +0 -461
  201. package/bin/agent-api-usage-journal.cjs +0 -259
  202. package/bin/agent-resume-snapshot.cjs +0 -241
  203. package/bin/agentspine-king-goal-inbox.mjs +0 -111
  204. package/bin/agentspine-king-goal-intake.mjs +0 -106
  205. package/bin/approval-rejection-stop.cjs +0 -15
  206. package/bin/assistant-message-offload-policy.cjs +0 -284
  207. package/bin/baseline-skill-performance-policy.cjs +0 -39
  208. package/bin/bash-search-scope-policy.cjs +0 -49
  209. package/bin/codebase-search-runtime.cjs +0 -23
  210. package/bin/cognitive-action-checkpoint.cjs +0 -1104
  211. package/bin/cognitive-attention-delivery.cjs +0 -76
  212. package/bin/cognitive-attention-policy.cjs +0 -143
  213. package/bin/cognitive-attention-runtime.cjs +0 -91
  214. package/bin/cognitive-context-projection.cjs +0 -73
  215. package/bin/cognitive-cross-portal-acceptance.cjs +0 -443
  216. package/bin/cognitive-effective-view.cjs +0 -77
  217. package/bin/cognitive-focus-projection.cjs +0 -206
  218. package/bin/cognitive-focus-scope.cjs +0 -37
  219. package/bin/cognitive-goal-autostart-policy.cjs +0 -72
  220. package/bin/cognitive-goal-time-trigger-controller.cjs +0 -146
  221. package/bin/cognitive-memory-adapter.cjs +0 -282
  222. package/bin/cognitive-memory-command.cjs +0 -293
  223. package/bin/cognitive-memory-provider.cjs +0 -92
  224. package/bin/cognitive-salience-policy.cjs +0 -159
  225. package/bin/cognitive-state-store.cjs +0 -508
  226. package/bin/cognitive-turn-lifecycle.cjs +0 -624
  227. package/bin/cognitive-work-focus.cjs +0 -180
  228. package/bin/compaction-history-archive.cjs +0 -166
  229. package/bin/compaction-history-startup.cjs +0 -50
  230. package/bin/compaction-model-policy.cjs +0 -31
  231. package/bin/compaction-stage-policy.cjs +0 -21
  232. package/bin/compaction-transaction-policy.cjs +0 -122
  233. package/bin/config-write-dedup-policy.cjs +0 -27
  234. package/bin/context-budget-ledger.cjs +0 -31
  235. package/bin/context-doctor-policy.cjs +0 -70
  236. package/bin/context-insight-policy.cjs +0 -36
  237. package/bin/context-performance-policy.cjs +0 -19
  238. package/bin/context-pressure-policy.cjs +0 -20
  239. package/bin/cron-run-output.cjs +0 -45
  240. package/bin/cron-run-store.cjs +0 -145
  241. package/bin/curiosity-scout-policy.cjs +0 -49
  242. package/bin/default-model-output-budget-policy.cjs +0 -28
  243. package/bin/durable-task-resume-policy.cjs +0 -130
  244. package/bin/durable-task-resume-runtime.cjs +0 -117
  245. package/bin/durable-task-resume-store.cjs +0 -88
  246. package/bin/editable-tool-approval-policy.cjs +0 -540
  247. package/bin/editable-tool-approval-runtime.cjs +0 -99
  248. package/bin/effective-system-prompt-cache-policy.cjs +0 -33
  249. package/bin/error-memory-performance-policy.cjs +0 -113
  250. package/bin/file-observation-policy.cjs +0 -133
  251. package/bin/foreground-output-capture-policy.cjs +0 -41
  252. package/bin/generated-source-health.cjs +0 -142
  253. package/bin/glob-pattern-policy.cjs +0 -13
  254. package/bin/goal-completion-evidence-policy.cjs +0 -120
  255. package/bin/grep-output-limit-policy.cjs +0 -39
  256. package/bin/historical-media-projection-policy.cjs +0 -48
  257. package/bin/history-offload-pressure-policy.cjs +0 -33
  258. package/bin/html-to-research-markdown.cjs +0 -147
  259. package/bin/identity-context-policy.cjs +0 -764
  260. package/bin/identity-journal-policy.cjs +0 -107
  261. package/bin/input-draft-persistence.cjs +0 -77
  262. package/bin/king-tui-function-contract.json +0 -33
  263. package/bin/launcher-restart-policy.cjs +0 -150
  264. package/bin/live-response-repetition-guard.cjs +0 -196
  265. package/bin/llm-config-log-dedup-policy.cjs +0 -76
  266. package/bin/loop-event-record-policy.cjs +0 -174
  267. package/bin/managed-context-startup-policy.cjs +0 -27
  268. package/bin/media-activity-layout-policy.cjs +0 -34
  269. package/bin/media-auto-retrieval-policy.cjs +0 -90
  270. package/bin/media-result-policy.cjs +0 -59
  271. package/bin/micro-compaction-policy.cjs +0 -145
  272. package/bin/mistake-relevance-policy.cjs +0 -319
  273. package/bin/model-retry-progress-policy.cjs +0 -46
  274. package/bin/native-large-file-io.cjs +0 -42
  275. package/bin/native-runtime-cache.cjs +0 -76
  276. package/bin/natural-presence-policy.cjs +0 -28
  277. package/bin/noninteractive-shell-env-policy.cjs +0 -19
  278. package/bin/observer-hooks.cjs +0 -14
  279. package/bin/outbound-claim-provenance.cjs +0 -150
  280. package/bin/oversized-context-offload-policy.cjs +0 -86
  281. package/bin/pending-media-policy.cjs +0 -182
  282. package/bin/pending-token-estimate-policy.cjs +0 -41
  283. package/bin/personal-memory-consent-policy.cjs +0 -72
  284. package/bin/personal-memory-performance-policy.cjs +0 -12
  285. package/bin/personality-choice-policy.cjs +0 -101
  286. package/bin/personality-memory-adapter.cjs +0 -379
  287. package/bin/personality-mode.cjs +0 -46
  288. package/bin/personality-setup-policy.cjs +0 -197
  289. package/bin/proactive-compaction-policy.cjs +0 -25
  290. package/bin/profile-identity-resolution.cjs +0 -136
  291. package/bin/profile-runtime.cjs +0 -318
  292. package/bin/profile-tool-exclusion-policy.cjs +0 -37
  293. package/bin/programmatic-context-isolation.cjs +0 -25
  294. package/bin/programmatic-tool-runtime.mjs +0 -627
  295. package/bin/provider-idle-timeout-policy.cjs +0 -14
  296. package/bin/provider-model-refresh-deadline.cjs +0 -53
  297. package/bin/provider-model-refresh-policy.cjs +0 -107
  298. package/bin/rate-limit-recovery-policy.cjs +0 -47
  299. package/bin/read-batch-policy.cjs +0 -32
  300. package/bin/read-continuation-policy.cjs +0 -59
  301. package/bin/recurring-cron-history-policy.cjs +0 -124
  302. package/bin/relationship-continuity-policy.cjs +0 -143
  303. package/bin/relationship-curiosity-policy.cjs +0 -107
  304. package/bin/relationship-learning-policy.cjs +0 -168
  305. package/bin/release-artifact-freeze-policy.cjs +0 -30
  306. package/bin/reload-plugin-bootstrap.cjs +0 -18
  307. package/bin/reload-queue-policy.cjs +0 -38
  308. package/bin/repeated-assistant-response-policy.cjs +0 -232
  309. package/bin/repeated-injection-projection.cjs +0 -107
  310. package/bin/repeated-user-message-projection.cjs +0 -8
  311. package/bin/research-page-result.cjs +0 -74
  312. package/bin/retry-checkpoint-policy.cjs +0 -13
  313. package/bin/runtime-exit-ledger.cjs +0 -144
  314. package/bin/scoped-cron-run-policy.cjs +0 -358
  315. package/bin/session-checkpoint-policy.cjs +0 -25
  316. package/bin/session-compaction-policy.cjs +0 -84
  317. package/bin/session-replay-policy.cjs +0 -20
  318. package/bin/session-replay-window-policy.cjs +0 -40
  319. package/bin/session-resume-checkpoint.cjs +0 -254
  320. package/bin/session-scrollback-archive.cjs +0 -229
  321. package/bin/skill-activation-performance-policy.cjs +0 -69
  322. package/bin/skill-listing-performance-policy.cjs +0 -92
  323. package/bin/soul-organization-policy.cjs +0 -78
  324. package/bin/soul-preservation-policy.cjs +0 -20
  325. package/bin/startup-preferences.cjs +0 -131
  326. package/bin/streaming-flush-performance-policy.cjs +0 -28
  327. package/bin/structured-agent-swarm-output.cjs +0 -325
  328. package/bin/structured-subagent-output.cjs +0 -252
  329. package/bin/subagent-context-fork-policy.cjs +0 -155
  330. package/bin/subagent-max-tokens-handoff-policy.cjs +0 -69
  331. package/bin/subagent-parent-responsiveness.cjs +0 -19
  332. package/bin/subagent-skill-policy.cjs +0 -206
  333. package/bin/subagent-timeout-policy.cjs +0 -182
  334. package/bin/subagent-tool-policy.cjs +0 -60
  335. package/bin/subagent-usage-rollup-policy.cjs +0 -29
  336. package/bin/system-prompt-context-policy.cjs +0 -124
  337. package/bin/system-prompt-token-cache-policy.cjs +0 -60
  338. package/bin/telegram-addressed-focus.cjs +0 -55
  339. package/bin/telegram-addressed-priority.cjs +0 -12
  340. package/bin/telegram-approval-relay.cjs +0 -290
  341. package/bin/telegram-bot-priority.cjs +0 -17
  342. package/bin/telegram-console-status-policy.cjs +0 -174
  343. package/bin/telegram-context-projection-policy.cjs +0 -141
  344. package/bin/telegram-delivery-lifecycle.cjs +0 -125
  345. package/bin/telegram-direct-focus-policy.cjs +0 -273
  346. package/bin/telegram-mcp-compatibility.cjs +0 -49
  347. package/bin/telegram-media-delivery-policy.cjs +0 -42
  348. package/bin/telegram-private-conversation-policy.cjs +0 -185
  349. package/bin/telegram-queue-handoff-policy.cjs +0 -73
  350. package/bin/telegram-remote-status-policy.cjs +0 -120
  351. package/bin/telegram-session-queue-runtime.mjs +0 -306
  352. package/bin/telegram-text-chunk-policy.cjs +0 -63
  353. package/bin/telegram-truncated-reply-policy.cjs +0 -37
  354. package/bin/telegram-urgent-policy.cjs +0 -45
  355. package/bin/telemetry-spool-policy.cjs +0 -57
  356. package/bin/thinking-activity-status-policy.cjs +0 -132
  357. package/bin/thinking-only-guard.cjs +0 -80
  358. package/bin/todo-list-turn-policy.cjs +0 -131
  359. package/bin/tool-call-loop-policy.cjs +0 -51
  360. package/bin/tool-file-persistence.cjs +0 -141
  361. package/bin/tool-result-offload-policy.cjs +0 -359
  362. package/bin/tool-result-offload-telemetry.cjs +0 -12
  363. package/bin/tool-schema-token-cache-policy.cjs +0 -41
  364. package/bin/tool-stream-preview-policy.cjs +0 -9
  365. package/bin/tui-functional-contract.cjs +0 -55
  366. package/bin/turn-tool-performance-policy.cjs +0 -486
  367. package/bin/usage-cache-efficiency-policy.cjs +0 -26
  368. package/bin/user-home-path-policy.cjs +0 -13
  369. package/bin/user-message-offload-policy.cjs +0 -103
  370. package/bin/user-prompt-hook-origin-policy.cjs +0 -34
  371. package/bin/user-tool-record-policy.cjs +0 -7
  372. package/bin/validated-learning-insight-policy.cjs +0 -58
  373. package/bin/validated-learning-outcome-trace.cjs +0 -107
  374. package/bin/validated-learning-performance-policy.cjs +0 -53
  375. package/bin/validated-learning-signal.cjs +0 -463
  376. package/bin/windows-bash-dialect-policy.cjs +0 -25
  377. package/bin/windows-node-crash-dump.cjs +0 -110
  378. package/bin/write-continuation-policy.cjs +0 -69
  379. package/codebase-index/README.md +0 -82
  380. package/codebase-index/codebase_index.py +0 -470
  381. package/standard-skills/agent-browser/SKILL.md +0 -19
  382. package/standard-skills/agent-browser/references/runtime.md +0 -8
  383. package/standard-skills/blun-session-inspector/SKILL.md +0 -41
  384. package/standard-skills/blun-session-inspector/scripts/inspect-session.cjs +0 -437
  385. package/standard-skills/design-taste-frontend/SKILL.md +0 -1206
  386. package/standard-skills/full-output-enforcement/SKILL.md +0 -49
  387. package/standard-skills/high-end-visual-design/SKILL.md +0 -98
  388. package/standard-skills/image-to-code/SKILL.md +0 -1228
  389. package/standard-skills/industrial-brutalist-ui/SKILL.md +0 -92
  390. package/standard-skills/minimalist-ui/SKILL.md +0 -85
  391. package/standard-skills/motion-design-taste/SKILL.md +0 -74
  392. package/standard-skills/playwright-testing/SKILL.md +0 -19
  393. package/standard-skills/playwright-testing/references/runtime.md +0 -7
  394. package/standard-skills/premortem/SKILL.md +0 -148
  395. package/standard-skills/redesign-existing-projects/SKILL.md +0 -178
  396. package/standard-skills/research-evidence/SKILL.md +0 -39
  397. package/standard-skills/research-evidence/references/evidence-format.md +0 -104
  398. package/standard-skills/research-evidence/scripts/evidence-collection.cjs +0 -260
  399. package/standard-skills/research-evidence/scripts/score-report.cjs +0 -130
  400. package/standard-skills/screenshot-lesen/SKILL.md +0 -52
  401. package/standard-skills/stitch-design-taste/DESIGN.md +0 -121
  402. package/standard-skills/stitch-design-taste/SKILL.md +0 -184
  403. package/standard-skills/telegram-channel/SKILL.md +0 -18
  404. package/standard-skills/telegram-channel/references/runtime.md +0 -7
  405. package/standard-skills/venture-flywheel/SKILL.md +0 -32
  406. package/standard-skills/venture-flywheel/identity/project-identity.cjs +0 -146
  407. package/standard-skills/venture-flywheel/policy/capability-engine.cjs +0 -114
  408. package/standard-skills/venture-flywheel/policy/repository-trust.cjs +0 -229
  409. package/standard-skills/venture-flywheel/references/BEISPIELE-phase0.md +0 -146
  410. package/standard-skills/venture-flywheel/references/CAPABILITY-MAP.md +0 -34
  411. package/standard-skills/venture-flywheel/references/SPEC-phase0-identity-trust.md +0 -77
  412. package/standard-skills/venture-flywheel/references/SPEC-phase0-state-events.md +0 -93
  413. package/standard-skills/venture-flywheel/schemas/capability-decision.schema.json +0 -13
  414. package/standard-skills/venture-flywheel/schemas/execution-event.schema.json +0 -44
  415. package/standard-skills/venture-flywheel/schemas/project-identity.schema.json +0 -32
  416. package/standard-skills/venture-flywheel/schemas/repository-trust.schema.json +0 -57
  417. package/standard-skills/venture-flywheel/schemas/run-transition.schema.json +0 -59
  418. package/standard-skills/venture-flywheel/state/execution-event.cjs +0 -191
  419. package/standard-skills/venture-flywheel/state/task-state-machine.cjs +0 -190
  420. package/standard-skills/web-lesen/SKILL.md +0 -73
  421. package/standard-skills/web-lesen/scripts/crawl_public.py +0 -379
  422. package/standard-skills/windows-mcp/SKILL.md +0 -19
  423. package/standard-skills/windows-mcp/references/runtime.md +0 -9
  424. package/telegram-plugin/DELIVERY.md +0 -36
  425. package/telegram-plugin/bin/telegram-approval-relay.cjs +0 -290
  426. package/telegram-plugin/bin/telegram-console-status-policy.cjs +0 -175
  427. package/telegram-plugin/bin/telegram-delivery-lifecycle.cjs +0 -125
  428. package/telegram-plugin/bin/telegram-direct-reply-policy.cjs +0 -48
  429. package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +0 -122
  430. package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +0 -186
  431. package/telegram-plugin/bin/telegram-remote-status-policy.cjs +0 -121
  432. package/telegram-plugin/bin/telegram-reply-parts.cjs +0 -149
  433. package/telegram-plugin/bin/telegram-text-chunk-policy.cjs +0 -63
  434. package/telegram-plugin/bin/telegram-typing-keepalive.cjs +0 -89
  435. package/telegram-plugin/compat/mcp-server-fa511cd1.mjs +0 -73825
  436. /package/{bin → scripts}/fix-node-pty-perms.js +0 -0
@@ -0,0 +1,1541 @@
1
+ #!/usr/bin/env python3
2
+ """Read-only, content-free health and readiness monitor for localization.
3
+
4
+ The monitor validates durable queue and quality-evidence state, authenticated
5
+ CMS metadata, signed approvals, publication outbox entries, and provider
6
+ availability without ever returning source text, target text, reviewer prose,
7
+ or transport exceptions.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import hashlib
13
+ import importlib.util
14
+ import json
15
+ import math
16
+ import sqlite3
17
+ import sys
18
+ from dataclasses import asdict, dataclass
19
+ from pathlib import Path
20
+ from typing import Any, Mapping, Protocol
21
+
22
+
23
+ SCHEMA = "blun.website-localization-health.v1"
24
+ PROVIDER_HEALTH_SCHEMA = "blun.localization-provider-health.v1"
25
+ SUPERVISOR_SCHEMA = "blun.website-localization-supervisor.v1"
26
+ SUPERVISOR_PHASES = {
27
+ "tombstone", "delivery", "release", "translation", "idle", "supervisor",
28
+ }
29
+ SUPERVISOR_STATUSES = {
30
+ "idle", "succeeded", "retry_wait", "failed", "blocked", "approved",
31
+ "delivery_ready", "delivered",
32
+ }
33
+ QUEUE_STATUSES = ("pending", "leased", "retry_wait", "succeeded", "failed")
34
+ DELIVERY_STATUSES = ("pending", "leased", "retry_wait", "succeeded", "failed")
35
+ EVIDENCE_STATUSES = ("pending", "leased", "retry_wait", "succeeded", "failed")
36
+
37
+
38
+ def _load_module(name: str, path: Path):
39
+ spec = importlib.util.spec_from_file_location(name, path)
40
+ if spec is None or spec.loader is None:
41
+ raise RuntimeError(f"cannot load required health dependency: {path.name}")
42
+ module = importlib.util.module_from_spec(spec)
43
+ sys.modules[spec.name] = module
44
+ spec.loader.exec_module(module)
45
+ return module
46
+
47
+
48
+ _ROOT = Path(__file__).resolve().parents[1]
49
+ _CMS = _load_module(
50
+ "blun_website_localization_health_cms",
51
+ _ROOT / "integrations" / "website_localization_cms.py",
52
+ )
53
+ _QUEUE = _CMS._QUEUE
54
+ _RELEASE = _CMS._RELEASE
55
+ _COORDINATOR = _load_module(
56
+ "blun_website_localization_health_coordinator",
57
+ _ROOT / "integrations" / "website_localization_release_coordinator.py",
58
+ )
59
+ _CAMPAIGN = _load_module(
60
+ "blun_website_localization_health_benchmark_campaign",
61
+ _ROOT / "integrations" / "website_localization_benchmark_campaign.py",
62
+ )
63
+ _BENCHMARK_REVIEW = _load_module(
64
+ "blun_website_localization_health_benchmark_review",
65
+ _ROOT / "integrations" / "website_localization_benchmark_review_store.py",
66
+ )
67
+ _REFERENCE_QUEUE = _load_module(
68
+ "blun_website_localization_health_native_reference_queue",
69
+ _ROOT / "integrations" / "website_localization_native_reference_queue.py",
70
+ )
71
+
72
+
73
+ class LocalizationHealthBlocked(RuntimeError):
74
+ """Raised only when the monitor itself receives an invalid dependency."""
75
+
76
+
77
+ class ProviderHealthProbe(Protocol):
78
+ def check(self, *, provider_id: str, model_id: str, model_version: str) -> Mapping[str, Any]: ...
79
+
80
+
81
+ class PublisherHealthProbe(Protocol):
82
+ def check(self, *, contract_sha256: str) -> Mapping[str, Any]: ...
83
+
84
+
85
+ @dataclass(frozen=True)
86
+ class ComponentHealth:
87
+ component: str
88
+ status: str
89
+ reasons: tuple[str, ...]
90
+ counts: tuple[tuple[str, int], ...]
91
+
92
+
93
+ @dataclass(frozen=True)
94
+ class ProviderHealth:
95
+ provider_id: str
96
+ model_id: str
97
+ model_version: str
98
+ status: str
99
+ reason: str | None
100
+
101
+
102
+ @dataclass(frozen=True)
103
+ class WebsiteVersionHealth:
104
+ event_id: str
105
+ site_id: str
106
+ website_version: str
107
+ plan_id: str
108
+ status: str
109
+ required_locales: int
110
+ approved_locales: int
111
+ queue_counts: tuple[tuple[str, int], ...]
112
+ blocked_locales: tuple[tuple[str, str], ...]
113
+
114
+
115
+ @dataclass(frozen=True)
116
+ class LocalizationHealthReport:
117
+ checked_at: float
118
+ status: str
119
+ components: tuple[ComponentHealth, ...]
120
+ providers: tuple[ProviderHealth, ...]
121
+ website_versions: tuple[WebsiteVersionHealth, ...]
122
+
123
+ def as_payload(self) -> dict[str, Any]:
124
+ return {
125
+ "schema": SCHEMA,
126
+ "checked_at": self.checked_at,
127
+ "status": self.status,
128
+ "components": [
129
+ {
130
+ "component": item.component,
131
+ "status": item.status,
132
+ "reasons": list(item.reasons),
133
+ "counts": dict(item.counts),
134
+ }
135
+ for item in self.components
136
+ ],
137
+ "providers": [asdict(item) for item in self.providers],
138
+ "website_versions": [
139
+ {
140
+ **asdict(item),
141
+ "queue_counts": dict(item.queue_counts),
142
+ "blocked_locales": [list(blocked) for blocked in item.blocked_locales],
143
+ }
144
+ for item in self.website_versions
145
+ ],
146
+ }
147
+
148
+
149
+ def _timestamp(value: Any) -> float:
150
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
151
+ raise LocalizationHealthBlocked("checked_at must be a finite timestamp")
152
+ value = float(value)
153
+ if value < 0 or value != value or value in {float("inf"), float("-inf")}:
154
+ raise LocalizationHealthBlocked("checked_at must be a finite timestamp")
155
+ return value
156
+
157
+
158
+ def _canonical_json(value: Any) -> str:
159
+ return json.dumps(
160
+ value,
161
+ ensure_ascii=False,
162
+ allow_nan=False,
163
+ sort_keys=True,
164
+ separators=(",", ":"),
165
+ )
166
+
167
+
168
+ def _hash(value: str) -> str:
169
+ return hashlib.sha256(value.encode("utf-8")).hexdigest()
170
+
171
+
172
+ def _counts(connection: sqlite3.Connection, table: str, statuses: tuple[str, ...]) -> dict[str, int]:
173
+ result = {status: 0 for status in statuses}
174
+ rows = connection.execute(
175
+ f"SELECT status, COUNT(*) AS count FROM {table} GROUP BY status"
176
+ ).fetchall()
177
+ for row in rows:
178
+ if row["status"] not in result:
179
+ raise ValueError("unknown status")
180
+ result[row["status"]] = int(row["count"])
181
+ return result
182
+
183
+
184
+ def _component(name: str, status: str, reasons: set[str], counts: dict[str, int]) -> ComponentHealth:
185
+ return ComponentHealth(
186
+ name,
187
+ status,
188
+ tuple(sorted(reasons)),
189
+ tuple(sorted((key, int(value)) for key, value in counts.items())),
190
+ )
191
+
192
+
193
+ class LocalizationHealthMonitor:
194
+ """Inspect a configured localization bridge without mutating its state."""
195
+
196
+ def __init__(
197
+ self,
198
+ bridge: Any,
199
+ evidence_state: Any | None = None,
200
+ supervisor: Any | None = None,
201
+ supervisor_stale_after_seconds: float | int = 30,
202
+ benchmark_store: Any | None = None,
203
+ benchmark_policy: Any | None = None,
204
+ benchmark_campaign_id: str | None = None,
205
+ benchmark_evidence_authority: Any | None = None,
206
+ benchmark_stale_after_seconds: float | int = 3600,
207
+ benchmark_review_store: Any | None = None,
208
+ benchmark_reviewer_route_id: str | None = None,
209
+ benchmark_reference_queue: Any | None = None,
210
+ ):
211
+ if not self._supports_bridge(bridge):
212
+ raise LocalizationHealthBlocked("bridge must be WebsiteLocalizationCMSBridge")
213
+ if evidence_state is not None and not isinstance(
214
+ getattr(evidence_state, "connection", None), sqlite3.Connection,
215
+ ):
216
+ raise LocalizationHealthBlocked("evidence state must use SQLite")
217
+ if supervisor is not None and not callable(getattr(supervisor, "status", None)):
218
+ raise LocalizationHealthBlocked("supervisor must expose read-only status")
219
+ benchmark_values = (
220
+ benchmark_store, benchmark_policy, benchmark_campaign_id,
221
+ benchmark_evidence_authority,
222
+ )
223
+ if any(value is not None for value in benchmark_values) and any(
224
+ value is None for value in benchmark_values
225
+ ):
226
+ raise LocalizationHealthBlocked("benchmark configuration is incomplete")
227
+ if benchmark_store is not None and (
228
+ not isinstance(getattr(benchmark_store, "connection", None), sqlite3.Connection)
229
+ or not callable(getattr(benchmark_store, "health", None))
230
+ or not callable(getattr(benchmark_store, "_verify_schema", None))
231
+ ):
232
+ raise LocalizationHealthBlocked("benchmark store is invalid")
233
+ if benchmark_store is not None and not callable(
234
+ getattr(benchmark_evidence_authority, "verify", None),
235
+ ):
236
+ raise LocalizationHealthBlocked("benchmark authority is invalid")
237
+ review_values = (benchmark_review_store, benchmark_reviewer_route_id)
238
+ if any(value is not None for value in review_values) and any(
239
+ value is None for value in review_values
240
+ ):
241
+ raise LocalizationHealthBlocked("benchmark review configuration is incomplete")
242
+ if benchmark_review_store is not None and benchmark_store is None:
243
+ raise LocalizationHealthBlocked("benchmark review requires campaign configuration")
244
+ if benchmark_reference_queue is not None and benchmark_store is None:
245
+ raise LocalizationHealthBlocked(
246
+ "native-reference queue requires campaign configuration",
247
+ )
248
+ if benchmark_review_store is not None and (
249
+ not isinstance(
250
+ getattr(benchmark_review_store, "connection", None),
251
+ sqlite3.Connection,
252
+ )
253
+ or not callable(getattr(benchmark_review_store, "health", None))
254
+ or not callable(getattr(benchmark_review_store, "_verify_schema", None))
255
+ or not isinstance(benchmark_reviewer_route_id, str)
256
+ or _CAMPAIGN._BENCHMARK.IDENTIFIER.fullmatch(
257
+ benchmark_reviewer_route_id,
258
+ ) is None
259
+ ):
260
+ raise LocalizationHealthBlocked("benchmark review store is invalid")
261
+ if benchmark_reference_queue is not None and (
262
+ getattr(
263
+ getattr(benchmark_reference_queue, "native_reference_queue", None),
264
+ "connection",
265
+ None,
266
+ ) is not benchmark_store.connection
267
+ or not callable(getattr(
268
+ benchmark_reference_queue, "native_reference_queue_health", None,
269
+ ))
270
+ ):
271
+ raise LocalizationHealthBlocked("native-reference queue is invalid")
272
+ if (
273
+ isinstance(supervisor_stale_after_seconds, bool)
274
+ or not isinstance(supervisor_stale_after_seconds, (int, float))
275
+ or not math.isfinite(float(supervisor_stale_after_seconds))
276
+ or float(supervisor_stale_after_seconds) <= 0
277
+ ):
278
+ raise LocalizationHealthBlocked("supervisor stale threshold is invalid")
279
+ if (
280
+ isinstance(benchmark_stale_after_seconds, bool)
281
+ or not isinstance(benchmark_stale_after_seconds, (int, float))
282
+ or not math.isfinite(float(benchmark_stale_after_seconds))
283
+ or not 0 < float(benchmark_stale_after_seconds) <= _CAMPAIGN.MAX_STALE_SECONDS
284
+ ):
285
+ raise LocalizationHealthBlocked("benchmark stale threshold is invalid")
286
+ self.bridge = bridge
287
+ self.queue = bridge.queue
288
+ self.release_store = bridge.release_store
289
+ self.evidence_state = evidence_state
290
+ self.supervisor = supervisor
291
+ self.supervisor_stale_after_seconds = float(supervisor_stale_after_seconds)
292
+ self.benchmark_store = benchmark_store
293
+ self.benchmark_policy = benchmark_policy
294
+ self.benchmark_campaign_id = benchmark_campaign_id
295
+ self.benchmark_evidence_authority = benchmark_evidence_authority
296
+ self.benchmark_stale_after_seconds = float(benchmark_stale_after_seconds)
297
+ self.benchmark_review_store = benchmark_review_store
298
+ self.benchmark_reviewer_route_id = benchmark_reviewer_route_id
299
+ self.benchmark_reference_queue = benchmark_reference_queue
300
+
301
+ @staticmethod
302
+ def _supports_bridge(bridge: Any) -> bool:
303
+ queue = getattr(bridge, "queue", None)
304
+ release_store = getattr(bridge, "release_store", None)
305
+ return (
306
+ isinstance(getattr(bridge, "connection", None), sqlite3.Connection)
307
+ and isinstance(getattr(queue, "connection", None), sqlite3.Connection)
308
+ and isinstance(getattr(release_store, "connection", None), sqlite3.Connection)
309
+ and all(callable(getattr(bridge, name, None)) for name in (
310
+ "_verify_schema", "_load_event", "delivery_status",
311
+ ))
312
+ and all(callable(getattr(queue, name, None)) for name in (
313
+ "_verify_schema", "plan_counts", "result", "status",
314
+ ))
315
+ and all(callable(getattr(release_store, name, None)) for name in (
316
+ "lookup", "readiness", "validated_result",
317
+ ))
318
+ )
319
+
320
+ @staticmethod
321
+ def _quick_check(connection: sqlite3.Connection) -> bool:
322
+ row = connection.execute("PRAGMA quick_check(1)").fetchone()
323
+ return row is not None and row[0] == "ok"
324
+
325
+ def _check_schemas(self) -> set[str]:
326
+ reasons: set[str] = set()
327
+ try:
328
+ self.queue._verify_schema()
329
+ except Exception:
330
+ reasons.add("queue.schema_invalid")
331
+ try:
332
+ columns = tuple(
333
+ row["name"]
334
+ for row in self.release_store.connection.execute(
335
+ "PRAGMA table_info(localization_approvals)"
336
+ )
337
+ )
338
+ if columns != _RELEASE._COLUMNS:
339
+ reasons.add("release.schema_invalid")
340
+ except Exception:
341
+ reasons.add("release.schema_invalid")
342
+ try:
343
+ self.bridge._verify_schema()
344
+ except Exception:
345
+ reasons.add("cms.schema_invalid")
346
+ if self.evidence_state is not None:
347
+ try:
348
+ columns = tuple(
349
+ row["name"]
350
+ for row in self.evidence_state.connection.execute(
351
+ "PRAGMA table_info(localization_quality_evidence_state)"
352
+ )
353
+ )
354
+ if columns != _COORDINATOR._EVIDENCE_COLUMNS:
355
+ reasons.add("evidence.schema_invalid")
356
+ except Exception:
357
+ reasons.add("evidence.schema_invalid")
358
+ if self.benchmark_store is not None:
359
+ try:
360
+ self.benchmark_store._verify_schema()
361
+ except Exception:
362
+ reasons.add("benchmark.campaign.schema_invalid")
363
+ if self.benchmark_review_store is not None:
364
+ try:
365
+ self.benchmark_review_store._verify_schema()
366
+ except Exception:
367
+ reasons.add("review.store.schema_unsupported")
368
+ connections = [
369
+ ("queue", self.queue.connection),
370
+ ("release", self.release_store.connection),
371
+ ("cms", self.bridge.connection),
372
+ ]
373
+ if self.evidence_state is not None:
374
+ connections.append(("evidence", self.evidence_state.connection))
375
+ if self.benchmark_store is not None:
376
+ connections.append(("benchmark", self.benchmark_store.connection))
377
+ if self.benchmark_review_store is not None:
378
+ connections.append(("benchmark_review", self.benchmark_review_store.connection))
379
+ for name, connection in connections:
380
+ try:
381
+ if not self._quick_check(connection):
382
+ reasons.add(f"{name}.database_invalid")
383
+ except Exception:
384
+ reasons.add(f"{name}.database_invalid")
385
+ return reasons
386
+
387
+ def _check_queue(self, now: float) -> tuple[dict[str, int], set[str]]:
388
+ reasons: set[str] = set()
389
+ counts = _counts(self.queue.connection, "localization_jobs", QUEUE_STATUSES)
390
+ rows = self.queue.connection.execute("SELECT * FROM localization_jobs").fetchall()
391
+ for row in rows:
392
+ try:
393
+ if _hash(row["payload_json"]) != row["payload_sha256"]:
394
+ raise ValueError
395
+ payload = json.loads(row["payload_json"])
396
+ if _canonical_json(payload) != row["payload_json"]:
397
+ raise ValueError
398
+ if payload.get("job_id") != row["job_id"]:
399
+ raise ValueError
400
+ if payload.get("target", {}).get("locale") != row["target_locale"]:
401
+ raise ValueError
402
+ if row["status"] == "succeeded":
403
+ self.queue.result(row["job_id"])
404
+ if row["status"] == "leased" and float(row["lease_expires_at"]) <= now:
405
+ reasons.add("queue.lease_expired")
406
+ if row["status"] in {"retry_wait", "failed"}:
407
+ code = row["last_error_code"]
408
+ if not isinstance(code, str) or _QUEUE.ERROR_CODE.fullmatch(code) is None:
409
+ raise ValueError
410
+ reasons.add("queue.error." + code)
411
+ except Exception:
412
+ reasons.add("queue.state_invalid")
413
+ if counts["failed"]:
414
+ reasons.add("queue.locale_failed")
415
+ return counts, reasons
416
+
417
+ def _check_evidence(
418
+ self,
419
+ event_verifier: Any,
420
+ now: float,
421
+ ) -> tuple[dict[str, int], set[str]]:
422
+ counts = {status: 0 for status in EVIDENCE_STATUSES}
423
+ reasons: set[str] = set()
424
+ if self.evidence_state is None:
425
+ return counts, reasons
426
+ connection = self.evidence_state.connection
427
+ counts = _counts(
428
+ connection,
429
+ "localization_quality_evidence_state",
430
+ EVIDENCE_STATUSES,
431
+ )
432
+ event_cache: dict[str, tuple[dict[str, Any], Any]] = {}
433
+ rows = connection.execute(
434
+ "SELECT * FROM localization_quality_evidence_state"
435
+ ).fetchall()
436
+ for row in rows:
437
+ try:
438
+ state = _COORDINATOR.QualityEvidenceStateStore._status_from_row(row)
439
+ superseded = self.bridge.connection.execute(
440
+ "SELECT 1 FROM cms_event_supersessions WHERE event_id = ?",
441
+ (state.event_id,),
442
+ ).fetchone() is not None
443
+ cancelled = self.bridge.connection.execute(
444
+ "SELECT 1 FROM cms_event_cancellations WHERE event_id = ?",
445
+ (state.event_id,),
446
+ ).fetchone() is not None
447
+ if state.event_id not in event_cache:
448
+ event_cache[state.event_id] = self.bridge._load_event(
449
+ state.event_id,
450
+ event_verifier,
451
+ allow_superseded=superseded,
452
+ allow_cancelled=cancelled,
453
+ )
454
+ event, plan = event_cache[state.event_id]
455
+ if plan.plan_id != state.plan_id:
456
+ raise ValueError
457
+ job = next(
458
+ item for item in plan.jobs if item.job_id == state.job_id
459
+ )
460
+ result, result_sha256 = _COORDINATOR._validated_result(
461
+ self.release_store, plan, job,
462
+ )
463
+ if result_sha256 != state.result_sha256:
464
+ raise ValueError
465
+ expected = _COORDINATOR._request(
466
+ event,
467
+ plan,
468
+ job,
469
+ result,
470
+ result_sha256,
471
+ state.evidence_revision,
472
+ )
473
+ if expected.request_id != state.request_id:
474
+ raise ValueError
475
+ approval = self.release_store.connection.execute("""
476
+ SELECT 1 FROM localization_approvals
477
+ WHERE job_id = ? AND result_sha256 = ?
478
+ """, (state.job_id, state.result_sha256)).fetchone()
479
+ if state.status == "succeeded" and approval is None:
480
+ raise ValueError
481
+ if state.status != "succeeded" and approval is not None:
482
+ if not superseded:
483
+ reasons.add("evidence.approval_unreconciled")
484
+ if not superseded:
485
+ if state.status == "leased" and state.lease_expires_at <= now:
486
+ reasons.add("evidence.lease_expired")
487
+ if state.status in {"retry_wait", "failed"}:
488
+ reasons.add("evidence.error." + state.last_error_code)
489
+ if state.status == "failed":
490
+ reasons.add("evidence.review_failed")
491
+ except Exception:
492
+ reasons.add("evidence.state_invalid")
493
+ return counts, reasons
494
+
495
+ def _check_approvals(self, authority: Any, now: float) -> tuple[dict[str, int], set[str]]:
496
+ counts = {"total": 0, "current": 0, "expired": 0}
497
+ reasons: set[str] = set()
498
+ rows = self.release_store.connection.execute(
499
+ "SELECT * FROM localization_approvals"
500
+ ).fetchall()
501
+ verify = getattr(authority, "verify", None)
502
+ for row in rows:
503
+ counts["total"] += 1
504
+ try:
505
+ if _hash(row["result_json"]) != row["result_sha256"]:
506
+ raise ValueError
507
+ if _hash(row["approval_json"]) != row["approval_sha256"]:
508
+ raise ValueError
509
+ result = json.loads(row["result_json"])
510
+ approval = json.loads(row["approval_json"])
511
+ if _canonical_json(result) != row["result_json"]:
512
+ raise ValueError
513
+ if _canonical_json(approval) != row["approval_json"]:
514
+ raise ValueError
515
+ if not isinstance(result, dict) or not isinstance(approval, dict):
516
+ raise ValueError
517
+ candidate = result.get("candidate")
518
+ if not isinstance(candidate, str) or _hash(candidate) != result.get("target_sha256"):
519
+ raise ValueError
520
+ result_binding = {
521
+ "job_id": row["job_id"],
522
+ "target_locale": row["target_locale"],
523
+ "target_sha256": row["target_sha256"],
524
+ }
525
+ if any(result.get(key) != value for key, value in result_binding.items()):
526
+ raise ValueError
527
+ approval_binding = {
528
+ "approval_id": row["approval_id"],
529
+ "job_id": row["job_id"],
530
+ "target_locale": row["target_locale"],
531
+ "target_sha256": row["target_sha256"],
532
+ "result_sha256": row["result_sha256"],
533
+ "approved_at": row["approved_at"],
534
+ "expires_at": row["expires_at"],
535
+ }
536
+ if any(approval.get(key) != value for key, value in approval_binding.items()):
537
+ raise ValueError
538
+ for key in (
539
+ "source_sha256", "target_sha256", "source_locale", "target_locale",
540
+ "content_type", "glossary_version", "policy_version", "provider",
541
+ "software_version", "worker_schema",
542
+ ):
543
+ if approval.get(key) != result.get(key):
544
+ raise ValueError
545
+ immutable = {
546
+ key: value
547
+ for key, value in approval.items()
548
+ if key not in {"approval_id", "approved_at", "expires_at"}
549
+ }
550
+ if approval["approval_id"] != "blun-l10n-approval-" + _hash(
551
+ _canonical_json(immutable)
552
+ ):
553
+ raise ValueError
554
+ signature = _RELEASE._signature(_RELEASE.ApprovalSignature(
555
+ row["signature_algorithm"], row["key_id"], row["signature"],
556
+ ))
557
+ if not callable(verify) or verify(
558
+ row["approval_json"].encode("utf-8"), signature,
559
+ ) is not True:
560
+ raise ValueError
561
+ except Exception:
562
+ reasons.add("release.approval_invalid")
563
+ continue
564
+ if float(row["expires_at"]) <= now:
565
+ counts["expired"] += 1
566
+ else:
567
+ counts["current"] += 1
568
+ return counts, reasons
569
+
570
+ def _check_deliveries(
571
+ self,
572
+ authority: Any,
573
+ now: float,
574
+ ) -> tuple[dict[str, int], set[str]]:
575
+ counts = _counts(
576
+ self.bridge.connection,
577
+ "cms_publication_deliveries",
578
+ DELIVERY_STATUSES,
579
+ )
580
+ counts["superseded"] = 0
581
+ counts["cancelled"] = 0
582
+ reasons: set[str] = set()
583
+ verify = getattr(authority, "verify", None)
584
+ rows = self.bridge.connection.execute(
585
+ "SELECT * FROM cms_publication_deliveries"
586
+ ).fetchall()
587
+ for row in rows:
588
+ try:
589
+ superseded = self.bridge.connection.execute(
590
+ "SELECT 1 FROM cms_event_supersessions WHERE event_id = ?",
591
+ (row["event_id"],),
592
+ ).fetchone() is not None
593
+ cancelled = self.bridge.connection.execute(
594
+ "SELECT 1 FROM cms_event_cancellations WHERE event_id = ?",
595
+ (row["event_id"],),
596
+ ).fetchone() is not None
597
+ if superseded and not cancelled and (
598
+ row["status"] != "failed" or row["last_error_code"] != "event_superseded"
599
+ ):
600
+ raise ValueError
601
+ if cancelled and (
602
+ row["status"] != "failed" or row["last_error_code"] != "event_cancelled"
603
+ ):
604
+ raise ValueError
605
+ if _hash(row["payload_json"]) != row["payload_sha256"]:
606
+ raise ValueError
607
+ payload = json.loads(row["payload_json"])
608
+ if _canonical_json(payload) != row["payload_json"]:
609
+ raise ValueError
610
+ if not isinstance(payload, dict) or set(payload) != {
611
+ "schema", "delivery_id", "event_id", "site_id", "website_version",
612
+ "plan_id", "source_id", "source_revision", "source_sequence",
613
+ "source_sha256",
614
+ "localizations",
615
+ }:
616
+ raise ValueError
617
+ if payload.get("schema") != _CMS.PUBLICATION_SCHEMA:
618
+ raise ValueError
619
+ if payload.get("delivery_id") != row["delivery_id"]:
620
+ raise ValueError
621
+ if payload.get("event_id") != row["event_id"] or payload.get("plan_id") != row["plan_id"]:
622
+ raise ValueError
623
+ unsigned = {key: value for key, value in payload.items() if key != "delivery_id"}
624
+ if payload["delivery_id"] != "blun-cms-delivery-" + _hash(
625
+ _canonical_json(unsigned)
626
+ ):
627
+ raise ValueError
628
+ localizations = payload.get("localizations")
629
+ if not isinstance(localizations, list) or not localizations:
630
+ raise ValueError
631
+ locales: list[str] = []
632
+ for item in localizations:
633
+ if not isinstance(item, dict) or set(item) != {
634
+ "locale", "target_text", "target_sha256", "approval_id",
635
+ "approval_expires_at", "release_evidence",
636
+ }:
637
+ raise ValueError
638
+ if not isinstance(item["locale"], str) or not isinstance(item["target_text"], str):
639
+ raise ValueError
640
+ if _hash(item["target_text"]) != item["target_sha256"]:
641
+ raise ValueError
642
+ if not _CMS._valid_release_evidence(
643
+ item["release_evidence"],
644
+ locale=item["locale"],
645
+ target_sha256=item["target_sha256"],
646
+ approval_id=item["approval_id"],
647
+ ):
648
+ raise ValueError
649
+ locales.append(item["locale"])
650
+ if locales != sorted(set(locales)):
651
+ raise ValueError
652
+ signature = _CMS._signature(_CMS.CMSMessageSignature(
653
+ row["signature_algorithm"], row["key_id"], row["signature"],
654
+ ))
655
+ if not callable(verify) or verify(
656
+ row["payload_json"].encode("utf-8"), signature,
657
+ ) is not True:
658
+ raise ValueError
659
+ if row["status"] != "succeeded":
660
+ if any(
661
+ not isinstance(item.get("approval_expires_at"), (int, float))
662
+ or isinstance(item.get("approval_expires_at"), bool)
663
+ or float(item["approval_expires_at"]) <= now
664
+ for item in localizations
665
+ ):
666
+ reasons.add("cms.delivery.approval_expired")
667
+ if row["status"] == "leased" and float(row["lease_expires_at"]) <= now:
668
+ reasons.add("cms.delivery.lease_expired")
669
+ if row["status"] in {"retry_wait", "failed"}:
670
+ code = row["last_error_code"]
671
+ if not isinstance(code, str) or _CMS.ERROR_CODE.fullmatch(code) is None:
672
+ raise ValueError
673
+ if superseded and not cancelled and code == "event_superseded":
674
+ counts["failed"] -= 1
675
+ counts["superseded"] += 1
676
+ elif cancelled and code == "event_cancelled":
677
+ counts["failed"] -= 1
678
+ counts["cancelled"] += 1
679
+ else:
680
+ reasons.add("cms.delivery.error." + code)
681
+ except Exception:
682
+ reasons.add("cms.delivery.invalid")
683
+ if counts["failed"]:
684
+ reasons.add("cms.delivery.failed")
685
+ return counts, reasons
686
+
687
+ def _check_tombstones(
688
+ self,
689
+ event_verifier: Any,
690
+ publication_authority: Any,
691
+ now: float,
692
+ ) -> tuple[dict[str, int], set[str]]:
693
+ counts = _counts(
694
+ self.bridge.connection,
695
+ "cms_tombstone_deliveries",
696
+ DELIVERY_STATUSES,
697
+ )
698
+ reasons: set[str] = set()
699
+ rows = self.bridge.connection.execute(
700
+ "SELECT * FROM cms_tombstone_deliveries"
701
+ ).fetchall()
702
+ for row in rows:
703
+ try:
704
+ request = self.bridge._tombstone_request_from_row(
705
+ row, event_verifier, publication_authority,
706
+ )
707
+ status = self.bridge.tombstone_status(request.delivery_id)
708
+ if (
709
+ status.event_id != row["event_id"]
710
+ or status.plan_id != row["plan_id"]
711
+ or status.payload_sha256 != request.payload_sha256
712
+ or not 0 <= status.attempts <= status.max_attempts <= _CMS.MAX_ATTEMPTS
713
+ ):
714
+ raise ValueError
715
+ if status.status == "leased" and (
716
+ status.lease_expires_at is None
717
+ or float(status.lease_expires_at) <= now
718
+ ):
719
+ reasons.add("cms.tombstone.lease_expired")
720
+ if status.status in {"retry_wait", "failed"}:
721
+ if (
722
+ not isinstance(status.last_error_code, str)
723
+ or _CMS.ERROR_CODE.fullmatch(status.last_error_code) is None
724
+ ):
725
+ raise ValueError
726
+ reasons.add("cms.tombstone.error." + status.last_error_code)
727
+ except Exception:
728
+ reasons.add("cms.tombstone.invalid")
729
+ if counts["failed"]:
730
+ reasons.add("cms.tombstone.failed")
731
+ return counts, reasons
732
+
733
+ def _check_supersessions(self, event_verifier: Any) -> set[str]:
734
+ reasons: set[str] = set()
735
+ topic_order: dict[tuple[str, str], list[tuple[int, float]]] = {}
736
+ topic_rows = self.bridge.connection.execute("""
737
+ SELECT event.event_id, event.created_at AS event_created_at,
738
+ topic.site_id, topic.source_id, topic.generation,
739
+ topic.created_at AS topic_created_at
740
+ FROM cms_change_events AS event
741
+ LEFT JOIN cms_event_topics AS topic ON topic.event_id = event.event_id
742
+ ORDER BY event.event_id
743
+ """).fetchall()
744
+ for row in topic_rows:
745
+ try:
746
+ event, _ = self.bridge._load_event(
747
+ row["event_id"],
748
+ event_verifier,
749
+ allow_superseded=True,
750
+ allow_cancelled=True,
751
+ allow_accepted=True,
752
+ )
753
+ if (
754
+ row["site_id"] != event["site_id"]
755
+ or row["source_id"] != event["localization"]["source_id"]
756
+ or isinstance(row["generation"], bool)
757
+ or not isinstance(row["generation"], int)
758
+ or row["generation"] <= 0
759
+ or row["topic_created_at"] != row["event_created_at"]
760
+ or (
761
+ "source_sequence" in event
762
+ and row["generation"] != event["source_sequence"]
763
+ )
764
+ ):
765
+ raise ValueError
766
+ if "source_sequence" not in event:
767
+ topic_order.setdefault(
768
+ (row["site_id"], row["source_id"]), [],
769
+ ).append((int(row["generation"]), float(row["topic_created_at"])))
770
+ except Exception:
771
+ reasons.add("cms.supersession.invalid")
772
+ for values in topic_order.values():
773
+ ordered = sorted(values)
774
+ if [generation for generation, _ in ordered] != list(
775
+ range(1, len(ordered) + 1)
776
+ ) or [created_at for _, created_at in ordered] != sorted(
777
+ created_at for _, created_at in ordered
778
+ ):
779
+ reasons.add("cms.supersession.invalid")
780
+ orphan_topics = self.bridge.connection.execute("""
781
+ SELECT COUNT(*)
782
+ FROM cms_event_topics AS topic
783
+ LEFT JOIN cms_change_events AS event ON event.event_id = topic.event_id
784
+ WHERE event.event_id IS NULL
785
+ """).fetchone()[0]
786
+ missing_supersessions = self.bridge.connection.execute("""
787
+ SELECT COUNT(*)
788
+ FROM cms_event_topics AS older
789
+ JOIN cms_change_events AS older_event ON older_event.event_id = older.event_id
790
+ LEFT JOIN cms_publication_deliveries AS delivery
791
+ ON delivery.event_id = older.event_id AND delivery.status = 'succeeded'
792
+ WHERE older_event.status = 'enqueued' AND delivery.event_id IS NULL
793
+ AND EXISTS (
794
+ SELECT 1
795
+ FROM cms_event_topics AS newer
796
+ JOIN cms_change_events AS newer_event
797
+ ON newer_event.event_id = newer.event_id
798
+ WHERE newer.site_id = older.site_id
799
+ AND newer.source_id = older.source_id
800
+ AND newer.generation > older.generation
801
+ AND newer_event.status = 'enqueued'
802
+ )
803
+ AND NOT EXISTS (
804
+ SELECT 1 FROM cms_event_supersessions AS supersession
805
+ WHERE supersession.event_id = older.event_id
806
+ )
807
+ """).fetchone()[0]
808
+ if orphan_topics or missing_supersessions:
809
+ reasons.add("cms.supersession.invalid")
810
+ rows = self.bridge.connection.execute("""
811
+ SELECT supersession.event_id, supersession.superseded_by_event_id,
812
+ older.site_id AS older_site_id, older.source_id AS older_source_id,
813
+ older.generation AS older_generation,
814
+ newer.site_id AS newer_site_id, newer.source_id AS newer_source_id,
815
+ newer.generation AS newer_generation,
816
+ delivery.status AS delivery_status
817
+ FROM cms_event_supersessions AS supersession
818
+ LEFT JOIN cms_event_topics AS older ON older.event_id = supersession.event_id
819
+ LEFT JOIN cms_event_topics AS newer
820
+ ON newer.event_id = supersession.superseded_by_event_id
821
+ LEFT JOIN cms_publication_deliveries AS delivery
822
+ ON delivery.event_id = supersession.event_id
823
+ ORDER BY supersession.event_id
824
+ """).fetchall()
825
+ for row in rows:
826
+ try:
827
+ if (
828
+ row["older_site_id"] is None
829
+ or row["newer_site_id"] is None
830
+ or row["older_site_id"] != row["newer_site_id"]
831
+ or row["older_source_id"] != row["newer_source_id"]
832
+ or int(row["older_generation"]) >= int(row["newer_generation"])
833
+ or row["delivery_status"] == "succeeded"
834
+ ):
835
+ raise ValueError
836
+ self.bridge._load_event(
837
+ row["event_id"], event_verifier, allow_superseded=True,
838
+ allow_cancelled=True,
839
+ )
840
+ self.bridge._load_event(
841
+ row["superseded_by_event_id"],
842
+ event_verifier,
843
+ allow_superseded=True,
844
+ allow_cancelled=True,
845
+ )
846
+ except Exception:
847
+ reasons.add("cms.supersession.invalid")
848
+ cancellation_rows = self.bridge.connection.execute("""
849
+ SELECT cancellation.*, delivery.status AS delivery_status
850
+ FROM cms_event_cancellations AS cancellation
851
+ LEFT JOIN cms_publication_deliveries AS delivery
852
+ ON delivery.event_id = cancellation.event_id
853
+ ORDER BY cancellation.cancellation_id
854
+ """).fetchall()
855
+ for row in cancellation_rows:
856
+ try:
857
+ if row["delivery_status"] == "succeeded":
858
+ raise ValueError
859
+ self.bridge._load_event(
860
+ row["event_id"],
861
+ event_verifier,
862
+ allow_superseded=True,
863
+ allow_cancelled=True,
864
+ allow_accepted=True,
865
+ )
866
+ except Exception:
867
+ reasons.add("cms.cancellation.invalid")
868
+ return reasons
869
+
870
+ def _versions(
871
+ self,
872
+ event_verifier: Any,
873
+ approval_authority: Any,
874
+ now: float,
875
+ ) -> tuple[tuple[WebsiteVersionHealth, ...], set[str], set[tuple[str, str, str]]]:
876
+ versions: list[WebsiteVersionHealth] = []
877
+ reasons: set[str] = set()
878
+ providers: set[tuple[str, str, str]] = set()
879
+ rows = self.bridge.connection.execute(
880
+ "SELECT event_id, status FROM cms_change_events ORDER BY event_id"
881
+ ).fetchall()
882
+ for row in rows:
883
+ cancelled = self.bridge.connection.execute(
884
+ "SELECT 1 FROM cms_event_cancellations WHERE event_id = ?",
885
+ (row["event_id"],),
886
+ ).fetchone() is not None
887
+ tombstone = self.bridge.connection.execute(
888
+ "SELECT status FROM cms_tombstone_deliveries WHERE event_id = ?",
889
+ (row["event_id"],),
890
+ ).fetchone()
891
+ if row["status"] != "enqueued" and not cancelled:
892
+ reasons.add("cms.event.awaiting_queue_resume")
893
+ continue
894
+ try:
895
+ superseded = self.bridge.connection.execute(
896
+ "SELECT 1 FROM cms_event_supersessions WHERE event_id = ?",
897
+ (row["event_id"],),
898
+ ).fetchone() is not None
899
+ event, plan = self.bridge._load_event(
900
+ row["event_id"], event_verifier,
901
+ allow_superseded=superseded,
902
+ allow_cancelled=cancelled,
903
+ allow_accepted=cancelled,
904
+ )
905
+ localization = event["localization"]
906
+ if not superseded and not cancelled and tombstone is None:
907
+ providers.add((
908
+ localization["provider_id"],
909
+ localization["model_id"],
910
+ localization["model_version"],
911
+ ))
912
+ if cancelled and row["status"] == "accepted":
913
+ queue_counts = {status: 0 for status in QUEUE_STATUSES}
914
+ queue_counts["cancelled"] = len(plan.jobs)
915
+ else:
916
+ queue_counts = self.queue.plan_counts(plan.plan_id)
917
+ readiness = self.release_store.readiness(
918
+ plan, approval_authority, now=now,
919
+ )
920
+ except Exception:
921
+ reasons.add("cms.event.invalid")
922
+ continue
923
+ delivery = self.bridge.connection.execute(
924
+ "SELECT status FROM cms_publication_deliveries WHERE event_id = ?",
925
+ (event["event_id"],),
926
+ ).fetchone()
927
+ delivery_status = delivery["status"] if delivery is not None else None
928
+ if cancelled:
929
+ status = "cancelled"
930
+ elif tombstone is not None and tombstone["status"] == "succeeded":
931
+ status = "deleted"
932
+ elif tombstone is not None and tombstone["status"] == "failed":
933
+ status = "deletion_failed"
934
+ elif tombstone is not None:
935
+ status = "deleting"
936
+ elif superseded:
937
+ status = "superseded"
938
+ elif delivery_status == "succeeded":
939
+ status = "published"
940
+ elif delivery_status == "failed":
941
+ status = "publication_failed"
942
+ elif delivery_status in {"pending", "leased", "retry_wait"}:
943
+ status = "publishing"
944
+ elif readiness.ready:
945
+ status = "ready"
946
+ elif queue_counts["failed"]:
947
+ status = "localization_failed"
948
+ elif queue_counts["succeeded"] == len(plan.jobs):
949
+ status = "awaiting_approval"
950
+ else:
951
+ status = "processing"
952
+ versions.append(WebsiteVersionHealth(
953
+ event_id=event["event_id"],
954
+ site_id=event["site_id"],
955
+ website_version=event["website_version"],
956
+ plan_id=plan.plan_id,
957
+ status=status,
958
+ required_locales=len(readiness.required_locales),
959
+ approved_locales=len(readiness.approved_locales),
960
+ queue_counts=tuple(sorted(queue_counts.items())),
961
+ blocked_locales=readiness.blocked,
962
+ ))
963
+ if any(code == "approval.expired" for _, code in readiness.blocked):
964
+ reasons.add("release.approval_expired")
965
+ return tuple(versions), reasons, providers
966
+
967
+ @staticmethod
968
+ def _providers(
969
+ provider_bindings: set[tuple[str, str, str]],
970
+ probe: ProviderHealthProbe | None,
971
+ ) -> tuple[tuple[ProviderHealth, ...], set[str]]:
972
+ statuses: list[ProviderHealth] = []
973
+ reasons: set[str] = set()
974
+ check = getattr(probe, "check", None)
975
+ for provider_id, model_id, model_version in sorted(provider_bindings):
976
+ reason = None
977
+ try:
978
+ if not callable(check):
979
+ raise ValueError("missing")
980
+ response = check(
981
+ provider_id=provider_id,
982
+ model_id=model_id,
983
+ model_version=model_version,
984
+ )
985
+ expected = {
986
+ "schema": PROVIDER_HEALTH_SCHEMA,
987
+ "provider": {
988
+ "id": provider_id,
989
+ "model_id": model_id,
990
+ "model_version": model_version,
991
+ },
992
+ "status": "healthy",
993
+ }
994
+ if not isinstance(response, Mapping) or dict(response) != expected:
995
+ raise ValueError("invalid")
996
+ status = "healthy"
997
+ except Exception:
998
+ status = "blocked"
999
+ reason = "provider.probe_missing" if not callable(check) else "provider.unavailable"
1000
+ reasons.add(reason)
1001
+ statuses.append(ProviderHealth(
1002
+ provider_id, model_id, model_version, status, reason,
1003
+ ))
1004
+ return tuple(statuses), reasons
1005
+
1006
+ def _publisher(self, probe: PublisherHealthProbe | None) -> ComponentHealth | None:
1007
+ if probe is None:
1008
+ return None
1009
+ reasons: set[str] = set()
1010
+ try:
1011
+ check = getattr(probe, "check", None)
1012
+ if not callable(check):
1013
+ raise ValueError
1014
+ capabilities = self.bridge.localization_capabilities()
1015
+ contract = capabilities["publication_http"]
1016
+ contract_sha256 = contract["sha256"]
1017
+ response = check(contract_sha256=contract_sha256)
1018
+ if (
1019
+ not isinstance(response, Mapping)
1020
+ or set(response) != {
1021
+ "schema", "probe_id", "contract_sha256", "status",
1022
+ }
1023
+ or response["schema"] != _CMS.PUBLICATION_HEALTH_ACK_SCHEMA
1024
+ or not isinstance(response["probe_id"], str)
1025
+ or not 16 <= len(response["probe_id"]) <= 256
1026
+ or _CMS.TOKEN.fullmatch(response["probe_id"]) is None
1027
+ or response["contract_sha256"] != contract_sha256
1028
+ or response["status"] != "healthy"
1029
+ ):
1030
+ raise ValueError
1031
+ except Exception:
1032
+ reasons.add("cms.publisher_unavailable")
1033
+ return _component(
1034
+ "cms_publisher",
1035
+ "blocked" if reasons else "healthy",
1036
+ reasons,
1037
+ {
1038
+ "configured": 1,
1039
+ "healthy": int(not reasons),
1040
+ "blocked": int(bool(reasons)),
1041
+ },
1042
+ )
1043
+
1044
+ def _check_supervisor(self, now: float) -> ComponentHealth | None:
1045
+ if self.supervisor is None:
1046
+ return None
1047
+ counts = {"revision": 0, "consecutive_blocked": 0, "lease_active": 0}
1048
+ reasons: set[str] = set()
1049
+ status = "blocked"
1050
+ try:
1051
+ value = self.supervisor.status(now=now)
1052
+ payload_method = getattr(value, "as_payload", None)
1053
+ payload = payload_method() if callable(payload_method) else value
1054
+ expected = {
1055
+ "schema", "status", "revision", "lease_active",
1056
+ "lease_expires_at", "next_tick_at", "consecutive_blocked",
1057
+ "last_started_at", "last_finished_at", "last_phase",
1058
+ "last_status", "last_error_code",
1059
+ }
1060
+ if not isinstance(payload, Mapping) or set(payload) != expected:
1061
+ raise ValueError
1062
+ if payload["schema"] != SUPERVISOR_SCHEMA or payload["status"] not in {
1063
+ "ready", "waiting", "leased", "recoverable",
1064
+ }:
1065
+ raise ValueError
1066
+ if not isinstance(payload["lease_active"], bool):
1067
+ raise ValueError
1068
+ revision = payload["revision"]
1069
+ blocked = payload["consecutive_blocked"]
1070
+ if any(
1071
+ isinstance(item, bool) or not isinstance(item, int) or item < 0
1072
+ for item in (revision, blocked)
1073
+ ):
1074
+ raise ValueError
1075
+ _timestamp(payload["next_tick_at"])
1076
+ for name in ("lease_expires_at", "last_started_at", "last_finished_at"):
1077
+ if payload[name] is not None:
1078
+ _timestamp(payload[name])
1079
+ error_code = payload["last_error_code"]
1080
+ if error_code is not None and (
1081
+ not isinstance(error_code, str) or _QUEUE.ERROR_CODE.fullmatch(error_code) is None
1082
+ ):
1083
+ raise ValueError
1084
+ last_phase = payload["last_phase"]
1085
+ last_status = payload["last_status"]
1086
+ if (last_phase is None) != (last_status is None):
1087
+ raise ValueError
1088
+ if last_phase is not None and (
1089
+ last_phase not in SUPERVISOR_PHASES
1090
+ or last_status not in SUPERVISOR_STATUSES
1091
+ ):
1092
+ raise ValueError
1093
+ blocked_status = last_status in {"blocked", "failed", "retry_wait"}
1094
+ if blocked_status != (error_code is not None):
1095
+ raise ValueError
1096
+ if (blocked > 0) != blocked_status:
1097
+ raise ValueError
1098
+ counts = {
1099
+ "revision": revision,
1100
+ "consecutive_blocked": blocked,
1101
+ "lease_active": int(payload["lease_active"]),
1102
+ }
1103
+ if payload["status"] == "recoverable":
1104
+ reasons.add("supervisor.lease_expired")
1105
+ if (
1106
+ payload["status"] == "ready"
1107
+ and now - float(payload["next_tick_at"])
1108
+ > self.supervisor_stale_after_seconds
1109
+ ):
1110
+ reasons.add("supervisor.heartbeat_stale")
1111
+ if blocked_status:
1112
+ reasons.add("supervisor.last_error." + error_code)
1113
+ status = "degraded" if reasons else "healthy"
1114
+ except Exception:
1115
+ reasons = {"supervisor.state_invalid"}
1116
+ status = "blocked"
1117
+ return _component("supervisor", status, reasons, counts)
1118
+
1119
+ def _check_benchmark(self, now: float) -> ComponentHealth | None:
1120
+ if self.benchmark_store is None:
1121
+ return None
1122
+ counts = {status: 0 for status in _CAMPAIGN.STATUSES}
1123
+ counts.update({"work_count": 0, "report_ready": 0})
1124
+ try:
1125
+ value = self.benchmark_store.health(
1126
+ self.benchmark_policy,
1127
+ self.benchmark_campaign_id,
1128
+ self.benchmark_evidence_authority,
1129
+ now=now,
1130
+ stale_after_seconds=self.benchmark_stale_after_seconds,
1131
+ )
1132
+ payload_method = getattr(value, "as_payload", None)
1133
+ payload = payload_method() if callable(payload_method) else value
1134
+ expected = {
1135
+ "schema", "campaign_id", "status", "reasons", "counts",
1136
+ "work_count", "report_ready", "last_progress_at",
1137
+ }
1138
+ if (
1139
+ not isinstance(payload, Mapping)
1140
+ or set(payload) != expected
1141
+ or payload["schema"] != _CAMPAIGN.HEALTH_SCHEMA
1142
+ or payload["campaign_id"] != self.benchmark_campaign_id
1143
+ or payload["status"] not in {"healthy", "degraded", "blocked"}
1144
+ or not isinstance(payload["reasons"], list)
1145
+ or payload["reasons"] != sorted(set(payload["reasons"]))
1146
+ or not isinstance(payload["counts"], Mapping)
1147
+ or set(payload["counts"]) != set(_CAMPAIGN.STATUSES)
1148
+ or not isinstance(payload["report_ready"], bool)
1149
+ ):
1150
+ raise ValueError
1151
+ work_count = payload["work_count"]
1152
+ if (
1153
+ isinstance(work_count, bool)
1154
+ or not isinstance(work_count, int)
1155
+ or work_count <= 0
1156
+ ):
1157
+ raise ValueError
1158
+ for name, count in payload["counts"].items():
1159
+ if isinstance(count, bool) or not isinstance(count, int) or count < 0:
1160
+ raise ValueError
1161
+ counts[name] = count
1162
+ if sum(counts[name] for name in _CAMPAIGN.STATUSES) != work_count:
1163
+ raise ValueError
1164
+ reasons = set(payload["reasons"])
1165
+ if any(
1166
+ not isinstance(reason, str)
1167
+ or _CAMPAIGN.HEALTH_REASON.fullmatch(reason) is None
1168
+ or not reason.startswith("benchmark.campaign.")
1169
+ for reason in reasons
1170
+ ):
1171
+ raise ValueError
1172
+ progress = payload["last_progress_at"]
1173
+ if progress is None or _timestamp(progress) > now:
1174
+ raise ValueError
1175
+ blocking_reasons = {
1176
+ "benchmark.campaign.failed",
1177
+ "benchmark.campaign.report_invalid",
1178
+ "benchmark.campaign.state_invalid",
1179
+ }
1180
+ expected_status = (
1181
+ "blocked" if counts["failed"] or reasons & blocking_reasons
1182
+ else "degraded" if reasons
1183
+ else "healthy"
1184
+ )
1185
+ if payload["status"] != expected_status:
1186
+ raise ValueError
1187
+ if counts["failed"] and "benchmark.campaign.failed" not in reasons:
1188
+ raise ValueError
1189
+ if payload["report_ready"] != (
1190
+ counts["succeeded"] == work_count
1191
+ and not reasons & {
1192
+ "benchmark.campaign.report_invalid",
1193
+ "benchmark.campaign.report_missing",
1194
+ }
1195
+ ):
1196
+ raise ValueError
1197
+ counts["work_count"] = work_count
1198
+ counts["report_ready"] = int(payload["report_ready"])
1199
+ return _component(
1200
+ "benchmark_campaign", payload["status"], reasons, counts,
1201
+ )
1202
+ except Exception:
1203
+ return _component(
1204
+ "benchmark_campaign",
1205
+ "blocked",
1206
+ {"benchmark.campaign.state_invalid"},
1207
+ counts,
1208
+ )
1209
+
1210
+ def _check_benchmark_reviews(self, now: float) -> ComponentHealth | None:
1211
+ if self.benchmark_review_store is None:
1212
+ return None
1213
+ counts = {
1214
+ "total": 0,
1215
+ "scoped": 0,
1216
+ "historical": 0,
1217
+ "target_native": 0,
1218
+ "source_fidelity": 0,
1219
+ "required": 0,
1220
+ "matched": 0,
1221
+ }
1222
+ try:
1223
+ rows = self.benchmark_store.connection.execute("""
1224
+ SELECT result_json, result_sha256
1225
+ FROM benchmark_campaign_work
1226
+ WHERE campaign_id = ? AND status = 'succeeded'
1227
+ ORDER BY target_locale, suite_case_key
1228
+ """, (self.benchmark_campaign_id,)).fetchall()
1229
+ expected_passes = []
1230
+ for row in rows:
1231
+ if (
1232
+ not isinstance(row["result_json"], str)
1233
+ or row["result_sha256"] != _hash(row["result_json"])
1234
+ ):
1235
+ raise ValueError
1236
+ result = json.loads(row["result_json"])
1237
+ if _canonical_json(result) != row["result_json"]:
1238
+ raise ValueError
1239
+ validated = _CAMPAIGN._BENCHMARK._validated_case_result(
1240
+ result,
1241
+ self.benchmark_policy,
1242
+ self.benchmark_evidence_authority,
1243
+ )
1244
+ expected_passes.extend({
1245
+ "phase": item["phase"],
1246
+ "request_sha256": item["request_sha256"],
1247
+ "response_sha256": item["response_sha256"],
1248
+ } for item in validated["passes"])
1249
+ value = self.benchmark_review_store.health(
1250
+ self.benchmark_policy,
1251
+ self.benchmark_reviewer_route_id,
1252
+ evidence_authority=self.benchmark_evidence_authority,
1253
+ expected_passes=tuple(expected_passes),
1254
+ now=now,
1255
+ )
1256
+ payload_method = getattr(value, "as_payload", None)
1257
+ payload = payload_method() if callable(payload_method) else value
1258
+ expected = {"schema", "route_id", "status", "reasons", "counts"}
1259
+ if (
1260
+ not isinstance(payload, Mapping)
1261
+ or set(payload) != expected
1262
+ or payload["schema"] != _BENCHMARK_REVIEW.HEALTH_SCHEMA
1263
+ or payload["route_id"] != self.benchmark_reviewer_route_id
1264
+ or payload["status"] not in {"healthy", "blocked"}
1265
+ or not isinstance(payload["reasons"], list)
1266
+ or payload["reasons"] != sorted(set(payload["reasons"]))
1267
+ or not isinstance(payload["counts"], Mapping)
1268
+ or set(payload["counts"]) != set(counts)
1269
+ ):
1270
+ raise ValueError
1271
+ for name, count in payload["counts"].items():
1272
+ if isinstance(count, bool) or not isinstance(count, int) or count < 0:
1273
+ raise ValueError
1274
+ counts[name] = count
1275
+ reasons = set(payload["reasons"])
1276
+ if any(
1277
+ not isinstance(reason, str)
1278
+ or _CAMPAIGN.HEALTH_REASON.fullmatch(reason) is None
1279
+ or not reason.startswith("review.store.")
1280
+ for reason in reasons
1281
+ ):
1282
+ raise ValueError
1283
+ if counts["matched"] > counts["required"]:
1284
+ raise ValueError
1285
+ expected_status = "blocked" if reasons else "healthy"
1286
+ if payload["status"] != expected_status:
1287
+ raise ValueError
1288
+ return _component("benchmark_reviews", expected_status, reasons, counts)
1289
+ except Exception:
1290
+ return _component(
1291
+ "benchmark_reviews",
1292
+ "blocked",
1293
+ {"review.store.state_invalid"},
1294
+ counts,
1295
+ )
1296
+
1297
+ def _check_native_reference_queue(
1298
+ self, now: float,
1299
+ ) -> ComponentHealth | None:
1300
+ if self.benchmark_reference_queue is None:
1301
+ return None
1302
+ counts = {status: 0 for status in _REFERENCE_QUEUE.STATUSES}
1303
+ counts["work_count"] = 0
1304
+ try:
1305
+ value = self.benchmark_reference_queue.native_reference_queue_health(
1306
+ now=now,
1307
+ stale_after_seconds=self.benchmark_stale_after_seconds,
1308
+ )
1309
+ payload_method = getattr(value, "as_payload", None)
1310
+ payload = payload_method() if callable(payload_method) else value
1311
+ expected = {
1312
+ "schema", "campaign_id", "status", "reasons", "counts",
1313
+ "work_count", "last_progress_at",
1314
+ }
1315
+ if (
1316
+ not isinstance(payload, Mapping)
1317
+ or set(payload) != expected
1318
+ or payload["schema"] != _REFERENCE_QUEUE.HEALTH_SCHEMA
1319
+ or payload["campaign_id"] != self.benchmark_campaign_id
1320
+ or payload["status"] not in {"healthy", "degraded", "blocked"}
1321
+ or not isinstance(payload["reasons"], list)
1322
+ or payload["reasons"] != sorted(set(payload["reasons"]))
1323
+ or not isinstance(payload["counts"], Mapping)
1324
+ or set(payload["counts"]) != set(_REFERENCE_QUEUE.STATUSES)
1325
+ ):
1326
+ raise ValueError
1327
+ work_count = payload["work_count"]
1328
+ if (
1329
+ isinstance(work_count, bool) or not isinstance(work_count, int)
1330
+ or work_count <= 0
1331
+ ):
1332
+ raise ValueError
1333
+ for name, count in payload["counts"].items():
1334
+ if isinstance(count, bool) or not isinstance(count, int) or count < 0:
1335
+ raise ValueError
1336
+ counts[name] = count
1337
+ if sum(counts[name] for name in _REFERENCE_QUEUE.STATUSES) != work_count:
1338
+ raise ValueError
1339
+ reasons = set(payload["reasons"])
1340
+ if any(
1341
+ not isinstance(reason, str)
1342
+ or _CAMPAIGN.HEALTH_REASON.fullmatch(reason) is None
1343
+ or not reason.startswith("native_reference.queue.")
1344
+ for reason in reasons
1345
+ ):
1346
+ raise ValueError
1347
+ progress = payload["last_progress_at"]
1348
+ if progress is None or _timestamp(progress) > now:
1349
+ raise ValueError
1350
+ expected_status = (
1351
+ "blocked" if counts["failed"] or any(
1352
+ reason in {
1353
+ "native_reference.queue.policy_expired",
1354
+ "native_reference.queue.failed",
1355
+ "native_reference.queue.state_invalid",
1356
+ } for reason in reasons
1357
+ )
1358
+ else "degraded" if reasons else "healthy"
1359
+ )
1360
+ if payload["status"] != expected_status:
1361
+ raise ValueError
1362
+ counts["work_count"] = work_count
1363
+ return _component(
1364
+ "benchmark_native_references",
1365
+ expected_status,
1366
+ reasons,
1367
+ counts,
1368
+ )
1369
+ except Exception:
1370
+ return _component(
1371
+ "benchmark_native_references",
1372
+ "blocked",
1373
+ {"native_reference.queue.state_invalid"},
1374
+ counts,
1375
+ )
1376
+
1377
+ def check(
1378
+ self,
1379
+ *,
1380
+ event_verifier: Any,
1381
+ approval_authority: Any,
1382
+ publication_authority: Any,
1383
+ provider_probe: ProviderHealthProbe | None,
1384
+ publisher_probe: PublisherHealthProbe | None = None,
1385
+ now: float | int,
1386
+ ) -> LocalizationHealthReport:
1387
+ now = _timestamp(now)
1388
+ storage_reasons = self._check_schemas()
1389
+ queue_counts: dict[str, int] = {status: 0 for status in QUEUE_STATUSES}
1390
+ approval_counts = {"total": 0, "current": 0, "expired": 0}
1391
+ delivery_counts: dict[str, int] = {status: 0 for status in DELIVERY_STATUSES}
1392
+ tombstone_counts: dict[str, int] = {status: 0 for status in DELIVERY_STATUSES}
1393
+ evidence_counts: dict[str, int] = {status: 0 for status in EVIDENCE_STATUSES}
1394
+ workflow_reasons: set[str] = set()
1395
+ versions: tuple[WebsiteVersionHealth, ...] = ()
1396
+ provider_bindings: set[tuple[str, str, str]] = set()
1397
+
1398
+ if not storage_reasons:
1399
+ try:
1400
+ queue_counts, queue_reasons = self._check_queue(now)
1401
+ evidence_counts, evidence_reasons = self._check_evidence(
1402
+ event_verifier, now,
1403
+ )
1404
+ approval_counts, approval_reasons = self._check_approvals(
1405
+ approval_authority, now,
1406
+ )
1407
+ delivery_counts, delivery_reasons = self._check_deliveries(
1408
+ publication_authority, now,
1409
+ )
1410
+ tombstone_counts, tombstone_reasons = self._check_tombstones(
1411
+ event_verifier, publication_authority, now,
1412
+ )
1413
+ supersession_reasons = self._check_supersessions(event_verifier)
1414
+ versions, event_reasons, provider_bindings = self._versions(
1415
+ event_verifier, approval_authority, now,
1416
+ )
1417
+ workflow_reasons.update(queue_reasons)
1418
+ workflow_reasons.update(evidence_reasons)
1419
+ workflow_reasons.update(approval_reasons)
1420
+ workflow_reasons.update(delivery_reasons)
1421
+ workflow_reasons.update(tombstone_reasons)
1422
+ workflow_reasons.update(supersession_reasons)
1423
+ workflow_reasons.update(event_reasons)
1424
+ except Exception:
1425
+ storage_reasons.add("monitor.state_unreadable")
1426
+
1427
+ providers, provider_reasons = self._providers(provider_bindings, provider_probe)
1428
+ publisher = self._publisher(publisher_probe)
1429
+ supervisor = self._check_supervisor(now)
1430
+ benchmark = self._check_benchmark(now)
1431
+ benchmark_reviews = self._check_benchmark_reviews(now)
1432
+ benchmark_references = self._check_native_reference_queue(now)
1433
+ blocking_workflow = {
1434
+ "queue.state_invalid",
1435
+ "evidence.state_invalid",
1436
+ "release.approval_invalid",
1437
+ "cms.delivery.invalid",
1438
+ "cms.tombstone.invalid",
1439
+ "cms.event.invalid",
1440
+ "cms.supersession.invalid",
1441
+ }
1442
+ queue_reasons = {reason for reason in workflow_reasons if reason.startswith("queue.")}
1443
+ evidence_reasons = {
1444
+ reason for reason in workflow_reasons if reason.startswith("evidence.")
1445
+ }
1446
+ release_reasons = {reason for reason in workflow_reasons if reason.startswith("release.")}
1447
+ cms_reasons = {reason for reason in workflow_reasons if reason.startswith("cms.")}
1448
+ components = (
1449
+ _component(
1450
+ "storage",
1451
+ "blocked" if storage_reasons else "healthy",
1452
+ storage_reasons,
1453
+ {"connections": 3 + int(self.evidence_state is not None) + int(
1454
+ self.benchmark_store is not None
1455
+ ) + int(self.benchmark_review_store is not None)},
1456
+ ),
1457
+ _component(
1458
+ "queue",
1459
+ "blocked" if queue_reasons & blocking_workflow else (
1460
+ "degraded" if queue_reasons else "healthy"
1461
+ ),
1462
+ queue_reasons,
1463
+ queue_counts,
1464
+ ),
1465
+ _component(
1466
+ "evidence",
1467
+ "blocked" if evidence_reasons & blocking_workflow else (
1468
+ "degraded" if evidence_reasons else "healthy"
1469
+ ),
1470
+ evidence_reasons,
1471
+ evidence_counts,
1472
+ ),
1473
+ _component(
1474
+ "release",
1475
+ "blocked" if release_reasons & blocking_workflow else (
1476
+ "degraded" if release_reasons else "healthy"
1477
+ ),
1478
+ release_reasons,
1479
+ approval_counts,
1480
+ ),
1481
+ _component(
1482
+ "cms",
1483
+ "blocked" if cms_reasons & blocking_workflow else (
1484
+ "degraded" if cms_reasons else "healthy"
1485
+ ),
1486
+ cms_reasons,
1487
+ {
1488
+ **delivery_counts,
1489
+ **{f"tombstone_{key}": value for key, value in tombstone_counts.items()},
1490
+ },
1491
+ ),
1492
+ _component(
1493
+ "providers",
1494
+ "blocked" if provider_reasons else "healthy",
1495
+ provider_reasons,
1496
+ {
1497
+ "configured": len(providers),
1498
+ "healthy": sum(item.status == "healthy" for item in providers),
1499
+ "blocked": sum(item.status == "blocked" for item in providers),
1500
+ },
1501
+ ),
1502
+ )
1503
+ if supervisor is not None:
1504
+ components = components + (supervisor,)
1505
+ if publisher is not None:
1506
+ components = components + (publisher,)
1507
+ if benchmark is not None:
1508
+ components = components + (benchmark,)
1509
+ if benchmark_reviews is not None:
1510
+ components = components + (benchmark_reviews,)
1511
+ if benchmark_references is not None:
1512
+ components = components + (benchmark_references,)
1513
+ if (
1514
+ storage_reasons
1515
+ or provider_reasons
1516
+ or (publisher is not None and publisher.status == "blocked")
1517
+ or workflow_reasons & blocking_workflow
1518
+ or (supervisor is not None and supervisor.status == "blocked")
1519
+ or (benchmark is not None and benchmark.status == "blocked")
1520
+ or (
1521
+ benchmark_reviews is not None
1522
+ and benchmark_reviews.status == "blocked"
1523
+ )
1524
+ or (
1525
+ benchmark_references is not None
1526
+ and benchmark_references.status == "blocked"
1527
+ )
1528
+ ):
1529
+ status = "blocked"
1530
+ elif workflow_reasons or (
1531
+ supervisor is not None and supervisor.status == "degraded"
1532
+ ) or (
1533
+ benchmark is not None and benchmark.status == "degraded"
1534
+ ) or (
1535
+ benchmark_references is not None
1536
+ and benchmark_references.status == "degraded"
1537
+ ):
1538
+ status = "degraded"
1539
+ else:
1540
+ status = "healthy"
1541
+ return LocalizationHealthReport(now, status, components, providers, versions)