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,875 @@
1
+ #!/usr/bin/env python3
2
+ """Secure source-side HTTPS client for the public CMS localization API.
3
+
4
+ The caller supplies already-versioned change, cancellation, and tombstone
5
+ objects. This adapter signs their canonical UTF-8 bytes, performs exactly one
6
+ HTTP attempt against a fixed origin, and binds every response to the operation
7
+ and request that produced it. Retry scheduling remains a host responsibility.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import hashlib
13
+ import importlib.util
14
+ import json
15
+ import math
16
+ import re
17
+ import secrets
18
+ import socket
19
+ import sys
20
+ import time
21
+ import urllib.error
22
+ import urllib.parse
23
+ import urllib.request
24
+ from dataclasses import dataclass
25
+ from pathlib import Path
26
+ from typing import Any, Callable, Mapping, Protocol
27
+
28
+
29
+ MAX_ORIGIN_LENGTH = 2048
30
+ MAX_HEADER_VALUE_LENGTH = 4096
31
+ MAX_RESPONSE_BYTES = 4_000_000
32
+ HEADER_NAME = re.compile(r"^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$")
33
+ TOKEN = re.compile(r"^[A-Za-z0-9_.:-]{1,256}$")
34
+ SIGNATURE_VALUE = re.compile(r"^[A-Za-z0-9_.:/+=-]{1,4096}$")
35
+ SHA256 = re.compile(r"^[0-9a-f]{64}$")
36
+ ERROR_CODE = re.compile(r"^[a-z][a-z0-9_.-]{0,127}$")
37
+ RESERVED_HEADERS = {
38
+ "accept", "accept-encoding", "connection", "content-length",
39
+ "content-type", "host", "transfer-encoding",
40
+ "x-localization-key-id", "x-localization-signature",
41
+ "x-localization-signature-algorithm",
42
+ }
43
+
44
+
45
+ def _load_module(name: str, path: Path):
46
+ spec = importlib.util.spec_from_file_location(name, path)
47
+ if spec is None or spec.loader is None:
48
+ raise RuntimeError(f"cannot load required CMS client dependency: {path.name}")
49
+ module = importlib.util.module_from_spec(spec)
50
+ sys.modules[spec.name] = module
51
+ spec.loader.exec_module(module)
52
+ return module
53
+
54
+
55
+ _ROOT = Path(__file__).resolve().parents[1]
56
+ _API = _load_module(
57
+ "blun_website_localization_cms_client_api",
58
+ _ROOT / "integrations" / "website_localization_api.py",
59
+ )
60
+ _CMS = _load_module(
61
+ "blun_website_localization_cms_client_cms",
62
+ _ROOT / "integrations" / "website_localization_cms.py",
63
+ )
64
+
65
+
66
+ class CMSClientFailed(RuntimeError):
67
+ """Stable, content-free client failure with an explicit retry decision."""
68
+
69
+ cms_client_failure = True
70
+
71
+ def __init__(self, code: str, *, retryable: bool):
72
+ if not isinstance(code, str) or ERROR_CODE.fullmatch(code) is None:
73
+ raise ValueError("CMS client error code is invalid")
74
+ if not isinstance(retryable, bool):
75
+ raise ValueError("CMS client retryability must be boolean")
76
+ super().__init__(code)
77
+ self.code = code
78
+ self.retryable = retryable
79
+
80
+
81
+ @dataclass(frozen=True)
82
+ class HTTPResult:
83
+ status: int
84
+ headers: tuple[tuple[str, str], ...]
85
+ body: bytes
86
+
87
+
88
+ class HTTPTransport(Protocol):
89
+ def post(
90
+ self,
91
+ url: str,
92
+ headers: Mapping[str, str],
93
+ body: bytes,
94
+ *,
95
+ timeout: float,
96
+ ) -> HTTPResult: ...
97
+
98
+
99
+ class _NoRedirect(urllib.request.HTTPRedirectHandler):
100
+ def redirect_request(self, req, fp, code, msg, headers, newurl):
101
+ return None
102
+
103
+
104
+ class URLTransport:
105
+ """Perform one bounded stdlib POST while refusing redirects."""
106
+
107
+ def __init__(self):
108
+ self._opener = urllib.request.build_opener(_NoRedirect)
109
+
110
+ @staticmethod
111
+ def _read(response: Any) -> bytes:
112
+ try:
113
+ return response.read(MAX_RESPONSE_BYTES + 1)
114
+ except (TimeoutError, socket.timeout, OSError):
115
+ raise CMSClientFailed("network", retryable=True) from None
116
+
117
+ def post(
118
+ self,
119
+ url: str,
120
+ headers: Mapping[str, str],
121
+ body: bytes,
122
+ *,
123
+ timeout: float,
124
+ ) -> HTTPResult:
125
+ request = urllib.request.Request(
126
+ url, data=body, headers=dict(headers), method="POST",
127
+ )
128
+ try:
129
+ response = self._opener.open(request, timeout=timeout)
130
+ except urllib.error.HTTPError as error:
131
+ try:
132
+ return HTTPResult(
133
+ int(error.code),
134
+ tuple(error.headers.items()) if error.headers is not None else (),
135
+ self._read(error),
136
+ )
137
+ finally:
138
+ error.close()
139
+ except (urllib.error.URLError, TimeoutError, socket.timeout, OSError):
140
+ raise CMSClientFailed("network", retryable=True) from None
141
+ try:
142
+ return HTTPResult(
143
+ int(response.status), tuple(response.headers.items()),
144
+ self._read(response),
145
+ )
146
+ finally:
147
+ response.close()
148
+
149
+
150
+ def _canonical_json(value: Any, *, code: str) -> bytes:
151
+ try:
152
+ encoded = json.dumps(
153
+ value,
154
+ ensure_ascii=False,
155
+ allow_nan=False,
156
+ sort_keys=True,
157
+ separators=(",", ":"),
158
+ ).encode("utf-8")
159
+ except (TypeError, ValueError, RecursionError):
160
+ raise CMSClientFailed(code, retryable=False) from None
161
+ if not encoded or len(encoded) > _API.MAX_MESSAGE_BYTES:
162
+ raise CMSClientFailed(code, retryable=False)
163
+ return encoded
164
+
165
+
166
+ def _pairs(items):
167
+ result = {}
168
+ for key, value in items:
169
+ if key in result:
170
+ raise ValueError("duplicate JSON key")
171
+ result[key] = value
172
+ return result
173
+
174
+
175
+ def _constant(_value):
176
+ raise ValueError("non-finite JSON number")
177
+
178
+
179
+ def _token(value: Any, *, code: str) -> str:
180
+ if not isinstance(value, str) or TOKEN.fullmatch(value) is None:
181
+ raise CMSClientFailed(code, retryable=False)
182
+ return value
183
+
184
+
185
+ def _valid_token(value: Any) -> bool:
186
+ return isinstance(value, str) and TOKEN.fullmatch(value) is not None
187
+
188
+
189
+ def _valid_sha256(value: Any, *, optional: bool = False) -> bool:
190
+ return (optional and value is None) or (
191
+ isinstance(value, str) and SHA256.fullmatch(value) is not None
192
+ )
193
+
194
+
195
+ def _valid_nonnegative_number(value: Any) -> bool:
196
+ return (
197
+ not isinstance(value, bool)
198
+ and isinstance(value, (int, float))
199
+ and math.isfinite(float(value))
200
+ and value >= 0
201
+ )
202
+
203
+
204
+ def _timestamp(value: Any, *, code: str) -> float:
205
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
206
+ raise CMSClientFailed(code, retryable=False)
207
+ result = float(value)
208
+ if result < 0 or not math.isfinite(result):
209
+ raise CMSClientFailed(code, retryable=False)
210
+ return result
211
+
212
+
213
+ def _origin(value: Any, allow_loopback_http: bool) -> str:
214
+ if (
215
+ not isinstance(value, str)
216
+ or value != value.strip()
217
+ or not value
218
+ or not value.isascii()
219
+ or len(value) > MAX_ORIGIN_LENGTH
220
+ or any(ord(character) <= 32 or ord(character) == 127 for character in value)
221
+ ):
222
+ raise ValueError("origin is invalid")
223
+ try:
224
+ parsed = urllib.parse.urlsplit(value)
225
+ hostname = parsed.hostname
226
+ port = parsed.port
227
+ except ValueError:
228
+ raise ValueError("origin is invalid") from None
229
+ if (
230
+ not hostname
231
+ or not hostname.isascii()
232
+ or parsed.username is not None
233
+ or parsed.password is not None
234
+ or parsed.query
235
+ or parsed.fragment
236
+ or parsed.path not in {"", "/"}
237
+ ):
238
+ raise ValueError("origin must contain only scheme and authority")
239
+ if parsed.scheme == "https":
240
+ pass
241
+ elif (
242
+ parsed.scheme == "http"
243
+ and allow_loopback_http
244
+ and hostname.lower() in {"localhost", "127.0.0.1", "::1"}
245
+ ):
246
+ pass
247
+ else:
248
+ raise ValueError("origin must use HTTPS")
249
+ if port is not None and not 1 <= port <= 65535:
250
+ raise ValueError("origin is invalid")
251
+ return value.rstrip("/")
252
+
253
+
254
+ def _authentication_headers(
255
+ provider: Callable[[], Mapping[str, str]],
256
+ ) -> dict[str, str]:
257
+ try:
258
+ supplied = provider()
259
+ except Exception:
260
+ raise CMSClientFailed("authentication", retryable=False) from None
261
+ if not isinstance(supplied, Mapping):
262
+ raise CMSClientFailed("authentication", retryable=False)
263
+ result: dict[str, str] = {}
264
+ normalized_names: set[str] = set()
265
+ for name, value in supplied.items():
266
+ normalized = name.lower() if isinstance(name, str) else ""
267
+ if (
268
+ not isinstance(name, str)
269
+ or HEADER_NAME.fullmatch(name) is None
270
+ or normalized in RESERVED_HEADERS
271
+ or normalized in normalized_names
272
+ or not isinstance(value, str)
273
+ or not value
274
+ or not value.isascii()
275
+ or len(value) > MAX_HEADER_VALUE_LENGTH
276
+ or any(ord(character) < 32 or ord(character) == 127 for character in value)
277
+ ):
278
+ raise CMSClientFailed("authentication", retryable=False)
279
+ normalized_names.add(normalized)
280
+ result[name] = value
281
+ return result
282
+
283
+
284
+ def _signature(value: Any) -> tuple[str, str, str]:
285
+ try:
286
+ algorithm = value.algorithm
287
+ key_id = value.key_id
288
+ signature = value.signature
289
+ except Exception:
290
+ raise CMSClientFailed("signing", retryable=False) from None
291
+ if (
292
+ not isinstance(algorithm, str)
293
+ or TOKEN.fullmatch(algorithm) is None
294
+ or not isinstance(key_id, str)
295
+ or TOKEN.fullmatch(key_id) is None
296
+ or not isinstance(signature, str)
297
+ or SIGNATURE_VALUE.fullmatch(signature) is None
298
+ ):
299
+ raise CMSClientFailed("signing", retryable=False)
300
+ return algorithm, key_id, signature
301
+
302
+
303
+ def _response_headers(value: Any) -> dict[str, str]:
304
+ if not isinstance(value, tuple):
305
+ raise CMSClientFailed("transport_invalid", retryable=True)
306
+ result: dict[str, str] = {}
307
+ for item in value:
308
+ if (
309
+ not isinstance(item, tuple)
310
+ or len(item) != 2
311
+ or not all(isinstance(part, str) for part in item)
312
+ ):
313
+ raise CMSClientFailed("transport_invalid", retryable=True)
314
+ name, content = item[0].lower(), item[1].strip()
315
+ if name in {"content-type", "content-length"}:
316
+ if name in result:
317
+ raise CMSClientFailed("response_headers", retryable=True)
318
+ result[name] = content
319
+ return result
320
+
321
+
322
+ def _copy_request(value: Any, *, schema: str, code: str) -> dict[str, Any]:
323
+ if not isinstance(value, Mapping):
324
+ raise CMSClientFailed(code, retryable=False)
325
+ raw = _canonical_json(dict(value), code=code)
326
+ try:
327
+ copied = json.loads(raw.decode("utf-8"), object_pairs_hook=_pairs)
328
+ except (UnicodeDecodeError, json.JSONDecodeError, ValueError, RecursionError):
329
+ raise CMSClientFailed(code, retryable=False) from None
330
+ if not isinstance(copied, dict) or copied.get("schema") != schema:
331
+ raise CMSClientFailed(code, retryable=False)
332
+ return copied
333
+
334
+
335
+ class CMSLocalizationHTTPClient:
336
+ """Call every public CMS API operation through one exact signed boundary."""
337
+
338
+ _OPERATIONS = {
339
+ "change": (_API.CHANGE_PATH, _CMS.CHANGE_SCHEMA),
340
+ "cancellation": (_API.CANCELLATION_PATH, _CMS.CANCELLATION_SCHEMA),
341
+ "tombstone": (_API.TOMBSTONE_PATH, _CMS.TOMBSTONE_SCHEMA),
342
+ "status": (_API.STATUS_PATH, _API.STATUS_REQUEST_SCHEMA),
343
+ "lifecycle": (_API.LIFECYCLE_PATH, _API.LIFECYCLE_REQUEST_SCHEMA),
344
+ "capabilities": (
345
+ _API.CAPABILITIES_PATH, _API.CAPABILITIES_REQUEST_SCHEMA,
346
+ ),
347
+ }
348
+
349
+ def __init__(
350
+ self,
351
+ origin: str,
352
+ authentication_headers: Callable[[], Mapping[str, str]],
353
+ signing_authority: Any,
354
+ *,
355
+ transport: HTTPTransport | None = None,
356
+ timeout: float = 30.0,
357
+ allow_loopback_http: bool = False,
358
+ clock: Callable[[], float | int] = time.time,
359
+ request_id_factory: Callable[[], str] | None = None,
360
+ ):
361
+ if not isinstance(allow_loopback_http, bool):
362
+ raise TypeError("allow_loopback_http must be boolean")
363
+ self.origin = _origin(origin, allow_loopback_http)
364
+ if not callable(authentication_headers):
365
+ raise TypeError("authentication_headers must be callable")
366
+ if not callable(getattr(signing_authority, "sign", None)):
367
+ raise TypeError("signing_authority must provide sign")
368
+ if not callable(clock):
369
+ raise TypeError("clock must be callable")
370
+ if (
371
+ isinstance(timeout, bool)
372
+ or not isinstance(timeout, (int, float))
373
+ or not 0 < timeout <= 300
374
+ ):
375
+ raise ValueError("timeout is outside the supported range")
376
+ self.authentication_headers = authentication_headers
377
+ self.signing_authority = signing_authority
378
+ self.transport = URLTransport() if transport is None else transport
379
+ if not callable(getattr(self.transport, "post", None)):
380
+ raise TypeError("transport must provide post")
381
+ self.timeout = float(timeout)
382
+ self.clock = clock
383
+ self.request_id_factory = (
384
+ (lambda: secrets.token_hex(16))
385
+ if request_id_factory is None else request_id_factory
386
+ )
387
+ if not callable(self.request_id_factory):
388
+ raise TypeError("request_id_factory must be callable")
389
+
390
+ def submit_change(self, change: Mapping[str, Any]) -> Mapping[str, Any]:
391
+ request = _copy_request(
392
+ change, schema=_CMS.CHANGE_SCHEMA, code="change_invalid",
393
+ )
394
+ response = self._post("change", request, {200, 202})
395
+ expected = {
396
+ "schema", "event_id", "plan_id", "job_count", "inserted_jobs",
397
+ "status",
398
+ }
399
+ if (
400
+ set(response) != expected
401
+ or response.get("schema") != _API.API_SCHEMA
402
+ or response.get("event_id") != request.get("event_id")
403
+ or not _valid_token(response.get("plan_id"))
404
+ or isinstance(response.get("job_count"), bool)
405
+ or not isinstance(response.get("job_count"), int)
406
+ or response["job_count"] <= 0
407
+ or isinstance(response.get("inserted_jobs"), bool)
408
+ or not isinstance(response.get("inserted_jobs"), int)
409
+ or not 0 <= response["inserted_jobs"] <= response["job_count"]
410
+ or response.get("status") not in {"enqueued", "superseded", "cancelled"}
411
+ ):
412
+ raise CMSClientFailed("response_binding", retryable=True)
413
+ return response
414
+
415
+ def cancel(self, cancellation: Mapping[str, Any]) -> Mapping[str, Any]:
416
+ request = _copy_request(
417
+ cancellation,
418
+ schema=_CMS.CANCELLATION_SCHEMA,
419
+ code="cancellation_invalid",
420
+ )
421
+ response = self._post("cancellation", request, {200, 202})
422
+ if (
423
+ set(response) != {
424
+ "schema", "cancellation_id", "event_id", "status",
425
+ "newly_cancelled",
426
+ }
427
+ or response.get("schema") != _API.API_SCHEMA
428
+ or response.get("cancellation_id") != request.get("cancellation_id")
429
+ or response.get("event_id") != request.get("event_id")
430
+ or response.get("status") != "cancelled"
431
+ or not isinstance(response.get("newly_cancelled"), bool)
432
+ ):
433
+ raise CMSClientFailed("response_binding", retryable=True)
434
+ return response
435
+
436
+ def request_tombstone(self, tombstone: Mapping[str, Any]) -> Mapping[str, Any]:
437
+ request = _copy_request(
438
+ tombstone, schema=_CMS.TOMBSTONE_SCHEMA, code="tombstone_invalid",
439
+ )
440
+ response = self._post("tombstone", request, {200, 202})
441
+ if (
442
+ set(response) != {
443
+ "schema", "tombstone_id", "event_id", "delivery_id",
444
+ "status", "newly_requested",
445
+ }
446
+ or response.get("schema") != _API.API_SCHEMA
447
+ or response.get("tombstone_id") != request.get("tombstone_id")
448
+ or response.get("event_id") != request.get("event_id")
449
+ or not _valid_token(response.get("delivery_id"))
450
+ or response.get("status") not in {
451
+ "pending", "leased", "retry_wait", "succeeded", "failed",
452
+ }
453
+ or not isinstance(response.get("newly_requested"), bool)
454
+ ):
455
+ raise CMSClientFailed("response_binding", retryable=True)
456
+ return response
457
+
458
+ def status(
459
+ self, event_id: str, site_id: str, *, request_id: str | None = None,
460
+ ) -> Mapping[str, Any]:
461
+ request = self._read_request(
462
+ _API.STATUS_REQUEST_SCHEMA, event_id, site_id, request_id,
463
+ )
464
+ response = self._post("status", request, {200})
465
+ expected = {
466
+ "schema", "status", "request_id", "event_id", "site_id",
467
+ "plan_id", "website_version", "source_sequence", "job_count",
468
+ "counts", "locales", "cancelled", "queue_recovery_pending",
469
+ }
470
+ if (
471
+ set(response) != expected
472
+ or response.get("schema") != _API.API_SCHEMA
473
+ or response.get("status") != "PROGRESS"
474
+ or response.get("request_id") != request["request_id"]
475
+ or response.get("event_id") != event_id
476
+ or response.get("site_id") != site_id
477
+ or not self._valid_progress(response)
478
+ ):
479
+ raise CMSClientFailed("response_binding", retryable=True)
480
+ return response
481
+
482
+ def lifecycle(
483
+ self, event_id: str, site_id: str, *, request_id: str | None = None,
484
+ ) -> Mapping[str, Any]:
485
+ request = self._read_request(
486
+ _API.LIFECYCLE_REQUEST_SCHEMA, event_id, site_id, request_id,
487
+ )
488
+ response = self._post("lifecycle", request, {200})
489
+ expected = {
490
+ "schema", "request_id", "event_id", "site_id", "plan_id",
491
+ "website_version", "source_sequence", "status",
492
+ "required_locales", "approved_locales", "blocked_locales",
493
+ "queue_counts", "delivery", "tombstone",
494
+ }
495
+ if (
496
+ set(response) != expected
497
+ or response.get("schema") != _API.LIFECYCLE_RESPONSE_SCHEMA
498
+ or response.get("request_id") != request["request_id"]
499
+ or response.get("event_id") != event_id
500
+ or response.get("site_id") != site_id
501
+ or not self._valid_lifecycle(response)
502
+ ):
503
+ raise CMSClientFailed("response_binding", retryable=True)
504
+ return response
505
+
506
+ def capabilities(self, *, request_id: str | None = None) -> Mapping[str, Any]:
507
+ request = {
508
+ "schema": _API.CAPABILITIES_REQUEST_SCHEMA,
509
+ "request_id": self._request_id(request_id),
510
+ "requested_at": _timestamp(self.clock(), code="clock_invalid"),
511
+ }
512
+ response = self._post("capabilities", request, {200})
513
+ if (
514
+ set(response) != {
515
+ "schema", "status", "request_id", "capabilities", "api_contract",
516
+ }
517
+ or response.get("schema") != _API.API_SCHEMA
518
+ or response.get("status") != "CAPABILITIES"
519
+ or response.get("request_id") != request["request_id"]
520
+ or not self._valid_capabilities(response)
521
+ ):
522
+ raise CMSClientFailed("response_binding", retryable=True)
523
+ return response
524
+
525
+ def _read_request(
526
+ self,
527
+ schema: str,
528
+ event_id: Any,
529
+ site_id: Any,
530
+ request_id: str | None,
531
+ ) -> dict[str, Any]:
532
+ return {
533
+ "schema": schema,
534
+ "request_id": self._request_id(request_id),
535
+ "event_id": _token(event_id, code="request_invalid"),
536
+ "site_id": _token(site_id, code="request_invalid"),
537
+ "requested_at": _timestamp(self.clock(), code="clock_invalid"),
538
+ }
539
+
540
+ def _request_id(self, supplied: str | None) -> str:
541
+ if supplied is None:
542
+ try:
543
+ supplied = self.request_id_factory()
544
+ except Exception:
545
+ raise CMSClientFailed("request_id_invalid", retryable=False) from None
546
+ return _token(supplied, code="request_id_invalid")
547
+
548
+ def _post(
549
+ self, operation: str, request: Mapping[str, Any], success: set[int],
550
+ ) -> dict[str, Any]:
551
+ path, schema = self._OPERATIONS[operation]
552
+ if request.get("schema") != schema:
553
+ raise CMSClientFailed("request_invalid", retryable=False)
554
+ body = _canonical_json(dict(request), code="request_invalid")
555
+ try:
556
+ raw_signature = self.signing_authority.sign(body)
557
+ except Exception:
558
+ raise CMSClientFailed("signing", retryable=False) from None
559
+ algorithm, key_id, signature = _signature(raw_signature)
560
+ headers = _authentication_headers(self.authentication_headers)
561
+ headers.update({
562
+ "Accept": "application/json",
563
+ "Accept-Encoding": "identity",
564
+ "Content-Type": "application/json; charset=utf-8",
565
+ "X-Localization-Signature-Algorithm": algorithm,
566
+ "X-Localization-Key-Id": key_id,
567
+ "X-Localization-Signature": signature,
568
+ })
569
+ try:
570
+ result = self.transport.post(
571
+ self.origin + path, headers, body, timeout=self.timeout,
572
+ )
573
+ except CMSClientFailed:
574
+ raise
575
+ except Exception:
576
+ raise CMSClientFailed("network", retryable=True) from None
577
+ return self._parse_response(result, success)
578
+
579
+ @staticmethod
580
+ def _parse_response(result: Any, success: set[int]) -> dict[str, Any]:
581
+ try:
582
+ status = result.status
583
+ raw_headers = result.headers
584
+ body = result.body
585
+ except Exception:
586
+ raise CMSClientFailed("transport_invalid", retryable=True) from None
587
+ if (
588
+ isinstance(status, bool)
589
+ or not isinstance(status, int)
590
+ or not 100 <= status <= 599
591
+ ):
592
+ raise CMSClientFailed("transport_invalid", retryable=True)
593
+ if 300 <= status <= 399:
594
+ raise CMSClientFailed("redirect", retryable=False)
595
+ headers = _response_headers(raw_headers)
596
+ content_type = headers.get("content-type", "").lower().replace(" ", "")
597
+ if content_type not in {"application/json", "application/json;charset=utf-8"}:
598
+ raise CMSClientFailed("response_content_type", retryable=True)
599
+ if (
600
+ not isinstance(body, bytes)
601
+ or not body
602
+ or len(body) > MAX_RESPONSE_BYTES
603
+ ):
604
+ raise CMSClientFailed("response_size", retryable=True)
605
+ declared = headers.get("content-length")
606
+ if declared is not None and (
607
+ not declared.isascii()
608
+ or not declared.isdecimal()
609
+ or int(declared) != len(body)
610
+ ):
611
+ raise CMSClientFailed("response_size", retryable=True)
612
+ try:
613
+ text = body.decode("utf-8")
614
+ if text.startswith("\ufeff"):
615
+ raise ValueError("BOM rejected")
616
+ value = json.loads(
617
+ text, object_pairs_hook=_pairs, parse_constant=_constant,
618
+ )
619
+ except (UnicodeDecodeError, json.JSONDecodeError, ValueError, RecursionError):
620
+ raise CMSClientFailed("response_json", retryable=True) from None
621
+ if (
622
+ not isinstance(value, dict)
623
+ or _canonical_json(value, code="response_json") != body
624
+ ):
625
+ raise CMSClientFailed("response_json", retryable=True)
626
+ if status not in success:
627
+ if (
628
+ set(value) == {"schema", "status", "error"}
629
+ and value.get("schema") == _API.API_SCHEMA
630
+ and value.get("status") == "BLOCK"
631
+ and isinstance(value.get("error"), str)
632
+ and ERROR_CODE.fullmatch(value["error"]) is not None
633
+ ):
634
+ retryable = status in {408, 425, 429} or 500 <= status <= 599
635
+ raise CMSClientFailed(value["error"], retryable=retryable)
636
+ raise CMSClientFailed("http_status", retryable=500 <= status <= 599)
637
+ return value
638
+
639
+ @staticmethod
640
+ def _valid_progress(value: Mapping[str, Any]) -> bool:
641
+ counts = value.get("counts")
642
+ locales = value.get("locales")
643
+ recovery = value.get("queue_recovery_pending")
644
+ locale_keys = {
645
+ "job_id", "target_locale", "status", "attempts", "max_attempts",
646
+ "next_attempt_at", "lease_expires_at", "lease_expired",
647
+ "last_error_code", "last_error_detail_hash", "result_sha256",
648
+ }
649
+ queue_statuses = {"pending", "leased", "retry_wait", "succeeded", "failed"}
650
+ synthetic_statuses = {"cancelled", "awaiting_queue_resume"}
651
+
652
+ def valid_locale(item: Any) -> bool:
653
+ if not isinstance(item, dict) or set(item) != locale_keys:
654
+ return False
655
+ status = item.get("status")
656
+ attempts = item.get("attempts")
657
+ maximum = item.get("max_attempts")
658
+ lease = item.get("lease_expires_at")
659
+ error = item.get("last_error_code")
660
+ synthetic = status in synthetic_statuses
661
+ return (
662
+ _valid_token(item.get("job_id"))
663
+ and _valid_token(item.get("target_locale"))
664
+ and status in queue_statuses | synthetic_statuses
665
+ and isinstance(attempts, int)
666
+ and not isinstance(attempts, bool)
667
+ and isinstance(maximum, int)
668
+ and not isinstance(maximum, bool)
669
+ and 0 <= attempts <= maximum
670
+ and ((synthetic and maximum == 0) or (not synthetic and maximum > 0))
671
+ and _valid_nonnegative_number(item.get("next_attempt_at"))
672
+ and (lease is None or _valid_nonnegative_number(lease))
673
+ and isinstance(item.get("lease_expired"), bool)
674
+ and (status == "leased") == (lease is not None)
675
+ and (error is None or (
676
+ isinstance(error, str) and ERROR_CODE.fullmatch(error) is not None
677
+ ))
678
+ and _valid_sha256(
679
+ item.get("last_error_detail_hash"), optional=True,
680
+ )
681
+ and _valid_sha256(item.get("result_sha256"), optional=True)
682
+ and (status == "succeeded")
683
+ == (item.get("result_sha256") is not None)
684
+ )
685
+
686
+ return (
687
+ _valid_token(value.get("plan_id"))
688
+ and _valid_token(value.get("website_version"))
689
+ and isinstance(value.get("source_sequence"), int)
690
+ and not isinstance(value.get("source_sequence"), bool)
691
+ and value["source_sequence"] > 0
692
+ and isinstance(value.get("job_count"), int)
693
+ and not isinstance(value.get("job_count"), bool)
694
+ and value["job_count"] >= 0
695
+ and isinstance(counts, dict)
696
+ and set(counts) in (queue_statuses, queue_statuses | {"cancelled"})
697
+ and ("cancelled" not in counts or value.get("cancelled") is True)
698
+ and all(
699
+ isinstance(count, int)
700
+ and not isinstance(count, bool)
701
+ and count >= 0
702
+ for count in counts.values()
703
+ )
704
+ and isinstance(recovery, bool)
705
+ and sum(counts.values()) == (0 if recovery else value["job_count"])
706
+ and isinstance(locales, list)
707
+ and len(locales) == value["job_count"]
708
+ and all(valid_locale(item) for item in locales)
709
+ and [item["target_locale"] for item in locales]
710
+ == sorted({item["target_locale"] for item in locales})
711
+ and isinstance(value.get("cancelled"), bool)
712
+ and not (value["cancelled"] and recovery)
713
+ and all(
714
+ item["status"] == "awaiting_queue_resume" for item in locales
715
+ ) == recovery
716
+ and (
717
+ all(item["status"] != "cancelled" for item in locales)
718
+ or value["cancelled"]
719
+ )
720
+ )
721
+
722
+ @staticmethod
723
+ def _valid_lifecycle(value: Mapping[str, Any]) -> bool:
724
+ required = value.get("required_locales")
725
+ approved = value.get("approved_locales")
726
+ blocked = value.get("blocked_locales")
727
+ counts = value.get("queue_counts")
728
+ statuses = {
729
+ "queue_recovery", "cancelled", "deleted", "deletion_failed",
730
+ "deleting", "published", "publication_failed",
731
+ "publication_blocked", "publishing", "ready",
732
+ "localization_failed", "awaiting_approval", "processing",
733
+ }
734
+ queue_statuses = {"pending", "leased", "retry_wait", "succeeded", "failed"}
735
+
736
+ def valid_attempt_state(item: Any, *, tombstone: bool) -> bool:
737
+ keys = {
738
+ "delivery_id", "status", "attempts", "max_attempts",
739
+ "next_attempt_at", "lease_expires_at", "lease_expired",
740
+ "last_error_code", "last_error_detail_hash",
741
+ }
742
+ if tombstone:
743
+ keys.add("tombstone_id")
744
+ if not isinstance(item, dict) or set(item) != keys:
745
+ return False
746
+ status = item.get("status")
747
+ attempts = item.get("attempts")
748
+ maximum = item.get("max_attempts")
749
+ lease = item.get("lease_expires_at")
750
+ error = item.get("last_error_code")
751
+ return (
752
+ _valid_token(item.get("delivery_id"))
753
+ and (not tombstone or _valid_token(item.get("tombstone_id")))
754
+ and status in queue_statuses
755
+ and isinstance(attempts, int)
756
+ and not isinstance(attempts, bool)
757
+ and isinstance(maximum, int)
758
+ and not isinstance(maximum, bool)
759
+ and maximum >= 1
760
+ and 0 <= attempts <= maximum
761
+ and _valid_nonnegative_number(item.get("next_attempt_at"))
762
+ and (lease is None or _valid_nonnegative_number(lease))
763
+ and isinstance(item.get("lease_expired"), bool)
764
+ and (status == "leased") == (lease is not None)
765
+ and (error is None or (
766
+ isinstance(error, str) and ERROR_CODE.fullmatch(error) is not None
767
+ ))
768
+ and _valid_sha256(
769
+ item.get("last_error_detail_hash"), optional=True,
770
+ )
771
+ and (status in {"retry_wait", "failed"}) == (error is not None)
772
+ )
773
+
774
+ return (
775
+ _valid_token(value.get("plan_id"))
776
+ and _valid_token(value.get("website_version"))
777
+ and isinstance(value.get("source_sequence"), int)
778
+ and not isinstance(value.get("source_sequence"), bool)
779
+ and value["source_sequence"] > 0
780
+ and value.get("status") in statuses
781
+ and isinstance(required, list)
782
+ and all(_valid_token(item) for item in required)
783
+ and required == sorted(set(required))
784
+ and isinstance(approved, list)
785
+ and all(_valid_token(item) for item in approved)
786
+ and approved == sorted(set(approved))
787
+ and set(approved).issubset(required)
788
+ and isinstance(blocked, list)
789
+ and all(
790
+ isinstance(item, list)
791
+ and len(item) == 2
792
+ and item[0] in required
793
+ and _valid_token(item[0])
794
+ and isinstance(item[1], str)
795
+ and ERROR_CODE.fullmatch(item[1]) is not None
796
+ for item in blocked
797
+ )
798
+ and blocked == [list(item) for item in sorted({tuple(item) for item in blocked})]
799
+ and isinstance(counts, dict)
800
+ and set(counts) in (queue_statuses, queue_statuses | {"cancelled"})
801
+ and ("cancelled" not in counts or value.get("status") == "cancelled")
802
+ and all(
803
+ isinstance(count, int)
804
+ and not isinstance(count, bool)
805
+ and count >= 0
806
+ for count in counts.values()
807
+ )
808
+ and (
809
+ (value.get("status") == "queue_recovery" and sum(counts.values()) == 0)
810
+ or (value.get("status") != "queue_recovery" and sum(counts.values()) == len(required))
811
+ )
812
+ and (value.get("delivery") is None or valid_attempt_state(
813
+ value["delivery"], tombstone=False,
814
+ ))
815
+ and (value.get("tombstone") is None or valid_attempt_state(
816
+ value["tombstone"], tombstone=True,
817
+ ))
818
+ )
819
+
820
+ @classmethod
821
+ def _valid_capabilities(cls, value: Mapping[str, Any]) -> bool:
822
+ capabilities = value.get("capabilities")
823
+ contract = value.get("api_contract")
824
+ if not isinstance(capabilities, dict) or not isinstance(contract, dict):
825
+ return False
826
+ unsigned_capabilities = dict(capabilities)
827
+ digest = unsigned_capabilities.pop("sha256", None)
828
+ unsigned_contract = dict(contract)
829
+ contract_digest = unsigned_contract.pop("sha256", None)
830
+ if (
831
+ not isinstance(digest, str)
832
+ or SHA256.fullmatch(digest) is None
833
+ or digest
834
+ != hashlib.sha256(
835
+ _canonical_json(unsigned_capabilities, code="response_binding")
836
+ ).hexdigest()
837
+ or not isinstance(contract_digest, str)
838
+ or SHA256.fullmatch(contract_digest) is None
839
+ or contract_digest
840
+ != hashlib.sha256(
841
+ _canonical_json(unsigned_contract, code="response_binding")
842
+ ).hexdigest()
843
+ or set(contract) != {
844
+ "schema", "api_schema", "error_schema", "operations", "sha256",
845
+ }
846
+ or contract.get("schema") != _API.API_CAPABILITIES_SCHEMA
847
+ or contract.get("api_schema") != _API.API_SCHEMA
848
+ or contract.get("error_schema") != _API.API_SCHEMA
849
+ ):
850
+ return False
851
+ operations = contract.get("operations")
852
+ if not isinstance(operations, list) or len(operations) != len(cls._OPERATIONS):
853
+ return False
854
+ for item, (name, (path, request_schema)) in zip(
855
+ operations, cls._OPERATIONS.items(),
856
+ ):
857
+ if (
858
+ not isinstance(item, dict)
859
+ or set(item) != {
860
+ "name", "method", "path", "request_schema",
861
+ "response_schema", "enabled",
862
+ }
863
+ or item.get("name") != name
864
+ or item.get("method") != "POST"
865
+ or item.get("path") != path
866
+ or item.get("request_schema") != request_schema
867
+ or item.get("response_schema")
868
+ != (
869
+ _API.LIFECYCLE_RESPONSE_SCHEMA
870
+ if name == "lifecycle" else _API.API_SCHEMA
871
+ )
872
+ or not isinstance(item.get("enabled"), bool)
873
+ ):
874
+ return False
875
+ return True