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