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,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const net = require("node:net");
|
|
4
|
+
|
|
5
|
+
const MAX_BYTES = 8 * 1024 * 1024;
|
|
6
|
+
const EXACT_LANGUAGE = /^(?:[A-Za-z]{2,8}|x)(?:-[A-Za-z0-9]{1,8})*$/;
|
|
7
|
+
const TRANSLATION_OPERATIONS = new Set([
|
|
8
|
+
"translate", "translation", "localize", "localization", "transcreate",
|
|
9
|
+
"translation-review", "translation-proofread", "i18n", "l10n",
|
|
10
|
+
]);
|
|
11
|
+
const RESPONSE_OPERATIONS = new Set(["respond", "response", "chat", "answer", "compose"]);
|
|
12
|
+
const HOST_FIELDS = new Set([
|
|
13
|
+
"task_kind", "language", "source_text", "content_type",
|
|
14
|
+
"short_text_reviewed", "key_path", "delivery_channel",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
class LanguageGuardBlocked extends Error {
|
|
18
|
+
constructor(message, code = "language_guard_blocked") {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "LanguageGuardBlocked";
|
|
21
|
+
this.code = code;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function strictString(value, field, required = false) {
|
|
26
|
+
if (value === undefined || value === null) return "";
|
|
27
|
+
if (typeof value !== "string") throw new LanguageGuardBlocked(`${field} must be a string`, "invalid_host_context");
|
|
28
|
+
if (required && !value.trim()) throw new LanguageGuardBlocked(`${field} is required`, "invalid_host_context");
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function routeHostContext(context = {}) {
|
|
33
|
+
if (!context || typeof context !== "object" || Array.isArray(context)) {
|
|
34
|
+
throw new LanguageGuardBlocked("host context must be an object", "invalid_host_context");
|
|
35
|
+
}
|
|
36
|
+
const explicit = strictString(context.task_kind, "task_kind").trim().toLowerCase();
|
|
37
|
+
const operation = strictString(context.operation, "operation").trim().toLowerCase();
|
|
38
|
+
const sourceText = strictString(context.source_text, "source_text");
|
|
39
|
+
const contentType = strictString(context.content_type, "content_type").trim() || "prose";
|
|
40
|
+
if (!new Set(["prose", "title", "meta_description", "ui"]).has(contentType)) {
|
|
41
|
+
throw new LanguageGuardBlocked("invalid content_type", "invalid_host_context");
|
|
42
|
+
}
|
|
43
|
+
const translationEvidence = Boolean(sourceText.trim()) || TRANSLATION_OPERATIONS.has(operation);
|
|
44
|
+
let taskKind;
|
|
45
|
+
if (explicit) {
|
|
46
|
+
if (!new Set(["response", "translation"]).has(explicit)) {
|
|
47
|
+
throw new LanguageGuardBlocked("invalid task_kind", "invalid_host_context");
|
|
48
|
+
}
|
|
49
|
+
taskKind = explicit;
|
|
50
|
+
} else if (translationEvidence) taskKind = "translation";
|
|
51
|
+
else if (!operation || RESPONSE_OPERATIONS.has(operation)) taskKind = "response";
|
|
52
|
+
else throw new LanguageGuardBlocked("unknown host operation", "invalid_host_context");
|
|
53
|
+
|
|
54
|
+
if (taskKind === "translation" && !sourceText.trim()) {
|
|
55
|
+
throw new LanguageGuardBlocked("translation route requires complete source_text", "invalid_host_context");
|
|
56
|
+
}
|
|
57
|
+
if (taskKind === "response" && sourceText.trim()) {
|
|
58
|
+
throw new LanguageGuardBlocked("source_text cannot be downgraded to response", "mode_confusion");
|
|
59
|
+
}
|
|
60
|
+
if (taskKind === "response" && TRANSLATION_OPERATIONS.has(operation)) {
|
|
61
|
+
throw new LanguageGuardBlocked("translation operation cannot be downgraded to response", "mode_confusion");
|
|
62
|
+
}
|
|
63
|
+
if (taskKind === "translation" && RESPONSE_OPERATIONS.has(operation)) {
|
|
64
|
+
throw new LanguageGuardBlocked("response operation conflicts with translation source", "mode_confusion");
|
|
65
|
+
}
|
|
66
|
+
const language = strictString(
|
|
67
|
+
taskKind === "translation"
|
|
68
|
+
? (context.target_language || context.language)
|
|
69
|
+
: (context.response_language || context.language),
|
|
70
|
+
taskKind === "translation" ? "target_language" : "response_language",
|
|
71
|
+
true,
|
|
72
|
+
).trim();
|
|
73
|
+
if (["auto", "all"].includes(language.toLowerCase()) || !EXACT_LANGUAGE.test(language)) {
|
|
74
|
+
throw new LanguageGuardBlocked("exact language or locale is required", "invalid_host_context");
|
|
75
|
+
}
|
|
76
|
+
return { taskKind, language, sourceText: taskKind === "translation" ? sourceText : "", contentType };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseAgentEnvelope(raw) {
|
|
80
|
+
const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw ?? "");
|
|
81
|
+
if (Buffer.byteLength(text, "utf8") > MAX_BYTES) {
|
|
82
|
+
throw new LanguageGuardBlocked("agent envelope is too large", "invalid_envelope");
|
|
83
|
+
}
|
|
84
|
+
let envelope;
|
|
85
|
+
try {
|
|
86
|
+
envelope = JSON.parse(text.replace(/^\uFEFF/, ""));
|
|
87
|
+
} catch {
|
|
88
|
+
throw new LanguageGuardBlocked("agent output is not one JSON release envelope", "invalid_envelope");
|
|
89
|
+
}
|
|
90
|
+
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
|
|
91
|
+
throw new LanguageGuardBlocked("agent envelope must be an object", "invalid_envelope");
|
|
92
|
+
}
|
|
93
|
+
for (const field of Object.keys(envelope)) {
|
|
94
|
+
if (HOST_FIELDS.has(field)) {
|
|
95
|
+
throw new LanguageGuardBlocked(`agent attempted to override host field ${field}`, "host_override");
|
|
96
|
+
}
|
|
97
|
+
if (!new Set(["target_text", "release_token"]).has(field)) {
|
|
98
|
+
throw new LanguageGuardBlocked(`unsupported agent envelope field ${field}`, "invalid_envelope");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (typeof envelope.target_text !== "string" || !envelope.target_text.trim()) {
|
|
102
|
+
throw new LanguageGuardBlocked("target_text is required", "invalid_envelope");
|
|
103
|
+
}
|
|
104
|
+
if (typeof envelope.release_token !== "string" || !envelope.release_token.trim()) {
|
|
105
|
+
throw new LanguageGuardBlocked("release_token is required", "invalid_envelope");
|
|
106
|
+
}
|
|
107
|
+
return envelope;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function endpointOptions(endpoint) {
|
|
111
|
+
const value = String(endpoint || "").trim();
|
|
112
|
+
if (value.startsWith("unix:") && value.slice(5)) return { path: value.slice(5) };
|
|
113
|
+
if (value.startsWith("tcp:")) {
|
|
114
|
+
const match = /^tcp:(127\.0\.0\.1|localhost|::1):(\d+)$/.exec(value);
|
|
115
|
+
const port = match ? Number(match[2]) : 0;
|
|
116
|
+
if (match && port >= 1 && port <= 65535) return { host: match[1], port };
|
|
117
|
+
}
|
|
118
|
+
throw new LanguageGuardBlocked("invalid isolated guard endpoint", "guard_unavailable");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function callGuardService(endpoint, request, { serviceToken = "", timeoutMs = 10000 } = {}) {
|
|
122
|
+
const payload = { ...request };
|
|
123
|
+
if (serviceToken) payload.service_token = serviceToken;
|
|
124
|
+
const encoded = `${JSON.stringify(payload)}\n`;
|
|
125
|
+
if (Buffer.byteLength(encoded, "utf8") > MAX_BYTES) {
|
|
126
|
+
return Promise.reject(new LanguageGuardBlocked("guard request is too large", "guard_unavailable"));
|
|
127
|
+
}
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
let settled = false;
|
|
130
|
+
let raw = "";
|
|
131
|
+
const socket = net.createConnection(endpointOptions(endpoint));
|
|
132
|
+
const finish = (error, value) => {
|
|
133
|
+
if (settled) return;
|
|
134
|
+
settled = true;
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
socket.destroy();
|
|
137
|
+
if (error) reject(error);
|
|
138
|
+
else resolve(value);
|
|
139
|
+
};
|
|
140
|
+
const timer = setTimeout(() => finish(new LanguageGuardBlocked("guard service timed out", "guard_unavailable")), timeoutMs);
|
|
141
|
+
timer.unref?.();
|
|
142
|
+
socket.setEncoding("utf8");
|
|
143
|
+
socket.once("connect", () => socket.write(encoded));
|
|
144
|
+
socket.on("data", chunk => {
|
|
145
|
+
raw += chunk;
|
|
146
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_BYTES) {
|
|
147
|
+
finish(new LanguageGuardBlocked("guard response is too large", "guard_unavailable"));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const newline = raw.indexOf("\n");
|
|
151
|
+
if (newline < 0) return;
|
|
152
|
+
try {
|
|
153
|
+
const response = JSON.parse(raw.slice(0, newline));
|
|
154
|
+
finish(null, response);
|
|
155
|
+
} catch {
|
|
156
|
+
finish(new LanguageGuardBlocked("guard returned invalid JSON", "guard_unavailable"));
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
socket.once("error", () => finish(new LanguageGuardBlocked("guard service is unavailable", "guard_unavailable")));
|
|
160
|
+
socket.once("end", () => {
|
|
161
|
+
if (!settled) finish(new LanguageGuardBlocked("guard closed without a decision", "guard_unavailable"));
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function verifyForDelivery({ rawEnvelope, hostContext, endpoint, serviceToken = "", agentId = "", channel = "" }) {
|
|
167
|
+
const envelope = parseAgentEnvelope(rawEnvelope);
|
|
168
|
+
const route = routeHostContext(hostContext);
|
|
169
|
+
const result = await callGuardService(endpoint, {
|
|
170
|
+
operation: "verify",
|
|
171
|
+
task_kind: route.taskKind,
|
|
172
|
+
source_text: route.sourceText,
|
|
173
|
+
target_text: envelope.target_text,
|
|
174
|
+
language: route.language,
|
|
175
|
+
release_token: envelope.release_token,
|
|
176
|
+
content_type: route.contentType,
|
|
177
|
+
short_text_reviewed: hostContext.short_text_reviewed === true,
|
|
178
|
+
agent_id: String(agentId || ""),
|
|
179
|
+
channel: String(channel || ""),
|
|
180
|
+
}, { serviceToken });
|
|
181
|
+
if (result?.valid !== true) {
|
|
182
|
+
const failedChecks = result?.checks && typeof result.checks === "object"
|
|
183
|
+
? Object.entries(result.checks).filter(([, passed]) => passed !== true).map(([name]) => name)
|
|
184
|
+
: [];
|
|
185
|
+
const detail = failedChecks.length ? ` (failed checks: ${failedChecks.join(", ")})` : "";
|
|
186
|
+
throw new LanguageGuardBlocked(`isolated guard rejected the exact output${detail}`, "receipt_rejected");
|
|
187
|
+
}
|
|
188
|
+
return { text: envelope.target_text, route, verification: result };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function splitTelegramMessage(value, limit = 3900) {
|
|
192
|
+
const chunks = [];
|
|
193
|
+
let remaining = String(value || "").trim();
|
|
194
|
+
while (remaining.length > limit) {
|
|
195
|
+
let splitAt = remaining.lastIndexOf("\n", limit);
|
|
196
|
+
if (splitAt < Math.floor(limit * 0.55)) splitAt = remaining.lastIndexOf(" ", limit);
|
|
197
|
+
if (splitAt < Math.floor(limit * 0.55)) splitAt = limit;
|
|
198
|
+
chunks.push(remaining.slice(0, splitAt).trimEnd());
|
|
199
|
+
remaining = remaining.slice(splitAt).trimStart();
|
|
200
|
+
}
|
|
201
|
+
if (remaining) chunks.push(remaining);
|
|
202
|
+
return chunks;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function guardedTelegramSend(options) {
|
|
206
|
+
const verified = await verifyForDelivery({ ...options, channel: options.channel || "telegram" });
|
|
207
|
+
if (typeof options.telegramRequest !== "function") {
|
|
208
|
+
throw new LanguageGuardBlocked("Telegram transport is unavailable", "delivery_unavailable");
|
|
209
|
+
}
|
|
210
|
+
let lastResult = null;
|
|
211
|
+
for (const text of splitTelegramMessage(verified.text)) {
|
|
212
|
+
lastResult = await options.telegramRequest(options.botToken, "sendMessage", {
|
|
213
|
+
chat_id: options.chatId,
|
|
214
|
+
text,
|
|
215
|
+
...(options.topicId ? { message_thread_id: options.topicId } : {}),
|
|
216
|
+
...(options.replyParameters ? { reply_parameters: options.replyParameters } : {}),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return { sent: true, chunks: splitTelegramMessage(verified.text).length, lastResult, verification: verified.verification };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
module.exports = {
|
|
223
|
+
LanguageGuardBlocked,
|
|
224
|
+
routeHostContext,
|
|
225
|
+
parseAgentEnvelope,
|
|
226
|
+
callGuardService,
|
|
227
|
+
verifyForDelivery,
|
|
228
|
+
splitTelegramMessage,
|
|
229
|
+
guardedTelegramSend,
|
|
230
|
+
};
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Content-free audit records for language-guard release decisions."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import stat
|
|
10
|
+
import time
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Iterator
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
SAFE_LABEL = re.compile(r"[^A-Za-z0-9_.:@/-]+")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _file_identity(details: os.stat_result) -> tuple[int, int, int, int, int]:
|
|
20
|
+
return (
|
|
21
|
+
details.st_dev,
|
|
22
|
+
details.st_ino,
|
|
23
|
+
details.st_size,
|
|
24
|
+
details.st_ctime_ns,
|
|
25
|
+
details.st_mtime_ns,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _directory_identity(details: os.stat_result) -> tuple[int, int, int, int, int]:
|
|
30
|
+
return (
|
|
31
|
+
details.st_dev,
|
|
32
|
+
details.st_ino,
|
|
33
|
+
details.st_mode,
|
|
34
|
+
details.st_uid,
|
|
35
|
+
details.st_gid,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _validate_directory(path: Path, details: os.stat_result) -> None:
|
|
40
|
+
if not stat.S_ISDIR(details.st_mode) or stat.S_ISLNK(details.st_mode):
|
|
41
|
+
raise RuntimeError(f"audit directory must be a real directory: {path}")
|
|
42
|
+
if os.name != "nt" and stat.S_IMODE(details.st_mode) & 0o022:
|
|
43
|
+
raise RuntimeError(f"audit directory must not be writable outside its owner: {path}")
|
|
44
|
+
if hasattr(os, "getuid") and details.st_uid != os.getuid():
|
|
45
|
+
raise RuntimeError(f"audit directory owner is invalid: {path}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _existing_directory_anchor(path: Path) -> Path:
|
|
49
|
+
candidate = path
|
|
50
|
+
while True:
|
|
51
|
+
try:
|
|
52
|
+
candidate.lstat()
|
|
53
|
+
return candidate
|
|
54
|
+
except FileNotFoundError:
|
|
55
|
+
parent = candidate.parent
|
|
56
|
+
if parent == candidate:
|
|
57
|
+
raise RuntimeError(f"audit directory has no existing anchor: {path}")
|
|
58
|
+
candidate = parent
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _assert_directory_unchanged(
|
|
62
|
+
path: Path, expected: tuple[int, int, int, int, int],
|
|
63
|
+
) -> None:
|
|
64
|
+
try:
|
|
65
|
+
current = path.lstat()
|
|
66
|
+
except OSError as error:
|
|
67
|
+
raise RuntimeError(f"audit directory cannot be rechecked: {path}") from error
|
|
68
|
+
_validate_directory(path, current)
|
|
69
|
+
if _directory_identity(current) != expected:
|
|
70
|
+
raise RuntimeError(f"audit directory changed during operation: {path}")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@contextmanager
|
|
74
|
+
def _open_audit_directory(path: Path, *, create: bool) -> Iterator[int | None]:
|
|
75
|
+
if os.name == "nt":
|
|
76
|
+
if create:
|
|
77
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
yield None
|
|
79
|
+
return
|
|
80
|
+
anchor = Path.home()
|
|
81
|
+
try:
|
|
82
|
+
relative = path.parent.relative_to(anchor)
|
|
83
|
+
except ValueError:
|
|
84
|
+
anchor = _existing_directory_anchor(path.parent)
|
|
85
|
+
relative = path.parent.relative_to(anchor)
|
|
86
|
+
flags = (
|
|
87
|
+
os.O_RDONLY
|
|
88
|
+
| getattr(os, "O_BINARY", 0)
|
|
89
|
+
| getattr(os, "O_DIRECTORY", 0)
|
|
90
|
+
| getattr(os, "O_NOFOLLOW", 0)
|
|
91
|
+
)
|
|
92
|
+
descriptor = None
|
|
93
|
+
current_path = anchor
|
|
94
|
+
try:
|
|
95
|
+
try:
|
|
96
|
+
descriptor = os.open(anchor, flags)
|
|
97
|
+
_validate_directory(anchor, os.fstat(descriptor))
|
|
98
|
+
for component in relative.parts:
|
|
99
|
+
current_path = current_path / component
|
|
100
|
+
try:
|
|
101
|
+
child = os.open(component, flags, dir_fd=descriptor)
|
|
102
|
+
except FileNotFoundError:
|
|
103
|
+
if not create:
|
|
104
|
+
raise
|
|
105
|
+
try:
|
|
106
|
+
os.mkdir(component, mode=0o700, dir_fd=descriptor)
|
|
107
|
+
except FileExistsError:
|
|
108
|
+
pass
|
|
109
|
+
child = os.open(component, flags, dir_fd=descriptor)
|
|
110
|
+
try:
|
|
111
|
+
_validate_directory(current_path, os.fstat(child))
|
|
112
|
+
except Exception:
|
|
113
|
+
os.close(child)
|
|
114
|
+
raise
|
|
115
|
+
os.close(descriptor)
|
|
116
|
+
descriptor = child
|
|
117
|
+
expected = _directory_identity(os.fstat(descriptor))
|
|
118
|
+
except (FileNotFoundError, RuntimeError):
|
|
119
|
+
raise
|
|
120
|
+
except OSError as error:
|
|
121
|
+
raise RuntimeError(
|
|
122
|
+
f"audit directory cannot be opened safely: {current_path}"
|
|
123
|
+
) from error
|
|
124
|
+
try:
|
|
125
|
+
yield descriptor
|
|
126
|
+
finally:
|
|
127
|
+
_assert_directory_unchanged(path.parent, expected)
|
|
128
|
+
finally:
|
|
129
|
+
if descriptor is not None:
|
|
130
|
+
os.close(descriptor)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _validate_file(path: Path, details: os.stat_result, label: str) -> None:
|
|
134
|
+
if not stat.S_ISREG(details.st_mode) or stat.S_ISLNK(details.st_mode):
|
|
135
|
+
raise RuntimeError(f"{label} must be a regular file: {path}")
|
|
136
|
+
if details.st_nlink != 1:
|
|
137
|
+
raise RuntimeError(f"{label} must not have additional hard links: {path}")
|
|
138
|
+
if os.name != "nt" and stat.S_IMODE(details.st_mode) & 0o022:
|
|
139
|
+
raise RuntimeError(f"{label} must not be writable outside its owner: {path}")
|
|
140
|
+
if hasattr(os, "getuid") and details.st_uid != os.getuid():
|
|
141
|
+
raise RuntimeError(f"{label} owner is invalid: {path}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _audit_lstat(path: Path, directory: int | None) -> os.stat_result:
|
|
145
|
+
if directory is None:
|
|
146
|
+
return path.lstat()
|
|
147
|
+
return os.stat(path.name, dir_fd=directory, follow_symlinks=False)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _open_audit_file(path: Path, directory: int | None, flags: int, mode: int = 0o600) -> int:
|
|
151
|
+
if directory is None:
|
|
152
|
+
return os.open(path, flags, mode)
|
|
153
|
+
return os.open(path.name, flags, mode, dir_fd=directory)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _path_matches_handle(path: Path, handle, label: str, directory: int | None) -> None:
|
|
157
|
+
try:
|
|
158
|
+
opened = os.fstat(handle.fileno())
|
|
159
|
+
current = _audit_lstat(path, directory)
|
|
160
|
+
except OSError as error:
|
|
161
|
+
raise RuntimeError(f"{label} is unreadable: {path}") from error
|
|
162
|
+
_validate_file(path, opened, label)
|
|
163
|
+
_validate_file(path, current, label)
|
|
164
|
+
if _file_identity(opened) != _file_identity(current):
|
|
165
|
+
raise RuntimeError(f"{label} changed while in use: {path}")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@contextmanager
|
|
169
|
+
def _protected_append(
|
|
170
|
+
path: Path, label: str, encoding: str, directory: int | None,
|
|
171
|
+
) -> Iterator[Any]:
|
|
172
|
+
"""Open one append-only state file without following or replacing links."""
|
|
173
|
+
descriptor = None
|
|
174
|
+
for _attempt in range(3):
|
|
175
|
+
try:
|
|
176
|
+
before = _audit_lstat(path, directory)
|
|
177
|
+
except FileNotFoundError:
|
|
178
|
+
flags = (
|
|
179
|
+
os.O_RDWR | os.O_APPEND | os.O_CREAT | os.O_EXCL
|
|
180
|
+
| getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
|
|
181
|
+
)
|
|
182
|
+
try:
|
|
183
|
+
descriptor = _open_audit_file(path, directory, flags)
|
|
184
|
+
before = None
|
|
185
|
+
break
|
|
186
|
+
except FileExistsError:
|
|
187
|
+
continue
|
|
188
|
+
except OSError as error:
|
|
189
|
+
raise RuntimeError(f"{label} cannot be created: {path}") from error
|
|
190
|
+
except OSError as error:
|
|
191
|
+
raise RuntimeError(f"{label} is unreadable: {path}") from error
|
|
192
|
+
_validate_file(path, before, label)
|
|
193
|
+
flags = (
|
|
194
|
+
os.O_RDWR | os.O_APPEND
|
|
195
|
+
| getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
|
|
196
|
+
)
|
|
197
|
+
try:
|
|
198
|
+
descriptor = _open_audit_file(path, directory, flags)
|
|
199
|
+
except OSError as error:
|
|
200
|
+
raise RuntimeError(f"{label} cannot be opened safely: {path}") from error
|
|
201
|
+
try:
|
|
202
|
+
opened = os.fstat(descriptor)
|
|
203
|
+
_validate_file(path, opened, label)
|
|
204
|
+
if _file_identity(opened) != _file_identity(before):
|
|
205
|
+
raise RuntimeError(f"{label} changed while opening: {path}")
|
|
206
|
+
except (OSError, RuntimeError):
|
|
207
|
+
os.close(descriptor)
|
|
208
|
+
descriptor = None
|
|
209
|
+
raise
|
|
210
|
+
break
|
|
211
|
+
if descriptor is None:
|
|
212
|
+
raise RuntimeError(f"{label} could not be reserved safely: {path}")
|
|
213
|
+
try:
|
|
214
|
+
with os.fdopen(descriptor, "a+", encoding=encoding, newline="\n") as handle:
|
|
215
|
+
descriptor = None
|
|
216
|
+
_path_matches_handle(path, handle, label, directory)
|
|
217
|
+
try:
|
|
218
|
+
yield handle
|
|
219
|
+
finally:
|
|
220
|
+
handle.flush()
|
|
221
|
+
os.fsync(handle.fileno())
|
|
222
|
+
_path_matches_handle(path, handle, label, directory)
|
|
223
|
+
finally:
|
|
224
|
+
if descriptor is not None:
|
|
225
|
+
os.close(descriptor)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def audit_paths_healthy(path: Path) -> bool:
|
|
229
|
+
"""Inspect existing audit state without creating or modifying it."""
|
|
230
|
+
try:
|
|
231
|
+
with _open_audit_directory(path, create=False) as directory:
|
|
232
|
+
for candidate, label in (
|
|
233
|
+
(path, "audit log"),
|
|
234
|
+
(path.with_suffix(path.suffix + ".lock"), "audit lock"),
|
|
235
|
+
):
|
|
236
|
+
try:
|
|
237
|
+
details = _audit_lstat(candidate, directory)
|
|
238
|
+
except FileNotFoundError:
|
|
239
|
+
continue
|
|
240
|
+
except OSError:
|
|
241
|
+
return False
|
|
242
|
+
try:
|
|
243
|
+
_validate_file(candidate, details, label)
|
|
244
|
+
except RuntimeError:
|
|
245
|
+
return False
|
|
246
|
+
return True
|
|
247
|
+
except FileNotFoundError:
|
|
248
|
+
return True
|
|
249
|
+
except (OSError, RuntimeError):
|
|
250
|
+
return False
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def safe_label(value: Any, limit: int = 160) -> str:
|
|
254
|
+
return SAFE_LABEL.sub("_", str(value or "").strip())[:limit]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def finding_codes(result: dict[str, Any]) -> list[str]:
|
|
258
|
+
output: list[str] = []
|
|
259
|
+
for finding in result.get("findings", []):
|
|
260
|
+
if isinstance(finding, dict) and finding.get("code"):
|
|
261
|
+
output.append(safe_label(finding["code"], 80))
|
|
262
|
+
reason = result.get("reason")
|
|
263
|
+
if reason:
|
|
264
|
+
output.append(safe_label(reason, 80))
|
|
265
|
+
checks = result.get("checks")
|
|
266
|
+
if isinstance(checks, dict):
|
|
267
|
+
output.extend(
|
|
268
|
+
f"failed-{safe_label(name, 70)}"
|
|
269
|
+
for name, passed in checks.items()
|
|
270
|
+
if passed is not True
|
|
271
|
+
)
|
|
272
|
+
return sorted(set(filter(None, output)))[:40]
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@contextmanager
|
|
276
|
+
def _exclusive_lock(handle) -> Iterator[None]:
|
|
277
|
+
if os.name == "nt":
|
|
278
|
+
import msvcrt
|
|
279
|
+
|
|
280
|
+
handle.seek(0)
|
|
281
|
+
if not handle.read(1):
|
|
282
|
+
handle.seek(0)
|
|
283
|
+
handle.write("0")
|
|
284
|
+
handle.flush()
|
|
285
|
+
handle.seek(0)
|
|
286
|
+
msvcrt.locking(handle.fileno(), msvcrt.LK_LOCK, 1)
|
|
287
|
+
try:
|
|
288
|
+
yield
|
|
289
|
+
finally:
|
|
290
|
+
handle.seek(0)
|
|
291
|
+
msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
|
|
292
|
+
else:
|
|
293
|
+
import fcntl
|
|
294
|
+
|
|
295
|
+
fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
|
|
296
|
+
try:
|
|
297
|
+
yield
|
|
298
|
+
finally:
|
|
299
|
+
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def append_audit(path: Path, record: dict[str, Any]) -> None:
|
|
303
|
+
"""Append a bounded record. Callers must never pass source or target text."""
|
|
304
|
+
forbidden = {"source", "source_text", "target", "target_text", "release_token", "service_token"}
|
|
305
|
+
if forbidden.intersection(record):
|
|
306
|
+
raise ValueError("audit records cannot contain text or tokens")
|
|
307
|
+
payload = {
|
|
308
|
+
"ts": int(time.time()),
|
|
309
|
+
"event": safe_label(record.get("event", "decision"), 40),
|
|
310
|
+
"allowed": record.get("allowed") is True,
|
|
311
|
+
"task_kind": safe_label(record.get("task_kind"), 24),
|
|
312
|
+
"language": safe_label(record.get("language"), 40),
|
|
313
|
+
"agent_id": safe_label(record.get("agent_id"), 120),
|
|
314
|
+
"channel": safe_label(record.get("channel"), 80),
|
|
315
|
+
"source_sha256": safe_label(record.get("source_sha256"), 64),
|
|
316
|
+
"target_sha256": safe_label(record.get("target_sha256"), 64),
|
|
317
|
+
"guard_version": safe_label(record.get("guard_version"), 32),
|
|
318
|
+
"codes": [safe_label(value, 80) for value in record.get("codes", [])[:40]],
|
|
319
|
+
}
|
|
320
|
+
line = json.dumps(payload, ensure_ascii=True, separators=(",", ":")) + "\n"
|
|
321
|
+
lock_path = path.with_suffix(path.suffix + ".lock")
|
|
322
|
+
with _open_audit_directory(path, create=True) as directory:
|
|
323
|
+
with _protected_append(lock_path, "audit lock", "ascii", directory) as lock_handle:
|
|
324
|
+
with _exclusive_lock(lock_handle):
|
|
325
|
+
_path_matches_handle(lock_path, lock_handle, "audit lock", directory)
|
|
326
|
+
with _protected_append(path, "audit log", "utf-8", directory) as handle:
|
|
327
|
+
handle.write(line)
|