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,1142 @@
1
+ #!/usr/bin/env python3
2
+ """Safe composition root for the durable terminal-notification receiver."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import hashlib
7
+ import importlib.util
8
+ import json
9
+ import math
10
+ import os
11
+ import sqlite3
12
+ import stat
13
+ import sys
14
+ import threading
15
+ import time
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+ from typing import Any, Callable, Mapping
19
+
20
+
21
+ def _load_module(name: str, path: Path):
22
+ spec = importlib.util.spec_from_file_location(name, path)
23
+ if spec is None or spec.loader is None:
24
+ raise RuntimeError(f"cannot load terminal receiver dependency: {path.name}")
25
+ module = importlib.util.module_from_spec(spec)
26
+ sys.modules[spec.name] = module
27
+ spec.loader.exec_module(module)
28
+ return module
29
+
30
+
31
+ _ROOT = Path(__file__).resolve().parents[1]
32
+ _RECEIVER = _load_module(
33
+ "blun_website_localization_terminal_receiver_runtime_dependency",
34
+ _ROOT
35
+ / "integrations"
36
+ / "website_localization_cms_terminal_notification_receiver.py",
37
+ )
38
+
39
+
40
+ class DurableTerminalReceiverRuntimeBlocked(RuntimeError):
41
+ """Private composition failure that must not cross the HTTP boundary."""
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class DurableTerminalReceiverRuntimeHealth:
46
+ status: str
47
+ state: str
48
+ received: int
49
+ processing_counts: dict[str, int]
50
+ processing_due: int
51
+ expired_leases: int
52
+ failed: int
53
+
54
+
55
+ def _database_path(value: Any) -> str:
56
+ try:
57
+ result = os.fspath(value)
58
+ except TypeError as error:
59
+ raise DurableTerminalReceiverRuntimeBlocked(
60
+ "database path is invalid"
61
+ ) from error
62
+ if isinstance(result, bytes):
63
+ raise DurableTerminalReceiverRuntimeBlocked(
64
+ "database path must be Unicode"
65
+ )
66
+ if (
67
+ not isinstance(result, str)
68
+ or not result
69
+ or "\x00" in result
70
+ or result.startswith("file:")
71
+ ):
72
+ raise DurableTerminalReceiverRuntimeBlocked("database path is invalid")
73
+ if result == ":memory:":
74
+ return result
75
+ if (
76
+ os.name != "posix"
77
+ or not os.path.isabs(result)
78
+ or os.path.normpath(result) != result
79
+ ):
80
+ raise DurableTerminalReceiverRuntimeBlocked(
81
+ "database path must be a canonical absolute POSIX path"
82
+ )
83
+ return result
84
+
85
+
86
+ def _validate_database_parent(database_path: str) -> None:
87
+ current = os.path.dirname(database_path)
88
+ first = True
89
+ while True:
90
+ try:
91
+ current_stat = os.lstat(current)
92
+ except OSError as error:
93
+ raise DurableTerminalReceiverRuntimeBlocked(
94
+ "terminal receiver database directory is unavailable"
95
+ ) from error
96
+ mode = stat.S_IMODE(current_stat.st_mode)
97
+ sticky_root_directory = (
98
+ current_stat.st_uid == 0
99
+ and bool(mode & stat.S_ISVTX)
100
+ and bool(mode & 0o002)
101
+ )
102
+ if (
103
+ not stat.S_ISDIR(current_stat.st_mode)
104
+ or current_stat.st_uid not in {0, os.geteuid()}
105
+ or (first and current_stat.st_uid != os.geteuid())
106
+ or (mode & 0o022 and not sticky_root_directory)
107
+ ):
108
+ raise DurableTerminalReceiverRuntimeBlocked(
109
+ "terminal receiver database directory is not private"
110
+ )
111
+ parent = os.path.dirname(current)
112
+ if parent == current:
113
+ break
114
+ current = parent
115
+ first = False
116
+
117
+
118
+ def _validate_database_file(
119
+ database_path: str,
120
+ expected_identity: tuple[int, int] | None = None,
121
+ ) -> tuple[int, int]:
122
+ try:
123
+ database_stat = os.lstat(database_path)
124
+ except OSError as error:
125
+ raise DurableTerminalReceiverRuntimeBlocked(
126
+ "terminal receiver database file is unavailable"
127
+ ) from error
128
+ identity = (database_stat.st_dev, database_stat.st_ino)
129
+ if (
130
+ not stat.S_ISREG(database_stat.st_mode)
131
+ or database_stat.st_uid != os.geteuid()
132
+ or database_stat.st_nlink != 1
133
+ or stat.S_IMODE(database_stat.st_mode) != 0o600
134
+ or (expected_identity is not None and identity != expected_identity)
135
+ ):
136
+ raise DurableTerminalReceiverRuntimeBlocked(
137
+ "terminal receiver database file is not private"
138
+ )
139
+ return identity
140
+
141
+
142
+ def _prepare_database_file(database_path: str) -> Callable[[], None]:
143
+ if database_path == ":memory:":
144
+ return lambda: None
145
+ _validate_database_parent(database_path)
146
+ try:
147
+ identity = _validate_database_file(database_path)
148
+ except DurableTerminalReceiverRuntimeBlocked as error:
149
+ if error.__cause__ is None or not isinstance(
150
+ error.__cause__, FileNotFoundError
151
+ ):
152
+ raise
153
+ flags = os.O_CREAT | os.O_EXCL | os.O_RDWR
154
+ flags |= getattr(os, "O_CLOEXEC", 0)
155
+ flags |= getattr(os, "O_NOFOLLOW", 0)
156
+ try:
157
+ descriptor = os.open(database_path, flags, 0o600)
158
+ except FileExistsError:
159
+ identity = _validate_database_file(database_path)
160
+ except OSError as create_error:
161
+ raise DurableTerminalReceiverRuntimeBlocked(
162
+ "terminal receiver database file could not be created"
163
+ ) from create_error
164
+ else:
165
+ try:
166
+ os.fchmod(descriptor, 0o600)
167
+ created_stat = os.fstat(descriptor)
168
+ identity = (created_stat.st_dev, created_stat.st_ino)
169
+ if (
170
+ not stat.S_ISREG(created_stat.st_mode)
171
+ or created_stat.st_uid != os.geteuid()
172
+ or created_stat.st_nlink != 1
173
+ or stat.S_IMODE(created_stat.st_mode) != 0o600
174
+ ):
175
+ raise DurableTerminalReceiverRuntimeBlocked(
176
+ "terminal receiver database file was not created privately"
177
+ )
178
+ finally:
179
+ os.close(descriptor)
180
+
181
+ def guard() -> None:
182
+ _validate_database_parent(database_path)
183
+ _validate_database_file(database_path, identity)
184
+
185
+ guard()
186
+ return guard
187
+
188
+
189
+ def _preflight_application(
190
+ authenticate: Callable[[Mapping[str, Any], Mapping[str, str]], Any],
191
+ *,
192
+ origin: str,
193
+ clock: Callable[[], float | int],
194
+ path: str,
195
+ require_https: bool,
196
+ ) -> None:
197
+ dummy_inbox = object.__new__(_RECEIVER.DurableCMSTerminalNotificationInbox)
198
+ try:
199
+ _RECEIVER.CMSTerminalNotificationReceiverApplication(
200
+ dummy_inbox,
201
+ authenticate,
202
+ origin=origin,
203
+ clock=clock,
204
+ path=path,
205
+ require_https=require_https,
206
+ )
207
+ except (TypeError, ValueError) as error:
208
+ raise DurableTerminalReceiverRuntimeBlocked(
209
+ "terminal receiver configuration is invalid"
210
+ ) from error
211
+
212
+
213
+ def _preflight_processing(
214
+ max_attempts: int,
215
+ base_delay_seconds: float | int,
216
+ max_delay_seconds: float | int,
217
+ ) -> None:
218
+ if (
219
+ isinstance(max_attempts, bool)
220
+ or not isinstance(max_attempts, int)
221
+ or not 1 <= max_attempts <= 20
222
+ ):
223
+ raise DurableTerminalReceiverRuntimeBlocked(
224
+ "terminal processing attempts are invalid"
225
+ )
226
+ try:
227
+ base_delay = _RECEIVER._duration(
228
+ base_delay_seconds, "processing_delay_invalid"
229
+ )
230
+ max_delay = _RECEIVER._duration(
231
+ max_delay_seconds, "processing_delay_invalid"
232
+ )
233
+ except Exception as error:
234
+ raise DurableTerminalReceiverRuntimeBlocked(
235
+ "terminal processing delay is invalid"
236
+ ) from error
237
+ if base_delay > max_delay:
238
+ raise DurableTerminalReceiverRuntimeBlocked(
239
+ "terminal processing delay range is invalid"
240
+ )
241
+
242
+
243
+ class DurableTerminalNotificationReceiverRuntime:
244
+ """Own one protected SQLite connection and its complete WSGI boundary."""
245
+
246
+ def __init__(
247
+ self,
248
+ connection: sqlite3.Connection,
249
+ inbox: Any,
250
+ application: Any,
251
+ database_guard: Callable[[], None],
252
+ clock: Callable[[], float | int],
253
+ ):
254
+ self._connection = connection
255
+ self.inbox = inbox
256
+ self.application = application
257
+ self._database_guard = database_guard
258
+ self._clock = clock
259
+ self._lock = threading.RLock()
260
+ self._owner_pid = os.getpid()
261
+ self._closed = False
262
+ self._worker_state = "unmanaged"
263
+ self._worker_error_code: str | None = None
264
+ self._worker_stop = threading.Event()
265
+ self._worker_thread: threading.Thread | None = None
266
+
267
+ def _assert_owner(self) -> None:
268
+ if os.getpid() != self._owner_pid:
269
+ raise DurableTerminalReceiverRuntimeBlocked(
270
+ "terminal receiver runtime belongs to another process"
271
+ )
272
+
273
+ def _require_open(self) -> None:
274
+ self._assert_owner()
275
+ if self._closed:
276
+ raise DurableTerminalReceiverRuntimeBlocked(
277
+ "terminal receiver runtime is closed"
278
+ )
279
+ try:
280
+ self._database_guard()
281
+ except Exception as error:
282
+ raise DurableTerminalReceiverRuntimeBlocked(
283
+ "terminal receiver database is unsafe"
284
+ ) from error
285
+
286
+ @staticmethod
287
+ def _unavailable(start_response: Callable[..., Any]):
288
+ value = {
289
+ "schema": _RECEIVER.ERROR_SCHEMA,
290
+ "status": "BLOCK",
291
+ "error_code": "notification_receiver.runtime_unavailable",
292
+ }
293
+ try:
294
+ body = json.dumps(
295
+ value, ensure_ascii=False, allow_nan=False, sort_keys=True,
296
+ separators=(",", ":"),
297
+ ).encode("utf-8")
298
+ start_response("503 Service Unavailable", (
299
+ ("Content-Type", "application/json; charset=utf-8"),
300
+ ("Content-Length", str(len(body))),
301
+ ("Cache-Control", "no-store"),
302
+ ("X-Content-Type-Options", "nosniff"),
303
+ ("Referrer-Policy", "no-referrer"),
304
+ ))
305
+ return [body]
306
+ except Exception:
307
+ return []
308
+
309
+ def __call__(self, environ: Mapping[str, Any], start_response: Callable[..., Any]):
310
+ try:
311
+ self._assert_owner()
312
+ with self._lock:
313
+ self._require_open()
314
+ if isinstance(environ, Mapping) and environ.get("PATH_INFO") in {
315
+ _RECEIVER.STATUS_PATH, _RECEIVER.READINESS_PATH,
316
+ _RECEIVER.CAPABILITIES_PATH, _RECEIVER.HEALTH_PATH,
317
+ }:
318
+ return self._control_request(environ, start_response)
319
+ self.require_worker_ready()
320
+ return self.application(environ, start_response)
321
+ except Exception:
322
+ return self._unavailable(start_response)
323
+
324
+ def _control_request(
325
+ self,
326
+ environ: Mapping[str, Any],
327
+ start_response: Callable[..., Any],
328
+ ):
329
+ """Serve authenticated, content-free receiver control routes."""
330
+
331
+ try:
332
+ path = environ.get("PATH_INFO")
333
+ if not isinstance(environ, Mapping):
334
+ _RECEIVER._blocked("environment_invalid", 400)
335
+ if self.application.require_https and (
336
+ environ.get("wsgi.url_scheme") != "https"
337
+ ):
338
+ _RECEIVER._blocked("https_required", 400)
339
+ if environ.get("QUERY_STRING") not in {None, ""}:
340
+ _RECEIVER._blocked("query_rejected", 400)
341
+ if environ.get("HTTP_TRANSFER_ENCODING") not in {None, ""}:
342
+ _RECEIVER._blocked("framing_invalid", 400)
343
+ headers = self.application._headers(environ)
344
+ if path == _RECEIVER.CAPABILITIES_PATH:
345
+ return self._capabilities_request(
346
+ environ, headers, start_response,
347
+ )
348
+ if path == _RECEIVER.HEALTH_PATH:
349
+ return self._health_request(environ, headers, start_response)
350
+ if path == _RECEIVER.READINESS_PATH:
351
+ return self._readiness_request(
352
+ environ, headers, start_response,
353
+ )
354
+ if path == _RECEIVER.STATUS_PATH:
355
+ return self._status_request(environ, headers, start_response)
356
+ _RECEIVER._blocked("path_not_found", 404)
357
+ except _RECEIVER.TerminalNotificationReceiverBlocked as failure:
358
+ return self.application._send(start_response, failure.status, {
359
+ "schema": _RECEIVER.ERROR_SCHEMA,
360
+ "status": "BLOCK",
361
+ "error_code": failure.code,
362
+ })
363
+ except Exception:
364
+ return self._unavailable(start_response)
365
+
366
+ def _authenticate_control(
367
+ self,
368
+ request: dict[str, Any],
369
+ headers: dict[str, str],
370
+ site_id: str | None,
371
+ scope: str,
372
+ ) -> None:
373
+ try:
374
+ principal = self.application.authenticate(
375
+ dict(request), dict(headers)
376
+ )
377
+ except Exception:
378
+ _RECEIVER._blocked("authentication_unavailable", 503)
379
+ _RECEIVER._principal(principal, site_id, scope)
380
+
381
+ def _capabilities_request(
382
+ self,
383
+ environ: Mapping[str, Any],
384
+ headers: dict[str, str],
385
+ start_response: Callable[..., Any],
386
+ ):
387
+ if environ.get("REQUEST_METHOD") != "GET":
388
+ _RECEIVER._blocked("method_not_allowed", 405)
389
+ if headers.get("content-length") not in {None, "0"}:
390
+ _RECEIVER._blocked("body_invalid", 400)
391
+ if "content-type" in headers:
392
+ _RECEIVER._blocked("content_type", 415)
393
+ body_sha256 = hashlib.sha256(b"").hexdigest()
394
+ request = {
395
+ "schema": _RECEIVER.AUTH_SCHEMA,
396
+ "method": "GET",
397
+ "origin": self.application.origin,
398
+ "path": _RECEIVER.CAPABILITIES_PATH,
399
+ "body_sha256": body_sha256,
400
+ }
401
+ self._authenticate_control(
402
+ request, headers, None, _RECEIVER.CAPABILITIES_SCOPE,
403
+ )
404
+ return self.application._send(start_response, 200, {
405
+ "schema": _RECEIVER.CAPABILITIES_RESPONSE_SCHEMA,
406
+ "capabilities": _RECEIVER.capabilities_payload(
407
+ self.application.path
408
+ ),
409
+ })
410
+
411
+ def _readiness_request(
412
+ self,
413
+ environ: Mapping[str, Any],
414
+ headers: dict[str, str],
415
+ start_response: Callable[..., Any],
416
+ ):
417
+ if environ.get("REQUEST_METHOD") != "GET":
418
+ _RECEIVER._blocked("method_not_allowed", 405)
419
+ if headers.get("content-length") not in {None, "0"}:
420
+ _RECEIVER._blocked("body_invalid", 400)
421
+ if "content-type" in headers:
422
+ _RECEIVER._blocked("content_type", 415)
423
+ body_sha256 = hashlib.sha256(b"").hexdigest()
424
+ request = {
425
+ "schema": _RECEIVER.AUTH_SCHEMA,
426
+ "method": "GET",
427
+ "origin": self.application.origin,
428
+ "path": _RECEIVER.READINESS_PATH,
429
+ "body_sha256": body_sha256,
430
+ }
431
+ self._authenticate_control(
432
+ request, headers, None, _RECEIVER.READINESS_SCOPE,
433
+ )
434
+ report = {
435
+ **self.worker_readiness(),
436
+ "capabilities_sha256": _RECEIVER.capabilities_payload(
437
+ self.application.path
438
+ )["sha256"],
439
+ }
440
+ status = 200 if report["status"] == "ready" else 503
441
+ return self.application._send(start_response, status, report)
442
+
443
+ def _health_request(
444
+ self,
445
+ environ: Mapping[str, Any],
446
+ headers: dict[str, str],
447
+ start_response: Callable[..., Any],
448
+ ):
449
+ if environ.get("REQUEST_METHOD") != "GET":
450
+ _RECEIVER._blocked("method_not_allowed", 405)
451
+ if headers.get("content-length") not in {None, "0"}:
452
+ _RECEIVER._blocked("body_invalid", 400)
453
+ if "content-type" in headers:
454
+ _RECEIVER._blocked("content_type", 415)
455
+ body_sha256 = hashlib.sha256(b"").hexdigest()
456
+ request = {
457
+ "schema": _RECEIVER.AUTH_SCHEMA,
458
+ "method": "GET",
459
+ "origin": self.application.origin,
460
+ "path": _RECEIVER.HEALTH_PATH,
461
+ "body_sha256": body_sha256,
462
+ }
463
+ self._authenticate_control(
464
+ request, headers, None, _RECEIVER.HEALTH_SCOPE,
465
+ )
466
+ report = self.worker_health()
467
+ counts = report.get("processing_counts") if isinstance(
468
+ report, Mapping
469
+ ) else None
470
+ metrics = tuple(
471
+ report.get(name) if isinstance(report, Mapping) else None
472
+ for name in (
473
+ "received", "processing_due", "expired_leases", "failed",
474
+ )
475
+ )
476
+ metrics_available = counts is not None
477
+ worker_ok = isinstance(report, Mapping) and report.get(
478
+ "worker_state"
479
+ ) in {"unmanaged", "running"}
480
+ healthy = (
481
+ isinstance(report, Mapping)
482
+ and report.get("status") == "ok"
483
+ and report.get("inbox_status") == "ok"
484
+ and report.get("error_code") is None
485
+ and worker_ok
486
+ )
487
+ valid_counts = (
488
+ isinstance(counts, dict)
489
+ and set(counts) == set(_RECEIVER.PROCESSING_STATUSES)
490
+ and all(
491
+ isinstance(value, int) and not isinstance(value, bool)
492
+ and value >= 0
493
+ for value in counts.values()
494
+ )
495
+ )
496
+ if not (
497
+ isinstance(report, dict)
498
+ and set(report) == set(_RECEIVER.HEALTH_RESPONSE_FIELDS)
499
+ and report.get("schema") == _RECEIVER.HEALTH_RESPONSE_SCHEMA
500
+ and report.get("status") in {"ok", "blocked"}
501
+ and report.get("runtime_state") == "open"
502
+ and report.get("worker_state") in {
503
+ "unmanaged", "starting", "running", "stopping", "stopped",
504
+ "failed",
505
+ }
506
+ and report.get("inbox_status") in {"ok", "blocked"}
507
+ and (report.get("error_code") is None or _RECEIVER._error(
508
+ report.get("error_code")
509
+ ))
510
+ and healthy == (report.get("status") == "ok")
511
+ and (report.get("status") == "ok") == (
512
+ report.get("error_code") is None
513
+ )
514
+ and (
515
+ (metrics_available and valid_counts and all(
516
+ isinstance(value, int) and not isinstance(value, bool)
517
+ and value >= 0
518
+ for value in metrics
519
+ ))
520
+ or (
521
+ not metrics_available
522
+ and counts is None
523
+ and all(value is None for value in metrics)
524
+ and report.get("status") == "blocked"
525
+ and report.get("inbox_status") == "blocked"
526
+ )
527
+ )
528
+ and (
529
+ not metrics_available
530
+ or (
531
+ sum(counts.values()) == report["received"]
532
+ and report["failed"] == counts["failed"]
533
+ and report["processing_due"] <= (
534
+ counts["pending"] + counts["retry_wait"]
535
+ )
536
+ and report["expired_leases"] <= counts["leased"]
537
+ and (report["inbox_status"] == "ok") == (
538
+ report["failed"] == 0
539
+ and report["expired_leases"] == 0
540
+ )
541
+ )
542
+ )
543
+ ):
544
+ _RECEIVER._blocked("health_invalid", 503)
545
+ response = {
546
+ **report,
547
+ "capabilities_sha256": _RECEIVER.capabilities_payload(
548
+ self.application.path
549
+ )["sha256"],
550
+ }
551
+ status = 200 if report["status"] == "ok" else 503
552
+ return self.application._send(start_response, status, response)
553
+
554
+ def _status_request(
555
+ self,
556
+ environ: Mapping[str, Any],
557
+ headers: dict[str, str],
558
+ start_response: Callable[..., Any],
559
+ ):
560
+ if environ.get("REQUEST_METHOD") != "POST":
561
+ _RECEIVER._blocked("method_not_allowed", 405)
562
+ if headers.get("content-type", "").lower().replace(" ", "") != (
563
+ "application/json;charset=utf-8"
564
+ ):
565
+ _RECEIVER._blocked("content_type", 415)
566
+ length = headers.get("content-length")
567
+ if (
568
+ not isinstance(length, str)
569
+ or not length.isascii()
570
+ or not length.isdecimal()
571
+ ):
572
+ _RECEIVER._blocked("content_length_required", 411)
573
+ size = int(length)
574
+ if size <= 0:
575
+ _RECEIVER._blocked("body_invalid", 400)
576
+ if size > _RECEIVER.MAX_BODY_BYTES:
577
+ _RECEIVER._blocked("body_too_large", 413)
578
+ try:
579
+ body = environ["wsgi.input"].read(size)
580
+ except Exception:
581
+ body = None
582
+ if not isinstance(body, bytes) or len(body) != size:
583
+ _RECEIVER._blocked("body_invalid", 400)
584
+ try:
585
+ text = body.decode("utf-8")
586
+ if text.startswith("\ufeff"):
587
+ raise ValueError("BOM rejected")
588
+ value = json.loads(
589
+ text,
590
+ object_pairs_hook=_RECEIVER._pairs,
591
+ parse_constant=_RECEIVER._constant,
592
+ )
593
+ except (
594
+ UnicodeDecodeError, json.JSONDecodeError, ValueError,
595
+ RecursionError,
596
+ ):
597
+ _RECEIVER._blocked("json_invalid", 400)
598
+ if not (
599
+ isinstance(value, dict)
600
+ and set(value) == {"schema", "event_id", "site_id"}
601
+ and value.get("schema") == _RECEIVER.STATUS_REQUEST_SCHEMA
602
+ and _RECEIVER._token(value.get("event_id"))
603
+ and _RECEIVER._token(value.get("site_id"))
604
+ and _RECEIVER._canonical(value) == body
605
+ ):
606
+ _RECEIVER._blocked("request_invalid", 400)
607
+ body_sha256 = hashlib.sha256(body).hexdigest()
608
+ if headers.get("x-localization-terminal-status-sha256") != body_sha256:
609
+ _RECEIVER._blocked("header_binding_invalid", 400)
610
+ request = {
611
+ "schema": _RECEIVER.AUTH_SCHEMA,
612
+ "method": "POST",
613
+ "origin": self.application.origin,
614
+ "path": _RECEIVER.STATUS_PATH,
615
+ "event_id": value["event_id"],
616
+ "site_id": value["site_id"],
617
+ "body_sha256": body_sha256,
618
+ }
619
+ self._authenticate_control(
620
+ request, headers, value["site_id"], _RECEIVER.STATUS_SCOPE,
621
+ )
622
+ processing = self.inbox.processing_status(
623
+ value["event_id"], now=self._clock(),
624
+ )
625
+ if processing.site_id != value["site_id"]:
626
+ _RECEIVER._blocked("not_found", 404)
627
+ response = {
628
+ "schema": _RECEIVER.STATUS_RESPONSE_SCHEMA,
629
+ "notification_id": processing.notification_id,
630
+ "event_id": processing.event_id,
631
+ "site_id": processing.site_id,
632
+ "terminal_status": processing.terminal_status,
633
+ "notification_sha256": processing.payload_sha256,
634
+ "processing_status": processing.status,
635
+ "attempts": processing.attempts,
636
+ "max_attempts": processing.max_attempts,
637
+ "next_attempt_at": processing.next_attempt_at,
638
+ "lease_expires_at": processing.lease_expires_at,
639
+ "lease_expired": processing.lease_expired,
640
+ "last_error_code": processing.last_error_code,
641
+ "processed_at": processing.processed_at,
642
+ "capabilities_sha256": _RECEIVER.capabilities_payload(
643
+ self.application.path
644
+ )["sha256"],
645
+ }
646
+ return self.application._send(start_response, 200, response)
647
+
648
+ def status(self, event_id: str):
649
+ self._assert_owner()
650
+ with self._lock:
651
+ self._require_open()
652
+ try:
653
+ return self.inbox.status(event_id)
654
+ except Exception as error:
655
+ raise DurableTerminalReceiverRuntimeBlocked(
656
+ "terminal receiver status is unavailable"
657
+ ) from error
658
+
659
+ def health(self) -> DurableTerminalReceiverRuntimeHealth:
660
+ self._assert_owner()
661
+ with self._lock:
662
+ self._require_open()
663
+ try:
664
+ health = self.inbox.health(now=self._clock())
665
+ except Exception as error:
666
+ raise DurableTerminalReceiverRuntimeBlocked(
667
+ "terminal receiver health is unavailable"
668
+ ) from error
669
+ if (
670
+ health.status not in {"ok", "blocked"}
671
+ or not isinstance(health.received, int)
672
+ or not isinstance(health.counts, dict)
673
+ or set(health.counts) != set(_RECEIVER.PROCESSING_STATUSES)
674
+ or any(
675
+ isinstance(value, bool) or not isinstance(value, int)
676
+ or value < 0
677
+ for value in health.counts.values()
678
+ )
679
+ or any(
680
+ isinstance(value, bool) or not isinstance(value, int)
681
+ or value < 0
682
+ for value in (
683
+ health.due, health.expired_leases, health.failed,
684
+ )
685
+ )
686
+ ):
687
+ raise DurableTerminalReceiverRuntimeBlocked(
688
+ "terminal receiver health is invalid"
689
+ )
690
+ return DurableTerminalReceiverRuntimeHealth(
691
+ health.status,
692
+ "open",
693
+ health.received,
694
+ dict(health.counts),
695
+ health.due,
696
+ health.expired_leases,
697
+ health.failed,
698
+ )
699
+
700
+ def process_next(
701
+ self,
702
+ callback: Callable[[Mapping[str, Any]], Any],
703
+ worker_id: str,
704
+ *,
705
+ now: float | int | None = None,
706
+ lease_seconds: float | int = 600,
707
+ ):
708
+ """Run at most one lease-bound host processing callback."""
709
+
710
+ self._assert_owner()
711
+ with self._lock:
712
+ self._require_open()
713
+ try:
714
+ return self.inbox.run_next_processing(
715
+ callback,
716
+ worker_id,
717
+ now=self._clock() if now is None else now,
718
+ lease_seconds=lease_seconds,
719
+ )
720
+ except Exception as error:
721
+ raise DurableTerminalReceiverRuntimeBlocked(
722
+ "terminal receiver processing is unavailable"
723
+ ) from error
724
+
725
+ @staticmethod
726
+ def _worker_configuration(
727
+ callback: Any,
728
+ worker_id: Any,
729
+ lease_seconds: float | int,
730
+ active_delay_seconds: float | int,
731
+ idle_delay_seconds: float | int,
732
+ blocked_delay_seconds: float | int,
733
+ ) -> tuple[float, float, float, float]:
734
+ if (
735
+ not callable(callback)
736
+ or not isinstance(worker_id, str)
737
+ or not _RECEIVER._token(worker_id)
738
+ or len(worker_id) > 128
739
+ ):
740
+ raise DurableTerminalReceiverRuntimeBlocked(
741
+ "terminal receiver worker configuration is invalid"
742
+ )
743
+ try:
744
+ values = tuple(
745
+ _RECEIVER._duration(value, "worker_duration_invalid")
746
+ for value in (
747
+ lease_seconds,
748
+ active_delay_seconds,
749
+ idle_delay_seconds,
750
+ blocked_delay_seconds,
751
+ )
752
+ )
753
+ except Exception as error:
754
+ raise DurableTerminalReceiverRuntimeBlocked(
755
+ "terminal receiver worker configuration is invalid"
756
+ ) from error
757
+ return values
758
+
759
+ @staticmethod
760
+ def _join_timeout(value: float | int) -> float:
761
+ if (
762
+ isinstance(value, bool)
763
+ or not isinstance(value, (int, float))
764
+ or not math.isfinite(float(value))
765
+ or not 0 < float(value) <= 300
766
+ ):
767
+ raise DurableTerminalReceiverRuntimeBlocked(
768
+ "terminal receiver worker timeout is invalid"
769
+ )
770
+ return float(value)
771
+
772
+ def _managed_worker(
773
+ self,
774
+ stop: threading.Event,
775
+ callback: Callable[[Mapping[str, Any]], Any],
776
+ worker_id: str,
777
+ configuration: tuple[float, float, float, float],
778
+ ) -> None:
779
+ try:
780
+ with self._lock:
781
+ if self._worker_state != "starting" or self._worker_stop is not stop:
782
+ return
783
+ self._worker_state = "running"
784
+ lease, active, idle, blocked = configuration
785
+ while not stop.is_set():
786
+ outcome = self.process_next(
787
+ callback, worker_id, lease_seconds=lease,
788
+ )
789
+ delay = (
790
+ idle if outcome is None
791
+ else blocked if outcome.status in {"retry_wait", "failed"}
792
+ else active
793
+ )
794
+ stop.wait(delay)
795
+ except Exception:
796
+ with self._lock:
797
+ self._worker_error_code = (
798
+ "notification_receiver.worker_blocked"
799
+ )
800
+ self._worker_state = "failed"
801
+ return
802
+ with self._lock:
803
+ if self._worker_state in {"running", "stopping"}:
804
+ self._worker_state = "stopped"
805
+
806
+ def start_worker(
807
+ self,
808
+ callback: Callable[[Mapping[str, Any]], Any],
809
+ worker_id: str,
810
+ *,
811
+ lease_seconds: float | int = 600,
812
+ active_delay_seconds: float | int = 0.05,
813
+ idle_delay_seconds: float | int = 1,
814
+ blocked_delay_seconds: float | int = 5,
815
+ ) -> None:
816
+ """Start one process-owned, interruptible processing worker."""
817
+
818
+ configuration = self._worker_configuration(
819
+ callback,
820
+ worker_id,
821
+ lease_seconds,
822
+ active_delay_seconds,
823
+ idle_delay_seconds,
824
+ blocked_delay_seconds,
825
+ )
826
+ self._assert_owner()
827
+ with self._lock:
828
+ self._require_open()
829
+ if self._worker_state in {"starting", "running"}:
830
+ return
831
+ if self._worker_state in {"stopping", "failed"}:
832
+ raise DurableTerminalReceiverRuntimeBlocked(
833
+ "terminal receiver worker is blocked"
834
+ )
835
+ stop = threading.Event()
836
+ thread = threading.Thread(
837
+ target=self._managed_worker,
838
+ args=(stop, callback, worker_id, configuration),
839
+ name="cms-terminal-receiver-worker",
840
+ daemon=False,
841
+ )
842
+ self._worker_stop = stop
843
+ self._worker_thread = thread
844
+ self._worker_error_code = None
845
+ self._worker_state = "starting"
846
+ try:
847
+ thread.start()
848
+ except Exception as error:
849
+ self._worker_thread = None
850
+ self._worker_state = "failed"
851
+ self._worker_error_code = (
852
+ "notification_receiver.worker_blocked"
853
+ )
854
+ raise DurableTerminalReceiverRuntimeBlocked(
855
+ "terminal receiver worker is blocked"
856
+ ) from error
857
+
858
+ def stop_worker(self, *, timeout_seconds: float | int = 30) -> None:
859
+ """Stop and join the managed worker without closing its database."""
860
+
861
+ timeout = self._join_timeout(timeout_seconds)
862
+ self._assert_owner()
863
+ thread = self._worker_thread
864
+ state = self._worker_state
865
+ if thread is None or state in {"unmanaged", "stopped"}:
866
+ return
867
+ if thread is threading.current_thread():
868
+ raise DurableTerminalReceiverRuntimeBlocked(
869
+ "terminal receiver worker cannot stop itself"
870
+ )
871
+ if state != "failed":
872
+ self._worker_state = "stopping"
873
+ self._worker_stop.set()
874
+ thread.join(timeout)
875
+ if thread.is_alive():
876
+ raise DurableTerminalReceiverRuntimeBlocked(
877
+ "terminal receiver worker stop timed out"
878
+ )
879
+ with self._lock:
880
+ if self._worker_state == "stopping":
881
+ self._worker_state = "stopped"
882
+
883
+ def require_worker_ready(self) -> None:
884
+ """Block hosted HTTP intake unless its processor is healthy."""
885
+
886
+ self._assert_owner()
887
+ if self._closed:
888
+ raise DurableTerminalReceiverRuntimeBlocked(
889
+ "terminal receiver runtime is closed"
890
+ )
891
+ if self._worker_state == "unmanaged":
892
+ return
893
+ if self._worker_state != "running" or self._worker_error_code is not None:
894
+ raise DurableTerminalReceiverRuntimeBlocked(
895
+ "terminal receiver worker is not ready"
896
+ )
897
+ if self.health().status != "ok":
898
+ raise DurableTerminalReceiverRuntimeBlocked(
899
+ "terminal receiver storage is blocked"
900
+ )
901
+
902
+ def worker_readiness(self) -> dict[str, Any]:
903
+ """Return a content-free readiness snapshot for a host supervisor."""
904
+
905
+ self._assert_owner()
906
+ with self._lock:
907
+ state = "closed" if self._closed else self._worker_state
908
+ error_code = self._worker_error_code
909
+ if state != "running":
910
+ return {
911
+ "schema": "blun.cms-terminal-receiver-readiness.v1",
912
+ "status": "not_ready",
913
+ "worker_state": state,
914
+ "inbox_status": None,
915
+ "error_code": error_code or (
916
+ "notification_receiver.worker_not_ready"
917
+ ),
918
+ }
919
+ try:
920
+ inbox_status = self.health().status
921
+ except Exception:
922
+ inbox_status = "blocked"
923
+ ready = inbox_status == "ok"
924
+ return {
925
+ "schema": "blun.cms-terminal-receiver-readiness.v1",
926
+ "status": "ready" if ready else "not_ready",
927
+ "worker_state": "running",
928
+ "inbox_status": inbox_status,
929
+ "error_code": None if ready else (
930
+ "notification_receiver.storage_blocked"
931
+ ),
932
+ }
933
+
934
+ def worker_health(self) -> dict[str, Any]:
935
+ """Return aggregate runtime, worker, and durable inbox health."""
936
+
937
+ self._assert_owner()
938
+ with self._lock:
939
+ self._require_open()
940
+ worker_state = self._worker_state
941
+ worker_error = self._worker_error_code
942
+ try:
943
+ inbox = self.health()
944
+ except Exception:
945
+ return {
946
+ "schema": _RECEIVER.HEALTH_RESPONSE_SCHEMA,
947
+ "status": "blocked",
948
+ "runtime_state": "open",
949
+ "worker_state": worker_state,
950
+ "inbox_status": "blocked",
951
+ "received": None,
952
+ "processing_counts": None,
953
+ "processing_due": None,
954
+ "expired_leases": None,
955
+ "failed": None,
956
+ "error_code": "notification_receiver.storage_blocked",
957
+ }
958
+ worker_ok = (
959
+ worker_state in {"unmanaged", "running"}
960
+ and worker_error is None
961
+ )
962
+ healthy = worker_ok and inbox.status == "ok"
963
+ if worker_error is not None:
964
+ error_code = worker_error
965
+ elif not worker_ok:
966
+ error_code = "notification_receiver.worker_not_ready"
967
+ elif inbox.status != "ok":
968
+ error_code = "notification_receiver.storage_blocked"
969
+ else:
970
+ error_code = None
971
+ return {
972
+ "schema": _RECEIVER.HEALTH_RESPONSE_SCHEMA,
973
+ "status": "ok" if healthy else "blocked",
974
+ "runtime_state": "open",
975
+ "worker_state": worker_state,
976
+ "inbox_status": inbox.status,
977
+ "received": inbox.received,
978
+ "processing_counts": dict(inbox.processing_counts),
979
+ "processing_due": inbox.processing_due,
980
+ "expired_leases": inbox.expired_leases,
981
+ "failed": inbox.failed,
982
+ "error_code": error_code,
983
+ }
984
+
985
+ def close(self, *, worker_timeout_seconds: float | int = 30) -> None:
986
+ self._assert_owner()
987
+ self.stop_worker(timeout_seconds=worker_timeout_seconds)
988
+ with self._lock:
989
+ if not self._closed:
990
+ self._connection.close()
991
+ self._closed = True
992
+
993
+ @property
994
+ def state(self) -> str:
995
+ if os.getpid() != self._owner_pid:
996
+ return "foreign-process"
997
+ with self._lock:
998
+ return "closed" if self._closed else "open"
999
+
1000
+ @property
1001
+ def worker_state(self) -> str:
1002
+ if os.getpid() != self._owner_pid:
1003
+ return "foreign-process"
1004
+ with self._lock:
1005
+ return "closed" if self._closed else self._worker_state
1006
+
1007
+ def __enter__(self) -> "DurableTerminalNotificationReceiverRuntime":
1008
+ self._require_open()
1009
+ return self
1010
+
1011
+ def __exit__(self, *_exc: Any) -> None:
1012
+ self.close()
1013
+
1014
+
1015
+ def open_durable_terminal_notification_receiver(
1016
+ database: str | os.PathLike[str],
1017
+ authenticate: Callable[[Mapping[str, Any], Mapping[str, str]], Any],
1018
+ *,
1019
+ origin: str,
1020
+ clock: Callable[[], float | int] = time.time,
1021
+ path: str = _RECEIVER.DEFAULT_PATH,
1022
+ require_https: bool = True,
1023
+ processing_max_attempts: int = 5,
1024
+ processing_base_delay_seconds: float | int = 5,
1025
+ processing_max_delay_seconds: float | int = 300,
1026
+ ) -> DurableTerminalNotificationReceiverRuntime:
1027
+ """Validate all configuration before opening one protected SQLite worker."""
1028
+
1029
+ database_path = _database_path(database)
1030
+ _preflight_application(
1031
+ authenticate,
1032
+ origin=origin,
1033
+ clock=clock,
1034
+ path=path,
1035
+ require_https=require_https,
1036
+ )
1037
+ _preflight_processing(
1038
+ processing_max_attempts,
1039
+ processing_base_delay_seconds,
1040
+ processing_max_delay_seconds,
1041
+ )
1042
+ database_guard = _prepare_database_file(database_path)
1043
+ try:
1044
+ connection = sqlite3.connect(
1045
+ database_path,
1046
+ timeout=5.0,
1047
+ isolation_level=None,
1048
+ check_same_thread=False,
1049
+ )
1050
+ except (OSError, sqlite3.Error) as error:
1051
+ raise DurableTerminalReceiverRuntimeBlocked(
1052
+ "terminal receiver database could not be opened"
1053
+ ) from error
1054
+ try:
1055
+ connection.execute("PRAGMA busy_timeout = 5000")
1056
+ connection.execute("PRAGMA foreign_keys = ON")
1057
+ connection.execute("PRAGMA secure_delete = ON")
1058
+ if connection.execute("PRAGMA foreign_keys").fetchone()[0] != 1:
1059
+ raise sqlite3.DatabaseError("foreign keys unavailable")
1060
+ if connection.execute("PRAGMA secure_delete").fetchone()[0] != 1:
1061
+ raise sqlite3.DatabaseError("secure deletion unavailable")
1062
+ database_guard()
1063
+ inbox = _RECEIVER.DurableCMSTerminalNotificationInbox(
1064
+ connection,
1065
+ database_guard=database_guard,
1066
+ processing_max_attempts=processing_max_attempts,
1067
+ processing_base_delay_seconds=processing_base_delay_seconds,
1068
+ processing_max_delay_seconds=processing_max_delay_seconds,
1069
+ )
1070
+ application = _RECEIVER.CMSTerminalNotificationReceiverApplication(
1071
+ inbox,
1072
+ authenticate,
1073
+ origin=origin,
1074
+ clock=clock,
1075
+ path=path,
1076
+ require_https=require_https,
1077
+ )
1078
+ except Exception as error:
1079
+ connection.close()
1080
+ raise DurableTerminalReceiverRuntimeBlocked(
1081
+ "terminal receiver runtime initialization failed"
1082
+ ) from error
1083
+ return DurableTerminalNotificationReceiverRuntime(
1084
+ connection, inbox, application, database_guard, clock,
1085
+ )
1086
+
1087
+
1088
+ def open_hosted_durable_terminal_notification_receiver(
1089
+ database: str | os.PathLike[str],
1090
+ authenticate: Callable[[Mapping[str, Any], Mapping[str, str]], Any],
1091
+ callback: Callable[[Mapping[str, Any]], Any],
1092
+ *,
1093
+ origin: str,
1094
+ worker_id: str,
1095
+ clock: Callable[[], float | int] = time.time,
1096
+ path: str = _RECEIVER.DEFAULT_PATH,
1097
+ require_https: bool = True,
1098
+ processing_max_attempts: int = 5,
1099
+ processing_base_delay_seconds: float | int = 5,
1100
+ processing_max_delay_seconds: float | int = 300,
1101
+ lease_seconds: float | int = 600,
1102
+ active_delay_seconds: float | int = 0.05,
1103
+ idle_delay_seconds: float | int = 1,
1104
+ blocked_delay_seconds: float | int = 5,
1105
+ ) -> DurableTerminalNotificationReceiverRuntime:
1106
+ """Open a durable receiver and start its supervised processor."""
1107
+
1108
+ configuration = (
1109
+ callback,
1110
+ worker_id,
1111
+ lease_seconds,
1112
+ active_delay_seconds,
1113
+ idle_delay_seconds,
1114
+ blocked_delay_seconds,
1115
+ )
1116
+ DurableTerminalNotificationReceiverRuntime._worker_configuration(
1117
+ *configuration
1118
+ )
1119
+ runtime = open_durable_terminal_notification_receiver(
1120
+ database,
1121
+ authenticate,
1122
+ origin=origin,
1123
+ clock=clock,
1124
+ path=path,
1125
+ require_https=require_https,
1126
+ processing_max_attempts=processing_max_attempts,
1127
+ processing_base_delay_seconds=processing_base_delay_seconds,
1128
+ processing_max_delay_seconds=processing_max_delay_seconds,
1129
+ )
1130
+ try:
1131
+ runtime.start_worker(
1132
+ callback,
1133
+ worker_id,
1134
+ lease_seconds=lease_seconds,
1135
+ active_delay_seconds=active_delay_seconds,
1136
+ idle_delay_seconds=idle_delay_seconds,
1137
+ blocked_delay_seconds=blocked_delay_seconds,
1138
+ )
1139
+ except Exception:
1140
+ runtime.close()
1141
+ raise
1142
+ return runtime