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,674 @@
1
+ #!/usr/bin/env python3
2
+ """Durable, content-free terminal notifications for a source CMS.
3
+
4
+ The localization lifecycle remains authoritative. This outbox copies only the
5
+ verified terminal binding and delivers it through a host-supplied callback.
6
+ Lost responses are replayed with the same notification identity.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ import math
14
+ import re
15
+ import secrets
16
+ import sqlite3
17
+ from contextlib import contextmanager
18
+ from dataclasses import dataclass, field
19
+ from typing import Any, Callable, Iterator, Mapping
20
+
21
+
22
+ SCHEMA_VERSION = 1
23
+ NOTIFICATION_SCHEMA = "blun.cms-source-terminal-notification.v1"
24
+ ACK_SCHEMA = "blun.cms-source-terminal-notification-ack.v1"
25
+ STATUSES = ("pending", "leased", "retry_wait", "succeeded", "failed")
26
+ TERMINAL_STATUSES = {
27
+ "cancelled", "deleted", "deletion_failed", "localization_failed",
28
+ "publication_blocked", "publication_failed", "published", "superseded",
29
+ }
30
+ ERROR_CODE = re.compile(r"^[a-z][a-z0-9_.-]{0,127}$")
31
+ _COLUMNS = (
32
+ "event_id", "notification_id", "payload_json", "payload_sha256",
33
+ "status", "attempts", "max_attempts", "next_attempt_at",
34
+ "lease_owner", "lease_token", "lease_expires_at", "last_error_code",
35
+ "ack_sha256", "created_at", "updated_at",
36
+ )
37
+
38
+
39
+ class TerminalNotificationBlocked(RuntimeError):
40
+ """Stable notification failure without customer content."""
41
+
42
+ def __init__(self, code: str):
43
+ super().__init__(code)
44
+ self.code = code
45
+
46
+
47
+ class TerminalNotificationFailure(RuntimeError):
48
+ """Public callback failure contract used to request a bounded retry."""
49
+
50
+ cms_notification_failure = True
51
+
52
+ def __init__(self, code: str, *, retryable: bool):
53
+ if not _error(code) or not isinstance(retryable, bool):
54
+ raise ValueError("invalid terminal notification failure")
55
+ super().__init__(code)
56
+ self.code = code
57
+ self.retryable = retryable
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class TerminalNotificationClaim:
62
+ event_id: str
63
+ notification_id: str
64
+ payload_sha256: str
65
+ attempt: int
66
+ max_attempts: int
67
+ lease_owner: str
68
+ lease_token: str
69
+ lease_expires_at: float
70
+ payload_json: str = field(repr=False, compare=False)
71
+
72
+
73
+ @dataclass(frozen=True)
74
+ class TerminalNotificationStatus:
75
+ event_id: str
76
+ notification_id: str
77
+ payload_sha256: str
78
+ status: str
79
+ attempts: int
80
+ max_attempts: int
81
+ next_attempt_at: float
82
+ lease_expires_at: float | None
83
+ lease_expired: bool
84
+ last_error_code: str | None
85
+ ack_sha256: str | None
86
+
87
+
88
+ @dataclass(frozen=True)
89
+ class TerminalNotificationOutcome:
90
+ event_id: str
91
+ notification_id: str
92
+ status: str
93
+ attempt: int
94
+ error_code: str | None
95
+
96
+
97
+ @dataclass(frozen=True)
98
+ class TerminalNotificationHealth:
99
+ status: str
100
+ counts: dict[str, int]
101
+ due: int
102
+ expired_leases: int
103
+ failed: int
104
+
105
+
106
+ def _canonical(value: Any) -> str:
107
+ try:
108
+ return json.dumps(
109
+ value, ensure_ascii=False, allow_nan=False, sort_keys=True,
110
+ separators=(",", ":"),
111
+ )
112
+ except (TypeError, ValueError, RecursionError):
113
+ raise TerminalNotificationBlocked("terminal_notification.payload_invalid") from None
114
+
115
+
116
+ def _hash(value: str) -> str:
117
+ return hashlib.sha256(value.encode("utf-8")).hexdigest()
118
+
119
+
120
+ def _token(value: Any, *, limit: int = 256) -> str:
121
+ if (
122
+ not isinstance(value, str) or not 1 <= len(value) <= limit
123
+ or any(
124
+ not (
125
+ "A" <= character <= "Z" or "a" <= character <= "z"
126
+ or "0" <= character <= "9" or character in "_.:-"
127
+ )
128
+ for character in value
129
+ )
130
+ ):
131
+ raise TerminalNotificationBlocked("terminal_notification.payload_invalid")
132
+ return value
133
+
134
+
135
+ def _sha(value: Any) -> str:
136
+ if (
137
+ not isinstance(value, str) or len(value) != 64
138
+ or any(character not in "0123456789abcdef" for character in value)
139
+ ):
140
+ raise TerminalNotificationBlocked("terminal_notification.payload_invalid")
141
+ return value
142
+
143
+
144
+ def _error(value: Any) -> bool:
145
+ return isinstance(value, str) and ERROR_CODE.fullmatch(value) is not None
146
+
147
+
148
+ def _time(value: Any) -> float:
149
+ if (
150
+ isinstance(value, bool) or not isinstance(value, (int, float))
151
+ or not math.isfinite(float(value)) or float(value) < 0
152
+ ):
153
+ raise TerminalNotificationBlocked("terminal_notification.time_invalid")
154
+ return float(value)
155
+
156
+
157
+ def _duration(value: Any, code: str) -> float:
158
+ result = _time(value)
159
+ if not 0 < result <= 86_400:
160
+ raise TerminalNotificationBlocked(code)
161
+ return result
162
+
163
+
164
+ def _payload(lifecycle: Any) -> tuple[str, str, str]:
165
+ def value(name: str) -> Any:
166
+ return (
167
+ lifecycle.get(name)
168
+ if isinstance(lifecycle, Mapping)
169
+ else getattr(lifecycle, name, None)
170
+ )
171
+
172
+ if (
173
+ value("state") != "terminal"
174
+ or value("remote_status") not in TERMINAL_STATUSES
175
+ ):
176
+ raise TerminalNotificationBlocked("terminal_notification.lifecycle_invalid")
177
+ event_id = _token(value("event_id"))
178
+ lifecycle_sha256 = value("lifecycle_sha256")
179
+ if lifecycle_sha256 is not None:
180
+ lifecycle_sha256 = _sha(lifecycle_sha256)
181
+ if (value("remote_status") in {"cancelled", "superseded"}) != (
182
+ lifecycle_sha256 is None
183
+ ):
184
+ raise TerminalNotificationBlocked("terminal_notification.lifecycle_invalid")
185
+ content = {
186
+ "schema": NOTIFICATION_SCHEMA,
187
+ "event_id": event_id,
188
+ "site_id": _token(value("site_id")),
189
+ "plan_id": _token(value("plan_id")),
190
+ "website_version": _token(value("website_version")),
191
+ "source_sequence": value("source_sequence"),
192
+ "job_count": value("job_count"),
193
+ "change_sha256": _sha(value("change_sha256")),
194
+ "lifecycle_binding_sha256": _sha(value("binding_sha256")),
195
+ "terminal_status": value("remote_status"),
196
+ "lifecycle_sha256": lifecycle_sha256,
197
+ }
198
+ for name in ("source_sequence", "job_count"):
199
+ item = content[name]
200
+ if isinstance(item, bool) or not isinstance(item, int) or item <= 0:
201
+ raise TerminalNotificationBlocked("terminal_notification.lifecycle_invalid")
202
+ identity = _hash(_canonical(content))
203
+ content["notification_id"] = "terminal-" + identity
204
+ rendered = _canonical(content)
205
+ return event_id, rendered, _hash(rendered)
206
+
207
+
208
+ @contextmanager
209
+ def _transaction(connection: sqlite3.Connection) -> Iterator[None]:
210
+ if connection.in_transaction:
211
+ raise TerminalNotificationBlocked("terminal_notification.transaction_nested")
212
+ try:
213
+ connection.execute("BEGIN IMMEDIATE")
214
+ yield
215
+ connection.commit()
216
+ except Exception:
217
+ if connection.in_transaction:
218
+ connection.rollback()
219
+ raise
220
+
221
+
222
+ class DurableCMSTerminalNotifier:
223
+ """Register and deliver exact terminal lifecycle evidence."""
224
+
225
+ def __init__(
226
+ self,
227
+ connection: sqlite3.Connection,
228
+ *,
229
+ base_delay_seconds: float | int = 5,
230
+ max_delay_seconds: float | int = 300,
231
+ ):
232
+ if not isinstance(connection, sqlite3.Connection):
233
+ raise TypeError("connection must be sqlite3.Connection")
234
+ self.connection = connection
235
+ self.connection.row_factory = sqlite3.Row
236
+ self.base_delay_seconds = _duration(
237
+ base_delay_seconds, "terminal_notification.delay_invalid"
238
+ )
239
+ self.max_delay_seconds = _duration(
240
+ max_delay_seconds, "terminal_notification.delay_invalid"
241
+ )
242
+ if self.base_delay_seconds > self.max_delay_seconds:
243
+ raise TerminalNotificationBlocked("terminal_notification.delay_invalid")
244
+ self._initialize()
245
+
246
+ def _initialize(self) -> None:
247
+ with _transaction(self.connection):
248
+ self.connection.execute("""
249
+ CREATE TABLE IF NOT EXISTS cms_source_terminal_notification_meta (
250
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
251
+ schema_version INTEGER NOT NULL CHECK (schema_version = 1)
252
+ )
253
+ """)
254
+ self.connection.execute("""
255
+ INSERT OR IGNORE INTO cms_source_terminal_notification_meta
256
+ VALUES (1, 1)
257
+ """)
258
+ self.connection.execute("""
259
+ CREATE TABLE IF NOT EXISTS cms_source_terminal_notification (
260
+ event_id TEXT PRIMARY KEY, notification_id TEXT NOT NULL UNIQUE,
261
+ payload_json TEXT NOT NULL, payload_sha256 TEXT NOT NULL,
262
+ status TEXT NOT NULL CHECK (
263
+ status IN ('pending', 'leased', 'retry_wait',
264
+ 'succeeded', 'failed')
265
+ ),
266
+ attempts INTEGER NOT NULL CHECK (attempts >= 0),
267
+ max_attempts INTEGER NOT NULL CHECK (max_attempts BETWEEN 1 AND 20),
268
+ next_attempt_at REAL NOT NULL, lease_owner TEXT, lease_token TEXT,
269
+ lease_expires_at REAL, last_error_code TEXT, ack_sha256 TEXT,
270
+ created_at REAL NOT NULL, updated_at REAL NOT NULL,
271
+ CHECK (
272
+ (status = 'leased' AND lease_owner IS NOT NULL
273
+ AND lease_token IS NOT NULL
274
+ AND lease_expires_at IS NOT NULL)
275
+ OR
276
+ (status <> 'leased' AND lease_owner IS NULL
277
+ AND lease_token IS NULL
278
+ AND lease_expires_at IS NULL)
279
+ )
280
+ )
281
+ """)
282
+ self.connection.execute("""
283
+ CREATE INDEX IF NOT EXISTS cms_source_terminal_notification_due
284
+ ON cms_source_terminal_notification (status, next_attempt_at, created_at, event_id)
285
+ """)
286
+ self._validate_schema()
287
+
288
+ def _validate_schema(self) -> None:
289
+ meta_columns = tuple(row["name"] for row in self.connection.execute(
290
+ "PRAGMA table_info(cms_source_terminal_notification_meta)"
291
+ ).fetchall())
292
+ columns = tuple(row["name"] for row in self.connection.execute(
293
+ "PRAGMA table_info(cms_source_terminal_notification)"
294
+ ).fetchall())
295
+ meta = self.connection.execute(
296
+ "SELECT singleton, schema_version FROM cms_source_terminal_notification_meta"
297
+ ).fetchall()
298
+ if (
299
+ meta_columns != ("singleton", "schema_version")
300
+ or columns != _COLUMNS or len(meta) != 1
301
+ or tuple(meta[0]) != (1, SCHEMA_VERSION)
302
+ ):
303
+ raise TerminalNotificationBlocked("terminal_notification.schema_altered")
304
+
305
+ def register(
306
+ self,
307
+ lifecycle: Any,
308
+ *,
309
+ max_attempts: int = 5,
310
+ now: float | int,
311
+ ) -> TerminalNotificationStatus:
312
+ self._validate_schema()
313
+ if (
314
+ isinstance(max_attempts, bool)
315
+ or not isinstance(max_attempts, int)
316
+ or not 1 <= max_attempts <= 20
317
+ ):
318
+ raise TerminalNotificationBlocked(
319
+ "terminal_notification.max_attempts_invalid"
320
+ )
321
+ now = _time(now)
322
+ event_id, payload_json, payload_sha256 = _payload(lifecycle)
323
+ notification_id = json.loads(payload_json)["notification_id"]
324
+ with _transaction(self.connection):
325
+ row = self.connection.execute(
326
+ "SELECT payload_sha256, max_attempts "
327
+ "FROM cms_source_terminal_notification WHERE event_id = ?",
328
+ (event_id,),
329
+ ).fetchone()
330
+ if row is not None:
331
+ if (
332
+ row["payload_sha256"] != payload_sha256
333
+ or row["max_attempts"] != max_attempts
334
+ ):
335
+ raise TerminalNotificationBlocked(
336
+ "terminal_notification.idempotency_collision"
337
+ )
338
+ else:
339
+ self.connection.execute("""
340
+ INSERT INTO cms_source_terminal_notification (
341
+ event_id, notification_id, payload_json, payload_sha256, status,
342
+ attempts, max_attempts, next_attempt_at, created_at, updated_at
343
+ ) VALUES (?, ?, ?, ?, 'pending', 0, ?, ?, ?, ?)
344
+ """, (event_id, notification_id, payload_json, payload_sha256,
345
+ max_attempts, now, now, now))
346
+ return self.status(event_id, now=now)
347
+
348
+ def claim(
349
+ self,
350
+ worker_id: str,
351
+ *,
352
+ now: float | int,
353
+ lease_seconds: float | int = 600,
354
+ ) -> TerminalNotificationClaim | None:
355
+ self._validate_schema()
356
+ worker_id = _token(worker_id, limit=128)
357
+ now = _time(now)
358
+ lease_seconds = _duration(
359
+ lease_seconds, "terminal_notification.lease_invalid"
360
+ )
361
+ with _transaction(self.connection):
362
+ self.connection.execute("""
363
+ UPDATE cms_source_terminal_notification
364
+ SET status = CASE WHEN attempts >= max_attempts
365
+ THEN 'failed' ELSE 'retry_wait' END, next_attempt_at = ?,
366
+ lease_owner = NULL, lease_token = NULL,
367
+ lease_expires_at = NULL,
368
+ last_error_code = 'lease_expired',
369
+ updated_at = ? WHERE status = 'leased' AND lease_expires_at <= ?
370
+ """, (now, now, now))
371
+ row = self.connection.execute("""
372
+ SELECT * FROM cms_source_terminal_notification
373
+ WHERE status IN ('pending','retry_wait') AND next_attempt_at <= ?
374
+ AND attempts < max_attempts ORDER BY created_at, event_id LIMIT 1
375
+ """, (now,)).fetchone()
376
+ if row is None:
377
+ return None
378
+ self._validated_row(row)
379
+ token = secrets.token_urlsafe(32)
380
+ expires = now + lease_seconds
381
+ updated = self.connection.execute("""
382
+ UPDATE cms_source_terminal_notification
383
+ SET status = 'leased', attempts = attempts + 1,
384
+ lease_owner = ?, lease_token = ?, lease_expires_at = ?,
385
+ last_error_code = NULL, updated_at = ?
386
+ WHERE event_id = ? AND status IN ('pending', 'retry_wait')
387
+ """, (worker_id, token, expires, now, row["event_id"]))
388
+ if updated.rowcount != 1:
389
+ raise TerminalNotificationBlocked(
390
+ "terminal_notification.claim_lost"
391
+ )
392
+ return TerminalNotificationClaim(
393
+ row["event_id"], row["notification_id"], row["payload_sha256"],
394
+ int(row["attempts"]) + 1, int(row["max_attempts"]), worker_id,
395
+ token, expires, row["payload_json"],
396
+ )
397
+
398
+ def run_once(
399
+ self,
400
+ callback: Callable[[Mapping[str, Any]], Any],
401
+ worker_id: str,
402
+ *,
403
+ now: float | int,
404
+ lease_seconds: float | int = 600,
405
+ ) -> TerminalNotificationOutcome | None:
406
+ if not callable(callback):
407
+ raise TerminalNotificationBlocked("terminal_notification.callback_invalid")
408
+ claim = self.claim(worker_id, now=now, lease_seconds=lease_seconds)
409
+ if claim is None:
410
+ return None
411
+ try:
412
+ response = callback(json.loads(claim.payload_json))
413
+ status = self.complete(claim, response, now=now)
414
+ except TerminalNotificationBlocked as error:
415
+ if error.code != "terminal_notification.response_invalid":
416
+ raise
417
+ status = self.fail(claim, error.code, now=now)
418
+ except Exception as error:
419
+ retryable = (
420
+ getattr(error, "cms_notification_failure", False) is True
421
+ and getattr(error, "retryable", None) is True
422
+ )
423
+ code = getattr(error, "code", None)
424
+ if not _error(code):
425
+ code, retryable = "terminal_notification.callback_failure", False
426
+ status = (
427
+ self.retry(claim, code, now=now)
428
+ if retryable
429
+ else self.fail(claim, code, now=now)
430
+ )
431
+ return TerminalNotificationOutcome(
432
+ status.event_id, status.notification_id, status.status,
433
+ status.attempts, status.last_error_code,
434
+ )
435
+
436
+ def complete(
437
+ self,
438
+ claim: TerminalNotificationClaim,
439
+ response: Any,
440
+ *,
441
+ now: float | int,
442
+ ) -> TerminalNotificationStatus:
443
+ now = _time(now)
444
+ if not isinstance(response, Mapping):
445
+ raise TerminalNotificationBlocked("terminal_notification.response_invalid")
446
+ try:
447
+ copied = json.loads(_canonical(dict(response)))
448
+ except TerminalNotificationBlocked:
449
+ raise TerminalNotificationBlocked(
450
+ "terminal_notification.response_invalid"
451
+ ) from None
452
+ expected = {
453
+ "schema": ACK_SCHEMA, "notification_id": claim.notification_id,
454
+ "event_id": claim.event_id,
455
+ "site_id": json.loads(claim.payload_json)["site_id"],
456
+ "status": "accepted", "notification_sha256": claim.payload_sha256,
457
+ }
458
+ if copied != expected:
459
+ raise TerminalNotificationBlocked("terminal_notification.response_invalid")
460
+ ack_json = _canonical(copied)
461
+ with _transaction(self.connection):
462
+ self._require_claim(claim, now)
463
+ updated = self.connection.execute("""
464
+ UPDATE cms_source_terminal_notification
465
+ SET status = 'succeeded', next_attempt_at = ?,
466
+ lease_owner = NULL, lease_token = NULL,
467
+ lease_expires_at = NULL,
468
+ last_error_code = NULL, ack_sha256 = ?, updated_at = ?
469
+ WHERE event_id = ? AND status = 'leased'
470
+ AND lease_owner = ? AND lease_token = ?
471
+ """, (
472
+ now, _hash(ack_json), now, claim.event_id,
473
+ claim.lease_owner, claim.lease_token,
474
+ ))
475
+ if updated.rowcount != 1:
476
+ raise TerminalNotificationBlocked("terminal_notification.completion_lost")
477
+ return self.status(claim.event_id, now=now)
478
+
479
+ def retry(
480
+ self, claim: TerminalNotificationClaim, code: str, *, now: float | int,
481
+ ) -> TerminalNotificationStatus:
482
+ return self._finish(claim, code, retryable=True, now=now)
483
+
484
+ def fail(
485
+ self, claim: TerminalNotificationClaim, code: str, *, now: float | int,
486
+ ) -> TerminalNotificationStatus:
487
+ return self._finish(claim, code, retryable=False, now=now)
488
+
489
+ def _finish(
490
+ self,
491
+ claim: TerminalNotificationClaim,
492
+ code: str,
493
+ *,
494
+ retryable: bool,
495
+ now: float | int,
496
+ ) -> TerminalNotificationStatus:
497
+ if not _error(code):
498
+ raise TerminalNotificationBlocked("terminal_notification.error_invalid")
499
+ now = _time(now)
500
+ with _transaction(self.connection):
501
+ row = self._require_claim(claim, now)
502
+ terminal = not retryable or int(row["attempts"]) >= int(
503
+ row["max_attempts"]
504
+ )
505
+ delay = min(
506
+ self.max_delay_seconds,
507
+ self.base_delay_seconds * (2 ** max(0, claim.attempt - 1)),
508
+ )
509
+ updated = self.connection.execute("""
510
+ UPDATE cms_source_terminal_notification SET status = ?, next_attempt_at = ?,
511
+ lease_owner = NULL, lease_token = NULL, lease_expires_at = NULL,
512
+ last_error_code = ?, updated_at = ?
513
+ WHERE event_id = ? AND status = 'leased'
514
+ AND lease_owner = ? AND lease_token = ?
515
+ """, (
516
+ "failed" if terminal else "retry_wait",
517
+ now if terminal else now + delay,
518
+ code, now, claim.event_id, claim.lease_owner,
519
+ claim.lease_token,
520
+ ))
521
+ if updated.rowcount != 1:
522
+ raise TerminalNotificationBlocked(
523
+ "terminal_notification.failure_lost"
524
+ )
525
+ return self.status(claim.event_id, now=now)
526
+
527
+ def status(
528
+ self, event_id: str, *, now: float | int,
529
+ ) -> TerminalNotificationStatus:
530
+ self._validate_schema()
531
+ event_id = _token(event_id)
532
+ now = _time(now)
533
+ row = self.connection.execute(
534
+ "SELECT * FROM cms_source_terminal_notification WHERE event_id = ?", (event_id,)
535
+ ).fetchone()
536
+ if row is None:
537
+ raise TerminalNotificationBlocked("terminal_notification.missing")
538
+ self._validated_row(row)
539
+ lease = row["lease_expires_at"]
540
+ return TerminalNotificationStatus(
541
+ event_id, row["notification_id"], row["payload_sha256"], row["status"],
542
+ int(row["attempts"]), int(row["max_attempts"]), float(row["next_attempt_at"]),
543
+ None if lease is None else float(lease),
544
+ row["status"] == "leased" and float(lease) <= now,
545
+ row["last_error_code"], row["ack_sha256"],
546
+ )
547
+
548
+ def health(self, *, now: float | int) -> TerminalNotificationHealth:
549
+ now = _time(now)
550
+ self._validate_schema()
551
+ quick = self.connection.execute("PRAGMA quick_check").fetchall()
552
+ if len(quick) != 1 or tuple(quick[0]) != ("ok",):
553
+ raise TerminalNotificationBlocked(
554
+ "terminal_notification.database_integrity"
555
+ )
556
+ rows = self.connection.execute(
557
+ "SELECT * FROM cms_source_terminal_notification ORDER BY event_id"
558
+ ).fetchall()
559
+ for row in rows:
560
+ self._validated_row(row)
561
+ counts = {status: 0 for status in STATUSES}
562
+ for row in rows:
563
+ counts[row["status"]] += 1
564
+ due = sum(
565
+ row["status"] in {"pending", "retry_wait"}
566
+ and row["next_attempt_at"] <= now
567
+ for row in rows
568
+ )
569
+ expired = sum(
570
+ row["status"] == "leased" and row["lease_expires_at"] <= now
571
+ for row in rows
572
+ )
573
+ return TerminalNotificationHealth(
574
+ "blocked" if expired or counts["failed"] else "ok", counts, due,
575
+ expired, counts["failed"],
576
+ )
577
+
578
+ def _require_claim(
579
+ self, claim: TerminalNotificationClaim, now: float,
580
+ ) -> sqlite3.Row:
581
+ row = self.connection.execute(
582
+ "SELECT * FROM cms_source_terminal_notification WHERE event_id = ?",
583
+ (claim.event_id,),
584
+ ).fetchone()
585
+ if (
586
+ row is None or row["status"] != "leased"
587
+ or row["lease_owner"] != claim.lease_owner
588
+ or row["lease_token"] != claim.lease_token
589
+ ):
590
+ raise TerminalNotificationBlocked("terminal_notification.claim_lost")
591
+ self._validated_row(row)
592
+ if float(row["lease_expires_at"]) <= now:
593
+ raise TerminalNotificationBlocked("terminal_notification.lease_expired")
594
+ return row
595
+
596
+ def _validated_row(self, row: sqlite3.Row) -> None:
597
+ try:
598
+ payload = json.loads(row["payload_json"])
599
+ lifecycle = dict(payload)
600
+ lifecycle.pop("notification_id")
601
+ expected_id = "terminal-" + _hash(_canonical(lifecycle))
602
+ lifecycle_sha256 = payload.get("lifecycle_sha256")
603
+ terminal_status = payload.get("terminal_status")
604
+ semantic_valid = (
605
+ _token(payload.get("event_id")) == payload.get("event_id")
606
+ and _token(payload.get("site_id")) == payload.get("site_id")
607
+ and _token(payload.get("plan_id")) == payload.get("plan_id")
608
+ and _token(payload.get("website_version"))
609
+ == payload.get("website_version")
610
+ and isinstance(payload.get("source_sequence"), int)
611
+ and not isinstance(payload.get("source_sequence"), bool)
612
+ and payload["source_sequence"] > 0
613
+ and isinstance(payload.get("job_count"), int)
614
+ and not isinstance(payload.get("job_count"), bool)
615
+ and payload["job_count"] > 0
616
+ and _sha(payload.get("change_sha256"))
617
+ == payload.get("change_sha256")
618
+ and _sha(payload.get("lifecycle_binding_sha256"))
619
+ == payload.get("lifecycle_binding_sha256")
620
+ and terminal_status in TERMINAL_STATUSES
621
+ and (
622
+ terminal_status in {"cancelled", "superseded"}
623
+ and lifecycle_sha256 is None
624
+ or terminal_status not in {"cancelled", "superseded"}
625
+ and _sha(lifecycle_sha256) == lifecycle_sha256
626
+ )
627
+ )
628
+ valid_payload = (
629
+ payload.get("schema") == NOTIFICATION_SCHEMA
630
+ and set(payload) == {
631
+ "schema", "notification_id", "event_id", "site_id", "plan_id",
632
+ "website_version", "source_sequence", "job_count", "change_sha256",
633
+ "lifecycle_binding_sha256", "terminal_status", "lifecycle_sha256",
634
+ }
635
+ and payload["event_id"] == row["event_id"]
636
+ and payload["notification_id"] == row["notification_id"] == expected_id
637
+ and _hash(row["payload_json"]) == row["payload_sha256"]
638
+ and semantic_valid
639
+ )
640
+ except Exception:
641
+ valid_payload = False
642
+ status = row["status"]
643
+ leased = status == "leased"
644
+ lease_values = (row["lease_owner"], row["lease_token"], row["lease_expires_at"])
645
+ succeeded = status == "succeeded"
646
+ error = row["last_error_code"]
647
+ expected_ack_sha256 = None
648
+ if valid_payload:
649
+ expected_ack_sha256 = _hash(_canonical({
650
+ "schema": ACK_SCHEMA,
651
+ "notification_id": payload["notification_id"],
652
+ "event_id": payload["event_id"],
653
+ "site_id": payload["site_id"],
654
+ "status": "accepted",
655
+ "notification_sha256": row["payload_sha256"],
656
+ }))
657
+ if (
658
+ not valid_payload or status not in STATUSES
659
+ or isinstance(row["attempts"], bool) or not isinstance(row["attempts"], int)
660
+ or not 0 <= row["attempts"] <= row["max_attempts"] <= 20
661
+ or row["max_attempts"] < 1
662
+ or leased != all(item is not None for item in lease_values)
663
+ or (not leased and any(item is not None for item in lease_values))
664
+ or (status in {"retry_wait", "failed"}) != (error is not None)
665
+ or (error is not None and not _error(error))
666
+ or succeeded != (row["ack_sha256"] is not None)
667
+ or (
668
+ row["ack_sha256"] is not None
669
+ and row["ack_sha256"] != expected_ack_sha256
670
+ )
671
+ or _time(row["next_attempt_at"]) < _time(row["created_at"])
672
+ or _time(row["updated_at"]) < _time(row["created_at"])
673
+ ):
674
+ raise TerminalNotificationBlocked("terminal_notification.state_invalid")