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,805 @@
1
+ #!/usr/bin/env python3
2
+ """Durable source-side outbox for CMS localization change events.
3
+
4
+ The website host owns the SQLite connection and the HTTPS client. One leased
5
+ attempt submits one immutable event. A crash after remote acceptance safely
6
+ replays the same event identity after lease expiry; the server remains the
7
+ idempotency authority.
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 sqlite3
19
+ import sys
20
+ from contextlib import contextmanager
21
+ from dataclasses import dataclass, field
22
+ from pathlib import Path
23
+ from typing import Any, Iterator, Mapping
24
+
25
+
26
+ SCHEMA_VERSION = 1
27
+ MAX_ATTEMPTS = 20
28
+ MAX_LEASE_SECONDS = 86_400.0
29
+ MAX_DELAY_SECONDS = 86_400.0
30
+ STATUSES = ("pending", "leased", "retry_wait", "succeeded", "failed")
31
+ TOKEN = re.compile(r"^[A-Za-z0-9_.:-]{1,256}$")
32
+ SHA256 = re.compile(r"^[0-9a-f]{64}$")
33
+ ERROR_CODE = re.compile(r"^[a-z][a-z0-9_.-]{0,127}$")
34
+ _COLUMNS = (
35
+ "event_id", "payload_json", "payload_sha256", "status", "attempts",
36
+ "max_attempts", "next_attempt_at", "lease_owner", "lease_token",
37
+ "lease_expires_at", "last_error_code", "last_error_detail_hash",
38
+ "remote_plan_id", "remote_job_count", "remote_status",
39
+ "response_sha256", "created_at", "updated_at",
40
+ )
41
+ _META_COLUMNS = ("singleton", "schema_version")
42
+
43
+
44
+ def _load_module(name: str, path: Path):
45
+ spec = importlib.util.spec_from_file_location(name, path)
46
+ if spec is None or spec.loader is None:
47
+ raise RuntimeError(f"cannot load CMS dispatch dependency: {path.name}")
48
+ module = importlib.util.module_from_spec(spec)
49
+ sys.modules[spec.name] = module
50
+ spec.loader.exec_module(module)
51
+ return module
52
+
53
+
54
+ _ROOT = Path(__file__).resolve().parents[1]
55
+ _CLIENT = _load_module(
56
+ "blun_website_localization_cms_dispatch_client",
57
+ _ROOT / "integrations" / "website_localization_cms_client.py",
58
+ )
59
+
60
+
61
+ class CMSDispatchBlocked(RuntimeError):
62
+ """Stable, content-free failure of the durable source dispatcher."""
63
+
64
+ def __init__(self, code: str):
65
+ if not isinstance(code, str) or ERROR_CODE.fullmatch(code) is None:
66
+ raise ValueError("CMS dispatch error code is invalid")
67
+ super().__init__(code)
68
+ self.code = code
69
+
70
+
71
+ @dataclass(frozen=True)
72
+ class ChangeDispatchClaim:
73
+ event_id: str
74
+ payload_sha256: str
75
+ attempt: int
76
+ max_attempts: int
77
+ lease_owner: str
78
+ lease_token: str
79
+ lease_expires_at: float
80
+ payload_json: str = field(repr=False, compare=False)
81
+
82
+
83
+ @dataclass(frozen=True)
84
+ class ChangeDispatchStatus:
85
+ event_id: str
86
+ payload_sha256: str
87
+ status: str
88
+ attempts: int
89
+ max_attempts: int
90
+ next_attempt_at: float
91
+ lease_expires_at: float | None
92
+ lease_expired: bool
93
+ last_error_code: str | None
94
+ last_error_detail_hash: str | None
95
+ remote_plan_id: str | None
96
+ remote_job_count: int | None
97
+ remote_status: str | None
98
+ response_sha256: str | None
99
+
100
+
101
+ @dataclass(frozen=True)
102
+ class ChangeDispatchOutcome:
103
+ event_id: str
104
+ status: str
105
+ attempt: int
106
+ max_attempts: int
107
+ next_attempt_at: float
108
+ error_code: str | None
109
+
110
+
111
+ @dataclass(frozen=True)
112
+ class ChangeDispatchHealth:
113
+ status: str
114
+ counts: dict[str, int]
115
+ due: int
116
+ expired_leases: int
117
+ failed: int
118
+
119
+
120
+ def _token(value: Any, code: str, *, limit: int = 256) -> str:
121
+ if (
122
+ not isinstance(value, str)
123
+ or len(value) > limit
124
+ or TOKEN.fullmatch(value) is None
125
+ ):
126
+ raise CMSDispatchBlocked(code)
127
+ return value
128
+
129
+
130
+ def _timestamp(value: Any, code: str) -> float:
131
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
132
+ raise CMSDispatchBlocked(code)
133
+ result = float(value)
134
+ if result < 0 or not math.isfinite(result):
135
+ raise CMSDispatchBlocked(code)
136
+ return result
137
+
138
+
139
+ def _duration(value: Any, code: str, *, maximum: float) -> float:
140
+ result = _timestamp(value, code)
141
+ if result <= 0 or result > maximum:
142
+ raise CMSDispatchBlocked(code)
143
+ return result
144
+
145
+
146
+ def _canonical_json(value: Any, code: str) -> str:
147
+ try:
148
+ result = json.dumps(
149
+ value, ensure_ascii=False, allow_nan=False, sort_keys=True,
150
+ separators=(",", ":"),
151
+ )
152
+ except (TypeError, ValueError, RecursionError):
153
+ raise CMSDispatchBlocked(code) from None
154
+ if not result or len(result.encode("utf-8")) > _CLIENT._API.MAX_MESSAGE_BYTES:
155
+ raise CMSDispatchBlocked(code)
156
+ return result
157
+
158
+
159
+ def _hash(value: str) -> str:
160
+ return hashlib.sha256(value.encode("utf-8")).hexdigest()
161
+
162
+
163
+ def _change(value: Any) -> tuple[str, str, str]:
164
+ if not isinstance(value, Mapping):
165
+ raise CMSDispatchBlocked("dispatch.change_invalid")
166
+ payload_json = _canonical_json(dict(value), "dispatch.change_invalid")
167
+ try:
168
+ copied = json.loads(payload_json)
169
+ except (json.JSONDecodeError, RecursionError):
170
+ raise CMSDispatchBlocked("dispatch.change_invalid") from None
171
+ if (
172
+ not isinstance(copied, dict)
173
+ or copied.get("schema") != _CLIENT._CMS.CHANGE_SCHEMA
174
+ ):
175
+ raise CMSDispatchBlocked("dispatch.change_invalid")
176
+ try:
177
+ _CLIENT._CMS.WebsiteLocalizationCMSBridge._validated_event(None, copied)
178
+ except Exception:
179
+ raise CMSDispatchBlocked("dispatch.change_invalid") from None
180
+ event_id = _token(copied.get("event_id"), "dispatch.change_invalid")
181
+ return event_id, payload_json, _hash(payload_json)
182
+
183
+
184
+ def _error(code: Any, detail_hash: Any = None) -> tuple[str, str | None]:
185
+ if not isinstance(code, str) or ERROR_CODE.fullmatch(code) is None:
186
+ raise CMSDispatchBlocked("dispatch.error_invalid")
187
+ if detail_hash is not None and (
188
+ not isinstance(detail_hash, str) or SHA256.fullmatch(detail_hash) is None
189
+ ):
190
+ raise CMSDispatchBlocked("dispatch.error_invalid")
191
+ return code, detail_hash
192
+
193
+
194
+ @contextmanager
195
+ def _transaction(connection: sqlite3.Connection) -> Iterator[None]:
196
+ if connection.in_transaction:
197
+ raise CMSDispatchBlocked("dispatch.transaction_nested")
198
+ try:
199
+ connection.execute("BEGIN IMMEDIATE")
200
+ yield
201
+ connection.commit()
202
+ except Exception:
203
+ if connection.in_transaction:
204
+ connection.rollback()
205
+ raise
206
+
207
+
208
+ class DurableCMSChangeDispatcher:
209
+ """Persist, lease, and submit exact CMS change events at least once."""
210
+
211
+ def __init__(
212
+ self,
213
+ connection: sqlite3.Connection,
214
+ *,
215
+ base_delay_seconds: float | int = 5,
216
+ max_delay_seconds: float | int = 300,
217
+ ):
218
+ if not isinstance(connection, sqlite3.Connection):
219
+ raise TypeError("connection must be sqlite3.Connection")
220
+ self.connection = connection
221
+ self.connection.row_factory = sqlite3.Row
222
+ self.base_delay_seconds = _duration(
223
+ base_delay_seconds, "dispatch.delay_invalid",
224
+ maximum=MAX_DELAY_SECONDS,
225
+ )
226
+ self.max_delay_seconds = _duration(
227
+ max_delay_seconds, "dispatch.delay_invalid",
228
+ maximum=MAX_DELAY_SECONDS,
229
+ )
230
+ if self.base_delay_seconds > self.max_delay_seconds:
231
+ raise CMSDispatchBlocked("dispatch.delay_invalid")
232
+ self._initialize()
233
+
234
+ def _initialize(self) -> None:
235
+ with _transaction(self.connection):
236
+ self.connection.execute("""
237
+ CREATE TABLE IF NOT EXISTS cms_source_change_outbox_meta (
238
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
239
+ schema_version INTEGER NOT NULL
240
+ CHECK (schema_version = 1)
241
+ )
242
+ """)
243
+ self.connection.execute("""
244
+ INSERT OR IGNORE INTO cms_source_change_outbox_meta
245
+ VALUES (1, 1)
246
+ """)
247
+ self.connection.execute("""
248
+ CREATE TABLE IF NOT EXISTS cms_source_change_outbox (
249
+ event_id TEXT PRIMARY KEY,
250
+ payload_json TEXT NOT NULL,
251
+ payload_sha256 TEXT NOT NULL,
252
+ status TEXT NOT NULL CHECK (
253
+ status IN ('pending', 'leased', 'retry_wait',
254
+ 'succeeded', 'failed')
255
+ ),
256
+ attempts INTEGER NOT NULL CHECK (attempts >= 0),
257
+ max_attempts INTEGER NOT NULL CHECK (
258
+ max_attempts >= 1 AND max_attempts <= 20
259
+ ),
260
+ next_attempt_at REAL NOT NULL,
261
+ lease_owner TEXT,
262
+ lease_token TEXT,
263
+ lease_expires_at REAL,
264
+ last_error_code TEXT,
265
+ last_error_detail_hash TEXT,
266
+ remote_plan_id TEXT,
267
+ remote_job_count INTEGER,
268
+ remote_status TEXT,
269
+ response_sha256 TEXT,
270
+ created_at REAL NOT NULL,
271
+ updated_at REAL NOT NULL,
272
+ CHECK (
273
+ (status = 'leased' AND lease_owner IS NOT NULL
274
+ AND lease_token IS NOT NULL
275
+ AND lease_expires_at IS NOT NULL)
276
+ OR
277
+ (status <> 'leased' AND lease_owner IS NULL
278
+ AND lease_token IS NULL
279
+ AND lease_expires_at IS NULL)
280
+ )
281
+ )
282
+ """)
283
+ self.connection.execute("""
284
+ CREATE INDEX IF NOT EXISTS cms_source_change_outbox_due
285
+ ON cms_source_change_outbox (
286
+ status, next_attempt_at, created_at, event_id
287
+ )
288
+ """)
289
+ self._validate_schema()
290
+
291
+ def _validate_schema(self) -> None:
292
+ meta_columns = tuple(
293
+ row["name"] for row in self.connection.execute(
294
+ "PRAGMA table_info(cms_source_change_outbox_meta)"
295
+ ).fetchall()
296
+ )
297
+ meta = self.connection.execute(
298
+ "SELECT singleton, schema_version FROM cms_source_change_outbox_meta"
299
+ ).fetchall()
300
+ columns = tuple(
301
+ row["name"] for row in self.connection.execute(
302
+ "PRAGMA table_info(cms_source_change_outbox)"
303
+ ).fetchall()
304
+ )
305
+ if (
306
+ meta_columns != _META_COLUMNS
307
+ or len(meta) != 1
308
+ or tuple(meta[0]) != (1, SCHEMA_VERSION)
309
+ or columns != _COLUMNS
310
+ ):
311
+ raise CMSDispatchBlocked("dispatch.schema_altered")
312
+
313
+ def enqueue(
314
+ self,
315
+ change: Mapping[str, Any],
316
+ *,
317
+ max_attempts: int = 5,
318
+ now: float | int,
319
+ ) -> ChangeDispatchStatus:
320
+ self._validate_schema()
321
+ if (
322
+ isinstance(max_attempts, bool)
323
+ or not isinstance(max_attempts, int)
324
+ or not 1 <= max_attempts <= MAX_ATTEMPTS
325
+ ):
326
+ raise CMSDispatchBlocked("dispatch.max_attempts_invalid")
327
+ now = _timestamp(now, "dispatch.time_invalid")
328
+ event_id, payload_json, payload_sha256 = _change(change)
329
+ with _transaction(self.connection):
330
+ row = self.connection.execute(
331
+ "SELECT payload_sha256, max_attempts FROM cms_source_change_outbox "
332
+ "WHERE event_id = ?",
333
+ (event_id,),
334
+ ).fetchone()
335
+ if row is not None:
336
+ if (
337
+ row["payload_sha256"] != payload_sha256
338
+ or row["max_attempts"] != max_attempts
339
+ ):
340
+ raise CMSDispatchBlocked("dispatch.idempotency_collision")
341
+ else:
342
+ self.connection.execute("""
343
+ INSERT INTO cms_source_change_outbox (
344
+ event_id, payload_json, payload_sha256, status,
345
+ attempts, max_attempts, next_attempt_at,
346
+ created_at, updated_at
347
+ ) VALUES (?, ?, ?, 'pending', 0, ?, ?, ?, ?)
348
+ """, (
349
+ event_id, payload_json, payload_sha256, max_attempts,
350
+ now, now, now,
351
+ ))
352
+ return self.status(event_id, now=now)
353
+
354
+ def claim(
355
+ self,
356
+ worker_id: str,
357
+ *,
358
+ now: float | int,
359
+ lease_seconds: float | int = 600,
360
+ ) -> ChangeDispatchClaim | None:
361
+ self._validate_schema()
362
+ worker_id = _token(worker_id, "dispatch.worker_invalid", limit=128)
363
+ now = _timestamp(now, "dispatch.time_invalid")
364
+ lease_seconds = _duration(
365
+ lease_seconds, "dispatch.lease_invalid", maximum=MAX_LEASE_SECONDS,
366
+ )
367
+ claim = None
368
+ integrity_error = None
369
+ with _transaction(self.connection):
370
+ self.connection.execute("""
371
+ UPDATE cms_source_change_outbox
372
+ SET status = 'failed', lease_owner = NULL, lease_token = NULL,
373
+ lease_expires_at = NULL, last_error_code = 'lease_expired',
374
+ last_error_detail_hash = NULL, updated_at = ?
375
+ WHERE status = 'leased' AND lease_expires_at <= ?
376
+ AND attempts >= max_attempts
377
+ """, (now, now))
378
+ self.connection.execute("""
379
+ UPDATE cms_source_change_outbox
380
+ SET status = 'retry_wait', next_attempt_at = ?,
381
+ lease_owner = NULL, lease_token = NULL,
382
+ lease_expires_at = NULL,
383
+ last_error_code = 'lease_expired',
384
+ last_error_detail_hash = NULL, updated_at = ?
385
+ WHERE status = 'leased' AND lease_expires_at <= ?
386
+ AND attempts < max_attempts
387
+ """, (now, now, now))
388
+ row = self.connection.execute("""
389
+ SELECT * FROM cms_source_change_outbox
390
+ WHERE status IN ('pending', 'retry_wait')
391
+ AND next_attempt_at <= ? AND attempts < max_attempts
392
+ ORDER BY created_at, event_id LIMIT 1
393
+ """, (now,)).fetchone()
394
+ if row is not None:
395
+ try:
396
+ self._validated_row(row)
397
+ except CMSDispatchBlocked:
398
+ self.connection.execute("""
399
+ UPDATE cms_source_change_outbox
400
+ SET status = 'failed', last_error_code =
401
+ 'dispatch.payload_integrity', updated_at = ?
402
+ WHERE event_id = ?
403
+ """, (now, row["event_id"]))
404
+ integrity_error = "dispatch.payload_integrity"
405
+ else:
406
+ lease_token = secrets.token_urlsafe(32)
407
+ lease_expires_at = now + lease_seconds
408
+ updated = self.connection.execute("""
409
+ UPDATE cms_source_change_outbox
410
+ SET status = 'leased', attempts = attempts + 1,
411
+ lease_owner = ?, lease_token = ?,
412
+ lease_expires_at = ?, last_error_code = NULL,
413
+ last_error_detail_hash = NULL, updated_at = ?
414
+ WHERE event_id = ?
415
+ AND status IN ('pending', 'retry_wait')
416
+ """, (
417
+ worker_id, lease_token, lease_expires_at, now,
418
+ row["event_id"],
419
+ ))
420
+ if updated.rowcount != 1:
421
+ raise CMSDispatchBlocked("dispatch.claim_lost")
422
+ claim = ChangeDispatchClaim(
423
+ event_id=row["event_id"],
424
+ payload_sha256=row["payload_sha256"],
425
+ attempt=int(row["attempts"]) + 1,
426
+ max_attempts=int(row["max_attempts"]),
427
+ lease_owner=worker_id,
428
+ lease_token=lease_token,
429
+ lease_expires_at=lease_expires_at,
430
+ payload_json=row["payload_json"],
431
+ )
432
+ if integrity_error is not None:
433
+ raise CMSDispatchBlocked(integrity_error)
434
+ return claim
435
+
436
+ def run_once(
437
+ self,
438
+ client: Any,
439
+ worker_id: str,
440
+ *,
441
+ now: float | int,
442
+ lease_seconds: float | int = 600,
443
+ ) -> ChangeDispatchOutcome | None:
444
+ submit = getattr(client, "submit_change", None)
445
+ if not callable(submit):
446
+ raise CMSDispatchBlocked("dispatch.client_invalid")
447
+ lease_seconds = _duration(
448
+ lease_seconds, "dispatch.lease_invalid", maximum=MAX_LEASE_SECONDS,
449
+ )
450
+ timeout = getattr(client, "timeout", None)
451
+ if (
452
+ timeout is not None
453
+ and (
454
+ isinstance(timeout, bool)
455
+ or not isinstance(timeout, (int, float))
456
+ or not math.isfinite(float(timeout))
457
+ or timeout <= 0
458
+ )
459
+ ):
460
+ raise CMSDispatchBlocked("dispatch.client_invalid")
461
+ if timeout is not None and lease_seconds <= float(timeout):
462
+ raise CMSDispatchBlocked("dispatch.lease_too_short")
463
+ claim = self.claim(
464
+ worker_id, now=now, lease_seconds=lease_seconds,
465
+ )
466
+ if claim is None:
467
+ return None
468
+ try:
469
+ response = submit(json.loads(claim.payload_json))
470
+ status = self.complete(claim, response, now=now)
471
+ except CMSDispatchBlocked as error:
472
+ if error.code != "dispatch.response_invalid":
473
+ raise
474
+ status = self.fail(claim, error.code, now=now)
475
+ except Exception as error:
476
+ if getattr(error, "cms_client_failure", False) is True:
477
+ code = getattr(error, "code", None)
478
+ retryable = getattr(error, "retryable", None)
479
+ try:
480
+ code, _ = _error(code)
481
+ except CMSDispatchBlocked:
482
+ code, retryable = "dispatch.client_failure", False
483
+ if retryable is True:
484
+ status = self.retry(claim, code, now=now)
485
+ else:
486
+ status = self.fail(claim, code, now=now)
487
+ else:
488
+ status = self.fail(
489
+ claim, "dispatch.client_failure", now=now,
490
+ )
491
+ return ChangeDispatchOutcome(
492
+ event_id=status.event_id,
493
+ status=status.status,
494
+ attempt=status.attempts,
495
+ max_attempts=status.max_attempts,
496
+ next_attempt_at=status.next_attempt_at,
497
+ error_code=status.last_error_code,
498
+ )
499
+
500
+ def complete(
501
+ self,
502
+ claim: ChangeDispatchClaim,
503
+ response: Any,
504
+ *,
505
+ now: float | int,
506
+ ) -> ChangeDispatchStatus:
507
+ self._validate_schema()
508
+ claim = self._claim(claim)
509
+ now = _timestamp(now, "dispatch.time_invalid")
510
+ if not isinstance(response, Mapping):
511
+ raise CMSDispatchBlocked("dispatch.response_invalid")
512
+ response_json = _canonical_json(
513
+ dict(response), "dispatch.response_invalid",
514
+ )
515
+ copied = json.loads(response_json)
516
+ if (
517
+ set(copied) != {
518
+ "schema", "event_id", "plan_id", "job_count",
519
+ "inserted_jobs", "status",
520
+ }
521
+ or copied.get("schema") != _CLIENT._API.API_SCHEMA
522
+ or copied.get("event_id") != claim.event_id
523
+ or not isinstance(copied.get("plan_id"), str)
524
+ or TOKEN.fullmatch(copied["plan_id"]) is None
525
+ or isinstance(copied.get("job_count"), bool)
526
+ or not isinstance(copied.get("job_count"), int)
527
+ or copied["job_count"] <= 0
528
+ or isinstance(copied.get("inserted_jobs"), bool)
529
+ or not isinstance(copied.get("inserted_jobs"), int)
530
+ or not 0 <= copied["inserted_jobs"] <= copied["job_count"]
531
+ or copied.get("status") not in {"enqueued", "superseded", "cancelled"}
532
+ ):
533
+ raise CMSDispatchBlocked("dispatch.response_invalid")
534
+ with _transaction(self.connection):
535
+ self._require_live_claim(claim, now)
536
+ updated = self.connection.execute("""
537
+ UPDATE cms_source_change_outbox
538
+ SET status = 'succeeded', next_attempt_at = ?,
539
+ lease_owner = NULL, lease_token = NULL,
540
+ lease_expires_at = NULL, last_error_code = NULL,
541
+ last_error_detail_hash = NULL, remote_plan_id = ?,
542
+ remote_job_count = ?, remote_status = ?,
543
+ response_sha256 = ?, updated_at = ?
544
+ WHERE event_id = ? AND status = 'leased'
545
+ AND lease_owner = ? AND lease_token = ?
546
+ """, (
547
+ now, copied["plan_id"], copied["job_count"], copied["status"],
548
+ _hash(response_json), now, claim.event_id,
549
+ claim.lease_owner, claim.lease_token,
550
+ ))
551
+ if updated.rowcount != 1:
552
+ raise CMSDispatchBlocked("dispatch.completion_lost")
553
+ return self.status(claim.event_id, now=now)
554
+
555
+ def retry(
556
+ self,
557
+ claim: ChangeDispatchClaim,
558
+ error_code: str,
559
+ *,
560
+ now: float | int,
561
+ ) -> ChangeDispatchStatus:
562
+ self._validate_schema()
563
+ claim = self._claim(claim)
564
+ error_code, detail_hash = _error(error_code)
565
+ now = _timestamp(now, "dispatch.time_invalid")
566
+ with _transaction(self.connection):
567
+ row = self._require_live_claim(claim, now)
568
+ terminal = int(row["attempts"]) >= int(row["max_attempts"])
569
+ status = "failed" if terminal else "retry_wait"
570
+ delay = min(
571
+ self.max_delay_seconds,
572
+ self.base_delay_seconds * (2 ** max(0, claim.attempt - 1)),
573
+ )
574
+ next_attempt_at = now if terminal else now + delay
575
+ self._finish_failure(
576
+ claim, status, error_code, detail_hash, next_attempt_at, now,
577
+ )
578
+ return self.status(claim.event_id, now=now)
579
+
580
+ def fail(
581
+ self,
582
+ claim: ChangeDispatchClaim,
583
+ error_code: str,
584
+ *,
585
+ now: float | int,
586
+ ) -> ChangeDispatchStatus:
587
+ self._validate_schema()
588
+ claim = self._claim(claim)
589
+ error_code, detail_hash = _error(error_code)
590
+ now = _timestamp(now, "dispatch.time_invalid")
591
+ with _transaction(self.connection):
592
+ self._require_live_claim(claim, now)
593
+ self._finish_failure(
594
+ claim, "failed", error_code, detail_hash, now, now,
595
+ )
596
+ return self.status(claim.event_id, now=now)
597
+
598
+ def _finish_failure(
599
+ self,
600
+ claim: ChangeDispatchClaim,
601
+ status: str,
602
+ error_code: str,
603
+ detail_hash: str | None,
604
+ next_attempt_at: float,
605
+ now: float,
606
+ ) -> None:
607
+ updated = self.connection.execute("""
608
+ UPDATE cms_source_change_outbox
609
+ SET status = ?, next_attempt_at = ?, lease_owner = NULL,
610
+ lease_token = NULL, lease_expires_at = NULL,
611
+ last_error_code = ?, last_error_detail_hash = ?,
612
+ updated_at = ?
613
+ WHERE event_id = ? AND status = 'leased'
614
+ AND lease_owner = ? AND lease_token = ?
615
+ """, (
616
+ status, next_attempt_at, error_code, detail_hash, now,
617
+ claim.event_id, claim.lease_owner, claim.lease_token,
618
+ ))
619
+ if updated.rowcount != 1:
620
+ raise CMSDispatchBlocked("dispatch.failure_lost")
621
+
622
+ def status(
623
+ self, event_id: str, *, now: float | int,
624
+ ) -> ChangeDispatchStatus:
625
+ self._validate_schema()
626
+ event_id = _token(event_id, "dispatch.event_invalid")
627
+ now = _timestamp(now, "dispatch.time_invalid")
628
+ row = self.connection.execute(
629
+ "SELECT * FROM cms_source_change_outbox WHERE event_id = ?",
630
+ (event_id,),
631
+ ).fetchone()
632
+ if row is None:
633
+ raise CMSDispatchBlocked("dispatch.event_missing")
634
+ self._validated_row(row)
635
+ lease_expires_at = row["lease_expires_at"]
636
+ return ChangeDispatchStatus(
637
+ event_id=row["event_id"],
638
+ payload_sha256=row["payload_sha256"],
639
+ status=row["status"],
640
+ attempts=int(row["attempts"]),
641
+ max_attempts=int(row["max_attempts"]),
642
+ next_attempt_at=float(row["next_attempt_at"]),
643
+ lease_expires_at=(
644
+ None if lease_expires_at is None else float(lease_expires_at)
645
+ ),
646
+ lease_expired=(
647
+ row["status"] == "leased"
648
+ and float(lease_expires_at) <= now
649
+ ),
650
+ last_error_code=row["last_error_code"],
651
+ last_error_detail_hash=row["last_error_detail_hash"],
652
+ remote_plan_id=row["remote_plan_id"],
653
+ remote_job_count=row["remote_job_count"],
654
+ remote_status=row["remote_status"],
655
+ response_sha256=row["response_sha256"],
656
+ )
657
+
658
+ def health(self, *, now: float | int) -> ChangeDispatchHealth:
659
+ now = _timestamp(now, "dispatch.time_invalid")
660
+ self._validate_schema()
661
+ quick = self.connection.execute("PRAGMA quick_check").fetchall()
662
+ if len(quick) != 1 or tuple(quick[0]) != ("ok",):
663
+ raise CMSDispatchBlocked("dispatch.database_integrity")
664
+ rows = self.connection.execute(
665
+ "SELECT * FROM cms_source_change_outbox ORDER BY event_id"
666
+ ).fetchall()
667
+ for row in rows:
668
+ self._validated_row(row)
669
+ counts = {status: 0 for status in STATUSES}
670
+ for row in rows:
671
+ counts[row["status"]] += 1
672
+ due = sum(
673
+ 1 for row in rows
674
+ if row["status"] in {"pending", "retry_wait"}
675
+ and row["next_attempt_at"] <= now
676
+ )
677
+ expired = sum(
678
+ 1 for row in rows
679
+ if row["status"] == "leased" and row["lease_expires_at"] <= now
680
+ )
681
+ failed = counts["failed"]
682
+ return ChangeDispatchHealth(
683
+ status="blocked" if expired or failed else "ok",
684
+ counts=counts,
685
+ due=due,
686
+ expired_leases=expired,
687
+ failed=failed,
688
+ )
689
+
690
+ def _validated_row(self, row: sqlite3.Row) -> None:
691
+ try:
692
+ event_id, payload_json, payload_sha256 = _change(
693
+ json.loads(row["payload_json"])
694
+ )
695
+ status = row["status"]
696
+ attempts = row["attempts"]
697
+ maximum = row["max_attempts"]
698
+ next_attempt_at = _timestamp(
699
+ row["next_attempt_at"], "dispatch.state_invalid",
700
+ )
701
+ created_at = _timestamp(
702
+ row["created_at"], "dispatch.state_invalid",
703
+ )
704
+ updated_at = _timestamp(
705
+ row["updated_at"], "dispatch.state_invalid",
706
+ )
707
+ except (json.JSONDecodeError, TypeError, CMSDispatchBlocked):
708
+ raise CMSDispatchBlocked("dispatch.state_invalid") from None
709
+ leases = (row["lease_owner"], row["lease_token"], row["lease_expires_at"])
710
+ leased = status == "leased"
711
+ remote = (
712
+ row["remote_plan_id"], row["remote_job_count"],
713
+ row["remote_status"], row["response_sha256"],
714
+ )
715
+ error = row["last_error_code"]
716
+ if (
717
+ event_id != row["event_id"]
718
+ or payload_json != row["payload_json"]
719
+ or payload_sha256 != row["payload_sha256"]
720
+ or status not in STATUSES
721
+ or isinstance(attempts, bool)
722
+ or not isinstance(attempts, int)
723
+ or isinstance(maximum, bool)
724
+ or not isinstance(maximum, int)
725
+ or not 0 <= attempts <= maximum <= MAX_ATTEMPTS
726
+ or maximum < 1
727
+ or next_attempt_at < created_at
728
+ or updated_at < created_at
729
+ or leased != all(item is not None for item in leases)
730
+ or (not leased and any(item is not None for item in leases))
731
+ or (leased and (
732
+ _token(leases[0], "dispatch.state_invalid", limit=128) != leases[0]
733
+ or _token(leases[1], "dispatch.state_invalid") != leases[1]
734
+ or _timestamp(leases[2], "dispatch.state_invalid") <= updated_at
735
+ ))
736
+ or (error is not None and (
737
+ not isinstance(error, str) or ERROR_CODE.fullmatch(error) is None
738
+ ))
739
+ or (status in {"retry_wait", "failed"}) != (error is not None)
740
+ or (
741
+ row["last_error_detail_hash"] is not None
742
+ and (
743
+ not isinstance(row["last_error_detail_hash"], str)
744
+ or SHA256.fullmatch(row["last_error_detail_hash"]) is None
745
+ )
746
+ )
747
+ or (status == "succeeded") != all(item is not None for item in remote)
748
+ or (status != "succeeded" and any(item is not None for item in remote))
749
+ or (status == "succeeded" and (
750
+ _token(remote[0], "dispatch.state_invalid") != remote[0]
751
+ or isinstance(remote[1], bool)
752
+ or not isinstance(remote[1], int)
753
+ or remote[1] <= 0
754
+ or remote[2] not in {"enqueued", "superseded", "cancelled"}
755
+ or not isinstance(remote[3], str)
756
+ or SHA256.fullmatch(remote[3]) is None
757
+ ))
758
+ ):
759
+ raise CMSDispatchBlocked("dispatch.state_invalid")
760
+
761
+ def _claim(self, claim: Any) -> ChangeDispatchClaim:
762
+ if not isinstance(claim, ChangeDispatchClaim):
763
+ raise CMSDispatchBlocked("dispatch.claim_invalid")
764
+ _token(claim.event_id, "dispatch.claim_invalid")
765
+ _token(claim.lease_owner, "dispatch.claim_invalid", limit=128)
766
+ _token(claim.lease_token, "dispatch.claim_invalid")
767
+ if (
768
+ not isinstance(claim.payload_sha256, str)
769
+ or SHA256.fullmatch(claim.payload_sha256) is None
770
+ or _hash(claim.payload_json) != claim.payload_sha256
771
+ or isinstance(claim.attempt, bool)
772
+ or not isinstance(claim.attempt, int)
773
+ or isinstance(claim.max_attempts, bool)
774
+ or not isinstance(claim.max_attempts, int)
775
+ or not 1 <= claim.attempt <= claim.max_attempts <= MAX_ATTEMPTS
776
+ ):
777
+ raise CMSDispatchBlocked("dispatch.claim_invalid")
778
+ _timestamp(claim.lease_expires_at, "dispatch.claim_invalid")
779
+ return claim
780
+
781
+ def _require_live_claim(
782
+ self, claim: ChangeDispatchClaim, now: float,
783
+ ) -> sqlite3.Row:
784
+ row = self.connection.execute(
785
+ "SELECT * FROM cms_source_change_outbox WHERE event_id = ?",
786
+ (claim.event_id,),
787
+ ).fetchone()
788
+ if row is None:
789
+ raise CMSDispatchBlocked("dispatch.claim_missing")
790
+ self._validated_row(row)
791
+ if (
792
+ row["status"] != "leased"
793
+ or row["payload_sha256"] != claim.payload_sha256
794
+ or row["attempts"] != claim.attempt
795
+ or row["max_attempts"] != claim.max_attempts
796
+ or row["lease_owner"] != claim.lease_owner
797
+ or row["lease_token"] != claim.lease_token
798
+ or row["lease_expires_at"] != claim.lease_expires_at
799
+ ):
800
+ raise CMSDispatchBlocked("dispatch.claim_lost")
801
+ if row["lease_expires_at"] <= now:
802
+ raise CMSDispatchBlocked("dispatch.lease_expired")
803
+ if now < row["updated_at"]:
804
+ raise CMSDispatchBlocked("dispatch.clock_regressed")
805
+ return row