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,804 @@
1
+ #!/usr/bin/env python3
2
+ """Contract-pinned HTTPS client for one terminal receiver."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import hashlib
7
+ import json
8
+ import math
9
+ import re
10
+ import socket
11
+ import urllib.error
12
+ import urllib.parse
13
+ import urllib.request
14
+ from dataclasses import dataclass
15
+ from typing import Any, Callable, Mapping, Protocol
16
+
17
+
18
+ AUTH_SCHEMA = "blun.cms-source-terminal-notification-http-auth.v1"
19
+ API_SCHEMA = "blun.cms-terminal-receiver-api.v1"
20
+ CAPABILITIES_SCHEMA = "blun.cms-terminal-receiver-capabilities.v1"
21
+ CAPABILITIES_RESPONSE_SCHEMA = (
22
+ "blun.cms-terminal-receiver-capabilities-response.v1"
23
+ )
24
+ HEALTH_SCHEMA = "blun.cms-terminal-receiver-health.v1"
25
+ READINESS_SCHEMA = "blun.cms-terminal-receiver-readiness.v1"
26
+ STATUS_REQUEST_SCHEMA = "blun.cms-terminal-receiver-status-request.v1"
27
+ STATUS_RESPONSE_SCHEMA = "blun.cms-terminal-receiver-status-response.v1"
28
+ ERROR_SCHEMA = "blun.cms-source-terminal-notification-http-error.v1"
29
+ PRINCIPAL_SCHEMA = "blun.cms-source-terminal-notification-principal.v1"
30
+ NOTIFICATION_SCHEMA = "blun.cms-source-terminal-notification.v1"
31
+ ACK_SCHEMA = "blun.cms-source-terminal-notification-ack.v1"
32
+ BASE_PATH = "/v1/localization/terminal-notifications"
33
+ CAPABILITIES_PATH = BASE_PATH + "/capabilities"
34
+ HEALTH_PATH = BASE_PATH + "/health"
35
+ READINESS_PATH = BASE_PATH + "/readiness"
36
+ STATUS_PATH = BASE_PATH + "/status"
37
+ MAX_RESPONSE_BYTES = 16_384
38
+ MAX_ENDPOINT_LENGTH = 2_048
39
+ MAX_HEADER_VALUE_LENGTH = 4_096
40
+ EMPTY_SHA256 = hashlib.sha256(b"").hexdigest()
41
+ SHA256 = re.compile(r"^[a-f0-9]{64}$")
42
+ TOKEN = re.compile(r"^[A-Za-z0-9_.:-]{1,256}$")
43
+ ERROR_CODE = re.compile(r"^[a-z][a-z0-9_.-]{0,127}$")
44
+ NOTIFICATION_ID = re.compile(r"^terminal-[a-f0-9]{64}$")
45
+ HEADER_NAME = re.compile(r"^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$")
46
+ PROCESSING_STATUSES = ("pending", "leased", "retry_wait", "succeeded", "failed")
47
+ TERMINAL_STATUSES = {
48
+ "cancelled", "deleted", "deletion_failed", "localization_failed",
49
+ "publication_blocked", "publication_failed", "published", "superseded",
50
+ }
51
+ WORKER_STATES = {
52
+ "unmanaged", "starting", "running", "stopping", "stopped", "failed",
53
+ }
54
+ RESERVED_HEADERS = {
55
+ "accept", "connection", "content-length", "content-type", "host",
56
+ "idempotency-key", "transfer-encoding",
57
+ "x-localization-terminal-notification-id",
58
+ "x-localization-terminal-notification-sha256",
59
+ "x-localization-terminal-status-sha256",
60
+ }
61
+ NOTIFICATION_FIELDS = {
62
+ "schema", "notification_id", "event_id", "site_id", "plan_id",
63
+ "website_version", "source_sequence", "job_count", "change_sha256",
64
+ "lifecycle_binding_sha256", "terminal_status", "lifecycle_sha256",
65
+ }
66
+ ACK_FIELDS = {
67
+ "schema", "notification_id", "event_id", "site_id", "status",
68
+ "notification_sha256",
69
+ }
70
+
71
+
72
+ class TerminalReceiverClientBlocked(RuntimeError):
73
+ """Stable, content-free control-plane failure."""
74
+
75
+ cms_notification_failure = True
76
+
77
+ def __init__(self, code: str, *, retryable: bool):
78
+ if ERROR_CODE.fullmatch(code) is None or not isinstance(retryable, bool):
79
+ raise ValueError("terminal receiver client failure is invalid")
80
+ super().__init__(code)
81
+ self.code = code
82
+ self.retryable = retryable
83
+
84
+
85
+ @dataclass(frozen=True)
86
+ class HTTPResult:
87
+ status: int
88
+ headers: tuple[tuple[str, str], ...]
89
+ body: bytes
90
+
91
+
92
+ class HTTPTransport(Protocol):
93
+ def request(
94
+ self,
95
+ method: str,
96
+ url: str,
97
+ headers: Mapping[str, str],
98
+ body: bytes | None,
99
+ *,
100
+ timeout: float,
101
+ ) -> HTTPResult: ...
102
+
103
+
104
+ class _NoRedirect(urllib.request.HTTPRedirectHandler):
105
+ def redirect_request(self, req, fp, code, msg, headers, newurl):
106
+ return None
107
+
108
+
109
+ class URLTransport:
110
+ """Perform one bounded HTTPS request without following redirects."""
111
+
112
+ def __init__(self):
113
+ self._opener = urllib.request.build_opener(_NoRedirect)
114
+
115
+ def request(self, method, url, headers, body, *, timeout):
116
+ request = urllib.request.Request(
117
+ url, data=body, headers=dict(headers), method=method,
118
+ )
119
+ try:
120
+ response = self._opener.open(request, timeout=timeout)
121
+ except urllib.error.HTTPError as error:
122
+ response = error
123
+ except (urllib.error.URLError, TimeoutError, socket.timeout, OSError):
124
+ _fail("network", retryable=True)
125
+ try:
126
+ raw = response.read(MAX_RESPONSE_BYTES + 1)
127
+ return HTTPResult(
128
+ int(response.status), tuple(response.headers.items()), raw,
129
+ )
130
+ except (TimeoutError, socket.timeout, OSError):
131
+ _fail("network", retryable=True)
132
+ finally:
133
+ response.close()
134
+
135
+
136
+ def _fail(code: str, *, retryable: bool = False) -> None:
137
+ raise TerminalReceiverClientBlocked(
138
+ "terminal_receiver_client." + code, retryable=retryable,
139
+ )
140
+
141
+
142
+ def _canonical(value: Any) -> bytes:
143
+ try:
144
+ raw = json.dumps(
145
+ value, ensure_ascii=False, allow_nan=False, sort_keys=True,
146
+ separators=(",", ":"),
147
+ ).encode("utf-8")
148
+ except (TypeError, ValueError, RecursionError):
149
+ _fail("request_invalid")
150
+ if not raw or len(raw) > MAX_RESPONSE_BYTES:
151
+ _fail("request_invalid")
152
+ return raw
153
+
154
+
155
+ def _pairs(items: list[tuple[str, Any]]) -> dict[str, Any]:
156
+ result: dict[str, Any] = {}
157
+ for key, value in items:
158
+ if key in result:
159
+ raise ValueError("duplicate key")
160
+ result[key] = value
161
+ return result
162
+
163
+
164
+ def _constant(_value: str) -> None:
165
+ raise ValueError("non-finite number")
166
+
167
+
168
+ def _token(value: Any) -> bool:
169
+ return isinstance(value, str) and TOKEN.fullmatch(value) is not None
170
+
171
+
172
+ def _validated_notification(value: Any) -> tuple[dict[str, Any], bytes, str]:
173
+ if not isinstance(value, Mapping):
174
+ _fail("request_invalid")
175
+ try:
176
+ copied = json.loads(_canonical(dict(value)).decode("utf-8"))
177
+ except TerminalReceiverClientBlocked:
178
+ raise
179
+ except Exception:
180
+ _fail("request_invalid")
181
+ lifecycle_sha = copied.get("lifecycle_sha256")
182
+ terminal_status = copied.get("terminal_status")
183
+ semantic = (
184
+ copied.get("schema") == NOTIFICATION_SCHEMA
185
+ and set(copied) == NOTIFICATION_FIELDS
186
+ and all(
187
+ _token(copied.get(name))
188
+ for name in ("event_id", "site_id", "plan_id", "website_version")
189
+ )
190
+ and all(
191
+ isinstance(copied.get(name), int)
192
+ and not isinstance(copied.get(name), bool)
193
+ and copied[name] > 0
194
+ for name in ("source_sequence", "job_count")
195
+ )
196
+ and all(
197
+ isinstance(copied.get(name), str)
198
+ and SHA256.fullmatch(copied[name]) is not None
199
+ for name in ("change_sha256", "lifecycle_binding_sha256")
200
+ )
201
+ and terminal_status in TERMINAL_STATUSES
202
+ and (
203
+ terminal_status in {"cancelled", "superseded"}
204
+ and lifecycle_sha is None
205
+ or terminal_status not in {"cancelled", "superseded"}
206
+ and isinstance(lifecycle_sha, str)
207
+ and SHA256.fullmatch(lifecycle_sha) is not None
208
+ )
209
+ )
210
+ identity_source = dict(copied)
211
+ notification_id = identity_source.pop("notification_id", None)
212
+ expected_id = "terminal-" + hashlib.sha256(
213
+ _canonical(identity_source),
214
+ ).hexdigest()
215
+ if (
216
+ not semantic
217
+ or not isinstance(notification_id, str)
218
+ or NOTIFICATION_ID.fullmatch(notification_id) is None
219
+ or notification_id != expected_id
220
+ ):
221
+ _fail("request_invalid")
222
+ body = _canonical(copied)
223
+ return copied, body, hashlib.sha256(body).hexdigest()
224
+
225
+
226
+ def _number(value: Any, *, nullable: bool = False) -> bool:
227
+ return (
228
+ nullable and value is None
229
+ or isinstance(value, (int, float))
230
+ and not isinstance(value, bool)
231
+ and math.isfinite(float(value))
232
+ and float(value) >= 0
233
+ )
234
+
235
+
236
+ def _endpoint(value: Any, allow_loopback_http: bool) -> tuple[str, str]:
237
+ if (
238
+ not isinstance(value, str)
239
+ or value != value.strip()
240
+ or len(value) > MAX_ENDPOINT_LENGTH
241
+ or any(ord(character) <= 32 or ord(character) == 127 for character in value)
242
+ ):
243
+ raise ValueError("origin is invalid")
244
+ try:
245
+ parsed = urllib.parse.urlsplit(value)
246
+ hostname = parsed.hostname
247
+ port = parsed.port
248
+ except ValueError:
249
+ raise ValueError("origin is invalid") from None
250
+ secure = parsed.scheme == "https"
251
+ loopback = (
252
+ parsed.scheme == "http"
253
+ and allow_loopback_http
254
+ and isinstance(hostname, str)
255
+ and hostname.lower() in {"localhost", "127.0.0.1", "::1"}
256
+ )
257
+ if (
258
+ not (secure or loopback)
259
+ or not hostname
260
+ or parsed.username is not None
261
+ or parsed.password is not None
262
+ or parsed.path
263
+ or parsed.query
264
+ or parsed.fragment
265
+ or port is not None and not 1 <= port <= 65_535
266
+ or value != f"{parsed.scheme}://{parsed.netloc}"
267
+ ):
268
+ raise ValueError("origin must be one exact HTTPS origin")
269
+ return value, parsed.scheme
270
+
271
+
272
+ def _authentication_headers(provider, context) -> dict[str, str]:
273
+ try:
274
+ supplied = provider(dict(context))
275
+ if not isinstance(supplied, Mapping):
276
+ raise TypeError
277
+ items = tuple(supplied.items())
278
+ except Exception:
279
+ _fail("authentication")
280
+ result: dict[str, str] = {}
281
+ names: set[str] = set()
282
+ for name, value in items:
283
+ normalized = name.lower() if isinstance(name, str) else ""
284
+ if (
285
+ not isinstance(name, str)
286
+ or HEADER_NAME.fullmatch(name) is None
287
+ or normalized in RESERVED_HEADERS
288
+ or normalized in names
289
+ or not isinstance(value, str)
290
+ or not value
291
+ or len(value) > MAX_HEADER_VALUE_LENGTH
292
+ or "\r" in value
293
+ or "\n" in value
294
+ or any(ord(character) < 32 or ord(character) == 127 for character in value)
295
+ ):
296
+ _fail("authentication")
297
+ names.add(normalized)
298
+ result[name] = value
299
+ if not result:
300
+ _fail("authentication")
301
+ return result
302
+
303
+
304
+ def _response_headers(value: Any) -> dict[str, str]:
305
+ if not isinstance(value, tuple):
306
+ _fail("transport_invalid", retryable=True)
307
+ result: dict[str, str] = {}
308
+ for item in value:
309
+ if (
310
+ not isinstance(item, tuple)
311
+ or len(item) != 2
312
+ or not all(isinstance(part, str) for part in item)
313
+ ):
314
+ _fail("transport_invalid", retryable=True)
315
+ name, content = item[0].lower(), item[1].strip()
316
+ if name in result:
317
+ _fail("response_headers")
318
+ result[name] = content
319
+ return result
320
+
321
+
322
+ def _json_response(result: Any, allowed_statuses: set[int]) -> dict[str, Any]:
323
+ if (
324
+ not isinstance(result, HTTPResult)
325
+ or isinstance(result.status, bool)
326
+ or not isinstance(result.status, int)
327
+ or not 100 <= result.status <= 599
328
+ ):
329
+ _fail("transport_invalid", retryable=True)
330
+ if result.status not in allowed_statuses:
331
+ if 300 <= result.status <= 399:
332
+ _fail("redirect")
333
+ retryable = result.status in {408, 425, 429} or result.status >= 500
334
+ _fail("http_status", retryable=retryable)
335
+ headers = _response_headers(result.headers)
336
+ content_type = headers.get("content-type", "").lower().replace(" ", "")
337
+ if content_type not in {"application/json", "application/json;charset=utf-8"}:
338
+ _fail("response_content_type")
339
+ if (
340
+ not isinstance(result.body, bytes)
341
+ or not result.body
342
+ or len(result.body) > MAX_RESPONSE_BYTES
343
+ ):
344
+ _fail("response_size")
345
+ declared = headers.get("content-length")
346
+ if declared is not None and (
347
+ not declared.isascii()
348
+ or not declared.isdecimal()
349
+ or int(declared) != len(result.body)
350
+ ):
351
+ _fail("response_size")
352
+ try:
353
+ text = result.body.decode("utf-8")
354
+ if text.startswith("\ufeff"):
355
+ raise ValueError
356
+ value = json.loads(
357
+ text, object_pairs_hook=_pairs, parse_constant=_constant,
358
+ )
359
+ except (UnicodeDecodeError, json.JSONDecodeError, ValueError, RecursionError):
360
+ _fail("response_json")
361
+ if not isinstance(value, dict):
362
+ _fail("response_binding")
363
+ return value
364
+
365
+
366
+ def _operation(name, method, path, scope, request_schema, request_fields,
367
+ response_schema, response_fields):
368
+ return {
369
+ "method": method,
370
+ "path": path,
371
+ "scope": scope,
372
+ "request_schema": request_schema,
373
+ "request_fields": request_fields,
374
+ "response_schema": response_schema,
375
+ "response_fields": response_fields,
376
+ "success_status": 200,
377
+ }
378
+
379
+
380
+ def _valid_capabilities(value: Any, expected_sha256: str) -> bool:
381
+ if not isinstance(value, dict) or set(value) != {
382
+ "schema", "api_schema", "authentication_request_schema",
383
+ "principal_schema", "error_schema", "limits", "processing_statuses",
384
+ "terminal_statuses", "operations", "sha256",
385
+ }:
386
+ return False
387
+ digest = value.get("sha256")
388
+ unsigned = dict(value)
389
+ unsigned.pop("sha256", None)
390
+ if (
391
+ digest != expected_sha256
392
+ or hashlib.sha256(_canonical(unsigned)).hexdigest() != digest
393
+ or value.get("schema") != CAPABILITIES_SCHEMA
394
+ or value.get("api_schema") != API_SCHEMA
395
+ or value.get("authentication_request_schema") != AUTH_SCHEMA
396
+ or value.get("principal_schema") != PRINCIPAL_SCHEMA
397
+ or value.get("error_schema") != ERROR_SCHEMA
398
+ or value.get("processing_statuses") != list(PROCESSING_STATUSES)
399
+ or value.get("terminal_statuses") != sorted(TERMINAL_STATUSES)
400
+ ):
401
+ return False
402
+ limits = value.get("limits")
403
+ if (
404
+ not isinstance(limits, dict)
405
+ or set(limits) != {
406
+ "max_body_bytes", "max_headers", "max_header_value_bytes",
407
+ "processing_max_attempts_min", "processing_max_attempts_max",
408
+ }
409
+ or limits != {
410
+ "max_body_bytes": 16_384,
411
+ "max_headers": 64,
412
+ "max_header_value_bytes": 4_096,
413
+ "processing_max_attempts_min": 1,
414
+ "processing_max_attempts_max": 20,
415
+ }
416
+ ):
417
+ return False
418
+ operations = value.get("operations")
419
+ if not isinstance(operations, dict) or set(operations) != {
420
+ "capabilities", "health", "notification", "readiness", "status",
421
+ }:
422
+ return False
423
+ expected = {
424
+ "capabilities": _operation(
425
+ "capabilities", "GET", CAPABILITIES_PATH,
426
+ "terminal-notification-capabilities:read", None, [],
427
+ CAPABILITIES_RESPONSE_SCHEMA, ["schema", "capabilities"],
428
+ ),
429
+ "health": _operation(
430
+ "health", "GET", HEALTH_PATH, "terminal-notification-health:read",
431
+ None, [], HEALTH_SCHEMA,
432
+ [
433
+ "schema", "status", "runtime_state", "worker_state",
434
+ "inbox_status", "received", "processing_counts",
435
+ "processing_due", "expired_leases", "failed", "error_code",
436
+ "capabilities_sha256",
437
+ ],
438
+ ),
439
+ "readiness": _operation(
440
+ "readiness", "GET", READINESS_PATH,
441
+ "terminal-notification-readiness:read", None, [], READINESS_SCHEMA,
442
+ [
443
+ "schema", "status", "worker_state", "inbox_status",
444
+ "error_code", "capabilities_sha256",
445
+ ],
446
+ ),
447
+ "status": _operation(
448
+ "status", "POST", STATUS_PATH, "terminal-notification-status:read",
449
+ STATUS_REQUEST_SCHEMA, ["schema", "event_id", "site_id"],
450
+ STATUS_RESPONSE_SCHEMA,
451
+ [
452
+ "schema", "notification_id", "event_id", "site_id",
453
+ "terminal_status", "notification_sha256", "processing_status",
454
+ "attempts", "max_attempts", "next_attempt_at",
455
+ "lease_expires_at", "lease_expired", "last_error_code",
456
+ "processed_at",
457
+ "capabilities_sha256",
458
+ ],
459
+ ),
460
+ }
461
+ notification = operations.get("notification")
462
+ if not isinstance(notification, dict):
463
+ return False
464
+ path = notification.get("path")
465
+ if (
466
+ not isinstance(path, str)
467
+ or not path.startswith("/")
468
+ or path.startswith("//")
469
+ or path in {CAPABILITIES_PATH, HEALTH_PATH, READINESS_PATH, STATUS_PATH}
470
+ ):
471
+ return False
472
+ expected["notification"] = _operation(
473
+ "notification", "POST", path, "terminal-notification:write",
474
+ NOTIFICATION_SCHEMA,
475
+ [
476
+ "schema", "notification_id", "event_id", "site_id", "plan_id",
477
+ "website_version", "source_sequence", "job_count", "change_sha256",
478
+ "lifecycle_binding_sha256", "terminal_status", "lifecycle_sha256",
479
+ ],
480
+ ACK_SCHEMA,
481
+ [
482
+ "schema", "notification_id", "event_id", "site_id", "status",
483
+ "notification_sha256",
484
+ ],
485
+ )
486
+ return operations == expected
487
+
488
+
489
+ class HTTPTerminalReceiverClient:
490
+ """Operate one receiver only after verifying its pinned live contract."""
491
+
492
+ def __init__(
493
+ self,
494
+ origin: str,
495
+ expected_capabilities_sha256: str,
496
+ authentication_headers: Callable[[Mapping[str, Any]], Mapping[str, str]],
497
+ *,
498
+ transport: HTTPTransport | None = None,
499
+ timeout: float | int = 30,
500
+ allow_loopback_http: bool = False,
501
+ ):
502
+ if not isinstance(allow_loopback_http, bool):
503
+ raise TypeError("allow_loopback_http must be boolean")
504
+ self.origin, _scheme = _endpoint(origin, allow_loopback_http)
505
+ if (
506
+ not isinstance(expected_capabilities_sha256, str)
507
+ or SHA256.fullmatch(expected_capabilities_sha256) is None
508
+ ):
509
+ raise ValueError("expected capability digest is invalid")
510
+ if not callable(authentication_headers):
511
+ raise TypeError("authentication_headers must be callable")
512
+ if (
513
+ isinstance(timeout, bool)
514
+ or not isinstance(timeout, (int, float))
515
+ or not math.isfinite(float(timeout))
516
+ or not 0 < float(timeout) <= 300
517
+ ):
518
+ raise ValueError("timeout is outside the supported range")
519
+ self.expected_capabilities_sha256 = expected_capabilities_sha256
520
+ self.authentication_headers = authentication_headers
521
+ self.transport = URLTransport() if transport is None else transport
522
+ if not callable(getattr(self.transport, "request", None)):
523
+ raise TypeError("transport must provide request")
524
+ self.timeout = float(timeout)
525
+
526
+ def __repr__(self) -> str:
527
+ return "HTTPTerminalReceiverClient(configured=True)"
528
+
529
+ def _request(self, method, path, body, allowed_statuses, context):
530
+ body_sha256 = hashlib.sha256(body or b"").hexdigest()
531
+ authentication = {
532
+ "schema": AUTH_SCHEMA,
533
+ "method": method,
534
+ "origin": self.origin,
535
+ "path": path,
536
+ "body_sha256": body_sha256,
537
+ **context,
538
+ }
539
+ headers = _authentication_headers(
540
+ self.authentication_headers, authentication,
541
+ )
542
+ headers["Accept"] = "application/json"
543
+ if body is not None:
544
+ headers["Content-Type"] = "application/json; charset=utf-8"
545
+ headers["X-Localization-Terminal-Status-SHA256"] = body_sha256
546
+ try:
547
+ result = self.transport.request(
548
+ method, self.origin + path, headers, body,
549
+ timeout=self.timeout,
550
+ )
551
+ except TerminalReceiverClientBlocked:
552
+ raise
553
+ except Exception:
554
+ _fail("network", retryable=True)
555
+ return result, _json_response(result, allowed_statuses)
556
+
557
+ def capabilities(self) -> Mapping[str, Any]:
558
+ _result, response = self._request(
559
+ "GET", CAPABILITIES_PATH, None, {200}, {},
560
+ )
561
+ if (
562
+ set(response) != {"schema", "capabilities"}
563
+ or response.get("schema") != CAPABILITIES_RESPONSE_SCHEMA
564
+ or not _valid_capabilities(
565
+ response.get("capabilities"),
566
+ self.expected_capabilities_sha256,
567
+ )
568
+ ):
569
+ _fail("capabilities_binding")
570
+ return response
571
+
572
+ def _verify_contract(self) -> None:
573
+ self.capabilities()
574
+
575
+ def notify(self, notification: Mapping[str, Any]) -> Mapping[str, Any]:
576
+ """Send one immutable notification through its freshly pinned route."""
577
+
578
+ payload, body, body_sha256 = _validated_notification(notification)
579
+ capabilities = self.capabilities()["capabilities"]
580
+ path = capabilities["operations"]["notification"]["path"]
581
+ context = {
582
+ "notification_id": payload["notification_id"],
583
+ "event_id": payload["event_id"],
584
+ "site_id": payload["site_id"],
585
+ }
586
+ authentication = {
587
+ "schema": AUTH_SCHEMA,
588
+ "method": "POST",
589
+ "origin": self.origin,
590
+ "path": path,
591
+ "body_sha256": body_sha256,
592
+ **context,
593
+ }
594
+ headers = _authentication_headers(
595
+ self.authentication_headers, authentication,
596
+ )
597
+ headers.update({
598
+ "Accept": "application/json",
599
+ "Content-Type": "application/json; charset=utf-8",
600
+ "Idempotency-Key": payload["notification_id"],
601
+ "X-Localization-Terminal-Notification-Id": (
602
+ payload["notification_id"]
603
+ ),
604
+ "X-Localization-Terminal-Notification-Sha256": body_sha256,
605
+ })
606
+ try:
607
+ result = self.transport.request(
608
+ "POST", self.origin + path, headers, body,
609
+ timeout=self.timeout,
610
+ )
611
+ except TerminalReceiverClientBlocked:
612
+ raise
613
+ except Exception:
614
+ _fail("network", retryable=True)
615
+ acknowledgement = _json_response(result, {200})
616
+ expected = {
617
+ "schema": ACK_SCHEMA,
618
+ "notification_id": payload["notification_id"],
619
+ "event_id": payload["event_id"],
620
+ "site_id": payload["site_id"],
621
+ "status": "accepted",
622
+ "notification_sha256": body_sha256,
623
+ }
624
+ if set(acknowledgement) != ACK_FIELDS or acknowledgement != expected:
625
+ _fail("notification_binding")
626
+ return acknowledgement
627
+
628
+ def __call__(self, notification: Mapping[str, Any]) -> Mapping[str, Any]:
629
+ """Allow direct use as a durable source-service terminal notifier."""
630
+
631
+ return self.notify(notification)
632
+
633
+ def readiness(self) -> Mapping[str, Any]:
634
+ self._verify_contract()
635
+ result, response = self._request(
636
+ "GET", READINESS_PATH, None, {200, 503}, {},
637
+ )
638
+ if not self._valid_readiness(response, result.status):
639
+ _fail("readiness_binding")
640
+ return response
641
+
642
+ def health(self) -> Mapping[str, Any]:
643
+ self._verify_contract()
644
+ result, response = self._request(
645
+ "GET", HEALTH_PATH, None, {200, 503}, {},
646
+ )
647
+ if not self._valid_health(response, result.status):
648
+ _fail("health_binding")
649
+ return response
650
+
651
+ def status(self, event_id: str, site_id: str) -> Mapping[str, Any]:
652
+ if not _token(event_id) or not _token(site_id):
653
+ _fail("request_invalid")
654
+ self._verify_contract()
655
+ request = {
656
+ "schema": STATUS_REQUEST_SCHEMA,
657
+ "event_id": event_id,
658
+ "site_id": site_id,
659
+ }
660
+ body = _canonical(request)
661
+ _result, response = self._request(
662
+ "POST", STATUS_PATH, body, {200},
663
+ {"event_id": event_id, "site_id": site_id},
664
+ )
665
+ if not self._valid_status(response, event_id, site_id):
666
+ _fail("status_binding")
667
+ return response
668
+
669
+ def _valid_readiness(
670
+ self, value: Mapping[str, Any], http_status: int,
671
+ ) -> bool:
672
+ if set(value) != {
673
+ "schema", "status", "worker_state", "inbox_status", "error_code",
674
+ "capabilities_sha256",
675
+ }:
676
+ return False
677
+ ready = value.get("status") == "ready"
678
+ error = value.get("error_code")
679
+ return (
680
+ value.get("schema") == READINESS_SCHEMA
681
+ and value.get("capabilities_sha256")
682
+ == self.expected_capabilities_sha256
683
+ and value.get("status") in {"ready", "not_ready"}
684
+ and value.get("worker_state") in WORKER_STATES | {"closed"}
685
+ and value.get("inbox_status") in {None, "ok", "blocked"}
686
+ and (error is None or isinstance(error, str) and ERROR_CODE.fullmatch(error))
687
+ and ready == (http_status == 200)
688
+ and ready == (
689
+ value.get("worker_state") == "running"
690
+ and value.get("inbox_status") == "ok"
691
+ and error is None
692
+ )
693
+ )
694
+
695
+ def _valid_health(
696
+ self, value: Mapping[str, Any], http_status: int,
697
+ ) -> bool:
698
+ fields = {
699
+ "schema", "status", "runtime_state", "worker_state", "inbox_status",
700
+ "received", "processing_counts", "processing_due", "expired_leases",
701
+ "failed", "error_code",
702
+ "capabilities_sha256",
703
+ }
704
+ if set(value) != fields:
705
+ return False
706
+ counts = value.get("processing_counts")
707
+ metrics = [
708
+ value.get("received"), value.get("processing_due"),
709
+ value.get("expired_leases"), value.get("failed"),
710
+ ]
711
+ available = isinstance(counts, dict)
712
+ valid_counts = (
713
+ available
714
+ and set(counts) == set(PROCESSING_STATUSES)
715
+ and all(isinstance(item, int) and not isinstance(item, bool) and item >= 0
716
+ for item in counts.values())
717
+ )
718
+ healthy = value.get("status") == "ok"
719
+ error = value.get("error_code")
720
+ return (
721
+ value.get("schema") == HEALTH_SCHEMA
722
+ and value.get("capabilities_sha256")
723
+ == self.expected_capabilities_sha256
724
+ and value.get("status") in {"ok", "blocked"}
725
+ and value.get("runtime_state") == "open"
726
+ and value.get("worker_state") in WORKER_STATES
727
+ and value.get("inbox_status") in {"ok", "blocked"}
728
+ and (error is None or isinstance(error, str) and ERROR_CODE.fullmatch(error))
729
+ and healthy == (http_status == 200)
730
+ and healthy == (
731
+ value.get("worker_state") in {"unmanaged", "running"}
732
+ and value.get("inbox_status") == "ok"
733
+ and error is None
734
+ )
735
+ and (
736
+ valid_counts
737
+ and all(isinstance(item, int) and not isinstance(item, bool) and item >= 0
738
+ for item in metrics)
739
+ and sum(counts.values()) == value.get("received")
740
+ and counts["failed"] == value.get("failed")
741
+ and value.get("processing_due") <= counts["pending"] + counts["retry_wait"]
742
+ and value.get("expired_leases") <= counts["leased"]
743
+ and (value.get("inbox_status") == "ok") == (
744
+ value.get("failed") == 0
745
+ and value.get("expired_leases") == 0
746
+ )
747
+ or not available
748
+ and all(item is None for item in metrics)
749
+ and not healthy
750
+ and value.get("inbox_status") == "blocked"
751
+ )
752
+ )
753
+
754
+ def _valid_status(
755
+ self, value: Mapping[str, Any], event_id: str, site_id: str,
756
+ ) -> bool:
757
+ fields = {
758
+ "schema", "notification_id", "event_id", "site_id",
759
+ "terminal_status", "notification_sha256", "processing_status",
760
+ "attempts", "max_attempts", "next_attempt_at", "lease_expires_at",
761
+ "lease_expired", "last_error_code", "processed_at",
762
+ "capabilities_sha256",
763
+ }
764
+ error = value.get("last_error_code")
765
+ return (
766
+ set(value) == fields
767
+ and value.get("schema") == STATUS_RESPONSE_SCHEMA
768
+ and value.get("capabilities_sha256")
769
+ == self.expected_capabilities_sha256
770
+ and isinstance(value.get("notification_id"), str)
771
+ and NOTIFICATION_ID.fullmatch(value["notification_id"]) is not None
772
+ and value.get("event_id") == event_id
773
+ and value.get("site_id") == site_id
774
+ and value.get("terminal_status") in TERMINAL_STATUSES
775
+ and isinstance(value.get("notification_sha256"), str)
776
+ and SHA256.fullmatch(value["notification_sha256"]) is not None
777
+ and value.get("processing_status") in PROCESSING_STATUSES
778
+ and all(
779
+ isinstance(value.get(name), int)
780
+ and not isinstance(value.get(name), bool)
781
+ and value[name] >= 0
782
+ for name in ("attempts", "max_attempts")
783
+ )
784
+ and 1 <= value.get("max_attempts", 0) <= 20
785
+ and value.get("attempts", 0) <= value.get("max_attempts", 0)
786
+ and _number(value.get("next_attempt_at"))
787
+ and _number(value.get("lease_expires_at"), nullable=True)
788
+ and isinstance(value.get("lease_expired"), bool)
789
+ and (error is None or isinstance(error, str) and ERROR_CODE.fullmatch(error))
790
+ and _number(value.get("processed_at"), nullable=True)
791
+ and (error is None or value.get("processing_status") in {
792
+ "retry_wait", "failed",
793
+ })
794
+ and (value.get("processing_status") == "succeeded") == (
795
+ value.get("processed_at") is not None
796
+ )
797
+ and (value.get("processing_status") == "leased") == (
798
+ value.get("lease_expires_at") is not None
799
+ )
800
+ and (
801
+ not value.get("lease_expired")
802
+ or value.get("processing_status") == "leased"
803
+ )
804
+ )