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,928 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Signed, version-bound translation memory and publication-readiness gate.
|
|
3
|
+
|
|
4
|
+
The host owns the SQLite connection, quality-receipt verifier, and isolated
|
|
5
|
+
approval authority. This module never reads a signing key and never publishes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import importlib.util
|
|
12
|
+
import json
|
|
13
|
+
import re
|
|
14
|
+
import sqlite3
|
|
15
|
+
import sys
|
|
16
|
+
import unicodedata
|
|
17
|
+
from contextlib import contextmanager
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Iterator, Protocol
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
SCHEMA_VERSION = 1
|
|
24
|
+
APPROVAL_SCHEMA = "blun.website-localization-approval.v3"
|
|
25
|
+
RECEIPT_BINDING_SCHEMA = "blun.localization-quality-receipt-binding.v2"
|
|
26
|
+
INDEPENDENT_MODEL_REVIEW_SCHEMA = "blun.independent-model-review.v1"
|
|
27
|
+
PUBLICATION_EVIDENCE_SCHEMA = "blun.website-localization-release-evidence.v1"
|
|
28
|
+
MAX_TEXT_BYTES = 2_000_000
|
|
29
|
+
MAX_RECEIPT_LENGTH = 16_384
|
|
30
|
+
MAX_TTL_SECONDS = 31_536_000.0
|
|
31
|
+
HEX64 = re.compile(r"^[0-9a-f]{64}$")
|
|
32
|
+
TOKEN = re.compile(r"^[A-Za-z0-9_.:-]{1,256}$")
|
|
33
|
+
_COLUMNS = (
|
|
34
|
+
"approval_id", "job_id", "target_locale", "target_sha256",
|
|
35
|
+
"result_json", "result_sha256", "approval_json", "approval_sha256",
|
|
36
|
+
"signature_algorithm", "key_id", "signature", "approved_at", "expires_at",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _load_module(name: str, path: Path):
|
|
41
|
+
spec = importlib.util.spec_from_file_location(name, path)
|
|
42
|
+
if spec is None or spec.loader is None:
|
|
43
|
+
raise RuntimeError(f"cannot load required release dependency: {path.name}")
|
|
44
|
+
module = importlib.util.module_from_spec(spec)
|
|
45
|
+
sys.modules[spec.name] = module
|
|
46
|
+
spec.loader.exec_module(module)
|
|
47
|
+
return module
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_ROOT = Path(__file__).resolve().parents[1]
|
|
51
|
+
_QUEUE = _load_module(
|
|
52
|
+
"blun_website_localization_release_queue",
|
|
53
|
+
_ROOT / "integrations" / "website_localization_queue.py",
|
|
54
|
+
)
|
|
55
|
+
_WORKER = _load_module(
|
|
56
|
+
"blun_website_localization_release_worker",
|
|
57
|
+
_ROOT / "integrations" / "website_localization_worker.py",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class LocalizationReleaseBlocked(RuntimeError):
|
|
62
|
+
"""Stable, content-free release failure."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, code: str, *, retryable: bool = False):
|
|
65
|
+
if not isinstance(code, str) or TOKEN.fullmatch(code) is None:
|
|
66
|
+
raise ValueError("release failure code is invalid")
|
|
67
|
+
if not isinstance(retryable, bool):
|
|
68
|
+
raise ValueError("release failure retryability must be boolean")
|
|
69
|
+
super().__init__(code)
|
|
70
|
+
self.code = code
|
|
71
|
+
self.retryable = retryable
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class ApprovalSignature:
|
|
76
|
+
algorithm: str
|
|
77
|
+
key_id: str
|
|
78
|
+
signature: str
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ApprovalAuthority(Protocol):
|
|
82
|
+
def sign(self, payload: bytes) -> ApprovalSignature: ...
|
|
83
|
+
def verify(self, payload: bytes, signature: ApprovalSignature) -> bool: ...
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class QualityReceiptVerifier(Protocol):
|
|
87
|
+
def verify(
|
|
88
|
+
self,
|
|
89
|
+
*,
|
|
90
|
+
binding: dict[str, Any],
|
|
91
|
+
receipt: str,
|
|
92
|
+
) -> bool: ...
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class IndependentModelReviewVerifier(Protocol):
|
|
96
|
+
def verify(
|
|
97
|
+
self,
|
|
98
|
+
*,
|
|
99
|
+
binding: dict[str, Any],
|
|
100
|
+
receipt: str,
|
|
101
|
+
) -> bool: ...
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass(frozen=True)
|
|
105
|
+
class ApprovedLocalization:
|
|
106
|
+
approval_id: str
|
|
107
|
+
job_id: str
|
|
108
|
+
target_locale: str
|
|
109
|
+
target_sha256: str
|
|
110
|
+
candidate: str
|
|
111
|
+
release_evidence: dict[str, Any]
|
|
112
|
+
approved_at: float
|
|
113
|
+
expires_at: float
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class WebsiteReadiness:
|
|
118
|
+
plan_id: str
|
|
119
|
+
ready: bool
|
|
120
|
+
required_locales: tuple[str, ...]
|
|
121
|
+
approved_locales: tuple[str, ...]
|
|
122
|
+
blocked: tuple[tuple[str, str], ...]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def validate_publication_evidence(value: Any) -> dict[str, Any]:
|
|
126
|
+
"""Validate the content-free release proof carried to a CMS."""
|
|
127
|
+
|
|
128
|
+
if not isinstance(value, dict) or set(value) != {
|
|
129
|
+
"schema", "job_id", "target_locale", "target_sha256", "approval_id",
|
|
130
|
+
"content_type", "result_sha256", "approval_sha256",
|
|
131
|
+
"quality_receipt_sha256", "commercial_profile", "commercial_review",
|
|
132
|
+
}:
|
|
133
|
+
raise LocalizationReleaseBlocked("publication.evidence.invalid")
|
|
134
|
+
if (
|
|
135
|
+
value.get("schema") != PUBLICATION_EVIDENCE_SCHEMA
|
|
136
|
+
or any(
|
|
137
|
+
not isinstance(value.get(field), str)
|
|
138
|
+
or TOKEN.fullmatch(value[field]) is None
|
|
139
|
+
for field in ("job_id", "target_locale", "approval_id", "content_type")
|
|
140
|
+
)
|
|
141
|
+
or any(
|
|
142
|
+
HEX64.fullmatch(str(value.get(field))) is None
|
|
143
|
+
for field in (
|
|
144
|
+
"target_sha256", "result_sha256", "approval_sha256",
|
|
145
|
+
"quality_receipt_sha256",
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
):
|
|
149
|
+
raise LocalizationReleaseBlocked("publication.evidence.invalid")
|
|
150
|
+
profile = value.get("commercial_profile")
|
|
151
|
+
review = value.get("commercial_review")
|
|
152
|
+
if (
|
|
153
|
+
(profile is None) != (review is None)
|
|
154
|
+
or (value["content_type"] == "commercial") != (profile is not None)
|
|
155
|
+
):
|
|
156
|
+
raise LocalizationReleaseBlocked("publication.evidence.invalid")
|
|
157
|
+
if profile is not None:
|
|
158
|
+
if not isinstance(profile, str) or TOKEN.fullmatch(profile) is None:
|
|
159
|
+
raise LocalizationReleaseBlocked("publication.evidence.invalid")
|
|
160
|
+
try:
|
|
161
|
+
review = _WORKER._COMMERCIAL.validate_summary(
|
|
162
|
+
review,
|
|
163
|
+
profile,
|
|
164
|
+
review_required=(
|
|
165
|
+
isinstance(review, dict)
|
|
166
|
+
and review.get("status") == "review_required"
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
except _WORKER._COMMERCIAL.CommercialReviewBlocked:
|
|
170
|
+
raise LocalizationReleaseBlocked("publication.evidence.invalid") from None
|
|
171
|
+
return json.loads(_canonical_json({**value, "commercial_review": review}))
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass(frozen=True)
|
|
175
|
+
class VerifiedResultCache:
|
|
176
|
+
"""Runner adapter for exact, signed translation-memory results."""
|
|
177
|
+
|
|
178
|
+
store: Any
|
|
179
|
+
authority: ApprovalAuthority
|
|
180
|
+
|
|
181
|
+
def resolve(
|
|
182
|
+
self,
|
|
183
|
+
job_payload: dict[str, Any],
|
|
184
|
+
*,
|
|
185
|
+
now: float,
|
|
186
|
+
) -> dict[str, Any] | None:
|
|
187
|
+
return self.store.cached_result(job_payload, self.authority, now=now)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _canonical_json(value: Any) -> str:
|
|
191
|
+
try:
|
|
192
|
+
return json.dumps(
|
|
193
|
+
value,
|
|
194
|
+
ensure_ascii=False,
|
|
195
|
+
allow_nan=False,
|
|
196
|
+
sort_keys=True,
|
|
197
|
+
separators=(",", ":"),
|
|
198
|
+
)
|
|
199
|
+
except (TypeError, ValueError) as error:
|
|
200
|
+
raise LocalizationReleaseBlocked("release.json.invalid") from error
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _hash_text(value: str) -> str:
|
|
204
|
+
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _text(value: Any, code: str, *, limit: int = 256) -> str:
|
|
208
|
+
if not isinstance(value, str) or not value or value != value.strip():
|
|
209
|
+
raise LocalizationReleaseBlocked(code)
|
|
210
|
+
if len(value) > limit or "\x00" in value or not unicodedata.is_normalized("NFC", value):
|
|
211
|
+
raise LocalizationReleaseBlocked(code)
|
|
212
|
+
return value
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _timestamp(value: Any, code: str) -> float:
|
|
216
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
217
|
+
raise LocalizationReleaseBlocked(code)
|
|
218
|
+
value = float(value)
|
|
219
|
+
if value < 0 or value != value or value in {float("inf"), float("-inf")}:
|
|
220
|
+
raise LocalizationReleaseBlocked(code)
|
|
221
|
+
return value
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _receipt(value: Any, code: str) -> str:
|
|
225
|
+
return _text(value, code, limit=MAX_RECEIPT_LENGTH)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _independent_model_review(value: Any, primary_provider: dict[str, Any]) -> tuple[dict[str, str], str]:
|
|
229
|
+
if not isinstance(value, dict) or set(value) != {"schema", "provider", "receipt"}:
|
|
230
|
+
raise LocalizationReleaseBlocked("independent_model_review.invalid")
|
|
231
|
+
provider = value.get("provider")
|
|
232
|
+
if not isinstance(provider, dict) or set(provider) != {"id", "model_id", "model_version"}:
|
|
233
|
+
raise LocalizationReleaseBlocked("independent_model_review.provider.invalid")
|
|
234
|
+
normalized = {
|
|
235
|
+
name: _text(provider.get(name), "independent_model_review.provider.invalid")
|
|
236
|
+
for name in ("id", "model_id", "model_version")
|
|
237
|
+
}
|
|
238
|
+
if value.get("schema") != INDEPENDENT_MODEL_REVIEW_SCHEMA:
|
|
239
|
+
raise LocalizationReleaseBlocked("independent_model_review.schema.invalid")
|
|
240
|
+
if normalized["id"] == primary_provider.get("id"):
|
|
241
|
+
raise LocalizationReleaseBlocked("independent_model_review.not_independent")
|
|
242
|
+
return normalized, _receipt(value.get("receipt"), "independent_model_review.receipt.required")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _receipt_binding(
|
|
246
|
+
job_id: str,
|
|
247
|
+
job: dict[str, Any],
|
|
248
|
+
result: dict[str, Any],
|
|
249
|
+
result_sha256: str,
|
|
250
|
+
*,
|
|
251
|
+
review_kind: str,
|
|
252
|
+
review_provider: dict[str, str] | None = None,
|
|
253
|
+
) -> dict[str, Any]:
|
|
254
|
+
"""Build the exact canonical context that an evidence receipt must bind."""
|
|
255
|
+
|
|
256
|
+
if review_kind not in {"quality", "qualified_human", "independent_model"}:
|
|
257
|
+
raise LocalizationReleaseBlocked("review.binding.invalid")
|
|
258
|
+
if (review_kind == "independent_model") != (review_provider is not None):
|
|
259
|
+
raise LocalizationReleaseBlocked("review.binding.invalid")
|
|
260
|
+
binding = {
|
|
261
|
+
"schema": RECEIPT_BINDING_SCHEMA,
|
|
262
|
+
"review_kind": review_kind,
|
|
263
|
+
"job_id": job_id,
|
|
264
|
+
"result_sha256": result_sha256,
|
|
265
|
+
"source_text": job["source"]["text"],
|
|
266
|
+
"target_text": result["candidate"],
|
|
267
|
+
"source_sha256": result["source_sha256"],
|
|
268
|
+
"target_sha256": result["target_sha256"],
|
|
269
|
+
"source_locale": result["source_locale"],
|
|
270
|
+
"target_locale": result["target_locale"],
|
|
271
|
+
"content_type": result["content_type"],
|
|
272
|
+
"glossary_version": result["glossary_version"],
|
|
273
|
+
"policy_version": result["policy_version"],
|
|
274
|
+
"primary_provider": result["provider"],
|
|
275
|
+
"review_provider": review_provider,
|
|
276
|
+
"software_version": result["software_version"],
|
|
277
|
+
"review_confidence": result["review_confidence"],
|
|
278
|
+
"quality_profile": result["quality_profile"],
|
|
279
|
+
"commercial_profile": job.get("commercial_profile"),
|
|
280
|
+
"commercial_review": result["commercial_review"],
|
|
281
|
+
"human_review_required": result["human_review_required"],
|
|
282
|
+
"independent_review_required": result["independent_review_required"],
|
|
283
|
+
}
|
|
284
|
+
return json.loads(_canonical_json(binding))
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _verification_failure(error: Exception, verifier_kind: str) -> None:
|
|
288
|
+
if getattr(type(error), "localization_receipt_verification_failure", None) is not True:
|
|
289
|
+
return
|
|
290
|
+
code = getattr(error, "code", None)
|
|
291
|
+
retryable = getattr(error, "retryable", None)
|
|
292
|
+
combined = f"{verifier_kind}.verifier.{code}"
|
|
293
|
+
if (
|
|
294
|
+
isinstance(code, str)
|
|
295
|
+
and TOKEN.fullmatch(code) is not None
|
|
296
|
+
and isinstance(retryable, bool)
|
|
297
|
+
and len(combined) <= 256
|
|
298
|
+
):
|
|
299
|
+
raise LocalizationReleaseBlocked(combined, retryable=retryable) from None
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _signature(value: Any) -> ApprovalSignature:
|
|
303
|
+
if not isinstance(value, ApprovalSignature):
|
|
304
|
+
raise LocalizationReleaseBlocked("approval.signature.invalid")
|
|
305
|
+
for item in (value.algorithm, value.key_id, value.signature):
|
|
306
|
+
if not isinstance(item, str) or TOKEN.fullmatch(item) is None:
|
|
307
|
+
raise LocalizationReleaseBlocked("approval.signature.invalid")
|
|
308
|
+
return value
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@contextmanager
|
|
312
|
+
def _transaction(connection: sqlite3.Connection) -> Iterator[None]:
|
|
313
|
+
if connection.in_transaction:
|
|
314
|
+
raise LocalizationReleaseBlocked("release.transaction.external")
|
|
315
|
+
try:
|
|
316
|
+
connection.execute("BEGIN IMMEDIATE")
|
|
317
|
+
yield
|
|
318
|
+
except Exception:
|
|
319
|
+
connection.rollback()
|
|
320
|
+
raise
|
|
321
|
+
else:
|
|
322
|
+
connection.commit()
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _plan_job(plan: Any, job_id: str) -> tuple[str, dict[str, Any]]:
|
|
326
|
+
plan_id = _text(getattr(plan, "plan_id", None), "plan.invalid")
|
|
327
|
+
jobs = getattr(plan, "jobs", None)
|
|
328
|
+
if not isinstance(jobs, tuple) or not jobs:
|
|
329
|
+
raise LocalizationReleaseBlocked("plan.invalid")
|
|
330
|
+
matches = [job for job in jobs if getattr(job, "job_id", None) == job_id]
|
|
331
|
+
if len(matches) != 1 or not callable(getattr(matches[0], "as_payload", None)):
|
|
332
|
+
raise LocalizationReleaseBlocked("plan.job.missing")
|
|
333
|
+
payload = matches[0].as_payload()
|
|
334
|
+
if not isinstance(payload, dict) or payload.get("job_id") != job_id:
|
|
335
|
+
raise LocalizationReleaseBlocked("plan.job.invalid")
|
|
336
|
+
return plan_id, _validated_job_payload(payload)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _validated_job_payload(payload: Any) -> dict[str, Any]:
|
|
340
|
+
try:
|
|
341
|
+
return _WORKER._validated_job(payload)
|
|
342
|
+
except _WORKER.LocalizationWorkerBlocked:
|
|
343
|
+
raise LocalizationReleaseBlocked("plan.job.invalid") from None
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _validate_result(job: dict[str, Any], result: Any) -> dict[str, Any]:
|
|
347
|
+
if not isinstance(result, dict):
|
|
348
|
+
raise LocalizationReleaseBlocked("result.invalid")
|
|
349
|
+
expected = {
|
|
350
|
+
"schema", "worker_schema", "job_id", "source_sha256", "target_sha256",
|
|
351
|
+
"source_locale", "target_locale", "content_type", "glossary_version",
|
|
352
|
+
"policy_version", "provider", "software_version", "candidate",
|
|
353
|
+
"quality_passes", "integrity", "review_confidence",
|
|
354
|
+
"quality_profile", "commercial_review", "human_review_required",
|
|
355
|
+
"independent_review_required", "release_required",
|
|
356
|
+
}
|
|
357
|
+
if set(result) != expected:
|
|
358
|
+
raise LocalizationReleaseBlocked("result.invalid")
|
|
359
|
+
candidate = result.get("candidate")
|
|
360
|
+
if not isinstance(candidate, str) or not candidate or len(candidate.encode("utf-8")) > MAX_TEXT_BYTES:
|
|
361
|
+
raise LocalizationReleaseBlocked("result.invalid")
|
|
362
|
+
if "\x00" in candidate or not unicodedata.is_normalized("NFC", candidate):
|
|
363
|
+
raise LocalizationReleaseBlocked("result.invalid")
|
|
364
|
+
binding = {
|
|
365
|
+
"job_id": job["job_id"],
|
|
366
|
+
"source_sha256": job["source"]["sha256"],
|
|
367
|
+
"source_locale": job["source"]["locale"],
|
|
368
|
+
"target_locale": job["target"]["locale"],
|
|
369
|
+
"content_type": job["content_type"],
|
|
370
|
+
"glossary_version": job["glossary_version"],
|
|
371
|
+
"policy_version": job["policy_version"],
|
|
372
|
+
"provider": job["provider"],
|
|
373
|
+
"software_version": job["software_version"],
|
|
374
|
+
}
|
|
375
|
+
if any(result.get(name) != value for name, value in binding.items()):
|
|
376
|
+
raise LocalizationReleaseBlocked("result.binding_mismatch")
|
|
377
|
+
expected_quality_profile = {
|
|
378
|
+
"locale": job["target"]["locale"],
|
|
379
|
+
"version": job["target"]["quality_profile_version"],
|
|
380
|
+
"sha256": job["target"]["quality_profile_sha256"],
|
|
381
|
+
}
|
|
382
|
+
if result.get("quality_profile") != expected_quality_profile:
|
|
383
|
+
raise LocalizationReleaseBlocked("result.quality_profile.invalid")
|
|
384
|
+
if result.get("schema") != _WORKER.RESULT_SCHEMA or result.get("worker_schema") != _WORKER.WORKER_SCHEMA:
|
|
385
|
+
raise LocalizationReleaseBlocked("result.schema.invalid")
|
|
386
|
+
if result.get("target_sha256") != _hash_text(candidate):
|
|
387
|
+
raise LocalizationReleaseBlocked("result.target_hash.invalid")
|
|
388
|
+
phases = result.get("quality_passes")
|
|
389
|
+
if not isinstance(phases, list) or [item.get("phase") for item in phases if isinstance(item, dict)] != list(_WORKER.PHASES):
|
|
390
|
+
raise LocalizationReleaseBlocked("result.quality.invalid")
|
|
391
|
+
for item in phases:
|
|
392
|
+
if set(item) != {"phase", "request_sha256", "response_sha256", "status"}:
|
|
393
|
+
raise LocalizationReleaseBlocked("result.quality.invalid")
|
|
394
|
+
if item["status"] != "PASS" or HEX64.fullmatch(str(item["request_sha256"])) is None:
|
|
395
|
+
raise LocalizationReleaseBlocked("result.quality.invalid")
|
|
396
|
+
if HEX64.fullmatch(str(item["response_sha256"])) is None:
|
|
397
|
+
raise LocalizationReleaseBlocked("result.quality.invalid")
|
|
398
|
+
if result.get("integrity") != {"status": "PASS", "guard": "translate-native-structure-and-token-gate"}:
|
|
399
|
+
raise LocalizationReleaseBlocked("result.integrity.invalid")
|
|
400
|
+
if result.get("release_required") is not True:
|
|
401
|
+
raise LocalizationReleaseBlocked("result.release.invalid")
|
|
402
|
+
review_confidence = result.get("review_confidence")
|
|
403
|
+
if (
|
|
404
|
+
not isinstance(review_confidence, dict)
|
|
405
|
+
or set(review_confidence) != {"target_native", "source_fidelity"}
|
|
406
|
+
or any(value not in {"high", "low"} for value in review_confidence.values())
|
|
407
|
+
):
|
|
408
|
+
raise LocalizationReleaseBlocked("result.review_confidence.invalid")
|
|
409
|
+
human_review_required = result.get("human_review_required")
|
|
410
|
+
expected_human_review = job["content_type"] == "legal"
|
|
411
|
+
if not isinstance(human_review_required, bool) or human_review_required is not expected_human_review:
|
|
412
|
+
raise LocalizationReleaseBlocked("result.human_review.invalid")
|
|
413
|
+
independent_review_required = result.get("independent_review_required")
|
|
414
|
+
expected_independent_review = (
|
|
415
|
+
job["content_type"] != "legal" and "low" in review_confidence.values()
|
|
416
|
+
)
|
|
417
|
+
if (
|
|
418
|
+
not isinstance(independent_review_required, bool)
|
|
419
|
+
or independent_review_required is not expected_independent_review
|
|
420
|
+
):
|
|
421
|
+
raise LocalizationReleaseBlocked("result.independent_review.invalid")
|
|
422
|
+
commercial_review = result.get("commercial_review")
|
|
423
|
+
if job["content_type"] == "commercial":
|
|
424
|
+
try:
|
|
425
|
+
_WORKER._COMMERCIAL.validate_summary(
|
|
426
|
+
commercial_review,
|
|
427
|
+
job["commercial_profile"],
|
|
428
|
+
review_required=independent_review_required,
|
|
429
|
+
)
|
|
430
|
+
except _WORKER._COMMERCIAL.CommercialReviewBlocked:
|
|
431
|
+
raise LocalizationReleaseBlocked("result.commercial_review.invalid") from None
|
|
432
|
+
elif commercial_review is not None:
|
|
433
|
+
raise LocalizationReleaseBlocked("result.commercial_review.invalid")
|
|
434
|
+
return result
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class LocalizationReleaseStore:
|
|
438
|
+
"""Append-only approvals and exact-version translation-memory lookups."""
|
|
439
|
+
|
|
440
|
+
def __init__(self, connection: sqlite3.Connection, queue: Any):
|
|
441
|
+
if not isinstance(connection, sqlite3.Connection):
|
|
442
|
+
raise LocalizationReleaseBlocked("release.connection.invalid")
|
|
443
|
+
if not isinstance(queue, _QUEUE.LocalizationQueue):
|
|
444
|
+
raise LocalizationReleaseBlocked("release.queue.invalid")
|
|
445
|
+
self.connection = connection
|
|
446
|
+
self.connection.row_factory = sqlite3.Row
|
|
447
|
+
self.queue = queue
|
|
448
|
+
version = int(connection.execute("PRAGMA user_version").fetchone()[0])
|
|
449
|
+
if version not in {0, SCHEMA_VERSION}:
|
|
450
|
+
raise LocalizationReleaseBlocked("release.schema.unsupported")
|
|
451
|
+
if version == 0:
|
|
452
|
+
with _transaction(connection):
|
|
453
|
+
connection.execute("""
|
|
454
|
+
CREATE TABLE localization_approvals (
|
|
455
|
+
approval_id TEXT PRIMARY KEY,
|
|
456
|
+
job_id TEXT NOT NULL,
|
|
457
|
+
target_locale TEXT NOT NULL,
|
|
458
|
+
target_sha256 TEXT NOT NULL,
|
|
459
|
+
result_json TEXT NOT NULL,
|
|
460
|
+
result_sha256 TEXT NOT NULL,
|
|
461
|
+
approval_json TEXT NOT NULL,
|
|
462
|
+
approval_sha256 TEXT NOT NULL,
|
|
463
|
+
signature_algorithm TEXT NOT NULL,
|
|
464
|
+
key_id TEXT NOT NULL,
|
|
465
|
+
signature TEXT NOT NULL,
|
|
466
|
+
approved_at REAL NOT NULL,
|
|
467
|
+
expires_at REAL NOT NULL
|
|
468
|
+
)
|
|
469
|
+
""")
|
|
470
|
+
connection.execute("""
|
|
471
|
+
CREATE INDEX localization_approvals_job
|
|
472
|
+
ON localization_approvals (job_id, approved_at DESC)
|
|
473
|
+
""")
|
|
474
|
+
connection.execute(f"PRAGMA user_version = {SCHEMA_VERSION}")
|
|
475
|
+
columns = tuple(row["name"] for row in connection.execute("PRAGMA table_info(localization_approvals)"))
|
|
476
|
+
if columns != _COLUMNS:
|
|
477
|
+
raise LocalizationReleaseBlocked("release.schema.altered")
|
|
478
|
+
|
|
479
|
+
def validated_result(self, plan: Any, job_id: str) -> dict[str, Any]:
|
|
480
|
+
"""Return an exact completed result only after all release checks pass."""
|
|
481
|
+
job_id = _text(job_id, "job.id.invalid")
|
|
482
|
+
_, job = _plan_job(plan, job_id)
|
|
483
|
+
result = _validate_result(job, self.queue.result(job_id))
|
|
484
|
+
return json.loads(_canonical_json(result))
|
|
485
|
+
|
|
486
|
+
def approve(
|
|
487
|
+
self,
|
|
488
|
+
plan: Any,
|
|
489
|
+
job_id: str,
|
|
490
|
+
quality_receipt: str,
|
|
491
|
+
quality_verifier: QualityReceiptVerifier,
|
|
492
|
+
authority: ApprovalAuthority,
|
|
493
|
+
*,
|
|
494
|
+
now: float | int,
|
|
495
|
+
ttl_seconds: float | int = 2_592_000,
|
|
496
|
+
human_review_receipt: str | None = None,
|
|
497
|
+
human_review_verifier: QualityReceiptVerifier | None = None,
|
|
498
|
+
independent_model_review: dict[str, Any] | None = None,
|
|
499
|
+
independent_model_review_verifier: IndependentModelReviewVerifier | None = None,
|
|
500
|
+
) -> ApprovedLocalization:
|
|
501
|
+
job_id = _text(job_id, "job.id.invalid")
|
|
502
|
+
_, job = _plan_job(plan, job_id)
|
|
503
|
+
result = self.validated_result(plan, job_id)
|
|
504
|
+
now = _timestamp(now, "approval.time.invalid")
|
|
505
|
+
ttl = _timestamp(ttl_seconds, "approval.ttl.invalid")
|
|
506
|
+
if ttl <= 0 or ttl > MAX_TTL_SECONDS:
|
|
507
|
+
raise LocalizationReleaseBlocked("approval.ttl.invalid")
|
|
508
|
+
result_json = _canonical_json(result)
|
|
509
|
+
result_hash = _hash_text(result_json)
|
|
510
|
+
quality_receipt = _receipt(quality_receipt, "quality.receipt.invalid")
|
|
511
|
+
verifier = getattr(quality_verifier, "verify", None)
|
|
512
|
+
if not callable(verifier):
|
|
513
|
+
raise LocalizationReleaseBlocked("quality.verifier.invalid")
|
|
514
|
+
try:
|
|
515
|
+
quality_ok = verifier(
|
|
516
|
+
binding=_receipt_binding(
|
|
517
|
+
job_id,
|
|
518
|
+
job,
|
|
519
|
+
result,
|
|
520
|
+
result_hash,
|
|
521
|
+
review_kind="quality",
|
|
522
|
+
),
|
|
523
|
+
receipt=quality_receipt,
|
|
524
|
+
) is True
|
|
525
|
+
except Exception as error:
|
|
526
|
+
_verification_failure(error, "quality")
|
|
527
|
+
quality_ok = False
|
|
528
|
+
if not quality_ok:
|
|
529
|
+
raise LocalizationReleaseBlocked("quality.receipt.rejected")
|
|
530
|
+
|
|
531
|
+
human_hash = None
|
|
532
|
+
independent_binding = None
|
|
533
|
+
if result["human_review_required"]:
|
|
534
|
+
if independent_model_review is not None:
|
|
535
|
+
raise LocalizationReleaseBlocked("independent_model_review.legal_forbidden")
|
|
536
|
+
human_review_receipt = _receipt(human_review_receipt, "human.receipt.required")
|
|
537
|
+
human_verify = getattr(human_review_verifier, "verify", None)
|
|
538
|
+
if not callable(human_verify):
|
|
539
|
+
raise LocalizationReleaseBlocked("human.verifier.required")
|
|
540
|
+
try:
|
|
541
|
+
human_ok = human_verify(
|
|
542
|
+
binding=_receipt_binding(
|
|
543
|
+
job_id,
|
|
544
|
+
job,
|
|
545
|
+
result,
|
|
546
|
+
result_hash,
|
|
547
|
+
review_kind="qualified_human",
|
|
548
|
+
),
|
|
549
|
+
receipt=human_review_receipt,
|
|
550
|
+
) is True
|
|
551
|
+
except Exception as error:
|
|
552
|
+
_verification_failure(error, "human")
|
|
553
|
+
human_ok = False
|
|
554
|
+
if not human_ok:
|
|
555
|
+
raise LocalizationReleaseBlocked("human.receipt.rejected")
|
|
556
|
+
human_hash = _hash_text(human_review_receipt)
|
|
557
|
+
elif result["independent_review_required"]:
|
|
558
|
+
if human_review_receipt is not None and independent_model_review is not None:
|
|
559
|
+
raise LocalizationReleaseBlocked("review.escalation.ambiguous")
|
|
560
|
+
if independent_model_review is not None:
|
|
561
|
+
provider, model_receipt = _independent_model_review(
|
|
562
|
+
independent_model_review, result["provider"],
|
|
563
|
+
)
|
|
564
|
+
model_verify = getattr(independent_model_review_verifier, "verify", None)
|
|
565
|
+
if not callable(model_verify):
|
|
566
|
+
raise LocalizationReleaseBlocked("independent_model_review.verifier.required")
|
|
567
|
+
try:
|
|
568
|
+
model_ok = model_verify(
|
|
569
|
+
binding=_receipt_binding(
|
|
570
|
+
job_id,
|
|
571
|
+
job,
|
|
572
|
+
result,
|
|
573
|
+
result_hash,
|
|
574
|
+
review_kind="independent_model",
|
|
575
|
+
review_provider=provider,
|
|
576
|
+
),
|
|
577
|
+
receipt=model_receipt,
|
|
578
|
+
) is True
|
|
579
|
+
except Exception as error:
|
|
580
|
+
_verification_failure(error, "independent_model_review")
|
|
581
|
+
model_ok = False
|
|
582
|
+
if not model_ok:
|
|
583
|
+
raise LocalizationReleaseBlocked("independent_model_review.receipt.rejected")
|
|
584
|
+
independent_binding = {
|
|
585
|
+
"schema": INDEPENDENT_MODEL_REVIEW_SCHEMA,
|
|
586
|
+
"provider": provider,
|
|
587
|
+
"receipt_sha256": _hash_text(model_receipt),
|
|
588
|
+
}
|
|
589
|
+
else:
|
|
590
|
+
human_review_receipt = _receipt(human_review_receipt, "human.receipt.required")
|
|
591
|
+
human_verify = getattr(human_review_verifier, "verify", None)
|
|
592
|
+
if not callable(human_verify):
|
|
593
|
+
raise LocalizationReleaseBlocked("human.verifier.required")
|
|
594
|
+
try:
|
|
595
|
+
human_ok = human_verify(
|
|
596
|
+
binding=_receipt_binding(
|
|
597
|
+
job_id,
|
|
598
|
+
job,
|
|
599
|
+
result,
|
|
600
|
+
result_hash,
|
|
601
|
+
review_kind="qualified_human",
|
|
602
|
+
),
|
|
603
|
+
receipt=human_review_receipt,
|
|
604
|
+
) is True
|
|
605
|
+
except Exception as error:
|
|
606
|
+
_verification_failure(error, "human")
|
|
607
|
+
human_ok = False
|
|
608
|
+
if not human_ok:
|
|
609
|
+
raise LocalizationReleaseBlocked("human.receipt.rejected")
|
|
610
|
+
human_hash = _hash_text(human_review_receipt)
|
|
611
|
+
elif any(value is not None for value in (
|
|
612
|
+
human_review_receipt, human_review_verifier,
|
|
613
|
+
independent_model_review, independent_model_review_verifier,
|
|
614
|
+
)):
|
|
615
|
+
raise LocalizationReleaseBlocked("review.escalation.unexpected")
|
|
616
|
+
|
|
617
|
+
immutable = {
|
|
618
|
+
"schema": APPROVAL_SCHEMA,
|
|
619
|
+
"job_id": job_id,
|
|
620
|
+
"source_sha256": result["source_sha256"],
|
|
621
|
+
"target_sha256": result["target_sha256"],
|
|
622
|
+
"source_locale": result["source_locale"],
|
|
623
|
+
"target_locale": result["target_locale"],
|
|
624
|
+
"content_type": result["content_type"],
|
|
625
|
+
"glossary_version": result["glossary_version"],
|
|
626
|
+
"policy_version": result["policy_version"],
|
|
627
|
+
"provider": result["provider"],
|
|
628
|
+
"software_version": result["software_version"],
|
|
629
|
+
"worker_schema": result["worker_schema"],
|
|
630
|
+
"review_confidence": result["review_confidence"],
|
|
631
|
+
"quality_profile": result["quality_profile"],
|
|
632
|
+
"result_sha256": result_hash,
|
|
633
|
+
"quality_receipt_sha256": _hash_text(quality_receipt),
|
|
634
|
+
"human_review_receipt_sha256": human_hash,
|
|
635
|
+
"independent_model_review": independent_binding,
|
|
636
|
+
}
|
|
637
|
+
approval_id = "blun-l10n-approval-" + _hash_text(_canonical_json(immutable))
|
|
638
|
+
payload = {
|
|
639
|
+
**immutable,
|
|
640
|
+
"approval_id": approval_id,
|
|
641
|
+
"approved_at": now,
|
|
642
|
+
"expires_at": now + ttl,
|
|
643
|
+
}
|
|
644
|
+
approval_json = _canonical_json(payload)
|
|
645
|
+
approval_bytes = approval_json.encode("utf-8")
|
|
646
|
+
prior = self.connection.execute(
|
|
647
|
+
"SELECT target_sha256 FROM localization_approvals WHERE job_id = ? LIMIT 1",
|
|
648
|
+
(job_id,),
|
|
649
|
+
).fetchone()
|
|
650
|
+
if prior is not None and prior["target_sha256"] != result["target_sha256"]:
|
|
651
|
+
raise LocalizationReleaseBlocked("translation_memory.immutable")
|
|
652
|
+
sign = getattr(authority, "sign", None)
|
|
653
|
+
verify = getattr(authority, "verify", None)
|
|
654
|
+
if not callable(sign) or not callable(verify):
|
|
655
|
+
raise LocalizationReleaseBlocked("approval.authority.invalid")
|
|
656
|
+
try:
|
|
657
|
+
signed = _signature(sign(approval_bytes))
|
|
658
|
+
verified = verify(approval_bytes, signed) is True
|
|
659
|
+
except LocalizationReleaseBlocked:
|
|
660
|
+
raise
|
|
661
|
+
except Exception:
|
|
662
|
+
raise LocalizationReleaseBlocked("approval.signing.failed") from None
|
|
663
|
+
if not verified:
|
|
664
|
+
raise LocalizationReleaseBlocked("approval.signature.rejected")
|
|
665
|
+
|
|
666
|
+
with _transaction(self.connection):
|
|
667
|
+
prior = self.connection.execute(
|
|
668
|
+
"SELECT target_sha256 FROM localization_approvals WHERE job_id = ? LIMIT 1",
|
|
669
|
+
(job_id,),
|
|
670
|
+
).fetchone()
|
|
671
|
+
if prior is not None and prior["target_sha256"] != result["target_sha256"]:
|
|
672
|
+
raise LocalizationReleaseBlocked("translation_memory.immutable")
|
|
673
|
+
existing = self.connection.execute(
|
|
674
|
+
"SELECT * FROM localization_approvals WHERE approval_id = ?",
|
|
675
|
+
(approval_id,),
|
|
676
|
+
).fetchone()
|
|
677
|
+
if existing is None:
|
|
678
|
+
self.connection.execute("""
|
|
679
|
+
INSERT INTO localization_approvals VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
680
|
+
""", (
|
|
681
|
+
approval_id, job_id, result["target_locale"], result["target_sha256"],
|
|
682
|
+
result_json, result_hash, approval_json, _hash_text(approval_json),
|
|
683
|
+
signed.algorithm, signed.key_id, signed.signature, now, now + ttl,
|
|
684
|
+
))
|
|
685
|
+
else:
|
|
686
|
+
return self._approved_from_row(existing, plan, authority, now)
|
|
687
|
+
return self.lookup(plan, job_id, authority, now=now)
|
|
688
|
+
|
|
689
|
+
def _approved_from_row(
|
|
690
|
+
self,
|
|
691
|
+
row: sqlite3.Row,
|
|
692
|
+
plan: Any,
|
|
693
|
+
authority: ApprovalAuthority,
|
|
694
|
+
now: float,
|
|
695
|
+
) -> ApprovedLocalization:
|
|
696
|
+
_, job = _plan_job(plan, row["job_id"])
|
|
697
|
+
approved, _ = self._verified_row(row, job, authority, now)
|
|
698
|
+
return approved
|
|
699
|
+
|
|
700
|
+
def _verified_row(
|
|
701
|
+
self,
|
|
702
|
+
row: sqlite3.Row,
|
|
703
|
+
job: dict[str, Any],
|
|
704
|
+
authority: ApprovalAuthority,
|
|
705
|
+
now: float,
|
|
706
|
+
) -> tuple[ApprovedLocalization, dict[str, Any]]:
|
|
707
|
+
if _hash_text(row["result_json"]) != row["result_sha256"]:
|
|
708
|
+
raise LocalizationReleaseBlocked("translation_memory.result_tampered")
|
|
709
|
+
if _hash_text(row["approval_json"]) != row["approval_sha256"]:
|
|
710
|
+
raise LocalizationReleaseBlocked("approval.payload.tampered")
|
|
711
|
+
try:
|
|
712
|
+
result = json.loads(row["result_json"])
|
|
713
|
+
payload = json.loads(row["approval_json"])
|
|
714
|
+
except json.JSONDecodeError:
|
|
715
|
+
raise LocalizationReleaseBlocked("translation_memory.json.invalid") from None
|
|
716
|
+
result = _validate_result(job, result)
|
|
717
|
+
expected_keys = {
|
|
718
|
+
"schema", "job_id", "source_sha256", "target_sha256", "source_locale",
|
|
719
|
+
"target_locale", "content_type", "glossary_version", "policy_version",
|
|
720
|
+
"provider", "software_version", "worker_schema", "result_sha256",
|
|
721
|
+
"review_confidence",
|
|
722
|
+
"quality_profile",
|
|
723
|
+
"quality_receipt_sha256", "human_review_receipt_sha256",
|
|
724
|
+
"independent_model_review", "approval_id",
|
|
725
|
+
"approved_at", "expires_at",
|
|
726
|
+
}
|
|
727
|
+
if set(payload) != expected_keys:
|
|
728
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
729
|
+
binding = {
|
|
730
|
+
"schema": APPROVAL_SCHEMA,
|
|
731
|
+
"job_id": row["job_id"],
|
|
732
|
+
"source_sha256": result["source_sha256"],
|
|
733
|
+
"target_sha256": result["target_sha256"],
|
|
734
|
+
"source_locale": result["source_locale"],
|
|
735
|
+
"target_locale": result["target_locale"],
|
|
736
|
+
"content_type": result["content_type"],
|
|
737
|
+
"glossary_version": result["glossary_version"],
|
|
738
|
+
"policy_version": result["policy_version"],
|
|
739
|
+
"provider": result["provider"],
|
|
740
|
+
"software_version": result["software_version"],
|
|
741
|
+
"worker_schema": result["worker_schema"],
|
|
742
|
+
"review_confidence": result["review_confidence"],
|
|
743
|
+
"quality_profile": result["quality_profile"],
|
|
744
|
+
"result_sha256": row["result_sha256"],
|
|
745
|
+
"approved_at": row["approved_at"],
|
|
746
|
+
"expires_at": row["expires_at"],
|
|
747
|
+
}
|
|
748
|
+
if any(payload.get(name) != value for name, value in binding.items()):
|
|
749
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
750
|
+
if payload.get("approval_id") != row["approval_id"]:
|
|
751
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
752
|
+
if HEX64.fullmatch(str(payload.get("quality_receipt_sha256"))) is None:
|
|
753
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
754
|
+
human_hash = payload.get("human_review_receipt_sha256")
|
|
755
|
+
independent = payload.get("independent_model_review")
|
|
756
|
+
if not (
|
|
757
|
+
result["human_review_required"] or result["independent_review_required"]
|
|
758
|
+
) and (human_hash is not None or independent is not None):
|
|
759
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
760
|
+
if result["human_review_required"]:
|
|
761
|
+
if HEX64.fullmatch(str(human_hash)) is None or independent is not None:
|
|
762
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
763
|
+
elif result["independent_review_required"]:
|
|
764
|
+
if (human_hash is None) == (independent is None):
|
|
765
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
766
|
+
elif human_hash is not None and HEX64.fullmatch(str(human_hash)) is None:
|
|
767
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
768
|
+
elif independent is not None:
|
|
769
|
+
if not isinstance(independent, dict) or set(independent) != {
|
|
770
|
+
"schema", "provider", "receipt_sha256",
|
|
771
|
+
}:
|
|
772
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
773
|
+
if independent.get("schema") != INDEPENDENT_MODEL_REVIEW_SCHEMA:
|
|
774
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
775
|
+
provider = independent.get("provider")
|
|
776
|
+
if not isinstance(provider, dict) or set(provider) != {"id", "model_id", "model_version"}:
|
|
777
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
778
|
+
if any(not isinstance(value, str) or not value for value in provider.values()):
|
|
779
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
780
|
+
if provider["id"] == result["provider"]["id"]:
|
|
781
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
782
|
+
if HEX64.fullmatch(str(independent.get("receipt_sha256"))) is None:
|
|
783
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
784
|
+
immutable = {
|
|
785
|
+
key: value
|
|
786
|
+
for key, value in payload.items()
|
|
787
|
+
if key not in {"approval_id", "approved_at", "expires_at"}
|
|
788
|
+
}
|
|
789
|
+
expected_id = "blun-l10n-approval-" + _hash_text(_canonical_json(immutable))
|
|
790
|
+
if expected_id != row["approval_id"]:
|
|
791
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
792
|
+
if row["target_locale"] != result["target_locale"] or row["target_sha256"] != result["target_sha256"]:
|
|
793
|
+
raise LocalizationReleaseBlocked("approval.binding_mismatch")
|
|
794
|
+
signature = _signature(ApprovalSignature(
|
|
795
|
+
row["signature_algorithm"], row["key_id"], row["signature"],
|
|
796
|
+
))
|
|
797
|
+
verify = getattr(authority, "verify", None)
|
|
798
|
+
try:
|
|
799
|
+
valid = callable(verify) and verify(row["approval_json"].encode("utf-8"), signature) is True
|
|
800
|
+
except Exception:
|
|
801
|
+
valid = False
|
|
802
|
+
if not valid:
|
|
803
|
+
raise LocalizationReleaseBlocked("approval.signature.invalid")
|
|
804
|
+
if row["expires_at"] <= now:
|
|
805
|
+
raise LocalizationReleaseBlocked("approval.expired")
|
|
806
|
+
return ApprovedLocalization(
|
|
807
|
+
approval_id=row["approval_id"],
|
|
808
|
+
job_id=row["job_id"],
|
|
809
|
+
target_locale=row["target_locale"],
|
|
810
|
+
target_sha256=row["target_sha256"],
|
|
811
|
+
candidate=result["candidate"],
|
|
812
|
+
release_evidence=validate_publication_evidence({
|
|
813
|
+
"schema": PUBLICATION_EVIDENCE_SCHEMA,
|
|
814
|
+
"job_id": row["job_id"],
|
|
815
|
+
"target_locale": row["target_locale"],
|
|
816
|
+
"target_sha256": row["target_sha256"],
|
|
817
|
+
"approval_id": row["approval_id"],
|
|
818
|
+
"content_type": result["content_type"],
|
|
819
|
+
"result_sha256": row["result_sha256"],
|
|
820
|
+
"approval_sha256": row["approval_sha256"],
|
|
821
|
+
"quality_receipt_sha256": payload["quality_receipt_sha256"],
|
|
822
|
+
"commercial_profile": job.get("commercial_profile"),
|
|
823
|
+
"commercial_review": json.loads(
|
|
824
|
+
_canonical_json(result["commercial_review"])
|
|
825
|
+
),
|
|
826
|
+
}),
|
|
827
|
+
approved_at=float(row["approved_at"]),
|
|
828
|
+
expires_at=float(row["expires_at"]),
|
|
829
|
+
), result
|
|
830
|
+
|
|
831
|
+
def verified_result_cache(
|
|
832
|
+
self,
|
|
833
|
+
authority: ApprovalAuthority,
|
|
834
|
+
) -> VerifiedResultCache:
|
|
835
|
+
"""Bind the exact approval verifier for use by a queue runner."""
|
|
836
|
+
return VerifiedResultCache(self, authority)
|
|
837
|
+
|
|
838
|
+
def cached_result(
|
|
839
|
+
self,
|
|
840
|
+
job_payload: dict[str, Any],
|
|
841
|
+
authority: ApprovalAuthority,
|
|
842
|
+
*,
|
|
843
|
+
now: float | int,
|
|
844
|
+
) -> dict[str, Any] | None:
|
|
845
|
+
"""Return an exact verified result, or ``None`` for a clean cache miss."""
|
|
846
|
+
job = _validated_job_payload(job_payload)
|
|
847
|
+
now = _timestamp(now, "approval.time.invalid")
|
|
848
|
+
row = self.connection.execute("""
|
|
849
|
+
SELECT * FROM localization_approvals
|
|
850
|
+
WHERE job_id = ? ORDER BY approved_at DESC, approval_id DESC LIMIT 1
|
|
851
|
+
""", (job["job_id"],)).fetchone()
|
|
852
|
+
if row is None:
|
|
853
|
+
return None
|
|
854
|
+
try:
|
|
855
|
+
_, result = self._verified_row(row, job, authority, now)
|
|
856
|
+
except LocalizationReleaseBlocked as error:
|
|
857
|
+
if error.code == "approval.expired":
|
|
858
|
+
return None
|
|
859
|
+
raise
|
|
860
|
+
return json.loads(_canonical_json(result))
|
|
861
|
+
|
|
862
|
+
def lookup(
|
|
863
|
+
self,
|
|
864
|
+
plan: Any,
|
|
865
|
+
job_id: str,
|
|
866
|
+
authority: ApprovalAuthority,
|
|
867
|
+
*,
|
|
868
|
+
now: float | int,
|
|
869
|
+
) -> ApprovedLocalization:
|
|
870
|
+
job_id = _text(job_id, "job.id.invalid")
|
|
871
|
+
_plan_job(plan, job_id)
|
|
872
|
+
now = _timestamp(now, "approval.time.invalid")
|
|
873
|
+
row = self.connection.execute("""
|
|
874
|
+
SELECT * FROM localization_approvals
|
|
875
|
+
WHERE job_id = ? ORDER BY approved_at DESC, approval_id DESC LIMIT 1
|
|
876
|
+
""", (job_id,)).fetchone()
|
|
877
|
+
if row is None:
|
|
878
|
+
raise LocalizationReleaseBlocked("approval.missing")
|
|
879
|
+
return self._approved_from_row(row, plan, authority, now)
|
|
880
|
+
|
|
881
|
+
def readiness(
|
|
882
|
+
self,
|
|
883
|
+
plan: Any,
|
|
884
|
+
authority: ApprovalAuthority,
|
|
885
|
+
*,
|
|
886
|
+
now: float | int,
|
|
887
|
+
) -> WebsiteReadiness:
|
|
888
|
+
plan_id = _text(getattr(plan, "plan_id", None), "plan.invalid")
|
|
889
|
+
jobs = getattr(plan, "jobs", None)
|
|
890
|
+
if not isinstance(jobs, tuple) or not jobs:
|
|
891
|
+
raise LocalizationReleaseBlocked("plan.invalid")
|
|
892
|
+
now = _timestamp(now, "approval.time.invalid")
|
|
893
|
+
required = tuple(sorted(job.as_payload()["target"]["locale"] for job in jobs))
|
|
894
|
+
approved: list[str] = []
|
|
895
|
+
blocked: list[tuple[str, str]] = []
|
|
896
|
+
for job in jobs:
|
|
897
|
+
locale = job.as_payload()["target"]["locale"]
|
|
898
|
+
try:
|
|
899
|
+
self.lookup(plan, job.job_id, authority, now=now)
|
|
900
|
+
except LocalizationReleaseBlocked as error:
|
|
901
|
+
blocked.append((locale, error.code))
|
|
902
|
+
else:
|
|
903
|
+
approved.append(locale)
|
|
904
|
+
return WebsiteReadiness(
|
|
905
|
+
plan_id=plan_id,
|
|
906
|
+
ready=not blocked and tuple(sorted(approved)) == required,
|
|
907
|
+
required_locales=required,
|
|
908
|
+
approved_locales=tuple(sorted(approved)),
|
|
909
|
+
blocked=tuple(sorted(blocked)),
|
|
910
|
+
)
|
|
911
|
+
|
|
912
|
+
def publication_bundle(
|
|
913
|
+
self,
|
|
914
|
+
plan: Any,
|
|
915
|
+
authority: ApprovalAuthority,
|
|
916
|
+
*,
|
|
917
|
+
now: float | int,
|
|
918
|
+
) -> tuple[ApprovedLocalization, ...]:
|
|
919
|
+
status = self.readiness(plan, authority, now=now)
|
|
920
|
+
if not status.ready:
|
|
921
|
+
raise LocalizationReleaseBlocked("website.not_ready")
|
|
922
|
+
return tuple(sorted(
|
|
923
|
+
(
|
|
924
|
+
self.lookup(plan, job.job_id, authority, now=now)
|
|
925
|
+
for job in plan.jobs
|
|
926
|
+
),
|
|
927
|
+
key=lambda item: item.target_locale,
|
|
928
|
+
))
|