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.
- package/CHANGELOG.md +11 -185
- package/LIESMICH.txt +51 -13
- package/README.md +44 -47
- package/agent-spine-plugin/.codex-plugin/plugin.json +16 -4
- package/agent-spine-plugin/CHANGELOG.md +37 -5
- package/agent-spine-plugin/README.md +3 -3
- package/agent-spine-plugin/blun.plugin.json +45 -10
- package/agent-spine-plugin/docs/artifact-evaluation.md +93 -0
- package/agent-spine-plugin/docs/host-integration.md +42 -27
- package/agent-spine-plugin/docs/preflight-recall.md +4 -2
- package/agent-spine-plugin/docs/session-timeline.md +97 -236
- package/agent-spine-plugin/docs/world-model.md +25 -0
- package/agent-spine-plugin/hooks/codex.json +1 -1
- package/agent-spine-plugin/hooks/hooks.json +1 -1
- package/agent-spine-plugin/package.json +1 -3
- package/agent-spine-plugin/scripts/check-hosts.js +3 -3
- package/agent-spine-plugin/scripts/release-check.js +10 -5
- package/agent-spine-plugin/scripts/run-checks.js +4 -1
- package/agent-spine-plugin/scripts/run-tests-hermetic.js +32 -6
- package/agent-spine-plugin/src/cli-learning.js +15 -0
- package/agent-spine-plugin/src/cli.js +2 -0
- package/agent-spine-plugin/src/hook.js +32 -32
- package/agent-spine-plugin/src/lib/action-lesson-recall.js +73 -8
- package/agent-spine-plugin/src/lib/briefing.js +146 -36
- package/agent-spine-plugin/src/lib/channel-continuity.js +19 -0
- package/agent-spine-plugin/src/lib/delivery-agent-usage.js +14 -7
- package/agent-spine-plugin/src/lib/gateway-group-response.js +128 -0
- package/agent-spine-plugin/src/lib/gateway-runs.js +24 -15
- package/agent-spine-plugin/src/lib/hook-briefing-use.js +13 -3
- package/agent-spine-plugin/src/lib/hook-context.js +16 -3
- package/agent-spine-plugin/src/lib/hook-output.js +129 -5
- package/agent-spine-plugin/src/lib/hook-timeline.js +5 -3
- package/agent-spine-plugin/src/lib/indexed-memory.js +2 -2
- package/agent-spine-plugin/src/lib/learning-artifact-evaluator.js +114 -0
- package/agent-spine-plugin/src/lib/learning-context.js +11 -4
- package/agent-spine-plugin/src/lib/learning-measurements.js +2 -2
- package/agent-spine-plugin/src/lib/mcp-runtime.js +89 -3
- package/agent-spine-plugin/src/lib/mcp-source-context.js +12 -2
- package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +91 -8
- package/agent-spine-plugin/src/lib/mcp-world-tools.js +2 -2
- package/agent-spine-plugin/src/lib/owned-file-lock.js +20 -1
- package/agent-spine-plugin/src/lib/persona-runtime.js +2 -2
- package/agent-spine-plugin/src/lib/preflight-delivery-id.js +27 -0
- package/agent-spine-plugin/src/lib/preflight.js +4 -4
- package/agent-spine-plugin/src/lib/session-timeline-codex.js +15 -0
- package/agent-spine-plugin/src/lib/session-timeline-contract.js +12 -4
- package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +36 -7
- package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +13 -10
- package/agent-spine-plugin/src/lib/session-timeline-invocation.js +1 -1
- package/agent-spine-plugin/src/lib/session-timeline-king.js +14 -0
- package/agent-spine-plugin/src/lib/session-timeline-prior.js +18 -12
- package/agent-spine-plugin/src/lib/session-timeline-provider.js +5 -0
- package/agent-spine-plugin/src/lib/session-timeline-query.js +2 -0
- package/agent-spine-plugin/src/lib/session-timeline-results.js +35 -10
- package/agent-spine-plugin/src/lib/session-timeline-source-open.js +30 -0
- package/agent-spine-plugin/src/lib/session-timeline.js +122 -75
- package/agent-spine-plugin/src/lib/source-roots.js +3 -2
- package/agent-spine-plugin/src/lib/task-knowledge-context.js +22 -1
- package/agent-spine-plugin/src/lib/timeline-continuation-update.js +100 -0
- package/agent-spine-plugin/src/lib/timeline-tool-guard.js +30 -7
- package/agent-spine-plugin/src/lib/timeline-user-feedback.js +217 -0
- package/agent-spine-plugin/src/lib/timeline-world-capture.js +233 -0
- package/agent-spine-plugin/src/lib/world-knowledge.js +59 -2
- package/agent-spine-plugin/src/lib/world-model.js +64 -9
- package/agent-spine-plugin/src/worker.js +13 -1
- package/bin/blun.js +43 -28
- package/bin/core-bootstrap.js +5 -4
- package/bin/king.js +43 -28
- package/bin/launcher-mode.js +1 -10
- package/bin/launcher-runtime.js +128 -295
- package/bin/managed-node.js +0 -0
- package/bin/managed-plugin-selection.cjs +0 -1
- package/bin/native-module-repair.js +0 -0
- package/bin/node-runtime.js +0 -0
- package/bin/node-version.js +0 -0
- package/bin/plugin-bootstrap.js +56 -120
- package/bin/private-paths.js +11 -34
- package/bin/standard-tools-bootstrap.js +34 -114
- package/bin/turn-thinking-policy.cjs +3 -11
- package/bin/update-copy.js +200 -0
- package/bin/update-lease.js +0 -0
- package/bin/update-notice.js +136 -289
- package/bin/verify-agent-behavior.cjs +122 -0
- package/bin/verify-agent-components.cjs +104 -0
- package/bin/verify-bundled-agent-sources.cjs +57 -0
- package/blun.mjs +143076 -135288
- package/bundled-agent-sources.json +701 -0
- package/package.json +12 -15
- package/standard-skills/translate-native/README.md +1293 -0
- package/standard-skills/translate-native/SKILL.md +172 -22
- package/standard-skills/translate-native/VERSION +1 -1
- package/standard-skills/translate-native/agents/openai.yaml +18 -0
- package/standard-skills/translate-native/assets/icon.svg +8 -0
- package/standard-skills/translate-native/docs/BLUN_CODE_INTEGRATION.md +76 -0
- package/standard-skills/translate-native/docs/PREMORTEM.md +489 -0
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION.md +2035 -0
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_API.md +1302 -0
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_EVIDENCE_HTTP.md +136 -0
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_HEALTH_HTTP.md +130 -0
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_HTTP_PROVIDER.md +175 -0
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_RECEIPT_VERIFIER_HTTP.md +86 -0
- package/standard-skills/translate-native/integrations/AGENT_RULES.md +32 -0
- package/standard-skills/translate-native/integrations/adapters/blun-code-language-guard.js +514 -0
- package/standard-skills/translate-native/integrations/adapters/node-language-guard.js +230 -0
- package/standard-skills/translate-native/integrations/audit_log.py +327 -0
- package/standard-skills/translate-native/integrations/claude_language_hook.js +1536 -0
- package/standard-skills/translate-native/integrations/commercial_localization_profile.py +42 -0
- package/standard-skills/translate-native/integrations/delivery-policy.example.json +28 -0
- package/standard-skills/translate-native/integrations/enforced_delivery.py +543 -0
- package/standard-skills/translate-native/integrations/guard_service.py +435 -0
- package/standard-skills/translate-native/integrations/language_gateway.py +67 -0
- package/standard-skills/translate-native/integrations/mcp_auth_headers.py +198 -0
- package/standard-skills/translate-native/integrations/mcp_http_gateway.py +429 -0
- package/standard-skills/translate-native/integrations/non_language_html_entities.js +1485 -0
- package/standard-skills/translate-native/integrations/pre_output_guard.py +65 -0
- package/standard-skills/translate-native/integrations/task_router.py +101 -0
- package/standard-skills/translate-native/integrations/website_localization.py +401 -0
- package/standard-skills/translate-native/integrations/website_localization_api.py +581 -0
- package/standard-skills/translate-native/integrations/website_localization_benchmark.py +1885 -0
- package/standard-skills/translate-native/integrations/website_localization_benchmark_campaign.py +1772 -0
- package/standard-skills/translate-native/integrations/website_localization_benchmark_candidate.py +506 -0
- package/standard-skills/translate-native/integrations/website_localization_benchmark_http.py +400 -0
- package/standard-skills/translate-native/integrations/website_localization_benchmark_review_store.py +781 -0
- package/standard-skills/translate-native/integrations/website_localization_benchmark_reviewer_http.py +500 -0
- package/standard-skills/translate-native/integrations/website_localization_benchmark_runtime.py +1107 -0
- package/standard-skills/translate-native/integrations/website_localization_benchmark_suite.py +463 -0
- package/standard-skills/translate-native/integrations/website_localization_cms.py +2835 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_client.py +875 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_dispatch.py +805 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_http.py +588 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_lifecycle_monitor.py +991 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_receiver.py +1441 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_receiver_runtime.py +414 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_receiver_store.py +1073 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_removal_dispatch.py +865 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_client.py +583 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery.py +964 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_runtime.py +665 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_http.py +1153 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_runtime.py +675 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_service.py +1125 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification.py +674 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_http.py +444 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver.py +1469 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_runtime.py +1142 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_processing_monitor.py +634 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_receiver_client.py +804 -0
- package/standard-skills/translate-native/integrations/website_localization_deepl_baseline.py +922 -0
- package/standard-skills/translate-native/integrations/website_localization_evidence_http.py +482 -0
- package/standard-skills/translate-native/integrations/website_localization_health.py +1541 -0
- package/standard-skills/translate-native/integrations/website_localization_health_http.py +372 -0
- package/standard-skills/translate-native/integrations/website_localization_http_provider.py +297 -0
- package/standard-skills/translate-native/integrations/website_localization_native_reference_http.py +479 -0
- package/standard-skills/translate-native/integrations/website_localization_native_reference_intake.py +363 -0
- package/standard-skills/translate-native/integrations/website_localization_native_reference_queue.py +1449 -0
- package/standard-skills/translate-native/integrations/website_localization_native_reference_store.py +420 -0
- package/standard-skills/translate-native/integrations/website_localization_quality_profiles.py +235 -0
- package/standard-skills/translate-native/integrations/website_localization_queue.py +671 -0
- package/standard-skills/translate-native/integrations/website_localization_receipt_verifier_http.py +516 -0
- package/standard-skills/translate-native/integrations/website_localization_release.py +928 -0
- package/standard-skills/translate-native/integrations/website_localization_release_coordinator.py +1008 -0
- package/standard-skills/translate-native/integrations/website_localization_runner.py +276 -0
- package/standard-skills/translate-native/integrations/website_localization_runtime.py +862 -0
- package/standard-skills/translate-native/integrations/website_localization_service.py +350 -0
- package/standard-skills/translate-native/integrations/website_localization_supervisor.py +511 -0
- package/standard-skills/translate-native/integrations/website_localization_worker.py +663 -0
- package/standard-skills/translate-native/provenance.json +3 -4
- package/standard-skills/translate-native/references/commercial-localization.md +177 -0
- package/standard-skills/translate-native/scripts/blun_language_guard.py +7 -1
- package/standard-skills/translate-native/scripts/check_commercial_review.py +80 -0
- package/standard-skills/translate-native/scripts/commercial_localization_profile.py +333 -0
- package/standard-tools/language-guard/LICENSE +21 -0
- package/standard-tools/language-guard/VERSION +1 -0
- package/standard-tools/language-guard/blun_language_guard.py +7 -1
- package/standard-tools/language-guard/check_commercial_review.py +80 -0
- package/standard-tools/language-guard/commercial_localization_profile.py +333 -0
- package/standard-tools/language-guard/language_gateway.py +62 -0
- package/standard-tools/language-guard/pre_output_guard.py +64 -0
- package/standard-tools/language-guard/provenance.json +4 -11
- package/standard-tools/manifest.json +34 -11
- package/telegram-plugin/commands/access.md +2 -10
- package/telegram-plugin/dist/bridge.mjs +64041 -687
- package/telegram-plugin/dist/mcp-server.mjs +72810 -9027
- package/telegram-plugin/dist/noise.mjs +28 -63511
- package/agent-spine-plugin/CONTRIBUTING.md +0 -52
- package/agent-spine-plugin/SECURITY.md +0 -47
- package/agent-spine-plugin/docs/assignment-continuation.md +0 -48
- package/agent-spine-plugin/docs/releasing.md +0 -85
- package/agent-spine-plugin/docs/structured-completion.md +0 -67
- package/bin/abort-listener-policy.cjs +0 -43
- package/bin/active-steer-priority-policy.cjs +0 -24
- package/bin/agent-api-http-adapter.mjs +0 -446
- package/bin/agent-api-private-http-server.mjs +0 -288
- package/bin/agent-api-runtime.mjs +0 -252
- package/bin/agent-api-service-environment.mjs +0 -236
- package/bin/agent-api-service-host.mjs +0 -209
- package/bin/agent-api-service-process.mjs +0 -171
- package/bin/agent-api-session-registry.mjs +0 -428
- package/bin/agent-api-tool-broker.cjs +0 -248
- package/bin/agent-api-turn-controller.mjs +0 -461
- package/bin/agent-api-usage-journal.cjs +0 -259
- package/bin/agent-resume-snapshot.cjs +0 -241
- package/bin/agentspine-king-goal-inbox.mjs +0 -111
- package/bin/agentspine-king-goal-intake.mjs +0 -106
- package/bin/approval-rejection-stop.cjs +0 -15
- package/bin/assistant-message-offload-policy.cjs +0 -284
- package/bin/baseline-skill-performance-policy.cjs +0 -39
- package/bin/bash-search-scope-policy.cjs +0 -49
- package/bin/codebase-search-runtime.cjs +0 -23
- package/bin/cognitive-action-checkpoint.cjs +0 -1104
- package/bin/cognitive-attention-delivery.cjs +0 -76
- package/bin/cognitive-attention-policy.cjs +0 -143
- package/bin/cognitive-attention-runtime.cjs +0 -91
- package/bin/cognitive-context-projection.cjs +0 -73
- package/bin/cognitive-cross-portal-acceptance.cjs +0 -443
- package/bin/cognitive-effective-view.cjs +0 -77
- package/bin/cognitive-focus-projection.cjs +0 -206
- package/bin/cognitive-focus-scope.cjs +0 -37
- package/bin/cognitive-goal-autostart-policy.cjs +0 -72
- package/bin/cognitive-goal-time-trigger-controller.cjs +0 -146
- package/bin/cognitive-memory-adapter.cjs +0 -282
- package/bin/cognitive-memory-command.cjs +0 -293
- package/bin/cognitive-memory-provider.cjs +0 -92
- package/bin/cognitive-salience-policy.cjs +0 -159
- package/bin/cognitive-state-store.cjs +0 -508
- package/bin/cognitive-turn-lifecycle.cjs +0 -624
- package/bin/cognitive-work-focus.cjs +0 -180
- package/bin/compaction-history-archive.cjs +0 -166
- package/bin/compaction-history-startup.cjs +0 -50
- package/bin/compaction-model-policy.cjs +0 -31
- package/bin/compaction-stage-policy.cjs +0 -21
- package/bin/compaction-transaction-policy.cjs +0 -122
- package/bin/config-write-dedup-policy.cjs +0 -27
- package/bin/context-budget-ledger.cjs +0 -31
- package/bin/context-doctor-policy.cjs +0 -70
- package/bin/context-insight-policy.cjs +0 -36
- package/bin/context-performance-policy.cjs +0 -19
- package/bin/context-pressure-policy.cjs +0 -20
- package/bin/cron-run-output.cjs +0 -45
- package/bin/cron-run-store.cjs +0 -145
- package/bin/curiosity-scout-policy.cjs +0 -49
- package/bin/default-model-output-budget-policy.cjs +0 -28
- package/bin/durable-task-resume-policy.cjs +0 -130
- package/bin/durable-task-resume-runtime.cjs +0 -117
- package/bin/durable-task-resume-store.cjs +0 -88
- package/bin/editable-tool-approval-policy.cjs +0 -540
- package/bin/editable-tool-approval-runtime.cjs +0 -99
- package/bin/effective-system-prompt-cache-policy.cjs +0 -33
- package/bin/error-memory-performance-policy.cjs +0 -113
- package/bin/file-observation-policy.cjs +0 -133
- package/bin/foreground-output-capture-policy.cjs +0 -41
- package/bin/generated-source-health.cjs +0 -142
- package/bin/glob-pattern-policy.cjs +0 -13
- package/bin/goal-completion-evidence-policy.cjs +0 -120
- package/bin/grep-output-limit-policy.cjs +0 -39
- package/bin/historical-media-projection-policy.cjs +0 -48
- package/bin/history-offload-pressure-policy.cjs +0 -33
- package/bin/html-to-research-markdown.cjs +0 -147
- package/bin/identity-context-policy.cjs +0 -764
- package/bin/identity-journal-policy.cjs +0 -107
- package/bin/input-draft-persistence.cjs +0 -77
- package/bin/king-tui-function-contract.json +0 -33
- package/bin/launcher-restart-policy.cjs +0 -150
- package/bin/live-response-repetition-guard.cjs +0 -196
- package/bin/llm-config-log-dedup-policy.cjs +0 -76
- package/bin/loop-event-record-policy.cjs +0 -174
- package/bin/managed-context-startup-policy.cjs +0 -27
- package/bin/media-activity-layout-policy.cjs +0 -34
- package/bin/media-auto-retrieval-policy.cjs +0 -90
- package/bin/media-result-policy.cjs +0 -59
- package/bin/micro-compaction-policy.cjs +0 -145
- package/bin/mistake-relevance-policy.cjs +0 -319
- package/bin/model-retry-progress-policy.cjs +0 -46
- package/bin/native-large-file-io.cjs +0 -42
- package/bin/native-runtime-cache.cjs +0 -76
- package/bin/natural-presence-policy.cjs +0 -28
- package/bin/noninteractive-shell-env-policy.cjs +0 -19
- package/bin/observer-hooks.cjs +0 -14
- package/bin/outbound-claim-provenance.cjs +0 -150
- package/bin/oversized-context-offload-policy.cjs +0 -86
- package/bin/pending-media-policy.cjs +0 -182
- package/bin/pending-token-estimate-policy.cjs +0 -41
- package/bin/personal-memory-consent-policy.cjs +0 -72
- package/bin/personal-memory-performance-policy.cjs +0 -12
- package/bin/personality-choice-policy.cjs +0 -101
- package/bin/personality-memory-adapter.cjs +0 -379
- package/bin/personality-mode.cjs +0 -46
- package/bin/personality-setup-policy.cjs +0 -197
- package/bin/proactive-compaction-policy.cjs +0 -25
- package/bin/profile-identity-resolution.cjs +0 -136
- package/bin/profile-runtime.cjs +0 -318
- package/bin/profile-tool-exclusion-policy.cjs +0 -37
- package/bin/programmatic-context-isolation.cjs +0 -25
- package/bin/programmatic-tool-runtime.mjs +0 -627
- package/bin/provider-idle-timeout-policy.cjs +0 -14
- package/bin/provider-model-refresh-deadline.cjs +0 -53
- package/bin/provider-model-refresh-policy.cjs +0 -107
- package/bin/rate-limit-recovery-policy.cjs +0 -47
- package/bin/read-batch-policy.cjs +0 -32
- package/bin/read-continuation-policy.cjs +0 -59
- package/bin/recurring-cron-history-policy.cjs +0 -124
- package/bin/relationship-continuity-policy.cjs +0 -143
- package/bin/relationship-curiosity-policy.cjs +0 -107
- package/bin/relationship-learning-policy.cjs +0 -168
- package/bin/release-artifact-freeze-policy.cjs +0 -30
- package/bin/reload-plugin-bootstrap.cjs +0 -18
- package/bin/reload-queue-policy.cjs +0 -38
- package/bin/repeated-assistant-response-policy.cjs +0 -232
- package/bin/repeated-injection-projection.cjs +0 -107
- package/bin/repeated-user-message-projection.cjs +0 -8
- package/bin/research-page-result.cjs +0 -74
- package/bin/retry-checkpoint-policy.cjs +0 -13
- package/bin/runtime-exit-ledger.cjs +0 -144
- package/bin/scoped-cron-run-policy.cjs +0 -358
- package/bin/session-checkpoint-policy.cjs +0 -25
- package/bin/session-compaction-policy.cjs +0 -84
- package/bin/session-replay-policy.cjs +0 -20
- package/bin/session-replay-window-policy.cjs +0 -40
- package/bin/session-resume-checkpoint.cjs +0 -254
- package/bin/session-scrollback-archive.cjs +0 -229
- package/bin/skill-activation-performance-policy.cjs +0 -69
- package/bin/skill-listing-performance-policy.cjs +0 -92
- package/bin/soul-organization-policy.cjs +0 -78
- package/bin/soul-preservation-policy.cjs +0 -20
- package/bin/startup-preferences.cjs +0 -131
- package/bin/streaming-flush-performance-policy.cjs +0 -28
- package/bin/structured-agent-swarm-output.cjs +0 -325
- package/bin/structured-subagent-output.cjs +0 -252
- package/bin/subagent-context-fork-policy.cjs +0 -155
- package/bin/subagent-max-tokens-handoff-policy.cjs +0 -69
- package/bin/subagent-parent-responsiveness.cjs +0 -19
- package/bin/subagent-skill-policy.cjs +0 -206
- package/bin/subagent-timeout-policy.cjs +0 -182
- package/bin/subagent-tool-policy.cjs +0 -60
- package/bin/subagent-usage-rollup-policy.cjs +0 -29
- package/bin/system-prompt-context-policy.cjs +0 -124
- package/bin/system-prompt-token-cache-policy.cjs +0 -60
- package/bin/telegram-addressed-focus.cjs +0 -55
- package/bin/telegram-addressed-priority.cjs +0 -12
- package/bin/telegram-approval-relay.cjs +0 -290
- package/bin/telegram-bot-priority.cjs +0 -17
- package/bin/telegram-console-status-policy.cjs +0 -174
- package/bin/telegram-context-projection-policy.cjs +0 -141
- package/bin/telegram-delivery-lifecycle.cjs +0 -125
- package/bin/telegram-direct-focus-policy.cjs +0 -273
- package/bin/telegram-mcp-compatibility.cjs +0 -49
- package/bin/telegram-media-delivery-policy.cjs +0 -42
- package/bin/telegram-private-conversation-policy.cjs +0 -185
- package/bin/telegram-queue-handoff-policy.cjs +0 -73
- package/bin/telegram-remote-status-policy.cjs +0 -120
- package/bin/telegram-session-queue-runtime.mjs +0 -306
- package/bin/telegram-text-chunk-policy.cjs +0 -63
- package/bin/telegram-truncated-reply-policy.cjs +0 -37
- package/bin/telegram-urgent-policy.cjs +0 -45
- package/bin/telemetry-spool-policy.cjs +0 -57
- package/bin/thinking-activity-status-policy.cjs +0 -132
- package/bin/thinking-only-guard.cjs +0 -80
- package/bin/todo-list-turn-policy.cjs +0 -131
- package/bin/tool-call-loop-policy.cjs +0 -51
- package/bin/tool-file-persistence.cjs +0 -141
- package/bin/tool-result-offload-policy.cjs +0 -359
- package/bin/tool-result-offload-telemetry.cjs +0 -12
- package/bin/tool-schema-token-cache-policy.cjs +0 -41
- package/bin/tool-stream-preview-policy.cjs +0 -9
- package/bin/tui-functional-contract.cjs +0 -55
- package/bin/turn-tool-performance-policy.cjs +0 -486
- package/bin/usage-cache-efficiency-policy.cjs +0 -26
- package/bin/user-home-path-policy.cjs +0 -13
- package/bin/user-message-offload-policy.cjs +0 -103
- package/bin/user-prompt-hook-origin-policy.cjs +0 -34
- package/bin/user-tool-record-policy.cjs +0 -7
- package/bin/validated-learning-insight-policy.cjs +0 -58
- package/bin/validated-learning-outcome-trace.cjs +0 -107
- package/bin/validated-learning-performance-policy.cjs +0 -53
- package/bin/validated-learning-signal.cjs +0 -463
- package/bin/windows-bash-dialect-policy.cjs +0 -25
- package/bin/windows-node-crash-dump.cjs +0 -110
- package/bin/write-continuation-policy.cjs +0 -69
- package/codebase-index/README.md +0 -82
- package/codebase-index/codebase_index.py +0 -470
- package/standard-skills/agent-browser/SKILL.md +0 -19
- package/standard-skills/agent-browser/references/runtime.md +0 -8
- package/standard-skills/blun-session-inspector/SKILL.md +0 -41
- package/standard-skills/blun-session-inspector/scripts/inspect-session.cjs +0 -437
- package/standard-skills/design-taste-frontend/SKILL.md +0 -1206
- package/standard-skills/full-output-enforcement/SKILL.md +0 -49
- package/standard-skills/high-end-visual-design/SKILL.md +0 -98
- package/standard-skills/image-to-code/SKILL.md +0 -1228
- package/standard-skills/industrial-brutalist-ui/SKILL.md +0 -92
- package/standard-skills/minimalist-ui/SKILL.md +0 -85
- package/standard-skills/motion-design-taste/SKILL.md +0 -74
- package/standard-skills/playwright-testing/SKILL.md +0 -19
- package/standard-skills/playwright-testing/references/runtime.md +0 -7
- package/standard-skills/premortem/SKILL.md +0 -148
- package/standard-skills/redesign-existing-projects/SKILL.md +0 -178
- package/standard-skills/research-evidence/SKILL.md +0 -39
- package/standard-skills/research-evidence/references/evidence-format.md +0 -104
- package/standard-skills/research-evidence/scripts/evidence-collection.cjs +0 -260
- package/standard-skills/research-evidence/scripts/score-report.cjs +0 -130
- package/standard-skills/screenshot-lesen/SKILL.md +0 -52
- package/standard-skills/stitch-design-taste/DESIGN.md +0 -121
- package/standard-skills/stitch-design-taste/SKILL.md +0 -184
- package/standard-skills/telegram-channel/SKILL.md +0 -18
- package/standard-skills/telegram-channel/references/runtime.md +0 -7
- package/standard-skills/venture-flywheel/SKILL.md +0 -32
- package/standard-skills/venture-flywheel/identity/project-identity.cjs +0 -146
- package/standard-skills/venture-flywheel/policy/capability-engine.cjs +0 -114
- package/standard-skills/venture-flywheel/policy/repository-trust.cjs +0 -229
- package/standard-skills/venture-flywheel/references/BEISPIELE-phase0.md +0 -146
- package/standard-skills/venture-flywheel/references/CAPABILITY-MAP.md +0 -34
- package/standard-skills/venture-flywheel/references/SPEC-phase0-identity-trust.md +0 -77
- package/standard-skills/venture-flywheel/references/SPEC-phase0-state-events.md +0 -93
- package/standard-skills/venture-flywheel/schemas/capability-decision.schema.json +0 -13
- package/standard-skills/venture-flywheel/schemas/execution-event.schema.json +0 -44
- package/standard-skills/venture-flywheel/schemas/project-identity.schema.json +0 -32
- package/standard-skills/venture-flywheel/schemas/repository-trust.schema.json +0 -57
- package/standard-skills/venture-flywheel/schemas/run-transition.schema.json +0 -59
- package/standard-skills/venture-flywheel/state/execution-event.cjs +0 -191
- package/standard-skills/venture-flywheel/state/task-state-machine.cjs +0 -190
- package/standard-skills/web-lesen/SKILL.md +0 -73
- package/standard-skills/web-lesen/scripts/crawl_public.py +0 -379
- package/standard-skills/windows-mcp/SKILL.md +0 -19
- package/standard-skills/windows-mcp/references/runtime.md +0 -9
- package/telegram-plugin/DELIVERY.md +0 -36
- package/telegram-plugin/bin/telegram-approval-relay.cjs +0 -290
- package/telegram-plugin/bin/telegram-console-status-policy.cjs +0 -175
- package/telegram-plugin/bin/telegram-delivery-lifecycle.cjs +0 -125
- package/telegram-plugin/bin/telegram-direct-reply-policy.cjs +0 -48
- package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +0 -122
- package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +0 -186
- package/telegram-plugin/bin/telegram-remote-status-policy.cjs +0 -121
- package/telegram-plugin/bin/telegram-reply-parts.cjs +0 -149
- package/telegram-plugin/bin/telegram-text-chunk-policy.cjs +0 -63
- package/telegram-plugin/bin/telegram-typing-keepalive.cjs +0 -89
- package/telegram-plugin/compat/mcp-server-fa511cd1.mjs +0 -73825
- /package/{bin → scripts}/fix-node-pty-perms.js +0 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Durably observe CMS processing after a terminal notification was accepted."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
import re
|
|
10
|
+
import secrets
|
|
11
|
+
import sqlite3
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Any, Callable, Iterator, Mapping
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
SCHEMA_VERSION = 1
|
|
18
|
+
STATUS_SCHEMA = "blun.cms-terminal-receiver-status-response.v1"
|
|
19
|
+
NOTIFICATION_SCHEMA = "blun.cms-source-terminal-notification.v1"
|
|
20
|
+
NOTIFICATION_FIELDS = {
|
|
21
|
+
"schema", "notification_id", "event_id", "site_id", "plan_id",
|
|
22
|
+
"website_version", "source_sequence", "job_count", "change_sha256",
|
|
23
|
+
"lifecycle_binding_sha256", "terminal_status", "lifecycle_sha256",
|
|
24
|
+
}
|
|
25
|
+
STATUSES = ("pending", "leased", "watching", "retry_wait", "succeeded", "failed")
|
|
26
|
+
REMOTE_STATUSES = ("pending", "leased", "retry_wait", "succeeded", "failed")
|
|
27
|
+
TERMINAL_STATUSES = {
|
|
28
|
+
"cancelled", "deleted", "deletion_failed", "localization_failed",
|
|
29
|
+
"publication_blocked", "publication_failed", "published", "superseded",
|
|
30
|
+
}
|
|
31
|
+
TOKEN = re.compile(r"^[A-Za-z0-9_.:-]{1,256}$")
|
|
32
|
+
SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
|
33
|
+
ERROR_CODE = re.compile(r"^[a-z][a-z0-9_.-]{0,127}$")
|
|
34
|
+
_COLUMNS = (
|
|
35
|
+
"event_id", "notification_id", "site_id", "terminal_status",
|
|
36
|
+
"notification_sha256", "status", "poll_attempts", "failures",
|
|
37
|
+
"max_failures", "next_poll_at", "lease_owner", "lease_token",
|
|
38
|
+
"lease_expires_at", "last_error_code", "receiver_status",
|
|
39
|
+
"receiver_attempts", "receiver_max_attempts", "receiver_next_attempt_at",
|
|
40
|
+
"receiver_lease_expires_at", "receiver_lease_expired",
|
|
41
|
+
"receiver_error_code", "receiver_processed_at", "created_at", "updated_at",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TerminalProcessingMonitorBlocked(RuntimeError):
|
|
46
|
+
"""Stable, content-free processing-observation failure."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, code: str):
|
|
49
|
+
super().__init__(code)
|
|
50
|
+
self.code = code
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class TerminalProcessingStatus:
|
|
55
|
+
event_id: str
|
|
56
|
+
notification_id: str
|
|
57
|
+
site_id: str
|
|
58
|
+
notification_sha256: str
|
|
59
|
+
status: str
|
|
60
|
+
poll_attempts: int
|
|
61
|
+
failures: int
|
|
62
|
+
max_failures: int
|
|
63
|
+
next_poll_at: float
|
|
64
|
+
lease_expires_at: float | None
|
|
65
|
+
lease_expired: bool
|
|
66
|
+
last_error_code: str | None
|
|
67
|
+
receiver_status: str | None
|
|
68
|
+
receiver_attempts: int | None
|
|
69
|
+
receiver_max_attempts: int | None
|
|
70
|
+
receiver_error_code: str | None
|
|
71
|
+
receiver_processed_at: float | None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class TerminalProcessingOutcome:
|
|
76
|
+
event_id: str
|
|
77
|
+
notification_id: str
|
|
78
|
+
status: str
|
|
79
|
+
attempt: int
|
|
80
|
+
error_code: str | None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True)
|
|
84
|
+
class TerminalProcessingHealth:
|
|
85
|
+
status: str
|
|
86
|
+
counts: dict[str, int]
|
|
87
|
+
due: int
|
|
88
|
+
expired_leases: int
|
|
89
|
+
failed: int
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _blocked(code: str) -> None:
|
|
93
|
+
raise TerminalProcessingMonitorBlocked("terminal_processing_monitor." + code)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _token(value: Any) -> bool:
|
|
97
|
+
return isinstance(value, str) and TOKEN.fullmatch(value) is not None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _sha(value: Any) -> bool:
|
|
101
|
+
return isinstance(value, str) and SHA256.fullmatch(value) is not None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _error(value: Any) -> bool:
|
|
105
|
+
return isinstance(value, str) and ERROR_CODE.fullmatch(value) is not None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _canonical_sha256(value: Any) -> str:
|
|
109
|
+
try:
|
|
110
|
+
rendered = json.dumps(
|
|
111
|
+
value, ensure_ascii=False, allow_nan=False, sort_keys=True,
|
|
112
|
+
separators=(",", ":"),
|
|
113
|
+
)
|
|
114
|
+
except (TypeError, ValueError, RecursionError):
|
|
115
|
+
_blocked("notification_invalid")
|
|
116
|
+
return hashlib.sha256(rendered.encode("utf-8")).hexdigest()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _time(value: Any, *, nullable: bool = False) -> float | None:
|
|
120
|
+
if value is None and nullable:
|
|
121
|
+
return None
|
|
122
|
+
if (
|
|
123
|
+
isinstance(value, bool) or not isinstance(value, (int, float))
|
|
124
|
+
or not math.isfinite(float(value)) or float(value) < 0
|
|
125
|
+
):
|
|
126
|
+
_blocked("time_invalid")
|
|
127
|
+
return float(value)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _valid_time(value: Any, *, nullable: bool = False) -> bool:
|
|
131
|
+
if value is None:
|
|
132
|
+
return nullable
|
|
133
|
+
return (
|
|
134
|
+
not isinstance(value, bool) and isinstance(value, (int, float))
|
|
135
|
+
and math.isfinite(float(value)) and float(value) >= 0
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _duration(value: Any, code: str) -> float:
|
|
140
|
+
result = _time(value)
|
|
141
|
+
if result is None or not 0 < result <= 86_400:
|
|
142
|
+
_blocked(code)
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _positive_integer(value: Any, code: str) -> int:
|
|
147
|
+
if isinstance(value, bool) or not isinstance(value, int) or not 1 <= value <= 20:
|
|
148
|
+
_blocked(code)
|
|
149
|
+
return value
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@contextmanager
|
|
153
|
+
def _transaction(connection: sqlite3.Connection) -> Iterator[None]:
|
|
154
|
+
if connection.in_transaction:
|
|
155
|
+
_blocked("transaction_nested")
|
|
156
|
+
try:
|
|
157
|
+
connection.execute("BEGIN IMMEDIATE")
|
|
158
|
+
yield
|
|
159
|
+
connection.commit()
|
|
160
|
+
except Exception:
|
|
161
|
+
if connection.in_transaction:
|
|
162
|
+
connection.rollback()
|
|
163
|
+
raise
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class DurableTerminalProcessingMonitor:
|
|
167
|
+
"""Poll exact receiver status with durable leases and bounded failures."""
|
|
168
|
+
|
|
169
|
+
def __init__(
|
|
170
|
+
self,
|
|
171
|
+
connection: sqlite3.Connection,
|
|
172
|
+
*,
|
|
173
|
+
poll_interval_seconds: float | int = 30,
|
|
174
|
+
base_delay_seconds: float | int = 5,
|
|
175
|
+
max_delay_seconds: float | int = 300,
|
|
176
|
+
):
|
|
177
|
+
if not isinstance(connection, sqlite3.Connection):
|
|
178
|
+
raise TypeError("connection must be sqlite3.Connection")
|
|
179
|
+
self.connection = connection
|
|
180
|
+
self.connection.row_factory = sqlite3.Row
|
|
181
|
+
self.poll_interval_seconds = _duration(
|
|
182
|
+
poll_interval_seconds, "poll_interval_invalid"
|
|
183
|
+
)
|
|
184
|
+
self.base_delay_seconds = _duration(base_delay_seconds, "delay_invalid")
|
|
185
|
+
self.max_delay_seconds = _duration(max_delay_seconds, "delay_invalid")
|
|
186
|
+
if self.base_delay_seconds > self.max_delay_seconds:
|
|
187
|
+
_blocked("delay_invalid")
|
|
188
|
+
self._initialize()
|
|
189
|
+
|
|
190
|
+
def _initialize(self) -> None:
|
|
191
|
+
with _transaction(self.connection):
|
|
192
|
+
self.connection.execute("""
|
|
193
|
+
CREATE TABLE IF NOT EXISTS cms_source_terminal_processing_meta (
|
|
194
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
195
|
+
schema_version INTEGER NOT NULL CHECK (schema_version = 1)
|
|
196
|
+
)
|
|
197
|
+
""")
|
|
198
|
+
self.connection.execute("""
|
|
199
|
+
INSERT OR IGNORE INTO cms_source_terminal_processing_meta VALUES (1, 1)
|
|
200
|
+
""")
|
|
201
|
+
self.connection.execute("""
|
|
202
|
+
CREATE TABLE IF NOT EXISTS cms_source_terminal_processing (
|
|
203
|
+
event_id TEXT PRIMARY KEY, notification_id TEXT NOT NULL UNIQUE,
|
|
204
|
+
site_id TEXT NOT NULL, terminal_status TEXT NOT NULL,
|
|
205
|
+
notification_sha256 TEXT NOT NULL,
|
|
206
|
+
status TEXT NOT NULL CHECK (status IN (
|
|
207
|
+
'pending','leased','watching','retry_wait','succeeded','failed'
|
|
208
|
+
)), poll_attempts INTEGER NOT NULL CHECK (poll_attempts >= 0),
|
|
209
|
+
failures INTEGER NOT NULL CHECK (failures >= 0),
|
|
210
|
+
max_failures INTEGER NOT NULL CHECK (max_failures BETWEEN 1 AND 20),
|
|
211
|
+
next_poll_at REAL NOT NULL, lease_owner TEXT, lease_token TEXT,
|
|
212
|
+
lease_expires_at REAL, last_error_code TEXT, receiver_status TEXT,
|
|
213
|
+
receiver_attempts INTEGER, receiver_max_attempts INTEGER,
|
|
214
|
+
receiver_next_attempt_at REAL, receiver_lease_expires_at REAL,
|
|
215
|
+
receiver_lease_expired INTEGER, receiver_error_code TEXT,
|
|
216
|
+
receiver_processed_at REAL, created_at REAL NOT NULL,
|
|
217
|
+
updated_at REAL NOT NULL,
|
|
218
|
+
CHECK ((status = 'leased' AND lease_owner IS NOT NULL
|
|
219
|
+
AND lease_token IS NOT NULL AND lease_expires_at IS NOT NULL)
|
|
220
|
+
OR (status <> 'leased' AND lease_owner IS NULL
|
|
221
|
+
AND lease_token IS NULL AND lease_expires_at IS NULL))
|
|
222
|
+
)
|
|
223
|
+
""")
|
|
224
|
+
self.connection.execute("""
|
|
225
|
+
CREATE INDEX IF NOT EXISTS cms_source_terminal_processing_due
|
|
226
|
+
ON cms_source_terminal_processing
|
|
227
|
+
(status, next_poll_at, created_at, event_id)
|
|
228
|
+
""")
|
|
229
|
+
self._validate_schema()
|
|
230
|
+
|
|
231
|
+
def _validate_schema(self) -> None:
|
|
232
|
+
meta_columns = tuple(row["name"] for row in self.connection.execute(
|
|
233
|
+
"PRAGMA table_info(cms_source_terminal_processing_meta)"
|
|
234
|
+
).fetchall())
|
|
235
|
+
columns = tuple(row["name"] for row in self.connection.execute(
|
|
236
|
+
"PRAGMA table_info(cms_source_terminal_processing)"
|
|
237
|
+
).fetchall())
|
|
238
|
+
meta = self.connection.execute(
|
|
239
|
+
"SELECT singleton, schema_version FROM cms_source_terminal_processing_meta"
|
|
240
|
+
).fetchall()
|
|
241
|
+
if (
|
|
242
|
+
meta_columns != ("singleton", "schema_version")
|
|
243
|
+
or columns != _COLUMNS or len(meta) != 1
|
|
244
|
+
or tuple(meta[0]) != (1, SCHEMA_VERSION)
|
|
245
|
+
):
|
|
246
|
+
_blocked("schema_altered")
|
|
247
|
+
|
|
248
|
+
def register(
|
|
249
|
+
self,
|
|
250
|
+
notification: Mapping[str, Any],
|
|
251
|
+
notification_sha256: str,
|
|
252
|
+
*,
|
|
253
|
+
max_failures: int = 5,
|
|
254
|
+
now: float | int,
|
|
255
|
+
) -> TerminalProcessingStatus:
|
|
256
|
+
self._validate_schema()
|
|
257
|
+
now = _time(now)
|
|
258
|
+
max_failures = _positive_integer(max_failures, "max_failures_invalid")
|
|
259
|
+
try:
|
|
260
|
+
event_id = notification["event_id"]
|
|
261
|
+
notification_id = notification["notification_id"]
|
|
262
|
+
site_id = notification["site_id"]
|
|
263
|
+
terminal_status = notification["terminal_status"]
|
|
264
|
+
except (KeyError, TypeError):
|
|
265
|
+
_blocked("notification_invalid")
|
|
266
|
+
if (
|
|
267
|
+
set(notification) != NOTIFICATION_FIELDS
|
|
268
|
+
or notification.get("schema") != NOTIFICATION_SCHEMA
|
|
269
|
+
or not all(
|
|
270
|
+
_token(item) for item in (event_id, notification_id, site_id)
|
|
271
|
+
)
|
|
272
|
+
or terminal_status not in TERMINAL_STATUSES
|
|
273
|
+
or not _sha(notification_sha256)
|
|
274
|
+
or _canonical_sha256(notification) != notification_sha256
|
|
275
|
+
):
|
|
276
|
+
_blocked("notification_invalid")
|
|
277
|
+
values = (
|
|
278
|
+
event_id, notification_id, site_id, terminal_status,
|
|
279
|
+
notification_sha256, max_failures,
|
|
280
|
+
)
|
|
281
|
+
with _transaction(self.connection):
|
|
282
|
+
row = self.connection.execute(
|
|
283
|
+
"SELECT notification_id, site_id, terminal_status, "
|
|
284
|
+
"notification_sha256, max_failures "
|
|
285
|
+
"FROM cms_source_terminal_processing WHERE event_id = ?",
|
|
286
|
+
(event_id,),
|
|
287
|
+
).fetchone()
|
|
288
|
+
if row is None:
|
|
289
|
+
self.connection.execute("""
|
|
290
|
+
INSERT INTO cms_source_terminal_processing (
|
|
291
|
+
event_id, notification_id, site_id, terminal_status,
|
|
292
|
+
notification_sha256, status, poll_attempts, failures,
|
|
293
|
+
max_failures, next_poll_at, created_at, updated_at
|
|
294
|
+
) VALUES (?, ?, ?, ?, ?, 'pending', 0, 0, ?, ?, ?, ?)
|
|
295
|
+
""", (*values, now, now, now))
|
|
296
|
+
elif tuple(row) != values[1:]:
|
|
297
|
+
_blocked("idempotency_collision")
|
|
298
|
+
return self.status(event_id, now=now)
|
|
299
|
+
|
|
300
|
+
def claim(
|
|
301
|
+
self,
|
|
302
|
+
worker_id: str,
|
|
303
|
+
*,
|
|
304
|
+
now: float | int,
|
|
305
|
+
lease_seconds: float | int = 600,
|
|
306
|
+
) -> sqlite3.Row | None:
|
|
307
|
+
self._validate_schema()
|
|
308
|
+
if not _token(worker_id) or len(worker_id) > 128:
|
|
309
|
+
_blocked("worker_invalid")
|
|
310
|
+
now = _time(now)
|
|
311
|
+
lease_seconds = _duration(lease_seconds, "lease_invalid")
|
|
312
|
+
with _transaction(self.connection):
|
|
313
|
+
self.connection.execute("""
|
|
314
|
+
UPDATE cms_source_terminal_processing
|
|
315
|
+
SET status = CASE WHEN failures + 1 >= max_failures
|
|
316
|
+
THEN 'failed' ELSE 'retry_wait' END,
|
|
317
|
+
failures = failures + 1, next_poll_at = ?,
|
|
318
|
+
lease_owner = NULL, lease_token = NULL,
|
|
319
|
+
lease_expires_at = NULL,
|
|
320
|
+
last_error_code = 'terminal_processing_monitor.lease_expired',
|
|
321
|
+
updated_at = ?
|
|
322
|
+
WHERE status = 'leased' AND lease_expires_at <= ?
|
|
323
|
+
""", (now, now, now))
|
|
324
|
+
row = self.connection.execute("""
|
|
325
|
+
SELECT * FROM cms_source_terminal_processing
|
|
326
|
+
WHERE status IN ('pending','watching','retry_wait')
|
|
327
|
+
AND next_poll_at <= ? AND failures < max_failures
|
|
328
|
+
ORDER BY created_at, event_id LIMIT 1
|
|
329
|
+
""", (now,)).fetchone()
|
|
330
|
+
if row is None:
|
|
331
|
+
return None
|
|
332
|
+
self._validated_row(row)
|
|
333
|
+
token = secrets.token_urlsafe(32)
|
|
334
|
+
expires_at = now + lease_seconds
|
|
335
|
+
updated = self.connection.execute("""
|
|
336
|
+
UPDATE cms_source_terminal_processing
|
|
337
|
+
SET status = 'leased', poll_attempts = poll_attempts + 1,
|
|
338
|
+
lease_owner = ?, lease_token = ?, lease_expires_at = ?,
|
|
339
|
+
last_error_code = NULL, updated_at = ?
|
|
340
|
+
WHERE event_id = ? AND status IN ('pending','watching','retry_wait')
|
|
341
|
+
""", (worker_id, token, expires_at, now, row["event_id"]))
|
|
342
|
+
if updated.rowcount != 1:
|
|
343
|
+
_blocked("claim_lost")
|
|
344
|
+
return self.connection.execute(
|
|
345
|
+
"SELECT * FROM cms_source_terminal_processing WHERE event_id = ?",
|
|
346
|
+
(row["event_id"],),
|
|
347
|
+
).fetchone()
|
|
348
|
+
|
|
349
|
+
def run_once(
|
|
350
|
+
self,
|
|
351
|
+
reader: Callable[[str, str], Any],
|
|
352
|
+
worker_id: str,
|
|
353
|
+
*,
|
|
354
|
+
now: float | int,
|
|
355
|
+
lease_seconds: float | int = 600,
|
|
356
|
+
) -> TerminalProcessingOutcome | None:
|
|
357
|
+
if not callable(reader):
|
|
358
|
+
_blocked("reader_invalid")
|
|
359
|
+
now = _time(now)
|
|
360
|
+
claim = self.claim(worker_id, now=now, lease_seconds=lease_seconds)
|
|
361
|
+
if claim is None:
|
|
362
|
+
return None
|
|
363
|
+
try:
|
|
364
|
+
response = reader(claim["event_id"], claim["site_id"])
|
|
365
|
+
self._complete(claim, response, now)
|
|
366
|
+
except TerminalProcessingMonitorBlocked:
|
|
367
|
+
raise
|
|
368
|
+
except Exception as error:
|
|
369
|
+
code = getattr(error, "code", None)
|
|
370
|
+
retryable = (
|
|
371
|
+
getattr(error, "cms_notification_failure", False) is True
|
|
372
|
+
and getattr(error, "retryable", None) is True
|
|
373
|
+
)
|
|
374
|
+
if not _error(code):
|
|
375
|
+
code, retryable = "terminal_processing_monitor.reader_failure", False
|
|
376
|
+
self._fail(claim, code, retryable, now)
|
|
377
|
+
result = self.status(claim["event_id"], now=now)
|
|
378
|
+
return TerminalProcessingOutcome(
|
|
379
|
+
result.event_id, result.notification_id, result.status,
|
|
380
|
+
result.poll_attempts, result.last_error_code,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
def _complete(self, claim: sqlite3.Row, response: Any, now: float) -> None:
|
|
384
|
+
if not isinstance(response, Mapping):
|
|
385
|
+
self._fail(
|
|
386
|
+
claim, "terminal_processing_monitor.response_invalid", False, now
|
|
387
|
+
)
|
|
388
|
+
return
|
|
389
|
+
value = dict(response)
|
|
390
|
+
fields = {
|
|
391
|
+
"schema", "notification_id", "event_id", "site_id",
|
|
392
|
+
"terminal_status", "notification_sha256", "processing_status",
|
|
393
|
+
"attempts", "max_attempts", "next_attempt_at", "lease_expires_at",
|
|
394
|
+
"lease_expired", "last_error_code", "processed_at",
|
|
395
|
+
"capabilities_sha256",
|
|
396
|
+
}
|
|
397
|
+
remote = value.get("processing_status")
|
|
398
|
+
attempts = value.get("attempts")
|
|
399
|
+
maximum = value.get("max_attempts")
|
|
400
|
+
error = value.get("last_error_code")
|
|
401
|
+
valid = (
|
|
402
|
+
set(value) == fields and value.get("schema") == STATUS_SCHEMA
|
|
403
|
+
and value.get("notification_id") == claim["notification_id"]
|
|
404
|
+
and value.get("event_id") == claim["event_id"]
|
|
405
|
+
and value.get("site_id") == claim["site_id"]
|
|
406
|
+
and value.get("terminal_status") == claim["terminal_status"]
|
|
407
|
+
and value.get("notification_sha256") == claim["notification_sha256"]
|
|
408
|
+
and remote in REMOTE_STATUSES
|
|
409
|
+
and isinstance(attempts, int) and not isinstance(attempts, bool)
|
|
410
|
+
and isinstance(maximum, int) and not isinstance(maximum, bool)
|
|
411
|
+
and 0 <= attempts <= maximum <= 20 and maximum >= 1
|
|
412
|
+
and _valid_time(value.get("next_attempt_at"))
|
|
413
|
+
and _valid_time(value.get("lease_expires_at"), nullable=True)
|
|
414
|
+
)
|
|
415
|
+
valid = bool(valid) and (
|
|
416
|
+
isinstance(value.get("lease_expired"), bool)
|
|
417
|
+
and (error is None or _error(error))
|
|
418
|
+
and _valid_time(value.get("processed_at"), nullable=True)
|
|
419
|
+
and _sha(value.get("capabilities_sha256"))
|
|
420
|
+
and (remote == "leased") == (value.get("lease_expires_at") is not None)
|
|
421
|
+
and (remote == "succeeded") == (value.get("processed_at") is not None)
|
|
422
|
+
and (error is None or remote in {"retry_wait", "failed"})
|
|
423
|
+
and (not value.get("lease_expired") or remote == "leased")
|
|
424
|
+
)
|
|
425
|
+
if not valid:
|
|
426
|
+
self._fail(
|
|
427
|
+
claim, "terminal_processing_monitor.response_invalid", False, now
|
|
428
|
+
)
|
|
429
|
+
return
|
|
430
|
+
status = "succeeded" if remote == "succeeded" else (
|
|
431
|
+
"failed" if remote == "failed" else "watching"
|
|
432
|
+
)
|
|
433
|
+
local_error = (
|
|
434
|
+
"terminal_processing_monitor.remote_failed"
|
|
435
|
+
if remote == "failed" else None
|
|
436
|
+
)
|
|
437
|
+
with _transaction(self.connection):
|
|
438
|
+
self._require_claim(claim, now)
|
|
439
|
+
updated = self.connection.execute("""
|
|
440
|
+
UPDATE cms_source_terminal_processing
|
|
441
|
+
SET status = ?, failures = 0, next_poll_at = ?,
|
|
442
|
+
lease_owner = NULL, lease_token = NULL, lease_expires_at = NULL,
|
|
443
|
+
last_error_code = ?, receiver_status = ?, receiver_attempts = ?,
|
|
444
|
+
receiver_max_attempts = ?, receiver_next_attempt_at = ?,
|
|
445
|
+
receiver_lease_expires_at = ?, receiver_lease_expired = ?,
|
|
446
|
+
receiver_error_code = ?, receiver_processed_at = ?, updated_at = ?
|
|
447
|
+
WHERE event_id = ? AND status = 'leased'
|
|
448
|
+
AND lease_owner = ? AND lease_token = ?
|
|
449
|
+
""", (
|
|
450
|
+
status, now if status != "watching" else now + self.poll_interval_seconds,
|
|
451
|
+
local_error, remote, attempts, maximum, value["next_attempt_at"],
|
|
452
|
+
value["lease_expires_at"], int(value["lease_expired"]), error,
|
|
453
|
+
value["processed_at"], now, claim["event_id"],
|
|
454
|
+
claim["lease_owner"], claim["lease_token"],
|
|
455
|
+
))
|
|
456
|
+
if updated.rowcount != 1:
|
|
457
|
+
_blocked("completion_lost")
|
|
458
|
+
|
|
459
|
+
def _fail(
|
|
460
|
+
self, claim: sqlite3.Row, code: str, retryable: bool, now: float
|
|
461
|
+
) -> None:
|
|
462
|
+
if not _error(code):
|
|
463
|
+
code, retryable = "terminal_processing_monitor.reader_failure", False
|
|
464
|
+
with _transaction(self.connection):
|
|
465
|
+
row = self._require_claim(claim, now)
|
|
466
|
+
failures = int(row["failures"]) + 1
|
|
467
|
+
terminal = not retryable or failures >= int(row["max_failures"])
|
|
468
|
+
delay = min(
|
|
469
|
+
self.max_delay_seconds,
|
|
470
|
+
self.base_delay_seconds * (2 ** max(0, failures - 1)),
|
|
471
|
+
)
|
|
472
|
+
updated = self.connection.execute("""
|
|
473
|
+
UPDATE cms_source_terminal_processing
|
|
474
|
+
SET status = ?, failures = ?, next_poll_at = ?,
|
|
475
|
+
lease_owner = NULL, lease_token = NULL, lease_expires_at = NULL,
|
|
476
|
+
last_error_code = ?, updated_at = ?
|
|
477
|
+
WHERE event_id = ? AND status = 'leased'
|
|
478
|
+
AND lease_owner = ? AND lease_token = ?
|
|
479
|
+
""", (
|
|
480
|
+
"failed" if terminal else "retry_wait", failures,
|
|
481
|
+
now if terminal else now + delay, code, now, claim["event_id"],
|
|
482
|
+
claim["lease_owner"], claim["lease_token"],
|
|
483
|
+
))
|
|
484
|
+
if updated.rowcount != 1:
|
|
485
|
+
_blocked("failure_lost")
|
|
486
|
+
|
|
487
|
+
def status(self, event_id: str, *, now: float | int) -> TerminalProcessingStatus:
|
|
488
|
+
self._validate_schema()
|
|
489
|
+
if not _token(event_id):
|
|
490
|
+
_blocked("event_invalid")
|
|
491
|
+
now = _time(now)
|
|
492
|
+
row = self.connection.execute(
|
|
493
|
+
"SELECT * FROM cms_source_terminal_processing WHERE event_id = ?",
|
|
494
|
+
(event_id,),
|
|
495
|
+
).fetchone()
|
|
496
|
+
if row is None:
|
|
497
|
+
_blocked("missing")
|
|
498
|
+
self._validated_row(row)
|
|
499
|
+
lease = row["lease_expires_at"]
|
|
500
|
+
return TerminalProcessingStatus(
|
|
501
|
+
row["event_id"], row["notification_id"], row["site_id"],
|
|
502
|
+
row["notification_sha256"], row["status"], int(row["poll_attempts"]),
|
|
503
|
+
int(row["failures"]), int(row["max_failures"]),
|
|
504
|
+
float(row["next_poll_at"]), None if lease is None else float(lease),
|
|
505
|
+
row["status"] == "leased" and float(lease) <= now,
|
|
506
|
+
row["last_error_code"], row["receiver_status"],
|
|
507
|
+
row["receiver_attempts"], row["receiver_max_attempts"],
|
|
508
|
+
row["receiver_error_code"], row["receiver_processed_at"],
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
def health(self, *, now: float | int) -> TerminalProcessingHealth:
|
|
512
|
+
now = _time(now)
|
|
513
|
+
self._validate_schema()
|
|
514
|
+
if tuple(self.connection.execute("PRAGMA quick_check").fetchone()) != ("ok",):
|
|
515
|
+
_blocked("database_integrity")
|
|
516
|
+
rows = self.connection.execute(
|
|
517
|
+
"SELECT * FROM cms_source_terminal_processing ORDER BY event_id"
|
|
518
|
+
).fetchall()
|
|
519
|
+
for row in rows:
|
|
520
|
+
self._validated_row(row)
|
|
521
|
+
counts = {status: 0 for status in STATUSES}
|
|
522
|
+
for row in rows:
|
|
523
|
+
counts[row["status"]] += 1
|
|
524
|
+
due = sum(
|
|
525
|
+
row["status"] in {"pending", "watching", "retry_wait"}
|
|
526
|
+
and row["next_poll_at"] <= now for row in rows
|
|
527
|
+
)
|
|
528
|
+
expired = sum(
|
|
529
|
+
row["status"] == "leased" and row["lease_expires_at"] <= now
|
|
530
|
+
for row in rows
|
|
531
|
+
)
|
|
532
|
+
return TerminalProcessingHealth(
|
|
533
|
+
"blocked" if expired or counts["failed"] else "ok",
|
|
534
|
+
counts, due, expired, counts["failed"],
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
def _require_claim(self, claim: sqlite3.Row, now: float) -> sqlite3.Row:
|
|
538
|
+
row = self.connection.execute(
|
|
539
|
+
"SELECT * FROM cms_source_terminal_processing WHERE event_id = ?",
|
|
540
|
+
(claim["event_id"],),
|
|
541
|
+
).fetchone()
|
|
542
|
+
if (
|
|
543
|
+
row is None or row["status"] != "leased"
|
|
544
|
+
or row["lease_owner"] != claim["lease_owner"]
|
|
545
|
+
or row["lease_token"] != claim["lease_token"]
|
|
546
|
+
):
|
|
547
|
+
_blocked("claim_lost")
|
|
548
|
+
self._validated_row(row)
|
|
549
|
+
if float(row["lease_expires_at"]) <= now:
|
|
550
|
+
_blocked("lease_expired")
|
|
551
|
+
return row
|
|
552
|
+
|
|
553
|
+
@staticmethod
|
|
554
|
+
def _validated_row(row: sqlite3.Row) -> None:
|
|
555
|
+
status = row["status"]
|
|
556
|
+
leased = status == "leased"
|
|
557
|
+
receiver = row["receiver_status"]
|
|
558
|
+
receiver_bundle = (
|
|
559
|
+
row["receiver_attempts"], row["receiver_max_attempts"],
|
|
560
|
+
row["receiver_next_attempt_at"], row["receiver_lease_expired"],
|
|
561
|
+
)
|
|
562
|
+
has_receiver = receiver is not None
|
|
563
|
+
receiver_attempts_valid = (
|
|
564
|
+
has_receiver
|
|
565
|
+
and all(item is not None for item in receiver_bundle)
|
|
566
|
+
and isinstance(row["receiver_attempts"], int)
|
|
567
|
+
and not isinstance(row["receiver_attempts"], bool)
|
|
568
|
+
and isinstance(row["receiver_max_attempts"], int)
|
|
569
|
+
and not isinstance(row["receiver_max_attempts"], bool)
|
|
570
|
+
and 0 <= row["receiver_attempts"] <= row["receiver_max_attempts"] <= 20
|
|
571
|
+
and row["receiver_max_attempts"] >= 1
|
|
572
|
+
and _valid_time(row["receiver_next_attempt_at"])
|
|
573
|
+
and row["receiver_lease_expired"] in {0, 1}
|
|
574
|
+
)
|
|
575
|
+
if (
|
|
576
|
+
not all(_token(row[name]) for name in (
|
|
577
|
+
"event_id", "notification_id", "site_id"
|
|
578
|
+
))
|
|
579
|
+
or row["terminal_status"] not in TERMINAL_STATUSES
|
|
580
|
+
or not _sha(row["notification_sha256"])
|
|
581
|
+
or status not in STATUSES
|
|
582
|
+
or not all(
|
|
583
|
+
isinstance(row[name], int) and not isinstance(row[name], bool)
|
|
584
|
+
for name in ("poll_attempts", "failures", "max_failures")
|
|
585
|
+
)
|
|
586
|
+
or not 0 <= row["failures"] <= row["max_failures"] <= 20
|
|
587
|
+
or row["max_failures"] < 1
|
|
588
|
+
or row["poll_attempts"] < row["failures"]
|
|
589
|
+
or leased != all(row[name] is not None for name in (
|
|
590
|
+
"lease_owner", "lease_token", "lease_expires_at"
|
|
591
|
+
))
|
|
592
|
+
or (not leased and any(row[name] is not None for name in (
|
|
593
|
+
"lease_owner", "lease_token", "lease_expires_at"
|
|
594
|
+
)))
|
|
595
|
+
or (row["last_error_code"] is not None and not _error(
|
|
596
|
+
row["last_error_code"]
|
|
597
|
+
))
|
|
598
|
+
or (status in {"retry_wait", "failed"}) != (
|
|
599
|
+
row["last_error_code"] is not None
|
|
600
|
+
)
|
|
601
|
+
or receiver is not None and receiver not in REMOTE_STATUSES
|
|
602
|
+
or has_receiver != receiver_attempts_valid
|
|
603
|
+
or (receiver == "leased") != (
|
|
604
|
+
row["receiver_lease_expires_at"] is not None
|
|
605
|
+
)
|
|
606
|
+
or not _valid_time(
|
|
607
|
+
row["receiver_lease_expires_at"], nullable=True,
|
|
608
|
+
)
|
|
609
|
+
or not _valid_time(row["receiver_processed_at"], nullable=True)
|
|
610
|
+
or (row["receiver_error_code"] is not None and not _error(
|
|
611
|
+
row["receiver_error_code"]
|
|
612
|
+
))
|
|
613
|
+
or (receiver in {"retry_wait", "failed"}) != (
|
|
614
|
+
row["receiver_error_code"] is not None
|
|
615
|
+
)
|
|
616
|
+
or (receiver == "succeeded") != (
|
|
617
|
+
row["receiver_processed_at"] is not None
|
|
618
|
+
)
|
|
619
|
+
or (
|
|
620
|
+
row["receiver_lease_expired"] == 1
|
|
621
|
+
and receiver != "leased"
|
|
622
|
+
)
|
|
623
|
+
or (status == "succeeded") != (receiver == "succeeded")
|
|
624
|
+
or (
|
|
625
|
+
status == "watching"
|
|
626
|
+
and receiver not in {"pending", "leased", "retry_wait"}
|
|
627
|
+
)
|
|
628
|
+
or (status == "failed" and row["last_error_code"] is None)
|
|
629
|
+
or not _valid_time(row["next_poll_at"])
|
|
630
|
+
or not _valid_time(row["created_at"])
|
|
631
|
+
or not _valid_time(row["updated_at"])
|
|
632
|
+
or row["updated_at"] < row["created_at"]
|
|
633
|
+
):
|
|
634
|
+
_blocked("state_invalid")
|