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,1302 @@
|
|
|
1
|
+
# CMS localization webhook API v2
|
|
2
|
+
|
|
3
|
+
`WebsiteLocalizationAPI` is the provider-neutral WSGI ingress exposed by the
|
|
4
|
+
composed website-localization runtime. It accepts a signed current CMS change,
|
|
5
|
+
cancellation, or published-content tombstone, durably enqueues one job per
|
|
6
|
+
required locale, and returns
|
|
7
|
+
content-free capabilities, queue progress, or end-to-end lifecycle status. It
|
|
8
|
+
never calls a model, approves a translation, prepares a publication, or returns
|
|
9
|
+
source or target prose.
|
|
10
|
+
|
|
11
|
+
## Host setup
|
|
12
|
+
|
|
13
|
+
Use `runtime.cms_api`, or construct `WebsiteLocalizationAPI` with the exact CMS
|
|
14
|
+
bridge, event verifier, clock, and queue attempt limit. Mount the callable behind
|
|
15
|
+
a production WSGI server and TLS terminator. HTTPS is required by default. Only
|
|
16
|
+
a trusted proxy may derive `wsgi.url_scheme`; never trust an arbitrary forwarded
|
|
17
|
+
header. The host owns keys, credential-to-site provisioning, network access,
|
|
18
|
+
rate limits, timeouts, database backup, and request-log redaction.
|
|
19
|
+
|
|
20
|
+
This tenant-facing API deliberately does not expose service-wide health. An
|
|
21
|
+
operator can enable the separately authenticated `runtime.health_http` reader
|
|
22
|
+
described in
|
|
23
|
+
[`WEBSITE_LOCALIZATION_HEALTH_HTTP.md`](WEBSITE_LOCALIZATION_HEALTH_HTTP.md).
|
|
24
|
+
|
|
25
|
+
`require_https=False` is only for an authenticated loopback or test transport.
|
|
26
|
+
The application rejects query strings, transfer encoding, ambiguous JSON,
|
|
27
|
+
invalid UTF-8, a UTF-8 BOM, non-finite numbers, unknown fields, missing or
|
|
28
|
+
truncated lengths, and bodies over 4,000,000 bytes. The front server remains
|
|
29
|
+
responsible for unambiguous HTTP framing and request-smuggling protection.
|
|
30
|
+
|
|
31
|
+
## Source-side reference client
|
|
32
|
+
|
|
33
|
+
`CMSLocalizationHTTPClient` in
|
|
34
|
+
`integrations/website_localization_cms_client.py` implements the sending side
|
|
35
|
+
for all six routes. Configure one origin containing only an HTTPS scheme and
|
|
36
|
+
authority, a callback that supplies host-owned authentication headers, and an
|
|
37
|
+
authority whose `sign(bytes)` method signs the exact canonical request bytes.
|
|
38
|
+
Credentials in the URL, base paths, query strings, fragments, redirects, and
|
|
39
|
+
authentication headers that collide with protocol headers are rejected.
|
|
40
|
+
|
|
41
|
+
Each method performs one HTTP attempt. The client does not retry: the CMS host
|
|
42
|
+
must decide whether and when to repeat an immutable event, cancellation,
|
|
43
|
+
tombstone, or short-lived read request. `submit_change`, `cancel`, and
|
|
44
|
+
`request_tombstone` accept complete versioned objects; `status`, `lifecycle`,
|
|
45
|
+
and `capabilities` create fresh signed read requests from the configured clock
|
|
46
|
+
and request-ID source. Caller mappings are copied through canonical
|
|
47
|
+
native-Unicode JSON before signing and are never mutated.
|
|
48
|
+
|
|
49
|
+
Successful responses require canonical bounded JSON, an allowed status code,
|
|
50
|
+
the exact response schema, and the originating request, event, and site
|
|
51
|
+
bindings. Capability responses additionally recompute both advertised hashes
|
|
52
|
+
and verify the exact ordered operation names, methods, paths, request schemas,
|
|
53
|
+
response schemas, and enabled booleans. Rehashing a substituted endpoint is
|
|
54
|
+
therefore insufficient. Stable server failures retain their content-free error
|
|
55
|
+
code and derive retryability from HTTP status; redirects and invalid bindings
|
|
56
|
+
fail closed.
|
|
57
|
+
|
|
58
|
+
### Durable change dispatch
|
|
59
|
+
|
|
60
|
+
`DurableCMSChangeDispatcher` in
|
|
61
|
+
`integrations/website_localization_cms_dispatch.py` is the optional persistent
|
|
62
|
+
outbox in front of `CMSLocalizationHTTPClient.submit_change`. The host supplies
|
|
63
|
+
and secures a SQLite connection, enqueues the complete versioned change before
|
|
64
|
+
returning from its content-change handler, and runs `run_once` from a supervised
|
|
65
|
+
worker. Different worker processes must use different connections to the same
|
|
66
|
+
database.
|
|
67
|
+
|
|
68
|
+
The outbox binds `event_id` to canonical native-Unicode payload bytes, their
|
|
69
|
+
SHA-256, and a fixed attempt ceiling. An exact enqueue is idempotent; changing
|
|
70
|
+
the event, payload, or attempt policy under the same ID is a collision. Claiming
|
|
71
|
+
is transactional, carries an expiring worker token, and permits one HTTPS call.
|
|
72
|
+
The lease must outlive the configured client timeout. Retryable client failures
|
|
73
|
+
use capped exponential backoff; permanent failures and the attempt ceiling are
|
|
74
|
+
terminal. A lease lost after remote acceptance replays the same event after
|
|
75
|
+
expiry, relying on the server's existing exact event idempotency instead of
|
|
76
|
+
inventing a second identity.
|
|
77
|
+
|
|
78
|
+
`status` and `health` are content-free. They return event and payload hashes,
|
|
79
|
+
attempt state, stable error codes, remote acknowledgement metadata, counts, and
|
|
80
|
+
expired-lease indicators, never source text, credentials, signatures, or raw
|
|
81
|
+
transport errors. Schema, payload, hash, lease, acknowledgement, and state
|
|
82
|
+
inconsistencies block rather than being repaired optimistically.
|
|
83
|
+
|
|
84
|
+
### Durable lifecycle monitoring
|
|
85
|
+
|
|
86
|
+
`DurableCMSLifecycleMonitor` in
|
|
87
|
+
`integrations/website_localization_cms_lifecycle_monitor.py` closes the
|
|
88
|
+
source-side loop after a successful change dispatch. Registration binds the
|
|
89
|
+
original canonical change hash to the returned event, site, plan, website
|
|
90
|
+
version, source sequence, and exact job count in a separate canonical binding
|
|
91
|
+
hash. A changed generation or a
|
|
92
|
+
dispatch that has not succeeded cannot be registered under the same event.
|
|
93
|
+
|
|
94
|
+
Each leased attempt invokes `CMSLocalizationHTTPClient.lifecycle` without a
|
|
95
|
+
caller-supplied request ID. The secure client therefore creates and signs a
|
|
96
|
+
fresh, purpose-bound request for every poll; an expired read request is never
|
|
97
|
+
stored for replay. Verified nonterminal states are polled again at the fixed
|
|
98
|
+
monitor interval. Retryable transport or service failures use capped
|
|
99
|
+
exponential backoff and a fixed consecutive-failure ceiling, while a successful
|
|
100
|
+
poll resets that failure streak. Permanent errors, malformed or
|
|
101
|
+
generation-mismatched responses, exhausted failures, and corrupted state stop
|
|
102
|
+
the event fail-closed.
|
|
103
|
+
|
|
104
|
+
Transactional expiring leases coordinate separate process connections and
|
|
105
|
+
recover a poll abandoned by a crashed worker. The monitor stores no source or
|
|
106
|
+
target text, credentials, signatures, or response prose. Its durable snapshot
|
|
107
|
+
contains only identities, locale names, counts, stable error codes, delivery
|
|
108
|
+
state, and the canonical lifecycle-response hash. `status` distinguishes
|
|
109
|
+
ongoing observation from verified terminal states; `health` remains blocked
|
|
110
|
+
for local monitor failure, expired ownership, or terminal localization,
|
|
111
|
+
publication, or deletion failure. A change already acknowledged as cancelled
|
|
112
|
+
or superseded becomes terminal without an unnecessary lifecycle request.
|
|
113
|
+
|
|
114
|
+
### Durable cancellation and tombstone dispatch
|
|
115
|
+
|
|
116
|
+
`DurableCMSRemovalDispatcher` in
|
|
117
|
+
`integrations/website_localization_cms_removal_dispatch.py` is the persistent
|
|
118
|
+
source-side outbox for removal. Enqueue the complete cancellation when content
|
|
119
|
+
must stop before publication, or the complete tombstone only when an
|
|
120
|
+
acknowledged publication must be deleted. The dispatcher preserves these as
|
|
121
|
+
distinct operations and invokes `CMSLocalizationHTTPClient.cancel` or
|
|
122
|
+
`request_tombstone` accordingly; it never guesses which removal phase applies.
|
|
123
|
+
|
|
124
|
+
The durable identity is `(operation, cancellation_id)` or
|
|
125
|
+
`(operation, tombstone_id)`, bound to the event, complete canonical request
|
|
126
|
+
bytes, SHA-256, and fixed attempt ceiling. Exact replay is idempotent; changed
|
|
127
|
+
website version, event, source ID, sequence, payload, or retry policy under that
|
|
128
|
+
identity is rejected. Transactional leases coordinate separate worker
|
|
129
|
+
connections. A lost lease after remote acceptance repeats the same immutable
|
|
130
|
+
request, so the service's cancellation or tombstone ledger converges without a
|
|
131
|
+
second logical deletion.
|
|
132
|
+
|
|
133
|
+
One `run_once` performs exactly one secure client operation. Retryable failures
|
|
134
|
+
enter capped exponential backoff, permanent failures and exhausted attempts
|
|
135
|
+
become terminal, and malformed acknowledgements fail closed. Content-free
|
|
136
|
+
status and health retain only operation/request/event IDs, hashes, attempt
|
|
137
|
+
state, stable errors, remote delivery identity and status, counts, and lease
|
|
138
|
+
times. They retain no website text, credentials, signatures, or private error
|
|
139
|
+
detail. Database, payload, claim, or response inconsistency blocks before a
|
|
140
|
+
network call.
|
|
141
|
+
|
|
142
|
+
### Coordinated source-CMS service
|
|
143
|
+
|
|
144
|
+
`CMSLocalizationSourceService` in
|
|
145
|
+
`integrations/website_localization_cms_source_service.py` composes the change
|
|
146
|
+
outbox, removal outbox, and lifecycle monitor into the complete source-side
|
|
147
|
+
worker. The host supplies three distinct durable SQLite connections, one
|
|
148
|
+
configured `CMSLocalizationHTTPClient`, stable worker identities, and a clock.
|
|
149
|
+
`enqueue_change` and `enqueue_removal` persist an immutable request before the
|
|
150
|
+
host acknowledges its own content change. `run_once` advances at most one
|
|
151
|
+
network operation; `run_forever` adds bounded active, idle, and blocked sleeps
|
|
152
|
+
without taking ownership of process signals or database connections.
|
|
153
|
+
|
|
154
|
+
The order is deliberate: due cancellations and tombstones run first, then a
|
|
155
|
+
locally missing lifecycle registration is repaired, then one new change is
|
|
156
|
+
sent, and only then is one lifecycle read performed. After a successful change
|
|
157
|
+
response, the service registers its exact event, plan, job count, website
|
|
158
|
+
generation, and canonical payload hash immediately. If the process exits after
|
|
159
|
+
remote acceptance or after the separate registration commit, the next tick
|
|
160
|
+
reconciles the two durable stores idempotently before any further change or
|
|
161
|
+
lifecycle network call. It never invents a new event identity or treats an
|
|
162
|
+
unregistered acknowledgement as completed monitoring.
|
|
163
|
+
|
|
164
|
+
The three underlying token-bound leases remain the concurrency authority, so
|
|
165
|
+
multiple supervised processes may use separate connections to the same three
|
|
166
|
+
database files. The service validates all dependencies, lease/timeout ordering,
|
|
167
|
+
worker IDs, connections, and delays before creating schemas. A conflicting
|
|
168
|
+
dispatch-to-lifecycle binding blocks the whole tick before network access.
|
|
169
|
+
`health` verifies all three stores plus every successful dispatch registration
|
|
170
|
+
and returns only counts, stable codes, and a pending-registration count. An
|
|
171
|
+
acknowledged change awaiting that local handoff is explicitly `degraded`; a
|
|
172
|
+
store failure or conflicting binding is `blocked`. Tick and health payloads
|
|
173
|
+
never contain website text, provider responses,
|
|
174
|
+
credentials, signatures, or exception messages.
|
|
175
|
+
|
|
176
|
+
Premortem: an acknowledgement can be lost between two local commits, a removal
|
|
177
|
+
can be starved by ordinary changes, a retry can duplicate logical work, or an
|
|
178
|
+
exception can leak customer prose. Registration reconciliation, removal-first
|
|
179
|
+
scheduling, the existing immutable IDs and leases, one external operation per
|
|
180
|
+
tick, and stable error reduction close those paths. Regression tests cover both
|
|
181
|
+
crash boundaries, restart recovery without resend, operation priority,
|
|
182
|
+
binding tampering, private exception redaction, health, loop delays, and
|
|
183
|
+
pre-schema configuration rejection.
|
|
184
|
+
|
|
185
|
+
### Durable source-CMS runtime
|
|
186
|
+
|
|
187
|
+
`open_durable_cms_source` in
|
|
188
|
+
`integrations/website_localization_cms_source_runtime.py` is the production
|
|
189
|
+
composition root for the coordinated source service. The host supplies three
|
|
190
|
+
canonical absolute SQLite paths, one already configured provider-neutral CMS
|
|
191
|
+
HTTP client, and stable worker IDs. The runtime validates the entire service
|
|
192
|
+
configuration in memory before it creates any persistent schema, opens and
|
|
193
|
+
owns three separate connections, and exposes the same `enqueue_change`,
|
|
194
|
+
`enqueue_removal`, `run_once`, `run_forever`, and `health` lifecycle through a
|
|
195
|
+
single process-bound object. It is a context manager and repeated `close` is
|
|
196
|
+
safe; all later work blocks after close.
|
|
197
|
+
|
|
198
|
+
Each filesystem database must be a regular owner-owned file with exactly one
|
|
199
|
+
link and mode `0600` beneath a private, non-aliased directory chain. URI paths,
|
|
200
|
+
relative paths, path reuse, symlinks, hard links, permissive files or parents,
|
|
201
|
+
and path identity replacement fail closed. The runtime rechecks all three
|
|
202
|
+
identities before and after every state transition, serializes access from
|
|
203
|
+
threads inside one process, and checks process ownership before acquiring its
|
|
204
|
+
lock. A pre-fork copy therefore cannot inherit SQLite connections or a locked
|
|
205
|
+
thread state. Multi-process supervisors instead construct one runtime after
|
|
206
|
+
each fork; independent connections then coordinate through the existing
|
|
207
|
+
transactional leases.
|
|
208
|
+
|
|
209
|
+
The runtime deliberately does not read environment variables, credentials, or
|
|
210
|
+
configuration files and does not own process signals. Authentication and the
|
|
211
|
+
request-signing authority stay inside the supplied `CMSLocalizationHTTPClient`;
|
|
212
|
+
their values, database paths, website content, and private exception messages
|
|
213
|
+
never enter the runtime representation or stable failure codes. A deployment
|
|
214
|
+
may therefore choose its own secret manager and supervisor without weakening
|
|
215
|
+
the provider-neutral contract.
|
|
216
|
+
|
|
217
|
+
### Durable terminal notification callback
|
|
218
|
+
|
|
219
|
+
Polling remains sufficient, but a deployment can pass `terminal_notifier` and
|
|
220
|
+
`notification_worker_id` to `open_durable_cms_source` or
|
|
221
|
+
`open_hosted_cms_source`. Both values are required together. After the durable
|
|
222
|
+
lifecycle monitor verifies a terminal state, the service first stores one
|
|
223
|
+
`blun.cms-source-terminal-notification.v1` object in the same protected
|
|
224
|
+
lifecycle database. It contains only:
|
|
225
|
+
|
|
226
|
+
- `notification_id`, `event_id`, `site_id`, and `plan_id`;
|
|
227
|
+
- `website_version`, `source_sequence`, and `job_count`;
|
|
228
|
+
- `change_sha256` and `lifecycle_binding_sha256`;
|
|
229
|
+
- `terminal_status` and `lifecycle_sha256`.
|
|
230
|
+
|
|
231
|
+
`lifecycle_sha256` is present for a signed lifecycle response and is `null`
|
|
232
|
+
only when the original dispatch acknowledgement already made `cancelled` or
|
|
233
|
+
`superseded` terminal. Source text, target text, locale prose, credentials,
|
|
234
|
+
provider responses, and private errors are never copied into this callback.
|
|
235
|
+
|
|
236
|
+
The host callback must atomically and idempotently record the notification and
|
|
237
|
+
return exactly:
|
|
238
|
+
|
|
239
|
+
```json
|
|
240
|
+
{
|
|
241
|
+
"schema": "blun.cms-source-terminal-notification-ack.v1",
|
|
242
|
+
"notification_id": "terminal-<sha256>",
|
|
243
|
+
"event_id": "cms-event-184",
|
|
244
|
+
"site_id": "public-site",
|
|
245
|
+
"status": "accepted",
|
|
246
|
+
"notification_sha256": "<sha256>"
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Returning normally with any other object is a terminal protocol failure. A
|
|
251
|
+
callback may raise `TerminalNotificationFailure(code, retryable=True)` only
|
|
252
|
+
for a content-free transient condition; retries use the same immutable
|
|
253
|
+
notification identity, an expiring lease, bounded exponential backoff, and
|
|
254
|
+
the configured `max_notification_attempts`. Unknown exceptions are reduced to
|
|
255
|
+
`terminal_notification.callback_failure` and fail closed. Notification health
|
|
256
|
+
is included in aggregate service health and therefore in hosted readiness.
|
|
257
|
+
Disabling the optional notifier preserves the existing authenticated status
|
|
258
|
+
polling contract and does not create notification state.
|
|
259
|
+
|
|
260
|
+
For a remote backend, pass an
|
|
261
|
+
`HTTPTerminalNotifierAdapter` from
|
|
262
|
+
`integrations/website_localization_cms_terminal_notification_http.py` as the
|
|
263
|
+
`terminal_notifier`. It sends the canonical notification object itself as the
|
|
264
|
+
request body to one configured HTTPS URL. Loopback HTTP is available only by
|
|
265
|
+
explicit test/development opt-in. Redirects are never followed, and the adapter
|
|
266
|
+
performs exactly one transport attempt; the durable notification outbox alone
|
|
267
|
+
decides whether and when to retry.
|
|
268
|
+
|
|
269
|
+
Each request reserves these exact transport headers:
|
|
270
|
+
|
|
271
|
+
- `Content-Type: application/json; charset=utf-8`;
|
|
272
|
+
- `Accept: application/json`;
|
|
273
|
+
- `Idempotency-Key: <notification_id>`;
|
|
274
|
+
- `X-Localization-Terminal-Notification-Id: <notification_id>`;
|
|
275
|
+
- `X-Localization-Terminal-Notification-Sha256: <body_sha256>`.
|
|
276
|
+
|
|
277
|
+
The host-supplied authentication callback receives a fresh copy of this
|
|
278
|
+
content-free request before any network access:
|
|
279
|
+
|
|
280
|
+
```json
|
|
281
|
+
{
|
|
282
|
+
"schema": "blun.cms-source-terminal-notification-http-auth.v1",
|
|
283
|
+
"method": "POST",
|
|
284
|
+
"origin": "https://cms.example.test",
|
|
285
|
+
"path": "/v1/localization/terminal-notifications",
|
|
286
|
+
"notification_id": "terminal-<sha256>",
|
|
287
|
+
"event_id": "cms-event-184",
|
|
288
|
+
"site_id": "public-site",
|
|
289
|
+
"body_sha256": "<sha256 of exact canonical request bytes>"
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
It may return deployment-specific authentication or signature headers, but it
|
|
294
|
+
cannot replace reserved framing, idempotency, or binding headers. Empty,
|
|
295
|
+
duplicated, injected, oversized, or reserved authentication headers block
|
|
296
|
+
before transport. The receiver returns the exact acknowledgement documented
|
|
297
|
+
above with HTTP `200` and JSON UTF-8 content type. A `3xx` response is a
|
|
298
|
+
permanent redirect failure; `408`, `425`, `429`, and `5xx` statuses plus network
|
|
299
|
+
failures are retryable by the outbox. Other statuses and malformed or
|
|
300
|
+
cross-bound successful responses are permanent protocol failures. Response
|
|
301
|
+
bodies and private exceptions are never copied into durable error state.
|
|
302
|
+
|
|
303
|
+
#### Reference terminal-notification receiver
|
|
304
|
+
|
|
305
|
+
`integrations/website_localization_cms_terminal_notification_receiver.py`
|
|
306
|
+
provides the matching provider-neutral WSGI endpoint. Construct one
|
|
307
|
+
`DurableCMSTerminalNotificationInbox` around a worker-owned SQLite connection,
|
|
308
|
+
then mount `CMSTerminalNotificationReceiverApplication` at the configured path.
|
|
309
|
+
For a multithreaded WSGI worker, open that connection with
|
|
310
|
+
`check_same_thread=False`; the inbox serializes access. Do not construct it before
|
|
311
|
+
forking: its process binding deliberately blocks inherited connections.
|
|
312
|
+
|
|
313
|
+
The receiver accepts only canonical UTF-8 JSON sent over HTTPS with the exact
|
|
314
|
+
content type, content length, idempotency key, notification ID, and notification
|
|
315
|
+
SHA-256 headers documented above. Before storage, it calls the host verifier as
|
|
316
|
+
`authenticate(authentication_context, normalized_headers)`. The first argument
|
|
317
|
+
is exactly the content-free object supplied to the sender's authentication-header
|
|
318
|
+
provider. The verifier must return:
|
|
319
|
+
|
|
320
|
+
```json
|
|
321
|
+
{
|
|
322
|
+
"schema": "blun.cms-source-terminal-notification-principal.v1",
|
|
323
|
+
"principal_id": "website-cms",
|
|
324
|
+
"credential_id": "cms-key",
|
|
325
|
+
"credential_version": "v1",
|
|
326
|
+
"scope": "terminal-notification:write",
|
|
327
|
+
"site_id": "public-site"
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
The returned `site_id` must equal the notification site. The receiver stores the
|
|
332
|
+
exact body and its hash in one SQLite transaction before returning the existing
|
|
333
|
+
`blun.cms-source-terminal-notification-ack.v1` acknowledgement. An exact replay
|
|
334
|
+
returns that same acknowledgement and preserves the original `received_at`.
|
|
335
|
+
Changed bytes under an existing event, notification, or payload identity return
|
|
336
|
+
HTTP `409`; malformed or wrongly bound requests are permanent `4xx` failures,
|
|
337
|
+
while unavailable authentication, damaged storage, and unexpected internal
|
|
338
|
+
failures return retryable `503`. Every error body is content free.
|
|
339
|
+
|
|
340
|
+
For deployment, prefer
|
|
341
|
+
`integrations/website_localization_cms_terminal_notification_receiver_runtime.py`.
|
|
342
|
+
Its `open_durable_terminal_notification_receiver` factory takes a canonical
|
|
343
|
+
absolute database path, the host verifier, exact HTTPS origin, and optional path
|
|
344
|
+
and clock. It validates the full HTTP boundary before opening SQLite, creates a
|
|
345
|
+
missing file exclusively with mode `0600`, and rejects unsafe parents, symlinks,
|
|
346
|
+
hard links, special files, permissive modes, and path replacement. Construct one
|
|
347
|
+
runtime after each WSGI worker forks; inherited runtimes report
|
|
348
|
+
`foreign-process` and block without entering a possibly inherited lock.
|
|
349
|
+
|
|
350
|
+
The runtime itself is the WSGI application. It serializes request execution with
|
|
351
|
+
close, rechecks the pinned database identity before every operation, and owns the
|
|
352
|
+
connection until `close()` or context-manager exit. `status(event_id)` returns
|
|
353
|
+
only the verified content-free receipt binding. `health()` checks SQLite
|
|
354
|
+
integrity and every stored notification and processing row, then returns only
|
|
355
|
+
content-free status, runtime state, counts, due work, expired leases, and
|
|
356
|
+
terminal failures. Closed, exchanged, damaged, or foreign-process runtimes
|
|
357
|
+
return content-free HTTP `503` and require a new worker runtime; they never
|
|
358
|
+
attempt repair or invent acknowledgement state.
|
|
359
|
+
|
|
360
|
+
#### Durable CMS-side processing
|
|
361
|
+
|
|
362
|
+
Schema V2 of the receiver inbox creates one processing row in the same
|
|
363
|
+
transaction as every newly received notification. The HTTP acknowledgement is
|
|
364
|
+
therefore impossible unless both the immutable receipt and its discoverable
|
|
365
|
+
host work item have committed. When a V1 database opens, the receiver validates
|
|
366
|
+
the exact old schema and every stored notification, creates the processing
|
|
367
|
+
ledger, backfills one pending row per receipt, and advances the schema version
|
|
368
|
+
inside one transaction. Any altered input rolls the migration back.
|
|
369
|
+
|
|
370
|
+
Call `runtime.process_next(callback, worker_id)` to advance at most one due
|
|
371
|
+
notification. The callback receives a fresh mapping of the exact content-free
|
|
372
|
+
terminal notification and must return:
|
|
373
|
+
|
|
374
|
+
```json
|
|
375
|
+
{"event_id":"cms-event-184","notification_id":"terminal-…","notification_sha256":"…","schema":"blun.cms-terminal-notification-processing-ack.v1","site_id":"public-site","status":"processed"}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The claim binds the worker ID, random lease token, attempt number, deadline,
|
|
379
|
+
notification identity, event, site, terminal status, and exact payload hash.
|
|
380
|
+
Completion revalidates those bindings and rejects expired or replaced claims.
|
|
381
|
+
A host may raise `TerminalNotificationProcessingFailure` with one stable error
|
|
382
|
+
code and an explicit retry decision. Retryable failures wait using the
|
|
383
|
+
configured bounded exponential delay; permanent failures and exhausted attempts
|
|
384
|
+
remain durably failed and make health `blocked`. Unexpected exceptions are
|
|
385
|
+
reduced to `processing_callback_failure`; their messages are never stored.
|
|
386
|
+
|
|
387
|
+
The runtime serializes processing with request handling and shutdown. A crash
|
|
388
|
+
leaves the lease durable; the next worker recovers it after expiry and a stale
|
|
389
|
+
owner cannot complete it. Consequently, the host callback must apply its own
|
|
390
|
+
state change idempotently under `notification_id`: a crash after the host commit
|
|
391
|
+
but before the processing completion commit deliberately repeats the same exact
|
|
392
|
+
notification. Runtime health now includes content-free processing counts, due
|
|
393
|
+
work, expired leases, and terminal failures.
|
|
394
|
+
|
|
395
|
+
For a single-process WSGI deployment, use
|
|
396
|
+
`open_hosted_durable_terminal_notification_receiver`. It preflights the handler,
|
|
397
|
+
worker ID, lease, and all loop delays before creating the database, then starts
|
|
398
|
+
one process-owned, non-daemon worker. The runtime accepts HTTP notifications
|
|
399
|
+
only while that managed worker is running and the inbox remains healthy.
|
|
400
|
+
`worker_readiness()` returns the content-free
|
|
401
|
+
`blun.cms-terminal-receiver-readiness.v1` object; supervisors must remove the
|
|
402
|
+
instance from service whenever its status is `not_ready`.
|
|
403
|
+
|
|
404
|
+
Idle, active, and blocked outcomes use separate interruptible delays. A private
|
|
405
|
+
loop exception becomes `notification_receiver.worker_blocked`, makes readiness
|
|
406
|
+
fail, and prevents the same in-memory runtime from restarting. `close()` first
|
|
407
|
+
signals and joins the worker and closes SQLite only after the callback returns.
|
|
408
|
+
If the configured join timeout expires, close fails while durable state remains
|
|
409
|
+
open; the supervisor must resolve or terminate the stuck callback before trying
|
|
410
|
+
again. Construct the hosted runtime after every process fork.
|
|
411
|
+
|
|
412
|
+
#### Terminal-receiver status, health, readiness, and capabilities
|
|
413
|
+
|
|
414
|
+
The durable runtime also serves four authenticated, content-free control routes
|
|
415
|
+
on the same exact HTTPS origin:
|
|
416
|
+
|
|
417
|
+
| Method | Path | Required scope | Purpose |
|
|
418
|
+
| --- | --- | --- | --- |
|
|
419
|
+
| `POST` | `/v1/localization/terminal-notifications/status` | `terminal-notification-status:read` | Read one site's durable processing state |
|
|
420
|
+
| `GET` | `/v1/localization/terminal-notifications/health` | `terminal-notification-health:read` | Inspect aggregate runtime, worker, and durable inbox health |
|
|
421
|
+
| `GET` | `/v1/localization/terminal-notifications/readiness` | `terminal-notification-readiness:read` | Check the managed worker and verified inbox |
|
|
422
|
+
| `GET` | `/v1/localization/terminal-notifications/capabilities` | `terminal-notification-capabilities:read` | Discover the exact active receiver contract |
|
|
423
|
+
|
|
424
|
+
Status accepts only canonical UTF-8 JSON and requires
|
|
425
|
+
`X-Localization-Terminal-Status-SHA256` to equal the exact body hash:
|
|
426
|
+
|
|
427
|
+
```json
|
|
428
|
+
{"event_id":"cms-event-184","schema":"blun.cms-terminal-receiver-status-request.v1","site_id":"public-site"}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Authentication receives the method, origin, path, event, site, and exact body
|
|
432
|
+
hash. The returned principal must use
|
|
433
|
+
`blun.cms-source-terminal-notification-principal.v1`, the dedicated status
|
|
434
|
+
scope, and the same `site_id`. A valid response uses
|
|
435
|
+
`blun.cms-terminal-receiver-status-response.v1` and contains only immutable
|
|
436
|
+
notification bindings plus processing status, attempt limits, lease timing,
|
|
437
|
+
stable error code, and completion time. It never includes the stored payload or
|
|
438
|
+
website text. A missing event and another site's event return the same
|
|
439
|
+
content-free `404` response.
|
|
440
|
+
|
|
441
|
+
Readiness is strictly `GET`, body-free, query-free, and separately scoped. Its
|
|
442
|
+
`blun.cms-terminal-receiver-readiness.v1` response contains only `status`,
|
|
443
|
+
`worker_state`, `inbox_status`, and `error_code`. HTTP `200` requires a running
|
|
444
|
+
managed worker and verified `ok` inbox; all other states return `503`. None
|
|
445
|
+
of these control routes claims a lease, invokes the host handler, changes retry
|
|
446
|
+
state, or performs a model call.
|
|
447
|
+
|
|
448
|
+
Health is also strictly `GET`, body-free, query-free, and separately scoped.
|
|
449
|
+
Its `blun.cms-terminal-receiver-health.v1` response combines `runtime_state`,
|
|
450
|
+
`worker_state`, verified `inbox_status`, received and per-state processing
|
|
451
|
+
counts, due work, expired leases, terminal failures, and one stable error code.
|
|
452
|
+
HTTP `200` requires an open runtime, a running or deliberately unmanaged worker,
|
|
453
|
+
and an `ok` inbox. A stopped or failed managed worker, failed processing record,
|
|
454
|
+
expired lease, unreadable health state, or damaged inbox returns content-free
|
|
455
|
+
`503`. Unavailable counts are `null`; the receiver never invents a healthy
|
|
456
|
+
snapshot from partial evidence. Authentication completes before SQLite health
|
|
457
|
+
inspection, and the read does not claim, retry, complete, or otherwise mutate
|
|
458
|
+
processing state.
|
|
459
|
+
|
|
460
|
+
Discovery is also strictly `GET`, body-free, query-free, and separately scoped.
|
|
461
|
+
Its `blun.cms-terminal-receiver-capabilities-response.v1` envelope contains a
|
|
462
|
+
`blun.cms-terminal-receiver-capabilities.v1` contract and canonical SHA-256.
|
|
463
|
+
The digest covers all active operations, including the runtime's configured
|
|
464
|
+
notification intake path, plus methods, scopes, request and response schemas,
|
|
465
|
+
required fields, success statuses, transport limits, processing states, and
|
|
466
|
+
terminal outcomes. It contains no site, endpoint origin, notification,
|
|
467
|
+
credential, website text, provider response, or private error detail. The
|
|
468
|
+
contract includes the health operation's exact method, path, distinct scope,
|
|
469
|
+
schema, fields, and success status.
|
|
470
|
+
|
|
471
|
+
The capability request authenticates the exact empty-body hash before the
|
|
472
|
+
contract is built. It never reads the inbox, checks worker readiness, claims a
|
|
473
|
+
lease, or calls a handler. A custom intake path that collides with any control
|
|
474
|
+
route is rejected before SQLite is created. Missing or altered notification
|
|
475
|
+
schema fields, reused scopes, a request body, content type, query, wrong method,
|
|
476
|
+
or private authenticator failure returns a content-free fail-closed response.
|
|
477
|
+
|
|
478
|
+
#### Contract-pinned operator client
|
|
479
|
+
|
|
480
|
+
`integrations/website_localization_cms_terminal_receiver_client.py` provides a
|
|
481
|
+
provider-neutral HTTPS client for the complete receiver contract. Construct
|
|
482
|
+
`HTTPTerminalReceiverClient` with the receiver's exact origin, an
|
|
483
|
+
`expected_capabilities_sha256` obtained through trusted deployment
|
|
484
|
+
configuration, and a callback that supplies authentication headers for the
|
|
485
|
+
provided canonical request context. Do not learn and trust the digest from the
|
|
486
|
+
same untrusted connection that it is intended to authenticate.
|
|
487
|
+
|
|
488
|
+
`capabilities()` verifies the response envelope, the canonical digest, every
|
|
489
|
+
schema and limit, all five operation definitions, distinct scopes, and the
|
|
490
|
+
configured notification path. `health()`, `readiness()`, and `status()` first
|
|
491
|
+
repeat that live contract verification; a contract change therefore blocks the
|
|
492
|
+
operational read until the deployment deliberately updates its pin. Every HTTP
|
|
493
|
+
request is separately authenticated and uses exactly one bounded transport
|
|
494
|
+
attempt with no redirects.
|
|
495
|
+
|
|
496
|
+
`notify()` validates the complete immutable terminal notification before any
|
|
497
|
+
network call, repeats the pinned discovery, and sends the canonical bytes to
|
|
498
|
+
the notification path taken from that verified live contract. The client is
|
|
499
|
+
callable, so the same instance can be passed directly as `terminal_notifier` to
|
|
500
|
+
`open_durable_cms_source` or `open_hosted_cms_source`; no separately configured
|
|
501
|
+
write adapter or path is required. Its authentication context binds the exact
|
|
502
|
+
method, origin, verified path, body SHA-256, notification ID, event, and site.
|
|
503
|
+
The client owns no retry loop: `TerminalReceiverClientBlocked` advertises
|
|
504
|
+
`cms_notification_failure` plus stable retryability, allowing only the durable
|
|
505
|
+
source notification outbox to schedule another attempt. A malformed payload or
|
|
506
|
+
capability mismatch blocks before the write request, while an acknowledgement
|
|
507
|
+
must exactly match its notification, event, site, acceptance state, and payload
|
|
508
|
+
hash.
|
|
509
|
+
|
|
510
|
+
Health and readiness accept both their documented `200` and `503` states, then
|
|
511
|
+
validate exact fields and cross-field invariants before returning the
|
|
512
|
+
content-free snapshot. Status binds the canonical request body and its SHA-256
|
|
513
|
+
to the event and site, then rejects a response for any other tenant or identity.
|
|
514
|
+
All three operational responses also contain `capabilities_sha256`, derived
|
|
515
|
+
from the receiver's live configured contract. The client requires this value to
|
|
516
|
+
equal its trusted pin after discovery, preventing an endpoint switch or stale
|
|
517
|
+
response between the two requests from passing as current evidence.
|
|
518
|
+
Malformed JSON, duplicate keys, unexpected fields, inconsistent counts,
|
|
519
|
+
rehashed semantic contract drift, redirects, and private transport failures
|
|
520
|
+
raise `TerminalReceiverClientBlocked` with only a stable code and retryability.
|
|
521
|
+
The three read methods never claim work, mutate receiver state, process a
|
|
522
|
+
notification, or contain website text. `notify()` performs only the documented
|
|
523
|
+
durable intake operation and returns its content-free acknowledgement.
|
|
524
|
+
|
|
525
|
+
For a single-process WSGI deployment, `open_hosted_cms_source` adds the owned
|
|
526
|
+
worker lifecycle. It starts one non-daemon background worker before returning,
|
|
527
|
+
uses interruptible state-specific waits, and joins that worker before closing
|
|
528
|
+
any database. `start_worker` and `stop_worker` remain available to supervisors
|
|
529
|
+
that need an explicit startup boundary. Worker exceptions become a stable
|
|
530
|
+
content-free failure state; the same in-memory runtime cannot restart after
|
|
531
|
+
such a failure and must be replaced from its durable databases.
|
|
532
|
+
|
|
533
|
+
### Authenticated source-CMS ingress
|
|
534
|
+
|
|
535
|
+
`integrations/website_localization_cms_source_http.py` provides the optional
|
|
536
|
+
WSGI boundary exposed as `runtime.http` when `open_durable_cms_source` receives
|
|
537
|
+
an `http_authenticator`. Mount it behind a production WSGI server and a trusted
|
|
538
|
+
TLS terminator. Construct the runtime after the worker process forks, and run
|
|
539
|
+
`runtime.run_forever` in the supervised background worker that owns the same
|
|
540
|
+
runtime. No HTTP route advances a lease or performs a network call.
|
|
541
|
+
|
|
542
|
+
The exact routes are:
|
|
543
|
+
|
|
544
|
+
| Method | Path | Required scope | Purpose |
|
|
545
|
+
| --- | --- | --- | --- |
|
|
546
|
+
| `POST` | `/v1/localization/source/changes` | `source-change:write` | Persist one complete signed CMS change |
|
|
547
|
+
| `POST` | `/v1/localization/source/removals` | `source-removal:write` | Persist one cancellation or tombstone |
|
|
548
|
+
| `POST` | `/v1/localization/source/status` | `source-status:read` | Read one site-bound event lifecycle |
|
|
549
|
+
| `GET` | `/v1/localization/source/health` | `source-health:read` | Read aggregate content-free health |
|
|
550
|
+
| `GET` | `/v1/localization/source/readiness` | `source-readiness:read` | Verify that the managed worker and durable service can accept work |
|
|
551
|
+
| `GET` | `/v1/localization/source/capabilities` | `source-capabilities:read` | Discover the exact active HTTP contract |
|
|
552
|
+
|
|
553
|
+
The capabilities route accepts no body or query. Its
|
|
554
|
+
`blun.cms-source-capabilities-response.v1` response contains one
|
|
555
|
+
`blun.cms-source-runtime-capabilities.v1` contract with the exact active
|
|
556
|
+
methods, paths, scopes, principal schemas, request and response schemas,
|
|
557
|
+
required top-level fields, success statuses, retry limits, and transport
|
|
558
|
+
bounds. The nested `sha256` is calculated over the canonical capability object
|
|
559
|
+
before that digest field is added. Clients can pin it during deployment and
|
|
560
|
+
reject unexpected contract drift without receiving website content or reading
|
|
561
|
+
the three runtime databases. The route has its own
|
|
562
|
+
`source-capabilities:read` credential, performs no state write, lease, repair,
|
|
563
|
+
or network call, and returns fail-closed if its route metadata is incomplete or
|
|
564
|
+
internally inconsistent.
|
|
565
|
+
|
|
566
|
+
The body-free readiness route uses
|
|
567
|
+
`blun.cms-source-readiness-response.v2`. It returns HTTP `200` only while the
|
|
568
|
+
managed worker is running and durable service health is `ok` or `degraded`;
|
|
569
|
+
startup, shutdown, a worker exception, closed state, or blocked durable health
|
|
570
|
+
returns HTTP `503`. Its separate `source-readiness:read` credential receives no
|
|
571
|
+
website content. After a runtime enters managed mode, change and removal routes
|
|
572
|
+
also require this worker state before persisting new work. Existing manually
|
|
573
|
+
driven runtimes retain their explicit `run_once` contract.
|
|
574
|
+
|
|
575
|
+
Change requests use
|
|
576
|
+
`blun.cms-source-change-enqueue-request.v1`; removal requests use
|
|
577
|
+
`blun.cms-source-removal-enqueue-request.v1`. Both contain the exact downstream
|
|
578
|
+
payload plus an explicit `max_attempts` from 1 through 20. Successful V2
|
|
579
|
+
responses return HTTP `202` with the canonical request identity, payload
|
|
580
|
+
SHA-256, durable state, attempt count, retry ceiling, and exact capability
|
|
581
|
+
digest. Replaying identical bytes converges on the same durable item. Reusing
|
|
582
|
+
an identity with different content or policy returns HTTP `409` and does not
|
|
583
|
+
alter stored work.
|
|
584
|
+
|
|
585
|
+
The status request is exact, query-free JSON and uses
|
|
586
|
+
`blun.cms-source-status-request.v1`:
|
|
587
|
+
|
|
588
|
+
```json
|
|
589
|
+
{
|
|
590
|
+
"schema": "blun.cms-source-status-request.v1",
|
|
591
|
+
"event_id": "cms-event-184",
|
|
592
|
+
"site_id": "public-site"
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
Its `blun.cms-source-status-response.v4` response contains one nested
|
|
597
|
+
`blun.cms-source-service-status.v3` snapshot. It binds the stored
|
|
598
|
+
`website_version`, `source_sequence`, canonical change hash, dispatch state and
|
|
599
|
+
attempts, remote plan and job count, local lifecycle state, remote lifecycle
|
|
600
|
+
status, lifecycle hash, required and approved locales, blocked locale reason
|
|
601
|
+
codes, and queue counts. It never contains source text, target text, delivery
|
|
602
|
+
payloads, credentials, signatures, provider bodies, or private exceptions.
|
|
603
|
+
The read performs no reconciliation, lease, retry, network call, or state
|
|
604
|
+
write. A caller can therefore distinguish queued work, a pending local
|
|
605
|
+
registration, active localization, approval, publication, cancellation, and a
|
|
606
|
+
terminal failure without accidentally advancing the worker.
|
|
607
|
+
|
|
608
|
+
Version 2 additionally exposes only the terminal notification state, its
|
|
609
|
+
content-free identity and hash, bounded attempt counters, and a public error
|
|
610
|
+
code. When the terminal notifier exposes the pinned receiver `status` method,
|
|
611
|
+
the snapshot also reports the independently durable processing observation,
|
|
612
|
+
poll failures, receiver attempts, and stable local and receiver error codes.
|
|
613
|
+
An intake acknowledgement is never presented as completed CMS processing.
|
|
614
|
+
|
|
615
|
+
The corresponding `blun.cms-source-health-response.v4` and nested
|
|
616
|
+
`blun.cms-source-service-health.v3` report notification and processing-observer
|
|
617
|
+
backlog plus component
|
|
618
|
+
health without revealing website content or callback responses.
|
|
619
|
+
|
|
620
|
+
Before parsing JSON or touching SQLite, the application calls the host-supplied
|
|
621
|
+
authenticator with this content-free request:
|
|
622
|
+
|
|
623
|
+
```json
|
|
624
|
+
{
|
|
625
|
+
"schema": "blun.cms-source-runtime-http-auth-request.v1",
|
|
626
|
+
"method": "POST",
|
|
627
|
+
"path": "/v1/localization/source/changes",
|
|
628
|
+
"headers": [["authorization", "<host credential>"]],
|
|
629
|
+
"body_sha256": "<sha256 of exact request bytes>"
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
For change, removal, health, and capabilities routes, the authenticator returns
|
|
634
|
+
exactly
|
|
635
|
+
`blun.cms-source-runtime-principal.v1` with `principal_id`, `credential_id`,
|
|
636
|
+
`credential_version`, and the one route-specific `scope`. Status uses the
|
|
637
|
+
separate `blun.cms-source-status-principal.v1` schema and additionally binds
|
|
638
|
+
one exact `site_id`. The request site must match that principal before SQLite
|
|
639
|
+
is queried; another site and an unknown event both return the same HTTP `404`
|
|
640
|
+
error. The runtime does not interpret credentials and never stores them.
|
|
641
|
+
Operators should give status, health, and write routes distinct credentials.
|
|
642
|
+
The WSGI server remains responsible for rejecting ambiguous wire-level HTTP
|
|
643
|
+
before constructing the WSGI environment.
|
|
644
|
+
|
|
645
|
+
Requests require an exact query-free HTTPS route, fixed `Content-Length`, UTF-8
|
|
646
|
+
JSON, and no transfer encoding. Health and capabilities accept no body or
|
|
647
|
+
content type. Every response sets `Cache-Control: no-store`,
|
|
648
|
+
`X-Content-Type-Options: nosniff`, and `Referrer-Policy: no-referrer`. Errors use
|
|
649
|
+
`blun.cms-source-runtime-http-error.v1` and never echo a body, header, path,
|
|
650
|
+
credential, exception, source string, or target string.
|
|
651
|
+
|
|
652
|
+
#### Contract-pinned website client
|
|
653
|
+
|
|
654
|
+
`integrations/website_localization_cms_source_client.py` is the provider-neutral
|
|
655
|
+
client for this complete ingress. Construct `CMSLocalizationSourceHTTPClient`
|
|
656
|
+
with one exact HTTPS origin, an `expected_capabilities_sha256` supplied through
|
|
657
|
+
trusted deployment configuration, and a callback that provides authentication
|
|
658
|
+
headers for the canonical request context. The callback receives the method,
|
|
659
|
+
origin, verified path and scope, body SHA-256, and the applicable event, site,
|
|
660
|
+
or request identity; it cannot replace framing, idempotency, or binding headers.
|
|
661
|
+
|
|
662
|
+
`submit_change()` and `submit_removal()` validate the complete V2 CMS change or
|
|
663
|
+
V1 cancellation/tombstone locally before discovery and before any write. Each
|
|
664
|
+
method fetches the capability object, requires its canonical digest and every
|
|
665
|
+
operation definition to match the installed contract and trusted pin, takes
|
|
666
|
+
the write path from that fresh result, and performs exactly one request. The
|
|
667
|
+
immutable event, cancellation, or tombstone identity is the idempotency key.
|
|
668
|
+
An accepted response must return the same operation, request and event IDs,
|
|
669
|
+
canonical inner-payload hash, retry ceiling, and capability digest.
|
|
670
|
+
|
|
671
|
+
`status()`, `health()`, and `readiness()` repeat discovery independently and
|
|
672
|
+
then validate every returned field and cross-field invariant. Status is bound
|
|
673
|
+
to the requested event and site. Health and readiness accept their documented
|
|
674
|
+
`200` and `503` states only when the HTTP status agrees with the nested state.
|
|
675
|
+
All five operational response schemas carry `capabilities_sha256`; a process
|
|
676
|
+
swap or stale response between discovery and the operation therefore blocks.
|
|
677
|
+
|
|
678
|
+
The adapter owns no retry loop. `CMSSourceClientBlocked` contains only a stable
|
|
679
|
+
content-free code and retryability decision, so the website host may apply its
|
|
680
|
+
own bounded retry policy. Network failures, `408`, `425`, `429`, and server
|
|
681
|
+
errors are retryable; redirects, contract drift, malformed JSON, unexpected
|
|
682
|
+
fields, cross-tenant evidence, payload mismatches, and invalid requests are
|
|
683
|
+
not. The client never returns website text through a status or health method
|
|
684
|
+
and never treats a failed or ambiguous response as accepted work.
|
|
685
|
+
|
|
686
|
+
#### Durable website-source delivery
|
|
687
|
+
|
|
688
|
+
`integrations/website_localization_cms_source_delivery.py` supplies the durable
|
|
689
|
+
retry policy for website and CMS hosts. Construct
|
|
690
|
+
`DurableCMSSourceDeliveryOutbox` with a caller-owned SQLite connection and one
|
|
691
|
+
already configured `CMSLocalizationSourceHTTPClient`. Enqueue the complete
|
|
692
|
+
change with `enqueue_change()` or a cancellation/tombstone with
|
|
693
|
+
`enqueue_removal()` before allowing the originating transaction to be treated
|
|
694
|
+
as handed off. Each stored item binds its canonical payload SHA-256, immutable
|
|
695
|
+
request and event IDs, site, source-service retry ceiling, delivery retry
|
|
696
|
+
ceiling, and the client's trusted capability digest.
|
|
697
|
+
|
|
698
|
+
`run_once()` claims at most one due item under an unpredictable token-bound
|
|
699
|
+
lease and performs exactly one client operation. Removal work is selected
|
|
700
|
+
before change work. A retryable client failure schedules durable exponential
|
|
701
|
+
backoff up to the delivery ceiling; a permanent failure or exhausted ceiling
|
|
702
|
+
becomes terminal. The independent `source_max_attempts` value is sent to the
|
|
703
|
+
remote source service and is never multiplied into the local delivery limit.
|
|
704
|
+
|
|
705
|
+
If the website process exits after remote acceptance but before the local
|
|
706
|
+
success commit, lease expiry makes the same canonical payload and idempotency
|
|
707
|
+
identity eligible again. The source service decides that replay idempotently.
|
|
708
|
+
A stale worker cannot complete or fail a lease after another worker has taken
|
|
709
|
+
it over. Choose `lease_seconds` greater than the configured client timeout,
|
|
710
|
+
run one tick at a time from a supervised worker, and keep the SQLite database
|
|
711
|
+
on durable private storage.
|
|
712
|
+
|
|
713
|
+
`status()` returns one content-free binding and attempt snapshot. `health()`
|
|
714
|
+
reports aggregate queue states, due work, expired leases, terminal failures,
|
|
715
|
+
and active rows pinned to another capability digest. SQLite schema or row
|
|
716
|
+
tampering blocks before client access. Contract drift does not silently move
|
|
717
|
+
queued writes to a newly advertised endpoint: the deployment must explicitly
|
|
718
|
+
resolve or migrate those immutable records first.
|
|
719
|
+
|
|
720
|
+
#### Hosted website-source delivery runtime
|
|
721
|
+
|
|
722
|
+
`integrations/website_localization_cms_source_delivery_runtime.py` is the
|
|
723
|
+
production composition root for that outbox. Call
|
|
724
|
+
`open_durable_cms_source_delivery()` with one absolute private database path,
|
|
725
|
+
one pinned source client, and a stable worker ID. It validates the client,
|
|
726
|
+
worker ID, timeout, lease, and backoff policy against an in-memory outbox before
|
|
727
|
+
creating the file. The resulting runtime owns its SQLite connection and must be
|
|
728
|
+
constructed after a prefork server creates the final worker process.
|
|
729
|
+
|
|
730
|
+
The database file is created mode `0600` without following links. Its complete
|
|
731
|
+
parent chain, owner, type, link count, permissions, device, and inode are
|
|
732
|
+
checked before and after every operation. A missing, replaced, linked, or
|
|
733
|
+
permission-weakened file blocks before client access. One reentrant runtime
|
|
734
|
+
lock serializes callers inside a process, while SQLite transactions and durable
|
|
735
|
+
leases let separately constructed processes share the same database safely.
|
|
736
|
+
An inherited runtime rejects the foreign process before attempting its lock.
|
|
737
|
+
|
|
738
|
+
`open_hosted_cms_source_delivery()` additionally starts one process-owned,
|
|
739
|
+
non-daemon background worker. It uses interruptible active, idle, and blocked
|
|
740
|
+
waits, claims one network attempt per tick, and gives removal events the same
|
|
741
|
+
priority defined by the outbox. Once managed, `enqueue_change()` and
|
|
742
|
+
`enqueue_removal()` accept new work only while that worker is alive and has no
|
|
743
|
+
recorded failure. Manual runtimes remain available for an external supervisor
|
|
744
|
+
that calls `run_once()` itself.
|
|
745
|
+
|
|
746
|
+
`worker_readiness()` returns only worker state, outbox health state, and a
|
|
747
|
+
stable error code. It is ready only when the managed worker is alive and the
|
|
748
|
+
outbox reports `ok`. `stop_worker()` signals before waiting, so an idle worker
|
|
749
|
+
wakes immediately. If a source-client call exceeds the configured join bound,
|
|
750
|
+
shutdown returns `source_delivery_runtime.worker_stop_timeout` and deliberately
|
|
751
|
+
keeps the database connection open; close it only after the worker finishes.
|
|
752
|
+
Worker exceptions are reduced to `source_delivery_runtime.worker_blocked`, and
|
|
753
|
+
the failed runtime cannot accept more managed source events.
|
|
754
|
+
|
|
755
|
+
Premortem: an invalid deployment could create state before discovering a bad
|
|
756
|
+
worker or timeout; two paths could alias one database; a pre-fork service could
|
|
757
|
+
reuse a vanished parent's lock; or a permission change could redirect the next
|
|
758
|
+
operation. In-memory configuration preflight, distinct canonical paths and
|
|
759
|
+
identities, process ownership checks before locking, per-call path guards, and
|
|
760
|
+
durable multi-process leases close those paths. Regression tests cover restart
|
|
761
|
+
persistence, owner-only creation, unsafe paths, changed permissions, close and
|
|
762
|
+
fork behavior, 24 concurrent thread ticks, and two independently constructed
|
|
763
|
+
workers converging on one change dispatch.
|
|
764
|
+
|
|
765
|
+
## Create or resume localization work
|
|
766
|
+
|
|
767
|
+
```http
|
|
768
|
+
POST /v2/localization/changes HTTP/1.1
|
|
769
|
+
Content-Type: application/json; charset=utf-8
|
|
770
|
+
Content-Length: <exact UTF-8 byte count>
|
|
771
|
+
X-Localization-Signature-Algorithm: <configured algorithm>
|
|
772
|
+
X-Localization-Key-Id: <site credential identifier>
|
|
773
|
+
X-Localization-Signature: <signature>
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
The body is one complete `blun.cms-content-change.v2` event. Its required
|
|
777
|
+
`source_sequence` is a positive, monotonically increasing generation for the
|
|
778
|
+
exact `(site_id, localization.source_id)` pair. The nested `localization`
|
|
779
|
+
mapping is the planner contract documented in `WEBSITE_LOCALIZATION.md` and
|
|
780
|
+
binds the source revision and text, source locale, content type, glossary and
|
|
781
|
+
policy versions, provider/model/software identity, and optional target locales.
|
|
782
|
+
Omitting `target_locales` selects every supported EU official-language locale
|
|
783
|
+
except an EU source language.
|
|
784
|
+
|
|
785
|
+
Sign the UTF-8 bytes of canonical JSON: sorted object keys, no insignificant
|
|
786
|
+
spaces, native Unicode unescaped, NFC text, and no `NaN` or infinity. Whitespace
|
|
787
|
+
in the transmitted JSON may differ because the service parses strictly and
|
|
788
|
+
reconstructs the canonical payload before verification.
|
|
789
|
+
|
|
790
|
+
New current work returns `202 Accepted`. An exact replay returns `200 OK` and
|
|
791
|
+
`inserted_jobs: 0`, allowing safe recovery after an uncertain response. Reusing
|
|
792
|
+
an event ID or source sequence for different content returns `409 Conflict`.
|
|
793
|
+
A delayed event below an already accepted source generation is recorded as
|
|
794
|
+
`superseded` and returns `200 OK`; it can never become publication work.
|
|
795
|
+
|
|
796
|
+
If the process stops after persisting the signed event but before the separate
|
|
797
|
+
queue transaction, the service supervisor revalidates the stored signature and
|
|
798
|
+
automatically resumes exactly one such event per tick. It uses the same queue
|
|
799
|
+
attempt limit configured for this API. The deterministic plan and job IDs make
|
|
800
|
+
that recovery idempotent; an accepted cancellation remains terminal and is
|
|
801
|
+
never resumed. A missing or invalid signature, changed stored bytes, or queue
|
|
802
|
+
failure blocks before a model call and stays visible through health as
|
|
803
|
+
`cms.event.awaiting_queue_resume` until a valid recovery succeeds.
|
|
804
|
+
|
|
805
|
+
```json
|
|
806
|
+
{"event_id":"cms-event-184","inserted_jobs":23,"job_count":23,"plan_id":"blun-l10n-plan-…","schema":"blun.website-localization-api.v2","status":"enqueued"}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
The response contains identifiers and counts only. Acceptance is not quality
|
|
810
|
+
approval or publication readiness.
|
|
811
|
+
|
|
812
|
+
## Cancel unpublished localization work
|
|
813
|
+
|
|
814
|
+
A CMS can withdraw one exact accepted event without submitting replacement
|
|
815
|
+
text:
|
|
816
|
+
|
|
817
|
+
```http
|
|
818
|
+
POST /v2/localization/cancellations HTTP/1.1
|
|
819
|
+
Content-Type: application/json; charset=utf-8
|
|
820
|
+
Content-Length: <exact UTF-8 byte count>
|
|
821
|
+
X-Localization-Signature-Algorithm: <configured algorithm>
|
|
822
|
+
X-Localization-Key-Id: <same site credential that created the event>
|
|
823
|
+
X-Localization-Signature: <signature>
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
```json
|
|
827
|
+
{"cancellation_id":"cms-cancellation-184","event_id":"cms-event-184","schema":"blun.cms-content-cancellation.v1","site_id":"public-site","source_id":"homepage.hero","source_sequence":184,"website_version":"website-2026-08-29.1"}
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
Sign the complete canonical cancellation object. The service requires every
|
|
831
|
+
event, site, source, signed source sequence, website-version, and credential
|
|
832
|
+
binding to match the stored event. The first accepted cancellation returns
|
|
833
|
+
`202 Accepted`; exact replay returns `200 OK`. Reusing either the cancellation
|
|
834
|
+
ID or event binding for different bytes returns `409 Conflict`.
|
|
835
|
+
|
|
836
|
+
Acceptance permanently removes that event from production scheduling, blocks
|
|
837
|
+
new approvals and publication preparation, and terminally closes any pending or
|
|
838
|
+
retrying outbox entry with `event_cancelled`. The cancellation ledger is
|
|
839
|
+
immutable and reverified on reads and health checks. It does not delete shared
|
|
840
|
+
queue artifacts or translation memory because another current plan may validly
|
|
841
|
+
reference the same deterministic job.
|
|
842
|
+
|
|
843
|
+
The same operation also closes the crash gap after the signed event has been
|
|
844
|
+
stored but before its locale plan reaches the queue. Such an `accepted` event
|
|
845
|
+
can be cancelled without first replaying it. Status and lifecycle then report
|
|
846
|
+
every required locale as `cancelled` even though no queue row exists. An exact
|
|
847
|
+
change-event replay returns `cancelled` and cannot recreate work. If the
|
|
848
|
+
cancellation arrives while the atomic queue insertion is running, the bridge
|
|
849
|
+
rechecks the immutable ledger before promoting the event to `enqueued`; any
|
|
850
|
+
already-created shared queue artifacts remain ineligible through that event.
|
|
851
|
+
|
|
852
|
+
A confirmed publication cannot be cancelled through this endpoint. A currently
|
|
853
|
+
leased delivery also returns `409 Conflict`: after an external request starts,
|
|
854
|
+
the service cannot truthfully retract bytes that the CMS may already have
|
|
855
|
+
accepted. The caller must wait for the lease outcome; an accepted publication
|
|
856
|
+
remains immutable, while a failed or retryable delivery can then be cancelled.
|
|
857
|
+
Removing content that was already published uses the separately signed
|
|
858
|
+
tombstone operation below.
|
|
859
|
+
|
|
860
|
+
## Delete an acknowledged publication
|
|
861
|
+
|
|
862
|
+
```http
|
|
863
|
+
POST /v2/localization/tombstones HTTP/1.1
|
|
864
|
+
Content-Type: application/json; charset=utf-8
|
|
865
|
+
Content-Length: <exact UTF-8 byte count>
|
|
866
|
+
X-Localization-Signature-Algorithm: <configured algorithm>
|
|
867
|
+
X-Localization-Key-Id: <same site credential that created the event>
|
|
868
|
+
X-Localization-Signature: <signature>
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
```json
|
|
872
|
+
{"event_id":"cms-event-184","schema":"blun.cms-content-tombstone.v1","site_id":"public-site","source_id":"homepage.hero","source_sequence":184,"tombstone_id":"cms-tombstone-184","website_version":"website-2026-08-29.1"}
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
The service accepts this request only after the exact publication has a valid,
|
|
876
|
+
signed `succeeded` acknowledgement. It binds the immutable tombstone to the
|
|
877
|
+
original tenant key, event, site, source generation, website version,
|
|
878
|
+
publication delivery ID, publication-payload hash, plan, and complete sorted
|
|
879
|
+
locale set. It never copies target text into the tombstone payload.
|
|
880
|
+
|
|
881
|
+
The first request returns `202 Accepted`; an exact replay returns `200 OK` and
|
|
882
|
+
the same deterministic delivery ID. Unknown, unpublished, cancelled,
|
|
883
|
+
differently bound, or colliding requests fail closed. A durable lease-based
|
|
884
|
+
outbox sends `blun.cms-localization-tombstone.v1`; the CMS must return the exact
|
|
885
|
+
signed `blun.cms-localization-tombstone-ack.v1` acknowledgement with status
|
|
886
|
+
`deleted`. Network and retryable acknowledgement failures use the bounded
|
|
887
|
+
outbox retry policy. Lifecycle and health report `deleting`,
|
|
888
|
+
`deletion_failed`, or `deleted`; accepting or retrying a tombstone does not call
|
|
889
|
+
a model or remove the immutable publication audit record.
|
|
890
|
+
|
|
891
|
+
## Discover the active localization contract
|
|
892
|
+
|
|
893
|
+
A CMS can discover the exact runtime contract before creating work. This avoids
|
|
894
|
+
copying a locale list or quality-profile version into an integration where it
|
|
895
|
+
can silently become stale. The read uses its own signed purpose and does not
|
|
896
|
+
require an existing event:
|
|
897
|
+
|
|
898
|
+
```http
|
|
899
|
+
POST /v2/localization/capabilities HTTP/1.1
|
|
900
|
+
Content-Type: application/json; charset=utf-8
|
|
901
|
+
Content-Length: <exact UTF-8 byte count>
|
|
902
|
+
X-Localization-Signature-Algorithm: <configured algorithm>
|
|
903
|
+
X-Localization-Key-Id: <credential identifier>
|
|
904
|
+
X-Localization-Signature: <signature>
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
```json
|
|
908
|
+
{"request_id":"capabilities-9","requested_at":1788955200,"schema":"blun.cms-localization-capabilities-request.v1"}
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
The request signature and five-minute freshness window prevent an old or
|
|
912
|
+
different API request from being replayed for discovery. The response contains
|
|
913
|
+
the active change, plan, job, publication, and commercial-profile versions;
|
|
914
|
+
the ordered quality phases; accepted content types; the official EU language
|
|
915
|
+
source; the default target-selection rule; and all 24 exact BCP-47 locale
|
|
916
|
+
profiles. Every locale entry includes its EU code, language code, native name,
|
|
917
|
+
script, direction, quality-profile version, and quality-profile SHA-256 digest.
|
|
918
|
+
It does not expose the full profile instructions, credentials, customer text,
|
|
919
|
+
provider data, or mutable service state.
|
|
920
|
+
|
|
921
|
+
The nested `publication_http` object is the separately hashed, machine-readable
|
|
922
|
+
contract for the built-in outbound CMS adapter. It declares publication,
|
|
923
|
+
tombstone, and content-free health payload, request, acknowledgement, and
|
|
924
|
+
response schemas; the nested release-evidence schema; exact
|
|
925
|
+
success values; accepted JSON content types; at-least-once delivery; and the
|
|
926
|
+
three headers that bind every attempt to its delivery ID and payload hash. It
|
|
927
|
+
also declares the three health headers that bind a fresh probe ID and this
|
|
928
|
+
contract's digest. It does not advertise an active endpoint or credential, or
|
|
929
|
+
claim that a custom host publisher uses this adapter. A receiver can therefore
|
|
930
|
+
implement and test the
|
|
931
|
+
supported callback protocol without copying prose from the integration guide.
|
|
932
|
+
The transport and discovery manifest use the same constants, so schema or
|
|
933
|
+
header drift changes the digest or blocks discovery rather than producing a
|
|
934
|
+
partial contract.
|
|
935
|
+
|
|
936
|
+
CMS implementations can use
|
|
937
|
+
`integrations/website_localization_cms_receiver.py` as the fail-closed
|
|
938
|
+
publication, tombstone, and health reference receiver. For publication, the host
|
|
939
|
+
supplies the expected source generation, complete required-locale set, content
|
|
940
|
+
type, and commercial profile. For deletion, it supplies the exact acknowledged
|
|
941
|
+
publication delivery and payload hash as well as the complete locale set. The
|
|
942
|
+
receiver validates those bindings, release evidence where applicable, hashes,
|
|
943
|
+
headers, expiry times, and the publisher signature before invoking the host's
|
|
944
|
+
atomic, idempotent commit or delete callback. It returns a signed `accepted` or
|
|
945
|
+
`deleted` acknowledgement only after the callback confirms the exact delivery
|
|
946
|
+
ID and payload hash. The host continues to own authentication, key provisioning,
|
|
947
|
+
durable transactions, and target-text log redaction.
|
|
948
|
+
|
|
949
|
+
For the content-free health challenge, the host supplies an authentication
|
|
950
|
+
callback, the exact active `publication_http.sha256`, a health callback, and the
|
|
951
|
+
same acknowledgement authority used by the publisher adapter. The receiver
|
|
952
|
+
strictly parses and binds the probe before authentication, authenticates before
|
|
953
|
+
comparing deployment state or calling health logic, and accepts only an exact
|
|
954
|
+
`healthy` receipt for the probe ID and contract digest. It signs that same
|
|
955
|
+
binding only after the host confirms health. Authentication outages, wrong
|
|
956
|
+
contracts, malformed transport, private health errors, false receipts, and
|
|
957
|
+
signing failures cannot produce a healthy acknowledgement.
|
|
958
|
+
|
|
959
|
+
`CMSReceiverApplication` exposes these three receiver operations through one
|
|
960
|
+
provider-neutral WSGI callable. Its default mount is
|
|
961
|
+
`/v1/localization/callback`, matching the built-in publisher's one configured
|
|
962
|
+
endpoint for publication, tombstone, and health requests. The application
|
|
963
|
+
requires an exact query-free HTTPS `POST`, canonical UTF-8 JSON, explicit
|
|
964
|
+
bounded `Content-Length`, non-chunked framing, and host authentication. It then
|
|
965
|
+
dispatches only the three published outer schemas. Publication and tombstone
|
|
966
|
+
expectation resolvers see a request only after its payload, binding headers,
|
|
967
|
+
hashes, and publisher signature have passed verification; host writes retain
|
|
968
|
+
the later exact-receipt requirement. WSGI errors use the content-free
|
|
969
|
+
`blun.cms-localization-receiver-error.v1` envelope with only a stable code and
|
|
970
|
+
retry decision. A TLS terminator must set `wsgi.url_scheme` from trusted proxy
|
|
971
|
+
configuration, and the host must keep authorization headers and verified target
|
|
972
|
+
text out of access logs.
|
|
973
|
+
|
|
974
|
+
`DurableCMSReceiverStore` in
|
|
975
|
+
`integrations/website_localization_cms_receiver_store.py` supplies a complete
|
|
976
|
+
SQLite reference implementation for the five stateful host callbacks. It uses
|
|
977
|
+
one dedicated host-owned connection. The CMS registers each monotonic current
|
|
978
|
+
source before delivery and explicitly pre-registers a tombstone against the
|
|
979
|
+
exact active publication before deletion. Commit and delete recheck those
|
|
980
|
+
bindings inside `BEGIN IMMEDIATE`, so the earlier resolver lookup cannot race a
|
|
981
|
+
source change. Replays are bound to immutable delivery and payload hashes, a
|
|
982
|
+
replacement preserves the last-known-good bundle until its complete locale set
|
|
983
|
+
commits. The successful replacement transaction then securely deletes the
|
|
984
|
+
superseded payload and locale rows while retaining only content-free replay
|
|
985
|
+
evidence; any cleanup failure rolls the switch back to the previous active
|
|
986
|
+
bundle. Explicit deletion follows the same content-minimizing rule. The store's
|
|
987
|
+
health callback validates schema, SQLite integrity, canonical payloads, locale rows, active pointers, and
|
|
988
|
+
tombstone state before confirming the probe. Source and tombstone expectations
|
|
989
|
+
carry separate canonical hashes, so a syntactically valid field substitution
|
|
990
|
+
also blocks. `read_active_bundle` is for trusted
|
|
991
|
+
CMS rendering code only. It requires the complete trusted publication
|
|
992
|
+
expectation and returns text only when the active signed payload matches its
|
|
993
|
+
event, website version, plan, source revision, sequence and hash, exact locale
|
|
994
|
+
set, content type, and commercial profile. Site/source-only lookup is rejected,
|
|
995
|
+
so a retained last-known-good bundle cannot be attached to a newer generation.
|
|
996
|
+
The method contains target text and is not part of any public status response.
|
|
997
|
+
A WSGI worker process owns its store connection; request
|
|
998
|
+
threads may share it only through the composed runtime's serialized boundary.
|
|
999
|
+
Multiple worker processes may open distinct
|
|
1000
|
+
connections to the same file and rely on the transactional writer lock.
|
|
1001
|
+
|
|
1002
|
+
`open_durable_cms_receiver` in
|
|
1003
|
+
`integrations/website_localization_cms_receiver_runtime.py` is the reference
|
|
1004
|
+
composition root. It validates the complete receiver configuration before it
|
|
1005
|
+
opens SQLite, requires distinct publication and acknowledgement authority
|
|
1006
|
+
objects, rejects SQLite URI configuration, and returns one owner for the store,
|
|
1007
|
+
trusted registration and rendering methods, connection lifetime, and WSGI
|
|
1008
|
+
callable. A failed preflight creates no database; a later initialization failure
|
|
1009
|
+
closes the connection and never returns a partial application. Instantiate it
|
|
1010
|
+
once after each WSGI worker starts and call `close` during that worker's orderly
|
|
1011
|
+
shutdown. Inside one worker, a shared reentrant lock serializes all eight store
|
|
1012
|
+
paths: both expectation resolvers, commit, delete, health, trusted registration,
|
|
1013
|
+
and rendering. The composition root opens SQLite for cross-thread access only
|
|
1014
|
+
behind that lock, so a multithreaded worker can share the WSGI callable without
|
|
1015
|
+
racing transactions or reads. This does not make a pre-fork connection safe.
|
|
1016
|
+
The runtime binds itself to its creator process and rejects inherited callbacks
|
|
1017
|
+
and trusted host operations before lock acquisition; each worker process must
|
|
1018
|
+
construct and own its own runtime after forking.
|
|
1019
|
+
|
|
1020
|
+
For a durable store, pass a canonical absolute POSIX database path. Its direct
|
|
1021
|
+
parent must be a real service-owned directory. Every ancestor must be root- or
|
|
1022
|
+
service-owned and non-shared-writable, apart from a root-owned sticky temporary
|
|
1023
|
+
directory. The runtime atomically creates a missing database as `0600` and
|
|
1024
|
+
accepts an existing one only when it is a service-owned, single-link regular
|
|
1025
|
+
file with
|
|
1026
|
+
that exact mode. Directory and file identity, permissions, ownership, and link
|
|
1027
|
+
count are checked again before every callback or trusted host operation;
|
|
1028
|
+
symlinks, hard links, path replacement, and permission drift fail closed.
|
|
1029
|
+
`:memory:` is retained solely for ephemeral test composition.
|
|
1030
|
+
|
|
1031
|
+
The separate `api_contract` object lists all six tenant operations with their
|
|
1032
|
+
exact path, `POST` method, request schema, response schema, and current
|
|
1033
|
+
`enabled` state. A standalone host without approval and publication authorities
|
|
1034
|
+
still advertises lifecycle and tombstone schemas but marks those operations
|
|
1035
|
+
disabled, so a CMS can fail closed before submitting work. The object uses
|
|
1036
|
+
`blun.website-localization-http-capabilities.v1` and carries its own SHA-256
|
|
1037
|
+
over every other canonical field. Paths and schemas come from the same runtime
|
|
1038
|
+
constants used for routing; they are not copied into a second configuration.
|
|
1039
|
+
|
|
1040
|
+
The nested `blun.website-localization-capabilities.v3` object carries a
|
|
1041
|
+
`sha256` value over all its other canonical fields. Consumers can pin that
|
|
1042
|
+
digest for a deployment and deliberately reconfigure when it changes. The
|
|
1043
|
+
runtime rebuilds and validates the complete registry on every read; duplicate,
|
|
1044
|
+
missing, noncanonical, or profile-mismatched entries return a fail-closed `503`
|
|
1045
|
+
without a partial locale list.
|
|
1046
|
+
|
|
1047
|
+
Within it, `commercial_profile` is a separately hashed
|
|
1048
|
+
`translate-native.commercial-capabilities.v2` object. Its nested and separately
|
|
1049
|
+
hashed `review_summary_contract` defines the exact content-free result schema,
|
|
1050
|
+
field set, verified/review-required state invariant, ten allowed ordered
|
|
1051
|
+
review dimensions, complete-evidence hash semantics, and excluded sensitive
|
|
1052
|
+
content. A CMS or independent-review adapter can validate targeted commercial
|
|
1053
|
+
escalation without receiving project prices, brands, source/target text, spans,
|
|
1054
|
+
or reviewer prose. Any registry or digest drift blocks the whole discovery
|
|
1055
|
+
response rather than advertising a partial contract.
|
|
1056
|
+
|
|
1057
|
+
```json
|
|
1058
|
+
{
|
|
1059
|
+
"capabilities": {
|
|
1060
|
+
"change_schema": "blun.cms-content-change.v2",
|
|
1061
|
+
"cancellation_schema": "blun.cms-content-cancellation.v1",
|
|
1062
|
+
"commercial_profile": {
|
|
1063
|
+
"profile": "translate-native.commercial.v2",
|
|
1064
|
+
"review_summary_contract": {
|
|
1065
|
+
"content_policy": {"project_brands": false, "project_prices": false, "reviewer_prose": false, "source_spans": false, "source_text": false, "target_spans": false, "target_text": false},
|
|
1066
|
+
"evidence_sha256": {"algorithm": "sha-256", "canonicalization": "utf-8-json-sort-keys-no-insignificant-whitespace", "covers": "complete-commercial-review-evidence"},
|
|
1067
|
+
"profile": "translate-native.commercial.v2",
|
|
1068
|
+
"required_fields": ["schema", "profile", "status", "review_required_dimensions", "evidence_sha256"],
|
|
1069
|
+
"result_schema": "translate-native.commercial-review-summary.v1",
|
|
1070
|
+
"review_required_dimensions": {"allowed": ["amount_currency", "discount_basis", "qualifiers", "tax_status", "billing_interval", "commitment", "renewal", "cancellation", "conditions", "offer_assignment"], "order": ["amount_currency", "discount_basis", "qualifiers", "tax_status", "billing_interval", "commitment", "renewal", "cancellation", "conditions", "offer_assignment"], "unique": true},
|
|
1071
|
+
"schema": "translate-native.commercial-review-summary-capabilities.v1",
|
|
1072
|
+
"sha256": "<sha256>",
|
|
1073
|
+
"statuses": {"review_required": {"requires_independent_review": true, "review_required_dimensions": "one-or-more"}, "verified": {"review_required_dimensions": "empty"}}
|
|
1074
|
+
},
|
|
1075
|
+
"schema": "translate-native.commercial-capabilities.v2",
|
|
1076
|
+
"sha256": "<sha256>"
|
|
1077
|
+
},
|
|
1078
|
+
"content_types": ["commercial", "cta", "documentation", "headline", "legal", "marketing", "seo", "ui"],
|
|
1079
|
+
"default_target_policy": "all-eu-official-locales-except-source-language",
|
|
1080
|
+
"eu_language_source": "https://european-union.europa.eu/principles-countries-history/languages_en",
|
|
1081
|
+
"job_schema": "blun.website-localization-job.v2",
|
|
1082
|
+
"locales": [{
|
|
1083
|
+
"direction": "ltr",
|
|
1084
|
+
"eu_code": "MT",
|
|
1085
|
+
"language": "mt",
|
|
1086
|
+
"locale": "mt-MT",
|
|
1087
|
+
"native_name": "Malti",
|
|
1088
|
+
"quality_profile_sha256": "<sha256>",
|
|
1089
|
+
"quality_profile_version": "eu-mt-MT-2026-09-1",
|
|
1090
|
+
"script": "Latn"
|
|
1091
|
+
}],
|
|
1092
|
+
"plan_schema": "blun.website-localization-plan.v2",
|
|
1093
|
+
"publication_http": {
|
|
1094
|
+
"binding_headers": [
|
|
1095
|
+
{"binding": "delivery_id", "name": "Idempotency-Key"},
|
|
1096
|
+
{"binding": "delivery_id", "name": "X-Localization-Delivery-Id"},
|
|
1097
|
+
{"binding": "payload_sha256", "name": "X-Localization-Payload-Sha256"}
|
|
1098
|
+
],
|
|
1099
|
+
"delivery_semantics": "at-least-once",
|
|
1100
|
+
"health_binding_headers": [
|
|
1101
|
+
{"binding": "probe_id", "name": "Idempotency-Key"},
|
|
1102
|
+
{"binding": "probe_id", "name": "X-Localization-Probe-Id"},
|
|
1103
|
+
{"binding": "contract_sha256", "name": "X-Localization-Contract-Sha256"}
|
|
1104
|
+
],
|
|
1105
|
+
"method": "POST",
|
|
1106
|
+
"operations": [{
|
|
1107
|
+
"acknowledgement_schema": "blun.cms-localization-publication-ack.v1",
|
|
1108
|
+
"acknowledgement_status": "accepted",
|
|
1109
|
+
"name": "publication",
|
|
1110
|
+
"payload_schema": "blun.cms-localization-publication.v3",
|
|
1111
|
+
"request_schema": "blun.cms-localization-publication-http.v1",
|
|
1112
|
+
"response_schema": "blun.cms-localization-publication-http-ack.v1"
|
|
1113
|
+
}],
|
|
1114
|
+
"request_content_type": "application/json; charset=utf-8",
|
|
1115
|
+
"release_evidence_schema": "blun.website-localization-release-evidence.v1",
|
|
1116
|
+
"response_content_types": ["application/json", "application/json; charset=utf-8"],
|
|
1117
|
+
"schema": "blun.cms-localization-publication-http-capabilities.v2",
|
|
1118
|
+
"sha256": "<sha256>"
|
|
1119
|
+
},
|
|
1120
|
+
"publication_schema": "blun.cms-localization-publication.v3",
|
|
1121
|
+
"quality_passes": ["target_native", "source_fidelity"],
|
|
1122
|
+
"schema": "blun.website-localization-capabilities.v3",
|
|
1123
|
+
"sha256": "<sha256>"
|
|
1124
|
+
},
|
|
1125
|
+
"api_contract": {
|
|
1126
|
+
"api_schema": "blun.website-localization-api.v2",
|
|
1127
|
+
"error_schema": "blun.website-localization-api.v2",
|
|
1128
|
+
"operations": [{
|
|
1129
|
+
"enabled": true,
|
|
1130
|
+
"method": "POST",
|
|
1131
|
+
"name": "lifecycle",
|
|
1132
|
+
"path": "/v2/localization/lifecycle",
|
|
1133
|
+
"request_schema": "blun.cms-localization-lifecycle-request.v1",
|
|
1134
|
+
"response_schema": "blun.cms-localization-lifecycle.v3"
|
|
1135
|
+
}],
|
|
1136
|
+
"schema": "blun.website-localization-http-capabilities.v1",
|
|
1137
|
+
"sha256": "<sha256>"
|
|
1138
|
+
},
|
|
1139
|
+
"request_id": "capabilities-9",
|
|
1140
|
+
"schema": "blun.website-localization-api.v2",
|
|
1141
|
+
"status": "CAPABILITIES"
|
|
1142
|
+
}
|
|
1143
|
+
```
|
|
1144
|
+
|
|
1145
|
+
The abbreviated example shows one locale, one inbound operation, one outbound
|
|
1146
|
+
adapter operation, and only the commercial profile fields relevant to summary
|
|
1147
|
+
discovery. A successful real response always contains the complete commercial
|
|
1148
|
+
profile, all 24 locales, all six inbound operations, and all three outbound
|
|
1149
|
+
operations, and otherwise blocks.
|
|
1150
|
+
|
|
1151
|
+
## Read per-locale progress
|
|
1152
|
+
|
|
1153
|
+
Status is a signed, short-lived POST rather than an unauthenticated identifier
|
|
1154
|
+
in a URL:
|
|
1155
|
+
|
|
1156
|
+
```http
|
|
1157
|
+
POST /v2/localization/status HTTP/1.1
|
|
1158
|
+
Content-Type: application/json; charset=utf-8
|
|
1159
|
+
Content-Length: <exact UTF-8 byte count>
|
|
1160
|
+
X-Localization-Signature-Algorithm: <configured algorithm>
|
|
1161
|
+
X-Localization-Key-Id: <same credential that created the event>
|
|
1162
|
+
X-Localization-Signature: <signature>
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
```json
|
|
1166
|
+
{"event_id":"cms-event-184","request_id":"status-7","requested_at":1788955200,"schema":"blun.cms-localization-status-request.v2","site_id":"public-site"}
|
|
1167
|
+
```
|
|
1168
|
+
|
|
1169
|
+
Sign the canonical status object. `requested_at` must be within five minutes of
|
|
1170
|
+
the service clock. The signed `site_id` and signature header's `key_id` must
|
|
1171
|
+
match the stored event scope exactly, even when the verifier recognizes several
|
|
1172
|
+
valid credentials. This prevents one valid tenant credential from probing
|
|
1173
|
+
another tenant's event. A rotated deployment must retain the original verifier
|
|
1174
|
+
identity for unfinished events or explicitly migrate them outside this module.
|
|
1175
|
+
|
|
1176
|
+
The read-only response echoes `request_id` and reports the bound site, website
|
|
1177
|
+
version, source sequence, plan and job counts, plus one item per target locale.
|
|
1178
|
+
Each item contains job/locale identity, state, attempts, retry timing, lease
|
|
1179
|
+
expiry, stable error code, optional private-detail hash, and optional result
|
|
1180
|
+
hash. The top-level `cancelled` flag records an accepted withdrawal.
|
|
1181
|
+
`lease_expired: true` makes recoverable crashes visible. A succeeded queue
|
|
1182
|
+
row is reloaded and hash-checked before it can appear successful.
|
|
1183
|
+
|
|
1184
|
+
If the signed event is durable but the process stopped before queue insertion,
|
|
1185
|
+
`queue_recovery_pending` is `true`. Every required locale then reports
|
|
1186
|
+
`awaiting_queue_resume` with the stable content-free reason
|
|
1187
|
+
`cms.event.awaiting_queue_resume`; all real queue counts remain zero. The read
|
|
1188
|
+
does not create jobs or invoke a provider. The supervised service separately
|
|
1189
|
+
revalidates and resumes that exact event. Once queue insertion succeeds, the
|
|
1190
|
+
flag becomes `false` and ordinary queue state replaces the synthetic status.
|
|
1191
|
+
|
|
1192
|
+
Status is not readiness. Valid independent evidence, signed per-locale
|
|
1193
|
+
approvals, and the all-required-locales release gate remain authoritative.
|
|
1194
|
+
Superseded events, wrong site or credential scope, missing work, altered queue
|
|
1195
|
+
identity, or a corrupt result return a complete fail-closed error rather than a
|
|
1196
|
+
partial optimistic status.
|
|
1197
|
+
|
|
1198
|
+
## Read verified end-to-end lifecycle
|
|
1199
|
+
|
|
1200
|
+
The tenant can separately ask whether the same event is cancelled, still
|
|
1201
|
+
translating, awaiting signed approvals, ready, publishing, blocked, failed, or
|
|
1202
|
+
confirmed as published. This operation has its own schema so a valid
|
|
1203
|
+
queue-progress request cannot be replayed for a broader lifecycle read:
|
|
1204
|
+
|
|
1205
|
+
```http
|
|
1206
|
+
POST /v2/localization/lifecycle HTTP/1.1
|
|
1207
|
+
Content-Type: application/json; charset=utf-8
|
|
1208
|
+
Content-Length: <exact UTF-8 byte count>
|
|
1209
|
+
X-Localization-Signature-Algorithm: <configured algorithm>
|
|
1210
|
+
X-Localization-Key-Id: <same credential that created the event>
|
|
1211
|
+
X-Localization-Signature: <signature>
|
|
1212
|
+
```
|
|
1213
|
+
|
|
1214
|
+
```json
|
|
1215
|
+
{"event_id":"cms-event-184","request_id":"lifecycle-8","requested_at":1788955200,"schema":"blun.cms-localization-lifecycle-request.v1","site_id":"public-site"}
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
The signature, five-minute freshness window, site scope, and original key scope
|
|
1219
|
+
are identical to the progress route. The distinct schema binds the signature to
|
|
1220
|
+
this purpose. The composed runtime always supplies the approval and publication
|
|
1221
|
+
verification authorities. A standalone `WebsiteLocalizationAPI` must supply
|
|
1222
|
+
both authorities together; otherwise this route returns a fail-closed `503`.
|
|
1223
|
+
|
|
1224
|
+
A successful response uses
|
|
1225
|
+
`blun.cms-localization-lifecycle.v3`. It contains the event, site, website
|
|
1226
|
+
version, plan and source-sequence identifiers; aggregate queue counts; required,
|
|
1227
|
+
approved and blocked locales; and an optional content-free delivery summary.
|
|
1228
|
+
The lifecycle `status` is exactly one of:
|
|
1229
|
+
|
|
1230
|
+
- `cancelled`: the exact unpublished event has an accepted signed cancellation;
|
|
1231
|
+
- `queue_recovery`: the event is durably accepted but its per-locale queue rows
|
|
1232
|
+
still await the supervised, idempotent recovery pass;
|
|
1233
|
+
- `processing`: at least one required locale still has queue work;
|
|
1234
|
+
- `localization_failed`: at least one required locale failed terminally;
|
|
1235
|
+
- `awaiting_approval`: all locale results exist, but signed release evidence is
|
|
1236
|
+
missing;
|
|
1237
|
+
- `ready`: every required locale has a current verified approval, but no signed
|
|
1238
|
+
CMS delivery exists yet;
|
|
1239
|
+
- `publishing`: a valid signed delivery is pending, leased, or waiting for retry;
|
|
1240
|
+
- `publication_blocked`: the prepared delivery can no longer be published, for
|
|
1241
|
+
example because an approval expired before acknowledgement;
|
|
1242
|
+
- `publication_failed`: bounded delivery attempts ended terminally;
|
|
1243
|
+
- `published`: the CMS returned the exact signed acknowledgement for the
|
|
1244
|
+
delivery.
|
|
1245
|
+
|
|
1246
|
+
The read path revalidates every successful queue result and signed approval,
|
|
1247
|
+
then checks the complete stored publication envelope, signature, event, plan,
|
|
1248
|
+
source revision and locale set. It does not prepare, claim, renew, retry, sign,
|
|
1249
|
+
repair, or publish anything. A published acknowledgement stays published after
|
|
1250
|
+
its former approval validity window ends; expiration before acknowledgement is
|
|
1251
|
+
reported as `publication_blocked`.
|
|
1252
|
+
|
|
1253
|
+
```json
|
|
1254
|
+
{"approved_locales":["fi-FI"],"blocked_locales":[],"delivery":{"attempts":0,"delivery_id":"blun-cms-delivery-…","last_error_code":null,"last_error_detail_hash":null,"lease_expired":false,"lease_expires_at":null,"max_attempts":5,"next_attempt_at":1788955201.0,"status":"pending"},"event_id":"cms-event-184","plan_id":"blun-l10n-plan-…","queue_counts":{"failed":0,"leased":0,"pending":0,"retry_wait":0,"succeeded":1},"request_id":"lifecycle-8","required_locales":["fi-FI"],"schema":"blun.cms-localization-lifecycle.v3","site_id":"public-site","source_sequence":42,"status":"publishing","website_version":"release-42"}
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
## Failure contract
|
|
1258
|
+
|
|
1259
|
+
Every failure has `Cache-Control: no-store` and contains no customer or provider
|
|
1260
|
+
prose:
|
|
1261
|
+
|
|
1262
|
+
```json
|
|
1263
|
+
{"error":"cms.status.scope_rejected","schema":"blun.website-localization-api.v2","status":"BLOCK"}
|
|
1264
|
+
```
|
|
1265
|
+
|
|
1266
|
+
`401` covers invalid, expired, or wrong-scope signed requests; `409` covers
|
|
1267
|
+
identity, source-sequence, supersession, cancellation, in-flight publication,
|
|
1268
|
+
and legacy-ingress conflicts;
|
|
1269
|
+
`413` and `415` cover body size and media type; `503` covers inconsistent or
|
|
1270
|
+
unavailable durable state, including missing lifecycle authorities and invalid
|
|
1271
|
+
release or delivery evidence, or an inconsistent capability registry. Other
|
|
1272
|
+
invalid input returns `400`, and unexpected failures reduce to `api.internal`
|
|
1273
|
+
with `500`. Clients may retry a transport failure or the exact signed change;
|
|
1274
|
+
they must never modify a request under the same event identity.
|
|
1275
|
+
|
|
1276
|
+
Premortem: schema-v1 ingress could bypass source ordering, a valid credential
|
|
1277
|
+
could enumerate another site's event, a timeout could duplicate locale work, a
|
|
1278
|
+
corrupt queue could look complete, or a status response could leak customer
|
|
1279
|
+
text. The v2-only endpoint, exact event/sequence idempotency, signed site and
|
|
1280
|
+
original-key scoping, durable planner identities, full result revalidation, and
|
|
1281
|
+
content-free responses keep those paths fail-closed.
|
|
1282
|
+
|
|
1283
|
+
Lifecycle premortem: a tenant could probe another site's release, queue success
|
|
1284
|
+
could be mistaken for approval, an expired signature could remain apparently
|
|
1285
|
+
ready, a corrupt outbox row could be skipped, or a status read could mutate a
|
|
1286
|
+
lease. Purpose-bound signed requests, original-credential scope, verified
|
|
1287
|
+
release readiness, complete signed-delivery revalidation, explicit blocked
|
|
1288
|
+
states, and read-only regression checks keep those paths fail-closed.
|
|
1289
|
+
|
|
1290
|
+
Capability premortem: an integration could pin a stale locale list, a valid
|
|
1291
|
+
signature could be replayed across purposes, a changed profile could retain an
|
|
1292
|
+
old digest, a duplicate language could displace another EU language, or a
|
|
1293
|
+
partial response could look authoritative. A separate fresh signed request,
|
|
1294
|
+
canonical whole-object digest, exact 24-language registry validation, unique
|
|
1295
|
+
locale/language/EU-code checks, and all-or-nothing response close those paths.
|
|
1296
|
+
|
|
1297
|
+
Cancellation premortem: a signed request could target another source revision,
|
|
1298
|
+
another accepted key could withdraw a tenant's event, a crash could leave a
|
|
1299
|
+
pending outbox active, or the endpoint could claim to retract a request already
|
|
1300
|
+
in flight. Exact immutable bindings, original-key scope, one transactional
|
|
1301
|
+
ledger/outbox transition, service eligibility filtering, and explicit leased
|
|
1302
|
+
and published conflicts keep those paths fail-closed.
|