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,671 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Transactional queue state for provider-neutral website localization jobs.
|
|
3
|
+
|
|
4
|
+
The trusted host owns and opens the SQLite connection. This module deliberately
|
|
5
|
+
does not choose a filesystem path: secure, platform-specific database placement
|
|
6
|
+
belongs to the host. Queue completion records worker output, but never means
|
|
7
|
+
that the output is reviewed, signed, or ready for publication.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
import secrets
|
|
16
|
+
import sqlite3
|
|
17
|
+
import time
|
|
18
|
+
import unicodedata
|
|
19
|
+
from contextlib import contextmanager
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from typing import Any, Iterator, Mapping
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
SCHEMA_VERSION = 1
|
|
25
|
+
MAX_ATTEMPTS = 20
|
|
26
|
+
MAX_LEASE_SECONDS = 86_400.0
|
|
27
|
+
MAX_PAYLOAD_BYTES = 2_000_000
|
|
28
|
+
MAX_RESULT_BYTES = 2_000_000
|
|
29
|
+
QUALITY_PASSES = ["target_native", "source_fidelity"]
|
|
30
|
+
ERROR_CODE = re.compile(r"^[a-z][a-z0-9_.-]{0,127}$")
|
|
31
|
+
_STATUSES = ("pending", "leased", "retry_wait", "succeeded", "failed")
|
|
32
|
+
_COLUMNS = (
|
|
33
|
+
"job_id",
|
|
34
|
+
"target_locale",
|
|
35
|
+
"payload_json",
|
|
36
|
+
"payload_sha256",
|
|
37
|
+
"status",
|
|
38
|
+
"attempts",
|
|
39
|
+
"max_attempts",
|
|
40
|
+
"next_attempt_at",
|
|
41
|
+
"lease_owner",
|
|
42
|
+
"lease_token",
|
|
43
|
+
"lease_expires_at",
|
|
44
|
+
"last_error_code",
|
|
45
|
+
"last_error_detail_hash",
|
|
46
|
+
"result_json",
|
|
47
|
+
"result_sha256",
|
|
48
|
+
"created_at",
|
|
49
|
+
"updated_at",
|
|
50
|
+
)
|
|
51
|
+
_PLAN_COLUMNS = ("plan_id", "job_id", "target_locale", "created_at")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class LocalizationQueueBlocked(RuntimeError):
|
|
55
|
+
"""Raised when queue state or an attempted transition is unsafe."""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class ClaimedJob:
|
|
60
|
+
job_id: str
|
|
61
|
+
plan_ids: tuple[str, ...]
|
|
62
|
+
target_locale: str
|
|
63
|
+
payload: dict[str, Any]
|
|
64
|
+
attempt: int
|
|
65
|
+
max_attempts: int
|
|
66
|
+
lease_owner: str
|
|
67
|
+
lease_token: str
|
|
68
|
+
lease_expires_at: float
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True)
|
|
72
|
+
class JobStatus:
|
|
73
|
+
job_id: str
|
|
74
|
+
plan_ids: tuple[str, ...]
|
|
75
|
+
target_locale: str
|
|
76
|
+
status: str
|
|
77
|
+
attempts: int
|
|
78
|
+
max_attempts: int
|
|
79
|
+
next_attempt_at: float
|
|
80
|
+
lease_expires_at: float | None
|
|
81
|
+
last_error_code: str | None
|
|
82
|
+
last_error_detail_hash: str | None
|
|
83
|
+
result_sha256: str | None
|
|
84
|
+
created_at: float
|
|
85
|
+
updated_at: float
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _canonical_json(value: Any) -> str:
|
|
89
|
+
try:
|
|
90
|
+
return json.dumps(
|
|
91
|
+
value,
|
|
92
|
+
ensure_ascii=False,
|
|
93
|
+
allow_nan=False,
|
|
94
|
+
sort_keys=True,
|
|
95
|
+
separators=(",", ":"),
|
|
96
|
+
)
|
|
97
|
+
except (TypeError, ValueError) as error:
|
|
98
|
+
raise LocalizationQueueBlocked("payload must be finite JSON") from error
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _hash_text(value: str) -> str:
|
|
102
|
+
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _validate_unicode(value: Any, *, field: str) -> None:
|
|
106
|
+
if isinstance(value, str):
|
|
107
|
+
if "\x00" in value or not unicodedata.is_normalized("NFC", value):
|
|
108
|
+
raise LocalizationQueueBlocked(f"{field} contains unsafe Unicode text")
|
|
109
|
+
elif isinstance(value, list):
|
|
110
|
+
for item in value:
|
|
111
|
+
_validate_unicode(item, field=field)
|
|
112
|
+
elif isinstance(value, dict):
|
|
113
|
+
for key, item in value.items():
|
|
114
|
+
if not isinstance(key, str):
|
|
115
|
+
raise LocalizationQueueBlocked(f"{field} object keys must be strings")
|
|
116
|
+
_validate_unicode(key, field=field)
|
|
117
|
+
_validate_unicode(item, field=field)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _field(name: str, value: Any, *, limit: int = 256) -> str:
|
|
121
|
+
if not isinstance(value, str) or not value or value != value.strip():
|
|
122
|
+
raise LocalizationQueueBlocked(f"{name} must be a non-empty string without outer whitespace")
|
|
123
|
+
if len(value) > limit or "\x00" in value or not unicodedata.is_normalized("NFC", value):
|
|
124
|
+
raise LocalizationQueueBlocked(f"{name} is invalid or too long")
|
|
125
|
+
return value
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _timestamp(name: str, value: float | int | None) -> float:
|
|
129
|
+
if value is None:
|
|
130
|
+
return time.time()
|
|
131
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
132
|
+
raise LocalizationQueueBlocked(f"{name} must be a finite timestamp")
|
|
133
|
+
value = float(value)
|
|
134
|
+
if value < 0 or value != value or value in {float("inf"), float("-inf")}:
|
|
135
|
+
raise LocalizationQueueBlocked(f"{name} must be a finite timestamp")
|
|
136
|
+
return value
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _duration(name: str, value: float | int, *, allow_zero: bool = False) -> float:
|
|
140
|
+
value = _timestamp(name, value)
|
|
141
|
+
if (value < 0 if allow_zero else value <= 0) or value > MAX_LEASE_SECONDS:
|
|
142
|
+
raise LocalizationQueueBlocked(f"{name} is outside the supported range")
|
|
143
|
+
return value
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _error(code: Any, detail: Any = None) -> tuple[str, str | None]:
|
|
147
|
+
code = _field("error_code", code, limit=128)
|
|
148
|
+
if not ERROR_CODE.fullmatch(code):
|
|
149
|
+
raise LocalizationQueueBlocked("error_code has an invalid format")
|
|
150
|
+
if detail is None:
|
|
151
|
+
return code, None
|
|
152
|
+
if not isinstance(detail, str):
|
|
153
|
+
raise LocalizationQueueBlocked("error_detail must be a string")
|
|
154
|
+
if "\x00" in detail or not unicodedata.is_normalized("NFC", detail):
|
|
155
|
+
raise LocalizationQueueBlocked("error_detail contains unsafe Unicode text")
|
|
156
|
+
return code, _hash_text(detail)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@contextmanager
|
|
160
|
+
def _transaction(connection: sqlite3.Connection) -> Iterator[None]:
|
|
161
|
+
if connection.in_transaction:
|
|
162
|
+
raise LocalizationQueueBlocked("queue operation cannot join an external transaction")
|
|
163
|
+
try:
|
|
164
|
+
connection.execute("BEGIN IMMEDIATE")
|
|
165
|
+
yield
|
|
166
|
+
except Exception:
|
|
167
|
+
connection.rollback()
|
|
168
|
+
raise
|
|
169
|
+
else:
|
|
170
|
+
connection.commit()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class LocalizationQueue:
|
|
174
|
+
"""Atomic at-least-once queue with expiring, attempt-bound worker leases."""
|
|
175
|
+
|
|
176
|
+
def __init__(self, connection: sqlite3.Connection):
|
|
177
|
+
if not isinstance(connection, sqlite3.Connection):
|
|
178
|
+
raise LocalizationQueueBlocked("connection must be sqlite3.Connection")
|
|
179
|
+
self.connection = connection
|
|
180
|
+
self.connection.row_factory = sqlite3.Row
|
|
181
|
+
self.connection.execute("PRAGMA foreign_keys = ON")
|
|
182
|
+
self.connection.execute("PRAGMA busy_timeout = 5000")
|
|
183
|
+
version = int(self.connection.execute("PRAGMA user_version").fetchone()[0])
|
|
184
|
+
if version not in {0, SCHEMA_VERSION}:
|
|
185
|
+
raise LocalizationQueueBlocked("unsupported localization queue schema")
|
|
186
|
+
if version == 0:
|
|
187
|
+
self._create_schema()
|
|
188
|
+
self._verify_schema()
|
|
189
|
+
|
|
190
|
+
def _create_schema(self) -> None:
|
|
191
|
+
with _transaction(self.connection):
|
|
192
|
+
version = int(self.connection.execute("PRAGMA user_version").fetchone()[0])
|
|
193
|
+
if version == SCHEMA_VERSION:
|
|
194
|
+
return
|
|
195
|
+
if version != 0:
|
|
196
|
+
raise LocalizationQueueBlocked("unsupported localization queue schema")
|
|
197
|
+
self.connection.execute(f"""
|
|
198
|
+
CREATE TABLE localization_jobs (
|
|
199
|
+
job_id TEXT PRIMARY KEY,
|
|
200
|
+
target_locale TEXT NOT NULL,
|
|
201
|
+
payload_json TEXT NOT NULL,
|
|
202
|
+
payload_sha256 TEXT NOT NULL,
|
|
203
|
+
status TEXT NOT NULL CHECK (status IN {_STATUSES}),
|
|
204
|
+
attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0),
|
|
205
|
+
max_attempts INTEGER NOT NULL CHECK (max_attempts BETWEEN 1 AND {MAX_ATTEMPTS}),
|
|
206
|
+
next_attempt_at REAL NOT NULL,
|
|
207
|
+
lease_owner TEXT,
|
|
208
|
+
lease_token TEXT,
|
|
209
|
+
lease_expires_at REAL,
|
|
210
|
+
last_error_code TEXT,
|
|
211
|
+
last_error_detail_hash TEXT,
|
|
212
|
+
result_json TEXT,
|
|
213
|
+
result_sha256 TEXT,
|
|
214
|
+
created_at REAL NOT NULL,
|
|
215
|
+
updated_at REAL NOT NULL
|
|
216
|
+
)
|
|
217
|
+
""")
|
|
218
|
+
self.connection.execute("""
|
|
219
|
+
CREATE TABLE localization_plan_jobs (
|
|
220
|
+
plan_id TEXT NOT NULL,
|
|
221
|
+
job_id TEXT NOT NULL,
|
|
222
|
+
target_locale TEXT NOT NULL,
|
|
223
|
+
created_at REAL NOT NULL,
|
|
224
|
+
PRIMARY KEY (plan_id, job_id),
|
|
225
|
+
UNIQUE (plan_id, target_locale),
|
|
226
|
+
FOREIGN KEY (job_id) REFERENCES localization_jobs (job_id)
|
|
227
|
+
)
|
|
228
|
+
""")
|
|
229
|
+
self.connection.execute("""
|
|
230
|
+
CREATE INDEX localization_jobs_ready
|
|
231
|
+
ON localization_jobs (status, next_attempt_at, created_at, job_id)
|
|
232
|
+
""")
|
|
233
|
+
self.connection.execute("""
|
|
234
|
+
CREATE INDEX localization_plan_jobs_job
|
|
235
|
+
ON localization_plan_jobs (job_id, plan_id)
|
|
236
|
+
""")
|
|
237
|
+
self.connection.execute(f"PRAGMA user_version = {SCHEMA_VERSION}")
|
|
238
|
+
|
|
239
|
+
def _verify_schema(self) -> None:
|
|
240
|
+
rows = self.connection.execute("PRAGMA table_info(localization_jobs)").fetchall()
|
|
241
|
+
columns = tuple(row["name"] for row in rows)
|
|
242
|
+
if columns != _COLUMNS:
|
|
243
|
+
raise LocalizationQueueBlocked("localization queue schema is incomplete or altered")
|
|
244
|
+
plan_rows = self.connection.execute("PRAGMA table_info(localization_plan_jobs)").fetchall()
|
|
245
|
+
plan_columns = tuple(row["name"] for row in plan_rows)
|
|
246
|
+
if plan_columns != _PLAN_COLUMNS:
|
|
247
|
+
raise LocalizationQueueBlocked("localization plan mapping schema is incomplete or altered")
|
|
248
|
+
|
|
249
|
+
def enqueue_plan(
|
|
250
|
+
self,
|
|
251
|
+
plan: Any,
|
|
252
|
+
*,
|
|
253
|
+
max_attempts: int = 3,
|
|
254
|
+
now: float | int | None = None,
|
|
255
|
+
) -> int:
|
|
256
|
+
if isinstance(max_attempts, bool) or not isinstance(max_attempts, int):
|
|
257
|
+
raise LocalizationQueueBlocked("max_attempts must be an integer")
|
|
258
|
+
if not 1 <= max_attempts <= MAX_ATTEMPTS:
|
|
259
|
+
raise LocalizationQueueBlocked("max_attempts is outside the supported range")
|
|
260
|
+
now = _timestamp("now", now)
|
|
261
|
+
plan_id = _field("plan_id", getattr(plan, "plan_id", None))
|
|
262
|
+
jobs = getattr(plan, "jobs", None)
|
|
263
|
+
if not isinstance(jobs, tuple) or not jobs:
|
|
264
|
+
raise LocalizationQueueBlocked("plan must contain an immutable non-empty job tuple")
|
|
265
|
+
|
|
266
|
+
prepared: list[tuple[str, str, str, str]] = []
|
|
267
|
+
targets: set[str] = set()
|
|
268
|
+
for job in jobs:
|
|
269
|
+
job_id = _field("job_id", getattr(job, "job_id", None))
|
|
270
|
+
payload_method = getattr(job, "as_payload", None)
|
|
271
|
+
if not callable(payload_method):
|
|
272
|
+
raise LocalizationQueueBlocked("every plan job must expose as_payload")
|
|
273
|
+
payload = payload_method()
|
|
274
|
+
if not isinstance(payload, dict):
|
|
275
|
+
raise LocalizationQueueBlocked("job payload must be a JSON object")
|
|
276
|
+
_validate_unicode(payload, field="job payload")
|
|
277
|
+
if payload.get("job_id") != job_id or payload.get("idempotency_key") != job_id:
|
|
278
|
+
raise LocalizationQueueBlocked("job identity and idempotency key must match")
|
|
279
|
+
target = payload.get("target")
|
|
280
|
+
target_locale = target.get("locale") if isinstance(target, dict) else None
|
|
281
|
+
target_locale = _field("target_locale", target_locale)
|
|
282
|
+
if target_locale in targets:
|
|
283
|
+
raise LocalizationQueueBlocked("plan contains duplicate target locales")
|
|
284
|
+
targets.add(target_locale)
|
|
285
|
+
if payload.get("quality_passes") != QUALITY_PASSES:
|
|
286
|
+
raise LocalizationQueueBlocked("job must require the two ordered quality passes")
|
|
287
|
+
if payload.get("release_required") is not True:
|
|
288
|
+
raise LocalizationQueueBlocked("job must require a signed release")
|
|
289
|
+
payload_json = _canonical_json(payload)
|
|
290
|
+
if len(payload_json.encode("utf-8")) > MAX_PAYLOAD_BYTES:
|
|
291
|
+
raise LocalizationQueueBlocked("job payload exceeds the byte limit")
|
|
292
|
+
prepared.append((job_id, target_locale, payload_json, _hash_text(payload_json)))
|
|
293
|
+
|
|
294
|
+
inserted = 0
|
|
295
|
+
with _transaction(self.connection):
|
|
296
|
+
for job_id, target_locale, payload_json, payload_hash in prepared:
|
|
297
|
+
existing = self.connection.execute(
|
|
298
|
+
"SELECT payload_sha256 FROM localization_jobs WHERE job_id = ?",
|
|
299
|
+
(job_id,),
|
|
300
|
+
).fetchone()
|
|
301
|
+
if existing is not None:
|
|
302
|
+
if existing["payload_sha256"] != payload_hash:
|
|
303
|
+
raise LocalizationQueueBlocked("idempotency collision with different job content")
|
|
304
|
+
else:
|
|
305
|
+
self.connection.execute("""
|
|
306
|
+
INSERT INTO localization_jobs (
|
|
307
|
+
job_id, target_locale, payload_json, payload_sha256,
|
|
308
|
+
status, attempts, max_attempts, next_attempt_at,
|
|
309
|
+
created_at, updated_at
|
|
310
|
+
) VALUES (?, ?, ?, ?, 'pending', 0, ?, ?, ?, ?)
|
|
311
|
+
""", (
|
|
312
|
+
job_id,
|
|
313
|
+
target_locale,
|
|
314
|
+
payload_json,
|
|
315
|
+
payload_hash,
|
|
316
|
+
max_attempts,
|
|
317
|
+
now,
|
|
318
|
+
now,
|
|
319
|
+
now,
|
|
320
|
+
))
|
|
321
|
+
inserted += 1
|
|
322
|
+
mapped = self.connection.execute("""
|
|
323
|
+
SELECT job_id FROM localization_plan_jobs
|
|
324
|
+
WHERE plan_id = ? AND target_locale = ?
|
|
325
|
+
""", (plan_id, target_locale)).fetchone()
|
|
326
|
+
if mapped is not None and mapped["job_id"] != job_id:
|
|
327
|
+
raise LocalizationQueueBlocked("plan target locale maps to different job content")
|
|
328
|
+
self.connection.execute("""
|
|
329
|
+
INSERT OR IGNORE INTO localization_plan_jobs (
|
|
330
|
+
plan_id, job_id, target_locale, created_at
|
|
331
|
+
) VALUES (?, ?, ?, ?)
|
|
332
|
+
""", (plan_id, job_id, target_locale, now))
|
|
333
|
+
return inserted
|
|
334
|
+
|
|
335
|
+
def claim(
|
|
336
|
+
self,
|
|
337
|
+
worker_id: Any,
|
|
338
|
+
*,
|
|
339
|
+
now: float | int | None = None,
|
|
340
|
+
lease_seconds: float | int = 300,
|
|
341
|
+
eligible_plan_ids: tuple[str, ...] | None = None,
|
|
342
|
+
) -> ClaimedJob | None:
|
|
343
|
+
worker_id = _field("worker_id", worker_id, limit=128)
|
|
344
|
+
now = _timestamp("now", now)
|
|
345
|
+
lease_seconds = _duration("lease_seconds", lease_seconds)
|
|
346
|
+
if eligible_plan_ids is not None:
|
|
347
|
+
if not isinstance(eligible_plan_ids, tuple):
|
|
348
|
+
raise LocalizationQueueBlocked("eligible_plan_ids must be an immutable tuple")
|
|
349
|
+
eligible_plan_ids = tuple(sorted({
|
|
350
|
+
_field("eligible_plan_id", value) for value in eligible_plan_ids
|
|
351
|
+
}))
|
|
352
|
+
blocked_error: str | None = None
|
|
353
|
+
claimed: ClaimedJob | None = None
|
|
354
|
+
with _transaction(self.connection):
|
|
355
|
+
self.connection.execute("""
|
|
356
|
+
UPDATE localization_jobs
|
|
357
|
+
SET status = 'failed', lease_owner = NULL, lease_token = NULL,
|
|
358
|
+
lease_expires_at = NULL, last_error_code = 'lease_expired',
|
|
359
|
+
updated_at = ?
|
|
360
|
+
WHERE status = 'leased' AND lease_expires_at <= ?
|
|
361
|
+
AND attempts >= max_attempts
|
|
362
|
+
""", (now, now))
|
|
363
|
+
self.connection.execute("""
|
|
364
|
+
UPDATE localization_jobs
|
|
365
|
+
SET status = 'retry_wait', next_attempt_at = ?,
|
|
366
|
+
lease_owner = NULL, lease_token = NULL, lease_expires_at = NULL,
|
|
367
|
+
last_error_code = 'lease_expired', updated_at = ?
|
|
368
|
+
WHERE status = 'leased' AND lease_expires_at <= ?
|
|
369
|
+
AND attempts < max_attempts
|
|
370
|
+
""", (now, now, now))
|
|
371
|
+
if eligible_plan_ids is not None and not eligible_plan_ids:
|
|
372
|
+
row = None
|
|
373
|
+
elif eligible_plan_ids is None:
|
|
374
|
+
row = self.connection.execute("""
|
|
375
|
+
SELECT * FROM localization_jobs
|
|
376
|
+
WHERE status IN ('pending', 'retry_wait')
|
|
377
|
+
AND next_attempt_at <= ? AND attempts < max_attempts
|
|
378
|
+
ORDER BY created_at, target_locale, job_id
|
|
379
|
+
LIMIT 1
|
|
380
|
+
""", (now,)).fetchone()
|
|
381
|
+
else:
|
|
382
|
+
placeholders = ",".join("?" for _ in eligible_plan_ids)
|
|
383
|
+
row = self.connection.execute(f"""
|
|
384
|
+
SELECT jobs.* FROM localization_jobs AS jobs
|
|
385
|
+
WHERE jobs.status IN ('pending', 'retry_wait')
|
|
386
|
+
AND jobs.next_attempt_at <= ? AND jobs.attempts < jobs.max_attempts
|
|
387
|
+
AND EXISTS (
|
|
388
|
+
SELECT 1 FROM localization_plan_jobs AS mapping
|
|
389
|
+
WHERE mapping.job_id = jobs.job_id
|
|
390
|
+
AND mapping.plan_id IN ({placeholders})
|
|
391
|
+
)
|
|
392
|
+
ORDER BY jobs.created_at, jobs.target_locale, jobs.job_id
|
|
393
|
+
LIMIT 1
|
|
394
|
+
""", (now, *eligible_plan_ids)).fetchone()
|
|
395
|
+
if row is not None:
|
|
396
|
+
payload_hash = _hash_text(row["payload_json"])
|
|
397
|
+
if payload_hash != row["payload_sha256"]:
|
|
398
|
+
self.connection.execute("""
|
|
399
|
+
UPDATE localization_jobs
|
|
400
|
+
SET status = 'failed', last_error_code = 'payload_integrity',
|
|
401
|
+
updated_at = ?
|
|
402
|
+
WHERE job_id = ?
|
|
403
|
+
""", (now, row["job_id"]))
|
|
404
|
+
blocked_error = "queued payload failed its integrity check"
|
|
405
|
+
else:
|
|
406
|
+
try:
|
|
407
|
+
payload = json.loads(row["payload_json"])
|
|
408
|
+
except json.JSONDecodeError:
|
|
409
|
+
self.connection.execute("""
|
|
410
|
+
UPDATE localization_jobs
|
|
411
|
+
SET status = 'failed', last_error_code = 'payload_integrity',
|
|
412
|
+
updated_at = ?
|
|
413
|
+
WHERE job_id = ?
|
|
414
|
+
""", (now, row["job_id"]))
|
|
415
|
+
blocked_error = "queued payload is no longer valid JSON"
|
|
416
|
+
else:
|
|
417
|
+
lease_token = secrets.token_urlsafe(32)
|
|
418
|
+
lease_expires_at = now + lease_seconds
|
|
419
|
+
updated = self.connection.execute("""
|
|
420
|
+
UPDATE localization_jobs
|
|
421
|
+
SET status = 'leased', attempts = attempts + 1,
|
|
422
|
+
lease_owner = ?, lease_token = ?, lease_expires_at = ?,
|
|
423
|
+
last_error_code = NULL, last_error_detail_hash = NULL,
|
|
424
|
+
updated_at = ?
|
|
425
|
+
WHERE job_id = ? AND status IN ('pending', 'retry_wait')
|
|
426
|
+
""", (
|
|
427
|
+
worker_id,
|
|
428
|
+
lease_token,
|
|
429
|
+
lease_expires_at,
|
|
430
|
+
now,
|
|
431
|
+
row["job_id"],
|
|
432
|
+
))
|
|
433
|
+
if updated.rowcount != 1:
|
|
434
|
+
raise LocalizationQueueBlocked("claim lost its transactional job identity")
|
|
435
|
+
claimed = ClaimedJob(
|
|
436
|
+
job_id=row["job_id"],
|
|
437
|
+
plan_ids=self._plan_ids(row["job_id"]),
|
|
438
|
+
target_locale=row["target_locale"],
|
|
439
|
+
payload=payload,
|
|
440
|
+
attempt=int(row["attempts"]) + 1,
|
|
441
|
+
max_attempts=int(row["max_attempts"]),
|
|
442
|
+
lease_owner=worker_id,
|
|
443
|
+
lease_token=lease_token,
|
|
444
|
+
lease_expires_at=lease_expires_at,
|
|
445
|
+
)
|
|
446
|
+
if blocked_error:
|
|
447
|
+
raise LocalizationQueueBlocked(blocked_error)
|
|
448
|
+
return claimed
|
|
449
|
+
|
|
450
|
+
def renew(
|
|
451
|
+
self,
|
|
452
|
+
claim: ClaimedJob,
|
|
453
|
+
*,
|
|
454
|
+
now: float | int | None = None,
|
|
455
|
+
lease_seconds: float | int = 300,
|
|
456
|
+
) -> ClaimedJob:
|
|
457
|
+
claim = self._claim_identity(claim)
|
|
458
|
+
now = _timestamp("now", now)
|
|
459
|
+
lease_seconds = _duration("lease_seconds", lease_seconds)
|
|
460
|
+
new_expiry = now + lease_seconds
|
|
461
|
+
with _transaction(self.connection):
|
|
462
|
+
self._require_live_lease(claim, now)
|
|
463
|
+
updated = self.connection.execute("""
|
|
464
|
+
UPDATE localization_jobs
|
|
465
|
+
SET lease_expires_at = ?, updated_at = ?
|
|
466
|
+
WHERE job_id = ? AND status = 'leased'
|
|
467
|
+
AND lease_owner = ? AND lease_token = ?
|
|
468
|
+
""", (
|
|
469
|
+
new_expiry,
|
|
470
|
+
now,
|
|
471
|
+
claim.job_id,
|
|
472
|
+
claim.lease_owner,
|
|
473
|
+
claim.lease_token,
|
|
474
|
+
))
|
|
475
|
+
if updated.rowcount != 1:
|
|
476
|
+
raise LocalizationQueueBlocked("lease renewal lost its job identity")
|
|
477
|
+
return ClaimedJob(
|
|
478
|
+
**{**claim.__dict__, "lease_expires_at": new_expiry},
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
def complete(
|
|
482
|
+
self,
|
|
483
|
+
claim: ClaimedJob,
|
|
484
|
+
result: Any,
|
|
485
|
+
*,
|
|
486
|
+
now: float | int | None = None,
|
|
487
|
+
) -> JobStatus:
|
|
488
|
+
claim = self._claim_identity(claim)
|
|
489
|
+
now = _timestamp("now", now)
|
|
490
|
+
if not isinstance(result, dict):
|
|
491
|
+
raise LocalizationQueueBlocked("worker result must be a JSON object")
|
|
492
|
+
_validate_unicode(result, field="worker result")
|
|
493
|
+
result_json = _canonical_json(result)
|
|
494
|
+
if len(result_json.encode("utf-8")) > MAX_RESULT_BYTES:
|
|
495
|
+
raise LocalizationQueueBlocked("worker result exceeds the byte limit")
|
|
496
|
+
result_hash = _hash_text(result_json)
|
|
497
|
+
with _transaction(self.connection):
|
|
498
|
+
self._require_live_lease(claim, now)
|
|
499
|
+
updated = self.connection.execute("""
|
|
500
|
+
UPDATE localization_jobs
|
|
501
|
+
SET status = 'succeeded', lease_owner = NULL, lease_token = NULL,
|
|
502
|
+
lease_expires_at = NULL, result_json = ?, result_sha256 = ?,
|
|
503
|
+
last_error_code = NULL, last_error_detail_hash = NULL,
|
|
504
|
+
updated_at = ?
|
|
505
|
+
WHERE job_id = ? AND status = 'leased'
|
|
506
|
+
AND lease_owner = ? AND lease_token = ?
|
|
507
|
+
""", (
|
|
508
|
+
result_json,
|
|
509
|
+
result_hash,
|
|
510
|
+
now,
|
|
511
|
+
claim.job_id,
|
|
512
|
+
claim.lease_owner,
|
|
513
|
+
claim.lease_token,
|
|
514
|
+
))
|
|
515
|
+
if updated.rowcount != 1:
|
|
516
|
+
raise LocalizationQueueBlocked("completion lost its job identity")
|
|
517
|
+
return self.status(claim.job_id)
|
|
518
|
+
|
|
519
|
+
def retry(
|
|
520
|
+
self,
|
|
521
|
+
claim: ClaimedJob,
|
|
522
|
+
error_code: Any,
|
|
523
|
+
*,
|
|
524
|
+
error_detail: Any = None,
|
|
525
|
+
delay_seconds: float | int = 0,
|
|
526
|
+
now: float | int | None = None,
|
|
527
|
+
) -> JobStatus:
|
|
528
|
+
claim = self._claim_identity(claim)
|
|
529
|
+
error_code, detail_hash = _error(error_code, error_detail)
|
|
530
|
+
now = _timestamp("now", now)
|
|
531
|
+
delay_seconds = _duration("delay_seconds", delay_seconds, allow_zero=True)
|
|
532
|
+
with _transaction(self.connection):
|
|
533
|
+
row = self._require_live_lease(claim, now)
|
|
534
|
+
terminal = int(row["attempts"]) >= int(row["max_attempts"])
|
|
535
|
+
status = "failed" if terminal else "retry_wait"
|
|
536
|
+
next_attempt_at = now if terminal else now + delay_seconds
|
|
537
|
+
updated = self.connection.execute("""
|
|
538
|
+
UPDATE localization_jobs
|
|
539
|
+
SET status = ?, next_attempt_at = ?, lease_owner = NULL,
|
|
540
|
+
lease_token = NULL, lease_expires_at = NULL,
|
|
541
|
+
last_error_code = ?, last_error_detail_hash = ?, updated_at = ?
|
|
542
|
+
WHERE job_id = ? AND status = 'leased'
|
|
543
|
+
AND lease_owner = ? AND lease_token = ?
|
|
544
|
+
""", (
|
|
545
|
+
status,
|
|
546
|
+
next_attempt_at,
|
|
547
|
+
error_code,
|
|
548
|
+
detail_hash,
|
|
549
|
+
now,
|
|
550
|
+
claim.job_id,
|
|
551
|
+
claim.lease_owner,
|
|
552
|
+
claim.lease_token,
|
|
553
|
+
))
|
|
554
|
+
if updated.rowcount != 1:
|
|
555
|
+
raise LocalizationQueueBlocked("retry lost its job identity")
|
|
556
|
+
return self.status(claim.job_id)
|
|
557
|
+
|
|
558
|
+
def fail(
|
|
559
|
+
self,
|
|
560
|
+
claim: ClaimedJob,
|
|
561
|
+
error_code: Any,
|
|
562
|
+
*,
|
|
563
|
+
error_detail: Any = None,
|
|
564
|
+
now: float | int | None = None,
|
|
565
|
+
) -> JobStatus:
|
|
566
|
+
claim = self._claim_identity(claim)
|
|
567
|
+
error_code, detail_hash = _error(error_code, error_detail)
|
|
568
|
+
now = _timestamp("now", now)
|
|
569
|
+
with _transaction(self.connection):
|
|
570
|
+
self._require_live_lease(claim, now)
|
|
571
|
+
updated = self.connection.execute("""
|
|
572
|
+
UPDATE localization_jobs
|
|
573
|
+
SET status = 'failed', lease_owner = NULL, lease_token = NULL,
|
|
574
|
+
lease_expires_at = NULL, last_error_code = ?,
|
|
575
|
+
last_error_detail_hash = ?, updated_at = ?
|
|
576
|
+
WHERE job_id = ? AND status = 'leased'
|
|
577
|
+
AND lease_owner = ? AND lease_token = ?
|
|
578
|
+
""", (
|
|
579
|
+
error_code,
|
|
580
|
+
detail_hash,
|
|
581
|
+
now,
|
|
582
|
+
claim.job_id,
|
|
583
|
+
claim.lease_owner,
|
|
584
|
+
claim.lease_token,
|
|
585
|
+
))
|
|
586
|
+
if updated.rowcount != 1:
|
|
587
|
+
raise LocalizationQueueBlocked("failure transition lost its job identity")
|
|
588
|
+
return self.status(claim.job_id)
|
|
589
|
+
|
|
590
|
+
def status(self, job_id: Any) -> JobStatus:
|
|
591
|
+
job_id = _field("job_id", job_id)
|
|
592
|
+
row = self.connection.execute("""
|
|
593
|
+
SELECT job_id, target_locale, status, attempts, max_attempts,
|
|
594
|
+
next_attempt_at, lease_expires_at, last_error_code,
|
|
595
|
+
last_error_detail_hash, result_sha256, created_at, updated_at
|
|
596
|
+
FROM localization_jobs WHERE job_id = ?
|
|
597
|
+
""", (job_id,)).fetchone()
|
|
598
|
+
if row is None:
|
|
599
|
+
raise LocalizationQueueBlocked("unknown localization job")
|
|
600
|
+
return JobStatus(plan_ids=self._plan_ids(job_id), **dict(row))
|
|
601
|
+
|
|
602
|
+
def result(self, job_id: Any) -> dict[str, Any]:
|
|
603
|
+
"""Return one completed result only after rechecking its stored bytes."""
|
|
604
|
+
job_id = _field("job_id", job_id)
|
|
605
|
+
row = self.connection.execute("""
|
|
606
|
+
SELECT status, result_json, result_sha256
|
|
607
|
+
FROM localization_jobs WHERE job_id = ?
|
|
608
|
+
""", (job_id,)).fetchone()
|
|
609
|
+
if row is None:
|
|
610
|
+
raise LocalizationQueueBlocked("unknown localization job")
|
|
611
|
+
if row["status"] != "succeeded":
|
|
612
|
+
raise LocalizationQueueBlocked("localization job has no completed result")
|
|
613
|
+
if not isinstance(row["result_json"], str) or not isinstance(row["result_sha256"], str):
|
|
614
|
+
raise LocalizationQueueBlocked("completed result is missing")
|
|
615
|
+
if _hash_text(row["result_json"]) != row["result_sha256"]:
|
|
616
|
+
raise LocalizationQueueBlocked("completed result failed its integrity check")
|
|
617
|
+
try:
|
|
618
|
+
result = json.loads(row["result_json"])
|
|
619
|
+
except json.JSONDecodeError as error:
|
|
620
|
+
raise LocalizationQueueBlocked("completed result is no longer valid JSON") from error
|
|
621
|
+
if not isinstance(result, dict) or result.get("job_id") != job_id:
|
|
622
|
+
raise LocalizationQueueBlocked("completed result has the wrong job identity")
|
|
623
|
+
_validate_unicode(result, field="worker result")
|
|
624
|
+
if _canonical_json(result) != row["result_json"]:
|
|
625
|
+
raise LocalizationQueueBlocked("completed result is not canonical JSON")
|
|
626
|
+
return result
|
|
627
|
+
|
|
628
|
+
def plan_counts(self, plan_id: Any) -> dict[str, int]:
|
|
629
|
+
plan_id = _field("plan_id", plan_id)
|
|
630
|
+
counts = {status: 0 for status in _STATUSES}
|
|
631
|
+
rows = self.connection.execute("""
|
|
632
|
+
SELECT jobs.status, COUNT(*) AS count
|
|
633
|
+
FROM localization_plan_jobs AS plans
|
|
634
|
+
JOIN localization_jobs AS jobs ON jobs.job_id = plans.job_id
|
|
635
|
+
WHERE plans.plan_id = ? GROUP BY jobs.status
|
|
636
|
+
""", (plan_id,)).fetchall()
|
|
637
|
+
for row in rows:
|
|
638
|
+
counts[row["status"]] = int(row["count"])
|
|
639
|
+
return counts
|
|
640
|
+
|
|
641
|
+
def _plan_ids(self, job_id: str) -> tuple[str, ...]:
|
|
642
|
+
rows = self.connection.execute("""
|
|
643
|
+
SELECT plan_id FROM localization_plan_jobs
|
|
644
|
+
WHERE job_id = ? ORDER BY plan_id
|
|
645
|
+
""", (job_id,)).fetchall()
|
|
646
|
+
plan_ids = tuple(row["plan_id"] for row in rows)
|
|
647
|
+
if not plan_ids:
|
|
648
|
+
raise LocalizationQueueBlocked("localization job has no owning plan")
|
|
649
|
+
return plan_ids
|
|
650
|
+
|
|
651
|
+
def _claim_identity(self, claim: Any) -> ClaimedJob:
|
|
652
|
+
if not isinstance(claim, ClaimedJob):
|
|
653
|
+
raise LocalizationQueueBlocked("transition requires the exact claimed job")
|
|
654
|
+
_field("job_id", claim.job_id)
|
|
655
|
+
_field("lease_owner", claim.lease_owner, limit=128)
|
|
656
|
+
_field("lease_token", claim.lease_token, limit=128)
|
|
657
|
+
return claim
|
|
658
|
+
|
|
659
|
+
def _require_live_lease(self, claim: ClaimedJob, now: float) -> sqlite3.Row:
|
|
660
|
+
row = self.connection.execute("""
|
|
661
|
+
SELECT status, attempts, max_attempts, lease_owner, lease_token,
|
|
662
|
+
lease_expires_at
|
|
663
|
+
FROM localization_jobs WHERE job_id = ?
|
|
664
|
+
""", (claim.job_id,)).fetchone()
|
|
665
|
+
if row is None or row["status"] != "leased":
|
|
666
|
+
raise LocalizationQueueBlocked("job no longer has an active lease")
|
|
667
|
+
if row["lease_owner"] != claim.lease_owner or row["lease_token"] != claim.lease_token:
|
|
668
|
+
raise LocalizationQueueBlocked("claim does not own the active lease")
|
|
669
|
+
if row["lease_expires_at"] <= now:
|
|
670
|
+
raise LocalizationQueueBlocked("claim lease has expired")
|
|
671
|
+
return row
|