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,42 @@
|
|
|
1
|
+
"""Compatibility import for the portable, skill-bundled commercial validator.
|
|
2
|
+
|
|
3
|
+
The production website runtime and skill-only CLI deliberately execute the same
|
|
4
|
+
implementation. This module is not a service and does not install or start one.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import importlib.util
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_PATH = (
|
|
14
|
+
Path(__file__).resolve().parents[1]
|
|
15
|
+
/ "translate-native"
|
|
16
|
+
/ "scripts"
|
|
17
|
+
/ "commercial_localization_profile.py"
|
|
18
|
+
)
|
|
19
|
+
_SPEC = importlib.util.spec_from_file_location(
|
|
20
|
+
"translate_native_portable_commercial",
|
|
21
|
+
_PATH,
|
|
22
|
+
)
|
|
23
|
+
if _SPEC is None or _SPEC.loader is None:
|
|
24
|
+
raise ImportError("commercial profile implementation unavailable")
|
|
25
|
+
_IMPL = importlib.util.module_from_spec(_SPEC)
|
|
26
|
+
_SPEC.loader.exec_module(_IMPL)
|
|
27
|
+
|
|
28
|
+
hashlib = _IMPL.hashlib
|
|
29
|
+
PUBLIC_PROFILE_SCHEMA = _IMPL.PUBLIC_PROFILE_SCHEMA
|
|
30
|
+
REVIEW_SUMMARY_CAPABILITIES_SCHEMA = _IMPL.REVIEW_SUMMARY_CAPABILITIES_SCHEMA
|
|
31
|
+
REVIEW_SUMMARY_SCHEMA = _IMPL.REVIEW_SUMMARY_SCHEMA
|
|
32
|
+
DIMENSIONS = _IMPL.DIMENSIONS
|
|
33
|
+
_canonical_json = _IMPL._canonical_json
|
|
34
|
+
public_review_summary_contract = _IMPL.public_review_summary_contract
|
|
35
|
+
public_profile = _IMPL.public_profile
|
|
36
|
+
CREATION_GUIDANCE = _IMPL.CREATION_GUIDANCE
|
|
37
|
+
NATIVE_GUIDANCE = _IMPL.NATIVE_GUIDANCE
|
|
38
|
+
FIDELITY_GUIDANCE = _IMPL.FIDELITY_GUIDANCE
|
|
39
|
+
CommercialReviewBlocked = _IMPL.CommercialReviewBlocked
|
|
40
|
+
review_contract = _IMPL.review_contract
|
|
41
|
+
validate_review = _IMPL.validate_review
|
|
42
|
+
validate_summary = _IMPL.validate_summary
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mandatory": true,
|
|
3
|
+
"fail_closed": true,
|
|
4
|
+
"host_owns": [
|
|
5
|
+
"task_kind",
|
|
6
|
+
"language",
|
|
7
|
+
"source_text",
|
|
8
|
+
"content_type",
|
|
9
|
+
"delivery_channel"
|
|
10
|
+
],
|
|
11
|
+
"agent_envelope_fields": [
|
|
12
|
+
"target_text",
|
|
13
|
+
"release_token"
|
|
14
|
+
],
|
|
15
|
+
"isolated_service": {
|
|
16
|
+
"required": true,
|
|
17
|
+
"endpoint": "platform installer supplies a loopback socket",
|
|
18
|
+
"token_file": "owner-only host path",
|
|
19
|
+
"audit_file": "content-free JSONL"
|
|
20
|
+
},
|
|
21
|
+
"routes": {
|
|
22
|
+
"response": "release_response",
|
|
23
|
+
"translation": "translate-native + release_translation"
|
|
24
|
+
},
|
|
25
|
+
"direct_delivery_allowed": false,
|
|
26
|
+
"raw_streaming_allowed": false,
|
|
27
|
+
"on_guard_error": "block"
|
|
28
|
+
}
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Host-owned, fail-closed delivery boundary for agent output.
|
|
3
|
+
|
|
4
|
+
The untrusted agent must return one JSON envelope on stdout:
|
|
5
|
+
|
|
6
|
+
{"target_text": "...", "release_token": "blg6...."}
|
|
7
|
+
|
|
8
|
+
The trusted host supplies task kind, locale, source text, and signing-key
|
|
9
|
+
location as command-line policy. Only the exact text covered by the receipt is
|
|
10
|
+
written to stdout. Diagnostics and rejected candidates are never written to
|
|
11
|
+
stdout.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import contextlib
|
|
18
|
+
import hashlib
|
|
19
|
+
import importlib.util
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import stat
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any, Awaitable, Callable, Iterator, Sequence, TypeVar
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
32
|
+
QUALITY_PATH = ROOT / "translate-native" / "scripts" / "language_quality.py"
|
|
33
|
+
CLIENT_PATH = ROOT / "translate-native" / "scripts" / "guard_service_client.py"
|
|
34
|
+
SPEC = importlib.util.spec_from_file_location("blun_delivery_quality", QUALITY_PATH)
|
|
35
|
+
assert SPEC and SPEC.loader
|
|
36
|
+
QUALITY = importlib.util.module_from_spec(SPEC)
|
|
37
|
+
SPEC.loader.exec_module(QUALITY)
|
|
38
|
+
|
|
39
|
+
CLIENT_SPEC = importlib.util.spec_from_file_location("blun_delivery_service_client", CLIENT_PATH)
|
|
40
|
+
assert CLIENT_SPEC and CLIENT_SPEC.loader
|
|
41
|
+
SERVICE_CLIENT = importlib.util.module_from_spec(CLIENT_SPEC)
|
|
42
|
+
CLIENT_SPEC.loader.exec_module(SERVICE_CLIENT)
|
|
43
|
+
|
|
44
|
+
DEFAULT_KEY_PATH = Path.home() / ".config" / "blun-language-guard" / "signing.key"
|
|
45
|
+
DEFAULT_POLICY_PATH = Path.home() / ".config" / "blun-language-guard" / "delivery-policy.json"
|
|
46
|
+
DEFAULT_MAX_BYTES = 4 * 1024 * 1024
|
|
47
|
+
MAX_POLICY_BYTES = 64 * 1024
|
|
48
|
+
FORBIDDEN_AGENT_FIELDS = {
|
|
49
|
+
"task_kind", "language", "source_text", "content_type",
|
|
50
|
+
"short_text_reviewed", "key_path", "delivery_channel",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class HostPolicy:
|
|
56
|
+
task_kind: str
|
|
57
|
+
language: str
|
|
58
|
+
source_text: str = ""
|
|
59
|
+
content_type: str = "prose"
|
|
60
|
+
short_text_reviewed: bool = False
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class DeliveryBlocked(ValueError):
|
|
64
|
+
"""Raised when an untrusted candidate is not eligible for delivery."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _exact_language(language: str) -> bool:
|
|
68
|
+
return (
|
|
69
|
+
isinstance(language, str)
|
|
70
|
+
and language.casefold() not in {"auto", "all"}
|
|
71
|
+
and re.fullmatch(r"(?:[A-Za-z]{2,8}|x)(?:-[A-Za-z0-9]{1,8})*", language)
|
|
72
|
+
is not None
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def validate_policy(policy: HostPolicy) -> None:
|
|
77
|
+
if policy.task_kind not in {"response", "translation"}:
|
|
78
|
+
raise DeliveryBlocked("host policy has an invalid task kind")
|
|
79
|
+
if not _exact_language(policy.language):
|
|
80
|
+
raise DeliveryBlocked("host policy requires an exact language or locale tag")
|
|
81
|
+
if policy.task_kind == "translation" and not policy.source_text.strip():
|
|
82
|
+
raise DeliveryBlocked("translation policy requires the complete source text")
|
|
83
|
+
if policy.task_kind == "response" and policy.source_text:
|
|
84
|
+
raise DeliveryBlocked("response policy cannot contain translation source text")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def parse_envelope(raw: str, max_bytes: int = DEFAULT_MAX_BYTES) -> dict[str, Any]:
|
|
88
|
+
if len(raw.encode("utf-8")) > max_bytes:
|
|
89
|
+
raise DeliveryBlocked("agent envelope exceeds the configured size limit")
|
|
90
|
+
try:
|
|
91
|
+
envelope = json.loads(raw.lstrip("\ufeff"))
|
|
92
|
+
except json.JSONDecodeError as error:
|
|
93
|
+
raise DeliveryBlocked("agent output is not one valid JSON release envelope") from error
|
|
94
|
+
if not isinstance(envelope, dict):
|
|
95
|
+
raise DeliveryBlocked("agent release envelope must be a JSON object")
|
|
96
|
+
forbidden = sorted(FORBIDDEN_AGENT_FIELDS.intersection(envelope))
|
|
97
|
+
if forbidden:
|
|
98
|
+
raise DeliveryBlocked(
|
|
99
|
+
"agent envelope attempts to override host-owned fields: " + ", ".join(forbidden)
|
|
100
|
+
)
|
|
101
|
+
if set(envelope) - {"target_text", "release_token"}:
|
|
102
|
+
raise DeliveryBlocked("agent release envelope contains unsupported fields")
|
|
103
|
+
if not isinstance(envelope.get("target_text"), str) or not envelope["target_text"].strip():
|
|
104
|
+
raise DeliveryBlocked("agent release envelope has no non-empty target_text")
|
|
105
|
+
if not isinstance(envelope.get("release_token"), str) or not envelope["release_token"].strip():
|
|
106
|
+
raise DeliveryBlocked("agent release envelope has no release_token")
|
|
107
|
+
return envelope
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def verify_envelope(envelope: dict[str, Any], policy: HostPolicy, key: bytes) -> str:
|
|
111
|
+
validate_policy(policy)
|
|
112
|
+
target = envelope["target_text"]
|
|
113
|
+
verification = QUALITY.verify_receipt(
|
|
114
|
+
envelope["release_token"],
|
|
115
|
+
policy.source_text,
|
|
116
|
+
target,
|
|
117
|
+
policy.language,
|
|
118
|
+
key,
|
|
119
|
+
policy.content_type,
|
|
120
|
+
policy.short_text_reviewed,
|
|
121
|
+
purpose=policy.task_kind,
|
|
122
|
+
)
|
|
123
|
+
if not verification.get("valid"):
|
|
124
|
+
failed = [name for name, passed in verification.get("checks", {}).items() if not passed]
|
|
125
|
+
detail = ", ".join(failed) if failed else "invalid receipt"
|
|
126
|
+
raise DeliveryBlocked("release receipt rejected: " + detail)
|
|
127
|
+
return target
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def verify_envelope_with_service(
|
|
131
|
+
envelope: dict[str, Any],
|
|
132
|
+
policy: HostPolicy,
|
|
133
|
+
endpoint: str,
|
|
134
|
+
*,
|
|
135
|
+
service_token: str = "",
|
|
136
|
+
timeout: float = 10.0,
|
|
137
|
+
) -> str:
|
|
138
|
+
validate_policy(policy)
|
|
139
|
+
target = envelope["target_text"]
|
|
140
|
+
result = SERVICE_CLIENT.call_guard_service(
|
|
141
|
+
endpoint,
|
|
142
|
+
{
|
|
143
|
+
"operation": "verify",
|
|
144
|
+
"task_kind": policy.task_kind,
|
|
145
|
+
"source_text": policy.source_text,
|
|
146
|
+
"target_text": target,
|
|
147
|
+
"language": policy.language,
|
|
148
|
+
"release_token": envelope["release_token"],
|
|
149
|
+
"content_type": policy.content_type,
|
|
150
|
+
"short_text_reviewed": policy.short_text_reviewed,
|
|
151
|
+
},
|
|
152
|
+
auth_token=service_token,
|
|
153
|
+
timeout=timeout,
|
|
154
|
+
)
|
|
155
|
+
if not result.get("valid"):
|
|
156
|
+
failed = [name for name, passed in result.get("checks", {}).items() if not passed]
|
|
157
|
+
detail = ", ".join(failed) if failed else "invalid receipt"
|
|
158
|
+
raise DeliveryBlocked("isolated guard rejected the receipt: " + detail)
|
|
159
|
+
return target
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def load_verification_key(path: Path) -> bytes:
|
|
163
|
+
"""Load an existing verifier key without silently creating a new trust root."""
|
|
164
|
+
environment_key = os.environ.get("BLUN_LANGUAGE_GUARD_KEY")
|
|
165
|
+
if environment_key:
|
|
166
|
+
return hashlib.sha256(environment_key.encode("utf-8")).digest()
|
|
167
|
+
try:
|
|
168
|
+
return QUALITY.load_existing_key(path)
|
|
169
|
+
except FileNotFoundError as error:
|
|
170
|
+
raise DeliveryBlocked("signing key is missing; delivery fails closed") from error
|
|
171
|
+
except OSError as error:
|
|
172
|
+
raise DeliveryBlocked("signing key cannot be read") from error
|
|
173
|
+
except ValueError as error:
|
|
174
|
+
if "permissions" in str(error):
|
|
175
|
+
raise DeliveryBlocked("signing key permissions are broader than owner-only") from error
|
|
176
|
+
raise DeliveryBlocked("signing key is invalid") from error
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _policy_identity(details: os.stat_result) -> tuple[int, int, int, int, int, int]:
|
|
180
|
+
return (
|
|
181
|
+
details.st_dev,
|
|
182
|
+
details.st_ino,
|
|
183
|
+
details.st_nlink,
|
|
184
|
+
details.st_size,
|
|
185
|
+
details.st_ctime_ns,
|
|
186
|
+
details.st_mtime_ns,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _policy_directory_identity(
|
|
191
|
+
details: os.stat_result,
|
|
192
|
+
) -> tuple[int, int, int, int, int]:
|
|
193
|
+
return (
|
|
194
|
+
details.st_dev,
|
|
195
|
+
details.st_ino,
|
|
196
|
+
details.st_mode,
|
|
197
|
+
details.st_uid,
|
|
198
|
+
details.st_gid,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _validate_policy_directory(path: Path, details: os.stat_result) -> None:
|
|
203
|
+
if not stat.S_ISDIR(details.st_mode) or stat.S_ISLNK(details.st_mode):
|
|
204
|
+
raise DeliveryBlocked(f"installed delivery policy directory is invalid: {path}")
|
|
205
|
+
if os.name != "nt" and stat.S_IMODE(details.st_mode) & 0o022:
|
|
206
|
+
raise DeliveryBlocked(
|
|
207
|
+
f"installed delivery policy directory is writable outside its owner: {path}"
|
|
208
|
+
)
|
|
209
|
+
if hasattr(os, "getuid") and details.st_uid != os.getuid():
|
|
210
|
+
raise DeliveryBlocked(
|
|
211
|
+
f"installed delivery policy directory has the wrong owner: {path}"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _existing_policy_directory_anchor(path: Path) -> Path:
|
|
216
|
+
candidate = path
|
|
217
|
+
while True:
|
|
218
|
+
try:
|
|
219
|
+
candidate.lstat()
|
|
220
|
+
return candidate
|
|
221
|
+
except FileNotFoundError:
|
|
222
|
+
parent = candidate.parent
|
|
223
|
+
if parent == candidate:
|
|
224
|
+
raise DeliveryBlocked(
|
|
225
|
+
f"installed delivery policy directory has no existing anchor: {path}"
|
|
226
|
+
)
|
|
227
|
+
candidate = parent
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _assert_policy_directory_unchanged(
|
|
231
|
+
path: Path,
|
|
232
|
+
expected: tuple[int, int, int, int, int],
|
|
233
|
+
) -> None:
|
|
234
|
+
try:
|
|
235
|
+
current = path.lstat()
|
|
236
|
+
except OSError as error:
|
|
237
|
+
raise DeliveryBlocked(
|
|
238
|
+
f"installed delivery policy directory cannot be rechecked: {path}"
|
|
239
|
+
) from error
|
|
240
|
+
_validate_policy_directory(path, current)
|
|
241
|
+
if _policy_directory_identity(current) != expected:
|
|
242
|
+
raise DeliveryBlocked(
|
|
243
|
+
f"installed delivery policy directory changed while reading: {path}"
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@contextlib.contextmanager
|
|
248
|
+
def _open_policy_directory(path: Path) -> Iterator[int | None]:
|
|
249
|
+
if os.name == "nt":
|
|
250
|
+
yield None
|
|
251
|
+
return
|
|
252
|
+
anchor = Path.home()
|
|
253
|
+
try:
|
|
254
|
+
relative = path.parent.relative_to(anchor)
|
|
255
|
+
except ValueError:
|
|
256
|
+
anchor = _existing_policy_directory_anchor(path.parent)
|
|
257
|
+
relative = path.parent.relative_to(anchor)
|
|
258
|
+
flags = (
|
|
259
|
+
os.O_RDONLY
|
|
260
|
+
| getattr(os, "O_BINARY", 0)
|
|
261
|
+
| getattr(os, "O_DIRECTORY", 0)
|
|
262
|
+
| getattr(os, "O_NOFOLLOW", 0)
|
|
263
|
+
)
|
|
264
|
+
descriptor = None
|
|
265
|
+
current_path = anchor
|
|
266
|
+
try:
|
|
267
|
+
try:
|
|
268
|
+
descriptor = os.open(anchor, flags)
|
|
269
|
+
_validate_policy_directory(anchor, os.fstat(descriptor))
|
|
270
|
+
for component in relative.parts:
|
|
271
|
+
current_path = current_path / component
|
|
272
|
+
try:
|
|
273
|
+
child = os.open(component, flags, dir_fd=descriptor)
|
|
274
|
+
except FileNotFoundError:
|
|
275
|
+
yield None
|
|
276
|
+
return
|
|
277
|
+
try:
|
|
278
|
+
_validate_policy_directory(current_path, os.fstat(child))
|
|
279
|
+
except Exception:
|
|
280
|
+
os.close(child)
|
|
281
|
+
raise
|
|
282
|
+
os.close(descriptor)
|
|
283
|
+
descriptor = child
|
|
284
|
+
expected = _policy_directory_identity(os.fstat(descriptor))
|
|
285
|
+
except DeliveryBlocked:
|
|
286
|
+
raise
|
|
287
|
+
except OSError as error:
|
|
288
|
+
raise DeliveryBlocked(
|
|
289
|
+
f"installed delivery policy directory cannot be opened safely: {current_path}"
|
|
290
|
+
) from error
|
|
291
|
+
try:
|
|
292
|
+
yield descriptor
|
|
293
|
+
finally:
|
|
294
|
+
_assert_policy_directory_unchanged(path.parent, expected)
|
|
295
|
+
finally:
|
|
296
|
+
if descriptor is not None:
|
|
297
|
+
os.close(descriptor)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _policy_lstat(path: Path, directory: int | None) -> os.stat_result:
|
|
301
|
+
if directory is None:
|
|
302
|
+
return path.lstat()
|
|
303
|
+
return os.stat(path.name, dir_fd=directory, follow_symlinks=False)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _open_policy_file(path: Path, directory: int | None, flags: int) -> int:
|
|
307
|
+
if directory is None:
|
|
308
|
+
return os.open(path, flags)
|
|
309
|
+
return os.open(path.name, flags, dir_fd=directory)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _policy_fstat(descriptor: int) -> os.stat_result:
|
|
313
|
+
return os.fstat(descriptor)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _read_protected_policy_at(
|
|
317
|
+
path: Path,
|
|
318
|
+
directory: int | None,
|
|
319
|
+
) -> dict[str, Any] | None:
|
|
320
|
+
try:
|
|
321
|
+
before = _policy_lstat(path, directory)
|
|
322
|
+
except FileNotFoundError:
|
|
323
|
+
return None
|
|
324
|
+
except OSError as error:
|
|
325
|
+
raise DeliveryBlocked("installed delivery policy is unreadable") from error
|
|
326
|
+
if not stat.S_ISREG(before.st_mode) or stat.S_ISLNK(before.st_mode):
|
|
327
|
+
raise DeliveryBlocked("installed delivery policy must be a regular file")
|
|
328
|
+
if before.st_nlink != 1:
|
|
329
|
+
raise DeliveryBlocked("installed delivery policy must not have additional hard links")
|
|
330
|
+
if before.st_size > MAX_POLICY_BYTES:
|
|
331
|
+
raise DeliveryBlocked("installed delivery policy exceeds the size limit")
|
|
332
|
+
if os.name != "nt" and stat.S_IMODE(before.st_mode) & 0o077:
|
|
333
|
+
raise DeliveryBlocked("installed delivery policy permissions are broader than owner-only")
|
|
334
|
+
if hasattr(os, "getuid") and before.st_uid != os.getuid():
|
|
335
|
+
raise DeliveryBlocked("installed delivery policy owner is invalid")
|
|
336
|
+
|
|
337
|
+
flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
|
|
338
|
+
try:
|
|
339
|
+
descriptor = _open_policy_file(path, directory, flags)
|
|
340
|
+
with os.fdopen(descriptor, "rb") as handle:
|
|
341
|
+
opened = _policy_fstat(handle.fileno())
|
|
342
|
+
if not stat.S_ISREG(opened.st_mode) or _policy_identity(opened) != _policy_identity(before):
|
|
343
|
+
raise DeliveryBlocked("installed delivery policy changed while opening")
|
|
344
|
+
raw = handle.read(MAX_POLICY_BYTES + 1)
|
|
345
|
+
after_read = _policy_fstat(handle.fileno())
|
|
346
|
+
after_path = _policy_lstat(path, directory)
|
|
347
|
+
except DeliveryBlocked:
|
|
348
|
+
raise
|
|
349
|
+
except OSError as error:
|
|
350
|
+
raise DeliveryBlocked("installed delivery policy is unreadable") from error
|
|
351
|
+
if len(raw) > MAX_POLICY_BYTES:
|
|
352
|
+
raise DeliveryBlocked("installed delivery policy exceeds the size limit")
|
|
353
|
+
if (
|
|
354
|
+
_policy_identity(after_read) != _policy_identity(opened)
|
|
355
|
+
or _policy_identity(after_path) != _policy_identity(opened)
|
|
356
|
+
):
|
|
357
|
+
raise DeliveryBlocked("installed delivery policy changed while reading")
|
|
358
|
+
try:
|
|
359
|
+
policy = json.loads(raw.decode("utf-8-sig"))
|
|
360
|
+
except (UnicodeError, json.JSONDecodeError) as error:
|
|
361
|
+
raise DeliveryBlocked("installed delivery policy is unreadable") from error
|
|
362
|
+
if not isinstance(policy, dict):
|
|
363
|
+
raise DeliveryBlocked("installed delivery policy is invalid")
|
|
364
|
+
return policy
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _read_protected_policy(path: Path) -> dict[str, Any] | None:
|
|
368
|
+
with _open_policy_directory(path) as directory:
|
|
369
|
+
if os.name != "nt" and directory is None:
|
|
370
|
+
return None
|
|
371
|
+
return _read_protected_policy_at(path, directory)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def load_installed_service_policy(path: Path = DEFAULT_POLICY_PATH) -> dict[str, Any]:
|
|
375
|
+
policy = _read_protected_policy(path)
|
|
376
|
+
if policy is None:
|
|
377
|
+
return {}
|
|
378
|
+
isolated = policy.get("isolated_service")
|
|
379
|
+
if (
|
|
380
|
+
policy.get("mandatory") is not True
|
|
381
|
+
or not isinstance(isolated, dict)
|
|
382
|
+
or isolated.get("required") is not True
|
|
383
|
+
or not isinstance(isolated.get("endpoint"), str)
|
|
384
|
+
or not isolated["endpoint"].strip()
|
|
385
|
+
or len(isolated["endpoint"]) > 4096
|
|
386
|
+
or not isinstance(isolated.get("token_file"), str)
|
|
387
|
+
or not isolated["token_file"].strip()
|
|
388
|
+
or len(isolated["token_file"]) > 4096
|
|
389
|
+
or ("fail_closed" in policy and policy["fail_closed"] is not True)
|
|
390
|
+
or ("direct_delivery_allowed" in policy and policy["direct_delivery_allowed"] is not False)
|
|
391
|
+
or ("raw_streaming_allowed" in policy and policy["raw_streaming_allowed"] is not False)
|
|
392
|
+
or ("on_guard_error" in policy and policy["on_guard_error"] != "block")
|
|
393
|
+
):
|
|
394
|
+
raise DeliveryBlocked("installed delivery policy is invalid")
|
|
395
|
+
return isolated
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
ResultT = TypeVar("ResultT")
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def guarded_send(
|
|
402
|
+
raw_envelope: str,
|
|
403
|
+
policy: HostPolicy,
|
|
404
|
+
key: bytes,
|
|
405
|
+
send: Callable[[str], ResultT],
|
|
406
|
+
*,
|
|
407
|
+
max_bytes: int = DEFAULT_MAX_BYTES,
|
|
408
|
+
) -> ResultT:
|
|
409
|
+
"""Verify first, then invoke a synchronous delivery function exactly once."""
|
|
410
|
+
target = verify_envelope(parse_envelope(raw_envelope, max_bytes), policy, key)
|
|
411
|
+
return send(target)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
async def guarded_send_async(
|
|
415
|
+
raw_envelope: str,
|
|
416
|
+
policy: HostPolicy,
|
|
417
|
+
key: bytes,
|
|
418
|
+
send: Callable[[str], Awaitable[ResultT]],
|
|
419
|
+
*,
|
|
420
|
+
max_bytes: int = DEFAULT_MAX_BYTES,
|
|
421
|
+
) -> ResultT:
|
|
422
|
+
"""Verify first, then invoke an asynchronous API/Telegram sender exactly once."""
|
|
423
|
+
target = verify_envelope(parse_envelope(raw_envelope, max_bytes), policy, key)
|
|
424
|
+
return await send(target)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _read_source(path: Path | None, task_kind: str) -> str:
|
|
428
|
+
if path is None:
|
|
429
|
+
return ""
|
|
430
|
+
if task_kind != "translation":
|
|
431
|
+
raise DeliveryBlocked("--source-file is valid only for translation tasks")
|
|
432
|
+
try:
|
|
433
|
+
return path.read_text(encoding="utf-8-sig")
|
|
434
|
+
except OSError as error:
|
|
435
|
+
raise DeliveryBlocked("cannot read the trusted translation source") from error
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _untrusted_environment(policy: HostPolicy) -> dict[str, str]:
|
|
439
|
+
environment = dict(os.environ)
|
|
440
|
+
environment.pop("BLUN_LANGUAGE_GUARD_KEY", None)
|
|
441
|
+
environment.pop("BLUN_LANGUAGE_GUARD_KEY_FILE", None)
|
|
442
|
+
environment.pop("BLUN_LANGUAGE_GUARD_SERVICE_TOKEN", None)
|
|
443
|
+
environment.pop("BLUN_LANGUAGE_GUARD_SERVICE_TOKEN_FILE", None)
|
|
444
|
+
environment["BLUN_LANGUAGE_GUARD_MANDATORY"] = "1"
|
|
445
|
+
environment["BLUN_LANGUAGE_GUARD_TASK_KIND"] = policy.task_kind
|
|
446
|
+
environment["BLUN_LANGUAGE_GUARD_LANGUAGE"] = policy.language
|
|
447
|
+
environment["BLUN_LANGUAGE_GUARD_CONTENT_TYPE"] = policy.content_type
|
|
448
|
+
return environment
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _run_agent(command: Sequence[str], timeout: float, max_bytes: int, policy: HostPolicy) -> str:
|
|
452
|
+
if not command:
|
|
453
|
+
return sys.stdin.read(max_bytes + 1)
|
|
454
|
+
actual = list(command)
|
|
455
|
+
if actual and actual[0] == "--":
|
|
456
|
+
actual = actual[1:]
|
|
457
|
+
if not actual:
|
|
458
|
+
raise DeliveryBlocked("missing agent command after --")
|
|
459
|
+
try:
|
|
460
|
+
completed = subprocess.run(
|
|
461
|
+
actual,
|
|
462
|
+
text=True,
|
|
463
|
+
encoding="utf-8",
|
|
464
|
+
errors="strict",
|
|
465
|
+
capture_output=True,
|
|
466
|
+
check=False,
|
|
467
|
+
timeout=timeout,
|
|
468
|
+
env=_untrusted_environment(policy),
|
|
469
|
+
)
|
|
470
|
+
except (OSError, subprocess.SubprocessError, UnicodeError) as error:
|
|
471
|
+
raise DeliveryBlocked("agent process failed before guarded delivery") from error
|
|
472
|
+
if completed.returncode != 0:
|
|
473
|
+
raise DeliveryBlocked(f"agent process exited with status {completed.returncode}")
|
|
474
|
+
if len(completed.stdout.encode("utf-8")) > max_bytes:
|
|
475
|
+
raise DeliveryBlocked("agent envelope exceeds the configured size limit")
|
|
476
|
+
return completed.stdout
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
480
|
+
parser = argparse.ArgumentParser(description="Deliver only language-guarded agent output")
|
|
481
|
+
parser.add_argument("--task-kind", required=True, choices=("response", "translation"))
|
|
482
|
+
parser.add_argument("--language", required=True, help="Trusted exact BCP-47 language tag")
|
|
483
|
+
parser.add_argument("--source-file", type=Path, help="Trusted complete source for translations")
|
|
484
|
+
parser.add_argument("--content-type", default="prose")
|
|
485
|
+
parser.add_argument("--short-text-reviewed", action="store_true")
|
|
486
|
+
parser.add_argument("--key-file", type=Path, default=DEFAULT_KEY_PATH)
|
|
487
|
+
parser.add_argument("--service-endpoint", default=os.environ.get("BLUN_LANGUAGE_GUARD_SERVICE_ENDPOINT", ""))
|
|
488
|
+
parser.add_argument("--service-token-file", type=Path)
|
|
489
|
+
parser.add_argument("--require-service", action="store_true", help="Disable same-user key verification")
|
|
490
|
+
parser.add_argument("--policy-file", type=Path, default=DEFAULT_POLICY_PATH)
|
|
491
|
+
parser.add_argument("--timeout", type=float, default=300.0)
|
|
492
|
+
parser.add_argument("--max-bytes", type=int, default=DEFAULT_MAX_BYTES)
|
|
493
|
+
parser.add_argument("command", nargs=argparse.REMAINDER)
|
|
494
|
+
return parser
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
498
|
+
args = build_parser().parse_args(argv)
|
|
499
|
+
try:
|
|
500
|
+
if args.timeout <= 0 or args.max_bytes <= 0:
|
|
501
|
+
raise DeliveryBlocked("timeout and max-bytes must be positive")
|
|
502
|
+
source = _read_source(args.source_file, args.task_kind)
|
|
503
|
+
policy = HostPolicy(
|
|
504
|
+
task_kind=args.task_kind,
|
|
505
|
+
language=args.language,
|
|
506
|
+
source_text=source,
|
|
507
|
+
content_type=args.content_type,
|
|
508
|
+
short_text_reviewed=args.short_text_reviewed,
|
|
509
|
+
)
|
|
510
|
+
validate_policy(policy)
|
|
511
|
+
raw = _run_agent(args.command, args.timeout, args.max_bytes, policy)
|
|
512
|
+
envelope = parse_envelope(raw, args.max_bytes)
|
|
513
|
+
installed_service = load_installed_service_policy(args.policy_file)
|
|
514
|
+
service_endpoint = args.service_endpoint or str(installed_service.get("endpoint", ""))
|
|
515
|
+
token_file = args.service_token_file
|
|
516
|
+
if token_file is None and installed_service.get("token_file"):
|
|
517
|
+
token_file = Path(str(installed_service["token_file"]))
|
|
518
|
+
require_service = args.require_service or installed_service.get("required") is True
|
|
519
|
+
if service_endpoint:
|
|
520
|
+
service_token = ""
|
|
521
|
+
if token_file:
|
|
522
|
+
service_token = SERVICE_CLIENT.load_service_token(token_file)
|
|
523
|
+
target = verify_envelope_with_service(
|
|
524
|
+
envelope,
|
|
525
|
+
policy,
|
|
526
|
+
service_endpoint,
|
|
527
|
+
service_token=service_token,
|
|
528
|
+
timeout=min(args.timeout, 30.0),
|
|
529
|
+
)
|
|
530
|
+
else:
|
|
531
|
+
if require_service:
|
|
532
|
+
raise DeliveryBlocked("isolated guard service is required")
|
|
533
|
+
key = load_verification_key(args.key_file)
|
|
534
|
+
target = verify_envelope(envelope, policy, key)
|
|
535
|
+
except (DeliveryBlocked, SERVICE_CLIENT.GuardServiceError, OSError) as error:
|
|
536
|
+
print(f"BLOCK: {error}", file=sys.stderr)
|
|
537
|
+
return 1
|
|
538
|
+
sys.stdout.write(target)
|
|
539
|
+
return 0
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
if __name__ == "__main__":
|
|
543
|
+
raise SystemExit(main())
|