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
package/standard-skills/translate-native/integrations/website_localization_release_coordinator.py
ADDED
|
@@ -0,0 +1,1008 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Idempotent bridge from completed locale results to signed CMS delivery."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import hashlib
|
|
7
|
+
import importlib.util
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
import secrets
|
|
11
|
+
import sqlite3
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
import unicodedata
|
|
15
|
+
from contextlib import contextmanager
|
|
16
|
+
from dataclasses import asdict, dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Callable, Mapping, Protocol
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
SCHEMA = "blun.website-localization-release-coordinator.v1"
|
|
22
|
+
EVIDENCE_REQUEST_SCHEMA = "blun.localization-quality-evidence-request.v5"
|
|
23
|
+
EVIDENCE_RESPONSE_SCHEMA = "blun.localization-quality-evidence-response.v2"
|
|
24
|
+
INDEPENDENT_MODEL_REVIEW_SCHEMA = "blun.independent-model-review.v1"
|
|
25
|
+
EVIDENCE_STATE_SCHEMA = "blun.localization-quality-evidence-state.v1"
|
|
26
|
+
TOKEN = re.compile(r"^[A-Za-z0-9_.:-]{1,256}$")
|
|
27
|
+
ERROR_CODE = re.compile(r"^[a-z][a-z0-9_.-]{0,127}$")
|
|
28
|
+
SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
|
29
|
+
MAX_RECEIPT_LENGTH = 16_384
|
|
30
|
+
MAX_ATTEMPTS = 20
|
|
31
|
+
MAX_LEASE_SECONDS = 3_600
|
|
32
|
+
EVIDENCE_STATES = {"pending", "leased", "retry_wait", "succeeded", "failed"}
|
|
33
|
+
_EVIDENCE_COLUMNS = (
|
|
34
|
+
"request_id", "event_id", "plan_id", "job_id", "result_sha256",
|
|
35
|
+
"evidence_revision", "status", "attempts", "max_attempts",
|
|
36
|
+
"next_attempt_at", "lease_owner", "lease_token", "lease_expires_at",
|
|
37
|
+
"last_error_code", "created_at", "updated_at",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _load_module(name: str, path: Path):
|
|
42
|
+
spec = importlib.util.spec_from_file_location(name, path)
|
|
43
|
+
if spec is None or spec.loader is None:
|
|
44
|
+
raise RuntimeError(f"cannot load release coordinator dependency: {path.name}")
|
|
45
|
+
module = importlib.util.module_from_spec(spec)
|
|
46
|
+
sys.modules[spec.name] = module
|
|
47
|
+
spec.loader.exec_module(module)
|
|
48
|
+
return module
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
_ROOT = Path(__file__).resolve().parents[1]
|
|
52
|
+
_CMS = _load_module(
|
|
53
|
+
"blun_website_localization_release_coordinator_cms",
|
|
54
|
+
_ROOT / "integrations" / "website_localization_cms.py",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class LocalizationReleaseCoordinatorBlocked(RuntimeError):
|
|
59
|
+
"""Stable failure that never contains source, target, receipt, or provider prose."""
|
|
60
|
+
|
|
61
|
+
def __init__(self, code: str, *, retryable: bool = False):
|
|
62
|
+
if not isinstance(code, str) or ERROR_CODE.fullmatch(code) is None:
|
|
63
|
+
raise ValueError("coordinator failure code is invalid")
|
|
64
|
+
if not isinstance(retryable, bool):
|
|
65
|
+
raise ValueError("coordinator retryability must be boolean")
|
|
66
|
+
super().__init__(code)
|
|
67
|
+
self.code = code
|
|
68
|
+
self.retryable = retryable
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class QualityEvidenceUnavailable(RuntimeError):
|
|
72
|
+
"""Adapter-declared, content-free evidence-provider failure."""
|
|
73
|
+
|
|
74
|
+
localization_evidence_failure = True
|
|
75
|
+
|
|
76
|
+
def __init__(self, code: str, *, retryable: bool):
|
|
77
|
+
if not isinstance(code, str) or ERROR_CODE.fullmatch(code) is None:
|
|
78
|
+
raise ValueError("evidence failure code is invalid")
|
|
79
|
+
if not isinstance(retryable, bool):
|
|
80
|
+
raise ValueError("evidence retryability must be boolean")
|
|
81
|
+
super().__init__(code)
|
|
82
|
+
self.code = code
|
|
83
|
+
self.retryable = retryable
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class _ReceiptVerificationGuardUnavailable(RuntimeError):
|
|
87
|
+
localization_receipt_verification_failure = True
|
|
88
|
+
|
|
89
|
+
def __init__(self):
|
|
90
|
+
super().__init__("operation_guard")
|
|
91
|
+
self.code = "operation_guard"
|
|
92
|
+
self.retryable = True
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class _GuardedReceiptVerifier:
|
|
96
|
+
def __init__(self, verifier: Any, guard: Callable[[float], Any], lease_seconds: float):
|
|
97
|
+
self.verifier = verifier
|
|
98
|
+
self.guard = guard
|
|
99
|
+
self.lease_seconds = lease_seconds
|
|
100
|
+
|
|
101
|
+
def verify(self, **values):
|
|
102
|
+
try:
|
|
103
|
+
self.guard(self.lease_seconds)
|
|
104
|
+
except Exception:
|
|
105
|
+
raise _ReceiptVerificationGuardUnavailable() from None
|
|
106
|
+
return self.verifier.verify(**values)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _guarded_verifier(
|
|
110
|
+
verifier: Any | None,
|
|
111
|
+
operation_guard: Callable[[float], Any] | None,
|
|
112
|
+
lease_seconds: float,
|
|
113
|
+
) -> Any | None:
|
|
114
|
+
if verifier is None or operation_guard is None:
|
|
115
|
+
return verifier
|
|
116
|
+
return _GuardedReceiptVerifier(verifier, operation_guard, lease_seconds)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True)
|
|
120
|
+
class QualityEvidenceRequest:
|
|
121
|
+
schema: str
|
|
122
|
+
request_id: str
|
|
123
|
+
evidence_revision: str
|
|
124
|
+
event_id: str
|
|
125
|
+
plan_id: str
|
|
126
|
+
job_id: str
|
|
127
|
+
result_sha256: str
|
|
128
|
+
source_sha256: str
|
|
129
|
+
target_sha256: str
|
|
130
|
+
source_locale: str
|
|
131
|
+
target_locale: str
|
|
132
|
+
content_type: str
|
|
133
|
+
glossary_version: str
|
|
134
|
+
policy_version: str
|
|
135
|
+
provider: dict[str, Any]
|
|
136
|
+
software_version: str
|
|
137
|
+
source_text: str
|
|
138
|
+
target_text: str
|
|
139
|
+
review_confidence: dict[str, str]
|
|
140
|
+
quality_profile: dict[str, str]
|
|
141
|
+
commercial_profile: str | None
|
|
142
|
+
commercial_review: dict[str, Any] | None
|
|
143
|
+
human_review_required: bool
|
|
144
|
+
independent_review_required: bool
|
|
145
|
+
|
|
146
|
+
def as_payload(self) -> dict[str, Any]:
|
|
147
|
+
return json.loads(_canonical_json(asdict(self)))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class QualityEvidenceProvider(Protocol):
|
|
151
|
+
def obtain(self, request: QualityEvidenceRequest) -> Mapping[str, Any]: ...
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass(frozen=True)
|
|
155
|
+
class QualityEvidenceClaim:
|
|
156
|
+
request_id: str
|
|
157
|
+
lease_owner: str
|
|
158
|
+
lease_token: str
|
|
159
|
+
lease_expires_at: float
|
|
160
|
+
attempt: int
|
|
161
|
+
max_attempts: int
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@dataclass(frozen=True)
|
|
165
|
+
class QualityEvidenceStatus:
|
|
166
|
+
schema: str
|
|
167
|
+
request_id: str
|
|
168
|
+
event_id: str
|
|
169
|
+
plan_id: str
|
|
170
|
+
job_id: str
|
|
171
|
+
result_sha256: str
|
|
172
|
+
evidence_revision: str
|
|
173
|
+
status: str
|
|
174
|
+
attempts: int
|
|
175
|
+
max_attempts: int
|
|
176
|
+
next_attempt_at: float
|
|
177
|
+
lease_expires_at: float | None
|
|
178
|
+
last_error_code: str | None
|
|
179
|
+
|
|
180
|
+
def as_payload(self) -> dict[str, Any]:
|
|
181
|
+
return asdict(self)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@dataclass(frozen=True)
|
|
185
|
+
class ReleaseCoordinatorOutcome:
|
|
186
|
+
schema: str
|
|
187
|
+
status: str
|
|
188
|
+
event_id: str
|
|
189
|
+
plan_id: str
|
|
190
|
+
job_id: str | None = None
|
|
191
|
+
target_locale: str | None = None
|
|
192
|
+
approval_id: str | None = None
|
|
193
|
+
delivery_id: str | None = None
|
|
194
|
+
|
|
195
|
+
def as_payload(self) -> dict[str, Any]:
|
|
196
|
+
return asdict(self)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _canonical_json(value: Any) -> str:
|
|
200
|
+
try:
|
|
201
|
+
return json.dumps(
|
|
202
|
+
value,
|
|
203
|
+
ensure_ascii=False,
|
|
204
|
+
allow_nan=False,
|
|
205
|
+
sort_keys=True,
|
|
206
|
+
separators=(",", ":"),
|
|
207
|
+
)
|
|
208
|
+
except (TypeError, ValueError):
|
|
209
|
+
raise LocalizationReleaseCoordinatorBlocked("coordinator.json.invalid") from None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _hash(value: str) -> str:
|
|
213
|
+
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _token(value: Any, code: str) -> str:
|
|
217
|
+
if not isinstance(value, str) or TOKEN.fullmatch(value) is None:
|
|
218
|
+
raise LocalizationReleaseCoordinatorBlocked(code)
|
|
219
|
+
return value
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _timestamp(value: Any) -> float:
|
|
223
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
224
|
+
raise LocalizationReleaseCoordinatorBlocked("coordinator.time.invalid")
|
|
225
|
+
value = float(value)
|
|
226
|
+
if value < 0 or value != value or value in {float("inf"), float("-inf")}:
|
|
227
|
+
raise LocalizationReleaseCoordinatorBlocked("coordinator.time.invalid")
|
|
228
|
+
return value
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _positive_duration(value: Any, code: str, maximum: float) -> float:
|
|
232
|
+
value = _timestamp(value)
|
|
233
|
+
if value <= 0 or value > maximum:
|
|
234
|
+
raise LocalizationReleaseCoordinatorBlocked(code)
|
|
235
|
+
return value
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _attempt_limit(value: Any) -> int:
|
|
239
|
+
if isinstance(value, bool) or not isinstance(value, int) or not 1 <= value <= MAX_ATTEMPTS:
|
|
240
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.max_attempts.invalid")
|
|
241
|
+
return value
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _stored_integer(value: Any, code: str, *, minimum: int, maximum: int) -> int:
|
|
245
|
+
if isinstance(value, bool) or not isinstance(value, int) or not minimum <= value <= maximum:
|
|
246
|
+
raise LocalizationReleaseCoordinatorBlocked(code)
|
|
247
|
+
return value
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@contextmanager
|
|
251
|
+
def _transaction(connection: sqlite3.Connection):
|
|
252
|
+
if connection.in_transaction:
|
|
253
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.state.transaction_active")
|
|
254
|
+
try:
|
|
255
|
+
connection.execute("BEGIN IMMEDIATE")
|
|
256
|
+
yield
|
|
257
|
+
connection.commit()
|
|
258
|
+
except Exception:
|
|
259
|
+
connection.rollback()
|
|
260
|
+
raise
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class QualityEvidenceStateStore:
|
|
264
|
+
"""Durable, content-free leases and bounded attempts for quality evidence."""
|
|
265
|
+
|
|
266
|
+
def __init__(self, connection: sqlite3.Connection):
|
|
267
|
+
if not isinstance(connection, sqlite3.Connection):
|
|
268
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.state.connection_invalid")
|
|
269
|
+
self.connection = connection
|
|
270
|
+
self.connection.row_factory = sqlite3.Row
|
|
271
|
+
with _transaction(self.connection):
|
|
272
|
+
self.connection.execute("""
|
|
273
|
+
CREATE TABLE IF NOT EXISTS localization_quality_evidence_state (
|
|
274
|
+
request_id TEXT PRIMARY KEY,
|
|
275
|
+
event_id TEXT NOT NULL,
|
|
276
|
+
plan_id TEXT NOT NULL,
|
|
277
|
+
job_id TEXT NOT NULL,
|
|
278
|
+
result_sha256 TEXT NOT NULL,
|
|
279
|
+
evidence_revision TEXT NOT NULL,
|
|
280
|
+
status TEXT NOT NULL CHECK (
|
|
281
|
+
status IN ('pending', 'leased', 'retry_wait', 'succeeded', 'failed')
|
|
282
|
+
),
|
|
283
|
+
attempts INTEGER NOT NULL CHECK (attempts >= 0),
|
|
284
|
+
max_attempts INTEGER NOT NULL CHECK (max_attempts >= 1),
|
|
285
|
+
next_attempt_at REAL NOT NULL,
|
|
286
|
+
lease_owner TEXT,
|
|
287
|
+
lease_token TEXT,
|
|
288
|
+
lease_expires_at REAL,
|
|
289
|
+
last_error_code TEXT,
|
|
290
|
+
created_at REAL NOT NULL,
|
|
291
|
+
updated_at REAL NOT NULL,
|
|
292
|
+
UNIQUE (event_id, plan_id, job_id, result_sha256, evidence_revision)
|
|
293
|
+
)
|
|
294
|
+
""")
|
|
295
|
+
self.connection.execute("""
|
|
296
|
+
CREATE INDEX IF NOT EXISTS localization_quality_evidence_due
|
|
297
|
+
ON localization_quality_evidence_state (
|
|
298
|
+
event_id, status, next_attempt_at, created_at, request_id
|
|
299
|
+
)
|
|
300
|
+
""")
|
|
301
|
+
columns = tuple(
|
|
302
|
+
row["name"]
|
|
303
|
+
for row in self.connection.execute(
|
|
304
|
+
"PRAGMA table_info(localization_quality_evidence_state)"
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
if columns != _EVIDENCE_COLUMNS:
|
|
308
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.state.schema_altered")
|
|
309
|
+
|
|
310
|
+
@staticmethod
|
|
311
|
+
def _binding(request: QualityEvidenceRequest) -> tuple[str, ...]:
|
|
312
|
+
if not isinstance(request, QualityEvidenceRequest):
|
|
313
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.request.invalid")
|
|
314
|
+
values = (
|
|
315
|
+
_token(request.request_id, "evidence.request_id.invalid"),
|
|
316
|
+
_token(request.event_id, "coordinator.event_id.invalid"),
|
|
317
|
+
_token(request.plan_id, "evidence.plan_id.invalid"),
|
|
318
|
+
_token(request.job_id, "evidence.job_id.invalid"),
|
|
319
|
+
request.result_sha256,
|
|
320
|
+
_token(request.evidence_revision, "coordinator.evidence_revision.invalid"),
|
|
321
|
+
)
|
|
322
|
+
if not isinstance(values[4], str) or SHA256.fullmatch(values[4]) is None:
|
|
323
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.result_sha256.invalid")
|
|
324
|
+
return values
|
|
325
|
+
|
|
326
|
+
@staticmethod
|
|
327
|
+
def _claim_values(claim: Any) -> QualityEvidenceClaim:
|
|
328
|
+
if not isinstance(claim, QualityEvidenceClaim):
|
|
329
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.claim.invalid")
|
|
330
|
+
_token(claim.request_id, "evidence.request_id.invalid")
|
|
331
|
+
_token(claim.lease_owner, "evidence.worker_id.invalid")
|
|
332
|
+
_token(claim.lease_token, "evidence.lease_token.invalid")
|
|
333
|
+
return claim
|
|
334
|
+
|
|
335
|
+
@staticmethod
|
|
336
|
+
def _status_from_row(row: sqlite3.Row) -> QualityEvidenceStatus:
|
|
337
|
+
request_id = _token(row["request_id"], "evidence.state.invalid")
|
|
338
|
+
event_id = _token(row["event_id"], "evidence.state.invalid")
|
|
339
|
+
plan_id = _token(row["plan_id"], "evidence.state.invalid")
|
|
340
|
+
job_id = _token(row["job_id"], "evidence.state.invalid")
|
|
341
|
+
result_sha256 = row["result_sha256"]
|
|
342
|
+
if not isinstance(result_sha256, str) or SHA256.fullmatch(result_sha256) is None:
|
|
343
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.state.invalid")
|
|
344
|
+
evidence_revision = _token(row["evidence_revision"], "evidence.state.invalid")
|
|
345
|
+
status = row["status"]
|
|
346
|
+
if status not in EVIDENCE_STATES:
|
|
347
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.state.invalid")
|
|
348
|
+
max_attempts = _stored_integer(
|
|
349
|
+
row["max_attempts"], "evidence.state.invalid", minimum=1, maximum=MAX_ATTEMPTS,
|
|
350
|
+
)
|
|
351
|
+
attempts = _stored_integer(
|
|
352
|
+
row["attempts"], "evidence.state.invalid", minimum=0, maximum=max_attempts,
|
|
353
|
+
)
|
|
354
|
+
next_attempt_at = _timestamp(row["next_attempt_at"])
|
|
355
|
+
lease_values = (row["lease_owner"], row["lease_token"], row["lease_expires_at"])
|
|
356
|
+
if status == "leased":
|
|
357
|
+
lease_owner = _token(lease_values[0], "evidence.state.invalid")
|
|
358
|
+
lease_token = _token(lease_values[1], "evidence.state.invalid")
|
|
359
|
+
lease_expires_at = _timestamp(lease_values[2])
|
|
360
|
+
if lease_expires_at <= next_attempt_at:
|
|
361
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.state.invalid")
|
|
362
|
+
del lease_owner, lease_token
|
|
363
|
+
elif lease_values != (None, None, None):
|
|
364
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.state.invalid")
|
|
365
|
+
else:
|
|
366
|
+
lease_expires_at = None
|
|
367
|
+
error_code = row["last_error_code"]
|
|
368
|
+
if error_code is not None and (
|
|
369
|
+
not isinstance(error_code, str) or ERROR_CODE.fullmatch(error_code) is None
|
|
370
|
+
):
|
|
371
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.state.invalid")
|
|
372
|
+
if status in {"pending", "leased", "succeeded"} and error_code is not None:
|
|
373
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.state.invalid")
|
|
374
|
+
_timestamp(row["created_at"])
|
|
375
|
+
_timestamp(row["updated_at"])
|
|
376
|
+
return QualityEvidenceStatus(
|
|
377
|
+
EVIDENCE_STATE_SCHEMA,
|
|
378
|
+
request_id,
|
|
379
|
+
event_id,
|
|
380
|
+
plan_id,
|
|
381
|
+
job_id,
|
|
382
|
+
result_sha256,
|
|
383
|
+
evidence_revision,
|
|
384
|
+
status,
|
|
385
|
+
attempts,
|
|
386
|
+
max_attempts,
|
|
387
|
+
next_attempt_at,
|
|
388
|
+
lease_expires_at,
|
|
389
|
+
error_code,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
def claim(
|
|
393
|
+
self,
|
|
394
|
+
request: QualityEvidenceRequest,
|
|
395
|
+
*,
|
|
396
|
+
worker_id: Any,
|
|
397
|
+
now: float | int,
|
|
398
|
+
lease_seconds: float | int,
|
|
399
|
+
max_attempts: int,
|
|
400
|
+
) -> QualityEvidenceClaim | None:
|
|
401
|
+
binding = self._binding(request)
|
|
402
|
+
worker_id = _token(worker_id, "evidence.worker_id.invalid")
|
|
403
|
+
now = _timestamp(now)
|
|
404
|
+
lease_seconds = _positive_duration(
|
|
405
|
+
lease_seconds, "evidence.lease.invalid", MAX_LEASE_SECONDS,
|
|
406
|
+
)
|
|
407
|
+
max_attempts = _attempt_limit(max_attempts)
|
|
408
|
+
blocked_code = None
|
|
409
|
+
claimed = None
|
|
410
|
+
with _transaction(self.connection):
|
|
411
|
+
row = self.connection.execute(
|
|
412
|
+
"SELECT * FROM localization_quality_evidence_state WHERE request_id = ?",
|
|
413
|
+
(request.request_id,),
|
|
414
|
+
).fetchone()
|
|
415
|
+
if row is None:
|
|
416
|
+
self.connection.execute("""
|
|
417
|
+
INSERT INTO localization_quality_evidence_state (
|
|
418
|
+
request_id, event_id, plan_id, job_id, result_sha256,
|
|
419
|
+
evidence_revision, status, attempts, max_attempts,
|
|
420
|
+
next_attempt_at, created_at, updated_at
|
|
421
|
+
) VALUES (?, ?, ?, ?, ?, ?, 'pending', 0, ?, ?, ?, ?)
|
|
422
|
+
""", (*binding, max_attempts, now, now, now))
|
|
423
|
+
row = self.connection.execute(
|
|
424
|
+
"SELECT * FROM localization_quality_evidence_state WHERE request_id = ?",
|
|
425
|
+
(request.request_id,),
|
|
426
|
+
).fetchone()
|
|
427
|
+
self._status_from_row(row)
|
|
428
|
+
actual = tuple(row[name] for name in _EVIDENCE_COLUMNS[:6])
|
|
429
|
+
if actual != binding:
|
|
430
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.state.binding_mismatch")
|
|
431
|
+
if row["max_attempts"] != max_attempts:
|
|
432
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.state.attempt_policy_changed")
|
|
433
|
+
status = row["status"]
|
|
434
|
+
if status not in EVIDENCE_STATES:
|
|
435
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.state.invalid")
|
|
436
|
+
if status == "leased" and _timestamp(row["lease_expires_at"]) <= now:
|
|
437
|
+
terminal = row["attempts"] >= row["max_attempts"]
|
|
438
|
+
status = "failed" if terminal else "retry_wait"
|
|
439
|
+
self.connection.execute("""
|
|
440
|
+
UPDATE localization_quality_evidence_state
|
|
441
|
+
SET status = ?, next_attempt_at = ?, lease_owner = NULL,
|
|
442
|
+
lease_token = NULL, lease_expires_at = NULL,
|
|
443
|
+
last_error_code = 'lease_expired', updated_at = ?
|
|
444
|
+
WHERE request_id = ? AND status = 'leased'
|
|
445
|
+
""", (status, now, now, request.request_id))
|
|
446
|
+
if status == "succeeded":
|
|
447
|
+
blocked_code = "evidence.revision.already_consumed"
|
|
448
|
+
elif status == "failed":
|
|
449
|
+
blocked_code = "evidence.attempts_exhausted"
|
|
450
|
+
else:
|
|
451
|
+
row = self.connection.execute(
|
|
452
|
+
"SELECT * FROM localization_quality_evidence_state WHERE request_id = ?",
|
|
453
|
+
(request.request_id,),
|
|
454
|
+
).fetchone()
|
|
455
|
+
if row["status"] != "leased" and _timestamp(row["next_attempt_at"]) <= now:
|
|
456
|
+
lease_token = secrets.token_urlsafe(32)
|
|
457
|
+
expires_at = now + lease_seconds
|
|
458
|
+
updated = self.connection.execute("""
|
|
459
|
+
UPDATE localization_quality_evidence_state
|
|
460
|
+
SET status = 'leased', attempts = attempts + 1,
|
|
461
|
+
lease_owner = ?, lease_token = ?, lease_expires_at = ?,
|
|
462
|
+
last_error_code = NULL, updated_at = ?
|
|
463
|
+
WHERE request_id = ? AND status IN ('pending', 'retry_wait')
|
|
464
|
+
AND next_attempt_at <= ? AND attempts < max_attempts
|
|
465
|
+
""", (
|
|
466
|
+
worker_id, lease_token, expires_at, now, request.request_id, now,
|
|
467
|
+
))
|
|
468
|
+
if updated.rowcount != 1:
|
|
469
|
+
raise LocalizationReleaseCoordinatorBlocked(
|
|
470
|
+
"evidence.claim.lost", retryable=True,
|
|
471
|
+
)
|
|
472
|
+
claimed = QualityEvidenceClaim(
|
|
473
|
+
request.request_id,
|
|
474
|
+
worker_id,
|
|
475
|
+
lease_token,
|
|
476
|
+
expires_at,
|
|
477
|
+
row["attempts"] + 1,
|
|
478
|
+
row["max_attempts"],
|
|
479
|
+
)
|
|
480
|
+
if blocked_code is not None:
|
|
481
|
+
raise LocalizationReleaseCoordinatorBlocked(blocked_code)
|
|
482
|
+
return claimed
|
|
483
|
+
|
|
484
|
+
def _live_row(self, claim: Any, now: float) -> sqlite3.Row:
|
|
485
|
+
claim = self._claim_values(claim)
|
|
486
|
+
row = self.connection.execute(
|
|
487
|
+
"SELECT * FROM localization_quality_evidence_state WHERE request_id = ?",
|
|
488
|
+
(claim.request_id,),
|
|
489
|
+
).fetchone()
|
|
490
|
+
if (
|
|
491
|
+
row is None or row["status"] != "leased"
|
|
492
|
+
or row["lease_owner"] != claim.lease_owner
|
|
493
|
+
or row["lease_token"] != claim.lease_token
|
|
494
|
+
):
|
|
495
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.lease_lost")
|
|
496
|
+
if _timestamp(row["lease_expires_at"]) <= now:
|
|
497
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.lease_expired")
|
|
498
|
+
return row
|
|
499
|
+
|
|
500
|
+
def succeed(self, claim: QualityEvidenceClaim, *, now: float | int) -> None:
|
|
501
|
+
now = _timestamp(now)
|
|
502
|
+
with _transaction(self.connection):
|
|
503
|
+
row = self.connection.execute(
|
|
504
|
+
"SELECT * FROM localization_quality_evidence_state WHERE request_id = ?",
|
|
505
|
+
(self._claim_values(claim).request_id,),
|
|
506
|
+
).fetchone()
|
|
507
|
+
if row is not None and row["status"] == "succeeded":
|
|
508
|
+
self._status_from_row(row)
|
|
509
|
+
return
|
|
510
|
+
self._live_row(claim, now)
|
|
511
|
+
updated = self.connection.execute("""
|
|
512
|
+
UPDATE localization_quality_evidence_state
|
|
513
|
+
SET status = 'succeeded', next_attempt_at = ?, lease_owner = NULL,
|
|
514
|
+
lease_token = NULL, lease_expires_at = NULL,
|
|
515
|
+
last_error_code = NULL, updated_at = ?
|
|
516
|
+
WHERE request_id = ? AND status = 'leased'
|
|
517
|
+
AND lease_owner = ? AND lease_token = ?
|
|
518
|
+
""", (
|
|
519
|
+
now, now, claim.request_id, claim.lease_owner, claim.lease_token,
|
|
520
|
+
))
|
|
521
|
+
if updated.rowcount != 1:
|
|
522
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.finish_lost")
|
|
523
|
+
|
|
524
|
+
def fail(
|
|
525
|
+
self,
|
|
526
|
+
claim: QualityEvidenceClaim,
|
|
527
|
+
error: LocalizationReleaseCoordinatorBlocked,
|
|
528
|
+
*,
|
|
529
|
+
now: float | int,
|
|
530
|
+
) -> None:
|
|
531
|
+
if not isinstance(error, LocalizationReleaseCoordinatorBlocked):
|
|
532
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.failure.invalid")
|
|
533
|
+
now = _timestamp(now)
|
|
534
|
+
with _transaction(self.connection):
|
|
535
|
+
current = self.connection.execute(
|
|
536
|
+
"SELECT * FROM localization_quality_evidence_state WHERE request_id = ?",
|
|
537
|
+
(self._claim_values(claim).request_id,),
|
|
538
|
+
).fetchone()
|
|
539
|
+
if current is not None and current["status"] == "succeeded":
|
|
540
|
+
self._status_from_row(current)
|
|
541
|
+
return
|
|
542
|
+
row = self._live_row(claim, now)
|
|
543
|
+
terminal = not error.retryable or row["attempts"] >= row["max_attempts"]
|
|
544
|
+
status = "failed" if terminal else "retry_wait"
|
|
545
|
+
next_attempt = now if terminal else now + min(
|
|
546
|
+
3_600.0, 5.0 * (2 ** (row["attempts"] - 1)),
|
|
547
|
+
)
|
|
548
|
+
updated = self.connection.execute("""
|
|
549
|
+
UPDATE localization_quality_evidence_state
|
|
550
|
+
SET status = ?, next_attempt_at = ?, lease_owner = NULL,
|
|
551
|
+
lease_token = NULL, lease_expires_at = NULL,
|
|
552
|
+
last_error_code = ?, updated_at = ?
|
|
553
|
+
WHERE request_id = ? AND status = 'leased'
|
|
554
|
+
AND lease_owner = ? AND lease_token = ?
|
|
555
|
+
""", (
|
|
556
|
+
status, next_attempt, error.code, now,
|
|
557
|
+
claim.request_id, claim.lease_owner, claim.lease_token,
|
|
558
|
+
))
|
|
559
|
+
if updated.rowcount != 1:
|
|
560
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.finish_lost")
|
|
561
|
+
|
|
562
|
+
def mark_approved(
|
|
563
|
+
self,
|
|
564
|
+
event_id: Any,
|
|
565
|
+
plan_id: Any,
|
|
566
|
+
job_id: Any,
|
|
567
|
+
result_sha256: Any,
|
|
568
|
+
*,
|
|
569
|
+
now: float | int,
|
|
570
|
+
) -> None:
|
|
571
|
+
event_id = _token(event_id, "coordinator.event_id.invalid")
|
|
572
|
+
plan_id = _token(plan_id, "evidence.plan_id.invalid")
|
|
573
|
+
job_id = _token(job_id, "evidence.job_id.invalid")
|
|
574
|
+
if not isinstance(result_sha256, str) or SHA256.fullmatch(result_sha256) is None:
|
|
575
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.result_sha256.invalid")
|
|
576
|
+
now = _timestamp(now)
|
|
577
|
+
with _transaction(self.connection):
|
|
578
|
+
rows = self.connection.execute("""
|
|
579
|
+
SELECT * FROM localization_quality_evidence_state
|
|
580
|
+
WHERE event_id = ? AND plan_id = ? AND job_id = ?
|
|
581
|
+
""", (event_id, plan_id, job_id)).fetchall()
|
|
582
|
+
for row in rows:
|
|
583
|
+
self._status_from_row(row)
|
|
584
|
+
if row["result_sha256"] != result_sha256:
|
|
585
|
+
raise LocalizationReleaseCoordinatorBlocked(
|
|
586
|
+
"evidence.state.binding_mismatch"
|
|
587
|
+
)
|
|
588
|
+
self.connection.execute("""
|
|
589
|
+
UPDATE localization_quality_evidence_state
|
|
590
|
+
SET status = 'succeeded', next_attempt_at = ?, lease_owner = NULL,
|
|
591
|
+
lease_token = NULL, lease_expires_at = NULL,
|
|
592
|
+
last_error_code = NULL, updated_at = ?
|
|
593
|
+
WHERE event_id = ? AND plan_id = ? AND job_id = ?
|
|
594
|
+
AND result_sha256 = ?
|
|
595
|
+
AND status != 'succeeded'
|
|
596
|
+
""", (now, now, event_id, plan_id, job_id, result_sha256))
|
|
597
|
+
|
|
598
|
+
def statuses(self, event_id: Any) -> tuple[QualityEvidenceStatus, ...]:
|
|
599
|
+
event_id = _token(event_id, "coordinator.event_id.invalid")
|
|
600
|
+
rows = self.connection.execute("""
|
|
601
|
+
SELECT * FROM localization_quality_evidence_state
|
|
602
|
+
WHERE event_id = ? ORDER BY created_at, request_id
|
|
603
|
+
""", (event_id,)).fetchall()
|
|
604
|
+
return tuple(self._status_from_row(row) for row in rows)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def _receipt(value: Any, code: str) -> str:
|
|
608
|
+
if (
|
|
609
|
+
not isinstance(value, str) or not value or value != value.strip()
|
|
610
|
+
or len(value) > MAX_RECEIPT_LENGTH or "\x00" in value
|
|
611
|
+
or not unicodedata.is_normalized("NFC", value)
|
|
612
|
+
):
|
|
613
|
+
raise LocalizationReleaseCoordinatorBlocked(code)
|
|
614
|
+
return value
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _external_code(error: Exception, fallback: str) -> str:
|
|
618
|
+
code = getattr(error, "code", None)
|
|
619
|
+
if isinstance(code, str) and ERROR_CODE.fullmatch(code) is not None:
|
|
620
|
+
combined = code if code.startswith(fallback + ".") else fallback + "." + code
|
|
621
|
+
if len(combined) <= 128:
|
|
622
|
+
return combined
|
|
623
|
+
return fallback + ".failed"
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def _load_event(bridge: Any, event_id: str, event_verifier: Any):
|
|
627
|
+
loader = getattr(bridge, "_load_event", None)
|
|
628
|
+
if not callable(loader):
|
|
629
|
+
raise LocalizationReleaseCoordinatorBlocked("coordinator.bridge.invalid")
|
|
630
|
+
try:
|
|
631
|
+
event, plan = loader(event_id, event_verifier)
|
|
632
|
+
except Exception as error:
|
|
633
|
+
raise LocalizationReleaseCoordinatorBlocked(_external_code(error, "cms")) from None
|
|
634
|
+
if not isinstance(event, dict) or not isinstance(getattr(plan, "jobs", None), tuple):
|
|
635
|
+
raise LocalizationReleaseCoordinatorBlocked("coordinator.bridge.invalid")
|
|
636
|
+
return event, plan
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def _validated_result(store: Any, plan: Any, job: Any) -> tuple[dict[str, Any], str]:
|
|
640
|
+
validated_result = getattr(store, "validated_result", None)
|
|
641
|
+
if not callable(validated_result):
|
|
642
|
+
raise LocalizationReleaseCoordinatorBlocked("coordinator.release_store.invalid")
|
|
643
|
+
try:
|
|
644
|
+
result = validated_result(plan, job.job_id)
|
|
645
|
+
except Exception as error:
|
|
646
|
+
raise LocalizationReleaseCoordinatorBlocked(_external_code(error, "result")) from None
|
|
647
|
+
result_json = _canonical_json(result)
|
|
648
|
+
return result, _hash(result_json)
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def _request(
|
|
652
|
+
event: dict[str, Any],
|
|
653
|
+
plan: Any,
|
|
654
|
+
job: Any,
|
|
655
|
+
result: dict[str, Any],
|
|
656
|
+
result_sha256: str,
|
|
657
|
+
evidence_revision: str,
|
|
658
|
+
) -> QualityEvidenceRequest:
|
|
659
|
+
binding = {
|
|
660
|
+
"schema": EVIDENCE_REQUEST_SCHEMA,
|
|
661
|
+
"evidence_revision": evidence_revision,
|
|
662
|
+
"event_id": event["event_id"],
|
|
663
|
+
"plan_id": plan.plan_id,
|
|
664
|
+
"job_id": job.job_id,
|
|
665
|
+
"result_sha256": result_sha256,
|
|
666
|
+
"source_sha256": result["source_sha256"],
|
|
667
|
+
"target_sha256": result["target_sha256"],
|
|
668
|
+
"source_locale": result["source_locale"],
|
|
669
|
+
"target_locale": result["target_locale"],
|
|
670
|
+
"content_type": result["content_type"],
|
|
671
|
+
"glossary_version": result["glossary_version"],
|
|
672
|
+
"policy_version": result["policy_version"],
|
|
673
|
+
"provider": json.loads(_canonical_json(result["provider"])),
|
|
674
|
+
"software_version": result["software_version"],
|
|
675
|
+
"review_confidence": json.loads(_canonical_json(result["review_confidence"])),
|
|
676
|
+
"quality_profile": json.loads(_canonical_json(result["quality_profile"])),
|
|
677
|
+
"commercial_profile": job.as_payload().get("commercial_profile"),
|
|
678
|
+
"commercial_review": json.loads(_canonical_json(result["commercial_review"])),
|
|
679
|
+
"human_review_required": result["human_review_required"],
|
|
680
|
+
"independent_review_required": result["independent_review_required"],
|
|
681
|
+
}
|
|
682
|
+
request_id = "blun-l10n-evidence-" + _hash(_canonical_json(binding))
|
|
683
|
+
return QualityEvidenceRequest(
|
|
684
|
+
**binding,
|
|
685
|
+
request_id=request_id,
|
|
686
|
+
source_text=job.as_payload()["source"]["text"],
|
|
687
|
+
target_text=result["candidate"],
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def _obtain_evidence(
|
|
692
|
+
provider: Any,
|
|
693
|
+
request: QualityEvidenceRequest,
|
|
694
|
+
) -> tuple[str, str | None, dict[str, Any] | None]:
|
|
695
|
+
obtain = getattr(provider, "obtain", None)
|
|
696
|
+
if not callable(obtain):
|
|
697
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.provider.invalid")
|
|
698
|
+
request_hash = _hash(_canonical_json(request.as_payload()))
|
|
699
|
+
try:
|
|
700
|
+
response = obtain(request)
|
|
701
|
+
except Exception as error:
|
|
702
|
+
if getattr(type(error), "localization_evidence_failure", None) is True:
|
|
703
|
+
retryable = getattr(error, "retryable", None)
|
|
704
|
+
if isinstance(retryable, bool):
|
|
705
|
+
raise LocalizationReleaseCoordinatorBlocked(
|
|
706
|
+
_external_code(error, "evidence"), retryable=retryable,
|
|
707
|
+
) from None
|
|
708
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.unavailable", retryable=True) from None
|
|
709
|
+
if _hash(_canonical_json(request.as_payload())) != request_hash:
|
|
710
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.request_mutated")
|
|
711
|
+
expected = {
|
|
712
|
+
"schema", "request_id", "result_sha256", "quality_receipt",
|
|
713
|
+
"human_review_receipt", "independent_model_review",
|
|
714
|
+
}
|
|
715
|
+
try:
|
|
716
|
+
if not isinstance(response, Mapping) or set(response) != expected:
|
|
717
|
+
raise ValueError
|
|
718
|
+
response = dict(response)
|
|
719
|
+
except Exception:
|
|
720
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.response.invalid") from None
|
|
721
|
+
if (
|
|
722
|
+
response["schema"] != EVIDENCE_RESPONSE_SCHEMA
|
|
723
|
+
or response["request_id"] != request.request_id
|
|
724
|
+
or response["result_sha256"] != request.result_sha256
|
|
725
|
+
):
|
|
726
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.response.binding_mismatch")
|
|
727
|
+
quality_receipt = _receipt(response["quality_receipt"], "evidence.quality_receipt.invalid")
|
|
728
|
+
human_receipt = response["human_review_receipt"]
|
|
729
|
+
independent = response["independent_model_review"]
|
|
730
|
+
if not (request.human_review_required or request.independent_review_required):
|
|
731
|
+
if human_receipt is not None or independent is not None:
|
|
732
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.review_escalation.unexpected")
|
|
733
|
+
return quality_receipt, None, None
|
|
734
|
+
if request.human_review_required:
|
|
735
|
+
if independent is not None:
|
|
736
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.independent_model_review.legal_forbidden")
|
|
737
|
+
human_receipt = _receipt(human_receipt, "evidence.human_receipt.required")
|
|
738
|
+
return quality_receipt, human_receipt, None
|
|
739
|
+
if (human_receipt is None) == (independent is None):
|
|
740
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.review_escalation.required")
|
|
741
|
+
if human_receipt is not None:
|
|
742
|
+
return quality_receipt, _receipt(
|
|
743
|
+
human_receipt, "evidence.human_receipt.required",
|
|
744
|
+
), None
|
|
745
|
+
if not isinstance(independent, Mapping) or set(independent) != {"schema", "provider", "receipt"}:
|
|
746
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.independent_model_review.invalid")
|
|
747
|
+
independent = dict(independent)
|
|
748
|
+
if independent["schema"] != INDEPENDENT_MODEL_REVIEW_SCHEMA:
|
|
749
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.independent_model_review.invalid")
|
|
750
|
+
reviewer = independent["provider"]
|
|
751
|
+
if not isinstance(reviewer, Mapping) or set(reviewer) != {"id", "model_id", "model_version"}:
|
|
752
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.independent_model_review.provider.invalid")
|
|
753
|
+
reviewer = {
|
|
754
|
+
name: _token(reviewer[name], "evidence.independent_model_review.provider.invalid")
|
|
755
|
+
for name in ("id", "model_id", "model_version")
|
|
756
|
+
}
|
|
757
|
+
if reviewer["id"] == request.provider["id"]:
|
|
758
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.independent_model_review.not_independent")
|
|
759
|
+
return quality_receipt, None, {
|
|
760
|
+
"schema": INDEPENDENT_MODEL_REVIEW_SCHEMA,
|
|
761
|
+
"provider": reviewer,
|
|
762
|
+
"receipt": _receipt(
|
|
763
|
+
independent["receipt"], "evidence.independent_model_review.receipt.required",
|
|
764
|
+
),
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def _outcome(status: str, event: dict[str, Any], plan: Any, **values) -> ReleaseCoordinatorOutcome:
|
|
769
|
+
return ReleaseCoordinatorOutcome(
|
|
770
|
+
schema=SCHEMA,
|
|
771
|
+
status=status,
|
|
772
|
+
event_id=event["event_id"],
|
|
773
|
+
plan_id=plan.plan_id,
|
|
774
|
+
**values,
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def _existing_delivery(bridge: Any, event_id: str, authority: Any, now: float):
|
|
779
|
+
connection = getattr(bridge, "connection", None)
|
|
780
|
+
request_from_row = getattr(bridge, "_request_from_row", None)
|
|
781
|
+
if connection is None or not callable(request_from_row):
|
|
782
|
+
raise LocalizationReleaseCoordinatorBlocked("coordinator.bridge.invalid")
|
|
783
|
+
try:
|
|
784
|
+
row = connection.execute(
|
|
785
|
+
"SELECT * FROM cms_publication_deliveries WHERE event_id = ?",
|
|
786
|
+
(event_id,),
|
|
787
|
+
).fetchone()
|
|
788
|
+
if row is None:
|
|
789
|
+
return None
|
|
790
|
+
status = row["status"]
|
|
791
|
+
if status not in {"pending", "leased", "retry_wait", "succeeded", "failed"}:
|
|
792
|
+
raise LocalizationReleaseCoordinatorBlocked("cms.delivery.invalid")
|
|
793
|
+
# A completed delivery is immutable history. A delivery that has not
|
|
794
|
+
# succeeded must still hold current approvals before being called ready.
|
|
795
|
+
request = request_from_row(row, authority, 0.0 if status == "succeeded" else now)
|
|
796
|
+
except Exception as error:
|
|
797
|
+
if isinstance(error, LocalizationReleaseCoordinatorBlocked):
|
|
798
|
+
raise
|
|
799
|
+
raise LocalizationReleaseCoordinatorBlocked(_external_code(error, "cms")) from None
|
|
800
|
+
return request, status
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def run_next_release(
|
|
804
|
+
bridge: Any,
|
|
805
|
+
event_id: str,
|
|
806
|
+
event_verifier: Any,
|
|
807
|
+
evidence_provider: QualityEvidenceProvider,
|
|
808
|
+
quality_verifier: Any,
|
|
809
|
+
approval_authority: Any,
|
|
810
|
+
publication_authority: Any,
|
|
811
|
+
*,
|
|
812
|
+
evidence_state: QualityEvidenceStateStore,
|
|
813
|
+
evidence_revision: str,
|
|
814
|
+
evidence_worker_id: str,
|
|
815
|
+
evidence_lease_seconds: float | int = 300,
|
|
816
|
+
evidence_max_attempts: int = 5,
|
|
817
|
+
now: float | int | None = None,
|
|
818
|
+
approval_ttl_seconds: float | int = 2_592_000,
|
|
819
|
+
delivery_max_attempts: int = 5,
|
|
820
|
+
human_review_verifier: Any | None = None,
|
|
821
|
+
independent_model_review_verifier: Any | None = None,
|
|
822
|
+
operation_guard: Callable[[float], Any] | None = None,
|
|
823
|
+
clock: Callable[[], float] = time.time,
|
|
824
|
+
) -> ReleaseCoordinatorOutcome:
|
|
825
|
+
"""Approve at most one completed locale and prepare only a complete bundle."""
|
|
826
|
+
|
|
827
|
+
event_id = _token(event_id, "coordinator.event_id.invalid")
|
|
828
|
+
evidence_revision = _token(evidence_revision, "coordinator.evidence_revision.invalid")
|
|
829
|
+
if not isinstance(evidence_state, QualityEvidenceStateStore):
|
|
830
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.state.invalid")
|
|
831
|
+
if operation_guard is not None and not callable(operation_guard):
|
|
832
|
+
raise LocalizationReleaseCoordinatorBlocked("evidence.operation_guard.invalid")
|
|
833
|
+
fixed_now = now is not None
|
|
834
|
+
now = _timestamp(clock() if now is None else now)
|
|
835
|
+
|
|
836
|
+
def current_time() -> float:
|
|
837
|
+
return now if fixed_now else _timestamp(clock())
|
|
838
|
+
|
|
839
|
+
event, plan = _load_event(bridge, event_id, event_verifier)
|
|
840
|
+
queue = getattr(bridge, "queue", None)
|
|
841
|
+
store = getattr(bridge, "release_store", None)
|
|
842
|
+
if queue is None or store is None:
|
|
843
|
+
raise LocalizationReleaseCoordinatorBlocked("coordinator.bridge.invalid")
|
|
844
|
+
existing_delivery = _existing_delivery(bridge, event_id, publication_authority, now)
|
|
845
|
+
if existing_delivery is not None:
|
|
846
|
+
delivery, delivery_status = existing_delivery
|
|
847
|
+
if delivery_status == "failed":
|
|
848
|
+
raise LocalizationReleaseCoordinatorBlocked("cms.delivery.failed")
|
|
849
|
+
return _outcome(
|
|
850
|
+
"delivered" if delivery_status == "succeeded" else "delivery_ready",
|
|
851
|
+
event,
|
|
852
|
+
plan,
|
|
853
|
+
delivery_id=delivery.delivery_id,
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
selected = None
|
|
857
|
+
for job in sorted(plan.jobs, key=lambda item: item.as_payload()["target"]["locale"]):
|
|
858
|
+
try:
|
|
859
|
+
status = queue.status(job.job_id)
|
|
860
|
+
except Exception as error:
|
|
861
|
+
raise LocalizationReleaseCoordinatorBlocked(_external_code(error, "queue")) from None
|
|
862
|
+
if status.status != "succeeded":
|
|
863
|
+
continue
|
|
864
|
+
try:
|
|
865
|
+
store.lookup(plan, job.job_id, approval_authority, now=now)
|
|
866
|
+
except Exception as error:
|
|
867
|
+
code = getattr(error, "code", None)
|
|
868
|
+
if code not in {"approval.missing", "approval.expired"}:
|
|
869
|
+
raise LocalizationReleaseCoordinatorBlocked(_external_code(error, "approval")) from None
|
|
870
|
+
if selected is None:
|
|
871
|
+
selected = job
|
|
872
|
+
else:
|
|
873
|
+
_, approved_result_sha256 = _validated_result(store, plan, job)
|
|
874
|
+
evidence_state.mark_approved(
|
|
875
|
+
event["event_id"],
|
|
876
|
+
plan.plan_id,
|
|
877
|
+
job.job_id,
|
|
878
|
+
approved_result_sha256,
|
|
879
|
+
now=now,
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
if selected is None:
|
|
883
|
+
try:
|
|
884
|
+
readiness = store.readiness(plan, approval_authority, now=now)
|
|
885
|
+
except Exception as error:
|
|
886
|
+
raise LocalizationReleaseCoordinatorBlocked(_external_code(error, "approval")) from None
|
|
887
|
+
if not readiness.ready:
|
|
888
|
+
return _outcome("waiting", event, plan)
|
|
889
|
+
try:
|
|
890
|
+
delivery = bridge.prepare_delivery(
|
|
891
|
+
event_id,
|
|
892
|
+
event_verifier,
|
|
893
|
+
approval_authority,
|
|
894
|
+
publication_authority,
|
|
895
|
+
now=now,
|
|
896
|
+
max_attempts=delivery_max_attempts,
|
|
897
|
+
)
|
|
898
|
+
except Exception as error:
|
|
899
|
+
raise LocalizationReleaseCoordinatorBlocked(_external_code(error, "cms")) from None
|
|
900
|
+
return _outcome("delivery_ready", event, plan, delivery_id=delivery.delivery_id)
|
|
901
|
+
|
|
902
|
+
result, result_sha256 = _validated_result(store, plan, selected)
|
|
903
|
+
request = _request(event, plan, selected, result, result_sha256, evidence_revision)
|
|
904
|
+
claim = evidence_state.claim(
|
|
905
|
+
request,
|
|
906
|
+
worker_id=evidence_worker_id,
|
|
907
|
+
now=now,
|
|
908
|
+
lease_seconds=evidence_lease_seconds,
|
|
909
|
+
max_attempts=evidence_max_attempts,
|
|
910
|
+
)
|
|
911
|
+
if claim is None:
|
|
912
|
+
return _outcome(
|
|
913
|
+
"waiting",
|
|
914
|
+
event,
|
|
915
|
+
plan,
|
|
916
|
+
job_id=selected.job_id,
|
|
917
|
+
target_locale=request.target_locale,
|
|
918
|
+
)
|
|
919
|
+
if operation_guard is not None:
|
|
920
|
+
try:
|
|
921
|
+
operation_guard(float(evidence_lease_seconds))
|
|
922
|
+
except Exception:
|
|
923
|
+
raise LocalizationReleaseCoordinatorBlocked(
|
|
924
|
+
"evidence.operation_guard.failed",
|
|
925
|
+
) from None
|
|
926
|
+
try:
|
|
927
|
+
quality_receipt, human_receipt, independent_review = _obtain_evidence(
|
|
928
|
+
evidence_provider, request,
|
|
929
|
+
)
|
|
930
|
+
except LocalizationReleaseCoordinatorBlocked as error:
|
|
931
|
+
evidence_state.fail(claim, error, now=current_time())
|
|
932
|
+
raise
|
|
933
|
+
approval_now = current_time()
|
|
934
|
+
guarded_quality_verifier = _guarded_verifier(
|
|
935
|
+
quality_verifier, operation_guard, float(evidence_lease_seconds),
|
|
936
|
+
)
|
|
937
|
+
guarded_human_verifier = _guarded_verifier(
|
|
938
|
+
human_review_verifier, operation_guard, float(evidence_lease_seconds),
|
|
939
|
+
)
|
|
940
|
+
guarded_independent_verifier = _guarded_verifier(
|
|
941
|
+
independent_model_review_verifier,
|
|
942
|
+
operation_guard,
|
|
943
|
+
float(evidence_lease_seconds),
|
|
944
|
+
)
|
|
945
|
+
try:
|
|
946
|
+
approved = store.approve(
|
|
947
|
+
plan,
|
|
948
|
+
selected.job_id,
|
|
949
|
+
quality_receipt,
|
|
950
|
+
guarded_quality_verifier,
|
|
951
|
+
approval_authority,
|
|
952
|
+
now=approval_now,
|
|
953
|
+
ttl_seconds=approval_ttl_seconds,
|
|
954
|
+
human_review_receipt=human_receipt,
|
|
955
|
+
human_review_verifier=(
|
|
956
|
+
guarded_human_verifier if human_receipt is not None else None
|
|
957
|
+
),
|
|
958
|
+
independent_model_review=independent_review,
|
|
959
|
+
independent_model_review_verifier=(
|
|
960
|
+
guarded_independent_verifier if independent_review is not None else None
|
|
961
|
+
),
|
|
962
|
+
)
|
|
963
|
+
except Exception as error:
|
|
964
|
+
code = _external_code(error, "release")
|
|
965
|
+
blocked = LocalizationReleaseCoordinatorBlocked(
|
|
966
|
+
code,
|
|
967
|
+
retryable=(
|
|
968
|
+
code == "release.approval.signing.failed"
|
|
969
|
+
or getattr(error, "retryable", False) is True
|
|
970
|
+
),
|
|
971
|
+
)
|
|
972
|
+
evidence_state.fail(claim, blocked, now=current_time())
|
|
973
|
+
raise blocked from None
|
|
974
|
+
evidence_state.succeed(claim, now=current_time())
|
|
975
|
+
|
|
976
|
+
try:
|
|
977
|
+
readiness = store.readiness(plan, approval_authority, now=approval_now)
|
|
978
|
+
except Exception as error:
|
|
979
|
+
raise LocalizationReleaseCoordinatorBlocked(_external_code(error, "approval")) from None
|
|
980
|
+
if not readiness.ready:
|
|
981
|
+
return _outcome(
|
|
982
|
+
"approved",
|
|
983
|
+
event,
|
|
984
|
+
plan,
|
|
985
|
+
job_id=selected.job_id,
|
|
986
|
+
target_locale=request.target_locale,
|
|
987
|
+
approval_id=approved.approval_id,
|
|
988
|
+
)
|
|
989
|
+
try:
|
|
990
|
+
delivery = bridge.prepare_delivery(
|
|
991
|
+
event_id,
|
|
992
|
+
event_verifier,
|
|
993
|
+
approval_authority,
|
|
994
|
+
publication_authority,
|
|
995
|
+
now=current_time(),
|
|
996
|
+
max_attempts=delivery_max_attempts,
|
|
997
|
+
)
|
|
998
|
+
except Exception as error:
|
|
999
|
+
raise LocalizationReleaseCoordinatorBlocked(_external_code(error, "cms")) from None
|
|
1000
|
+
return _outcome(
|
|
1001
|
+
"delivery_ready",
|
|
1002
|
+
event,
|
|
1003
|
+
plan,
|
|
1004
|
+
job_id=selected.job_id,
|
|
1005
|
+
target_locale=request.target_locale,
|
|
1006
|
+
approval_id=approved.approval_id,
|
|
1007
|
+
delivery_id=delivery.delivery_id,
|
|
1008
|
+
)
|