@zergai/ztc 0.4.0-beta.1 → 0.5.0-beta.1
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/README.md +398 -45
- package/RUST_THIRD_PARTY_LICENSES.txt +1080 -822
- package/THIRD_PARTY_NOTICES.md +273 -12
- package/bin/zmux-darwin-arm64 +0 -0
- package/bin/ztc-audio-darwin-arm64 +0 -0
- package/bin/ztc-capture-darwin-arm64 +0 -0
- package/bin/ztc-computer-darwin-arm64 +0 -0
- package/bin/ztc-llm-darwin-arm64 +0 -0
- package/bin/ztc-tts-darwin-arm64 +0 -0
- package/bin/ztc-tui-darwin-arm64 +0 -0
- package/bin/ztc-zlicky-overlay-darwin-arm64 +0 -0
- package/bin/zterm-darwin-arm64 +0 -0
- package/dist/App.js +1206 -209
- package/dist/agent/agent.d.ts +74 -8
- package/dist/agent/agent.js +1073 -184
- package/dist/agent/backends/account_rotation.d.ts +4 -0
- package/dist/agent/backends/account_rotation.js +8 -2
- package/dist/agent/backends/anthropic.d.ts +13 -0
- package/dist/agent/backends/anthropic.js +169 -28
- package/dist/agent/backends/claude_cli.js +18 -8
- package/dist/agent/backends/codex_app_server.d.ts +1 -0
- package/dist/agent/backends/codex_app_server.js +55 -3
- package/dist/agent/backends/codex_auth.d.ts +1 -0
- package/dist/agent/backends/codex_auth.js +67 -13
- package/dist/agent/backends/codex_buffered_recovery.d.ts +27 -0
- package/dist/agent/backends/codex_buffered_recovery.js +293 -0
- package/dist/agent/backends/gemini.js +3 -1
- package/dist/agent/backends/http_error.d.ts +26 -0
- package/dist/agent/backends/http_error.js +221 -0
- package/dist/agent/backends/index.d.ts +9 -2
- package/dist/agent/backends/index.js +22 -2
- package/dist/agent/backends/openai.d.ts +0 -6
- package/dist/agent/backends/openai.js +5 -2
- package/dist/agent/backends/openai_compatible.d.ts +5 -4
- package/dist/agent/backends/openai_compatible.js +26 -19
- package/dist/agent/backends/openai_responses.d.ts +8 -3
- package/dist/agent/backends/openai_responses.js +77 -33
- package/dist/agent/backends/provider_ids.d.ts +1 -0
- package/dist/agent/backends/provider_ids.js +4 -0
- package/dist/agent/backends/request_metrics.js +12 -2
- package/dist/agent/backends/types.d.ts +109 -6
- package/dist/agent/backends/types.js +5 -1
- package/dist/agent/backends/zrouter_stream.d.ts +6 -1
- package/dist/agent/backends/zrouter_stream.js +160 -42
- package/dist/agent/background_registry.d.ts +2 -0
- package/dist/agent/background_registry.js +1 -0
- package/dist/agent/claim_observation.d.ts +6 -0
- package/dist/agent/claim_observation.js +54 -0
- package/dist/agent/commands/accounts.js +46 -177
- package/dist/agent/commands/attention.d.ts +2 -0
- package/dist/agent/commands/attention.js +67 -0
- package/dist/agent/commands/auth.js +18 -2
- package/dist/agent/commands/board.d.ts +2 -0
- package/dist/agent/commands/board.js +169 -0
- package/dist/agent/commands/clipboard.js +50 -1
- package/dist/agent/commands/compact.js +21 -13
- package/dist/agent/commands/config.js +4 -1
- package/dist/agent/commands/context.js +6 -1
- package/dist/agent/commands/desktop.d.ts +2 -0
- package/dist/agent/commands/desktop.js +29 -0
- package/dist/agent/commands/effort.js +14 -6
- package/dist/agent/commands/environment.d.ts +3 -0
- package/dist/agent/commands/environment.js +109 -0
- package/dist/agent/commands/evidence.d.ts +2 -0
- package/dist/agent/commands/evidence.js +9 -0
- package/dist/agent/commands/fork.js +4 -3
- package/dist/agent/commands/goal.d.ts +33 -1
- package/dist/agent/commands/goal.js +174 -14
- package/dist/agent/commands/goal_exit.d.ts +36 -0
- package/dist/agent/commands/goal_exit.js +120 -0
- package/dist/agent/commands/help.js +3 -1
- package/dist/agent/commands/history.js +3 -1
- package/dist/agent/commands/inbox.d.ts +2 -0
- package/dist/agent/commands/inbox.js +148 -0
- package/dist/agent/commands/index.js +16 -1
- package/dist/agent/commands/input_mode.js +8 -6
- package/dist/agent/commands/job.d.ts +2 -0
- package/dist/agent/commands/job.js +93 -0
- package/dist/agent/commands/keybindings.js +4 -1
- package/dist/agent/commands/label.d.ts +2 -0
- package/dist/agent/commands/label.js +77 -0
- package/dist/agent/commands/layout.d.ts +2 -0
- package/dist/agent/commands/layout.js +20 -0
- package/dist/agent/commands/mirror.js +16 -1
- package/dist/agent/commands/model.js +12 -2
- package/dist/agent/commands/permissions.d.ts +3 -1
- package/dist/agent/commands/permissions.js +79 -89
- package/dist/agent/commands/plan.js +5 -15
- package/dist/agent/commands/profile.js +21 -2
- package/dist/agent/commands/provider_selection.js +20 -1
- package/dist/agent/commands/rename.js +1 -1
- package/dist/agent/commands/resources.d.ts +2 -0
- package/dist/agent/commands/resources.js +67 -0
- package/dist/agent/commands/rewind.d.ts +3 -0
- package/dist/agent/commands/rewind.js +16 -0
- package/dist/agent/commands/runbooks.d.ts +2 -0
- package/dist/agent/commands/runbooks.js +73 -0
- package/dist/agent/commands/runbooks_lazy.d.ts +2 -0
- package/dist/agent/commands/runbooks_lazy.js +8 -0
- package/dist/agent/commands/sessions.d.ts +1 -1
- package/dist/agent/commands/sessions.js +33 -5
- package/dist/agent/commands/settings.js +185 -39
- package/dist/agent/commands/setup.d.ts +2 -0
- package/dist/agent/commands/setup.js +78 -0
- package/dist/agent/commands/share.d.ts +2 -0
- package/dist/agent/commands/share.js +64 -0
- package/dist/agent/commands/status.js +14 -2
- package/dist/agent/commands/types.d.ts +99 -6
- package/dist/agent/commands/update.js +8 -0
- package/dist/agent/commands/usage.d.ts +2 -0
- package/dist/agent/commands/usage.js +8 -0
- package/dist/agent/factory.d.ts +11 -0
- package/dist/agent/factory.js +113 -88
- package/dist/agent/loop_policy.d.ts +6 -3
- package/dist/agent/loop_policy.js +12 -3
- package/dist/agent/measurement_guidance.d.ts +2 -0
- package/dist/agent/measurement_guidance.js +5 -0
- package/dist/agent/request_encoder.d.ts +8 -1
- package/dist/agent/request_encoder.js +95 -23
- package/dist/agent/runtime/benchmark_comparison.d.ts +30 -0
- package/dist/agent/runtime/benchmark_comparison.js +94 -0
- package/dist/agent/runtime/benchmark_contract.d.ts +48 -0
- package/dist/agent/runtime/benchmark_contract.js +90 -0
- package/dist/agent/runtime/checked_commands.d.ts +32 -0
- package/dist/agent/runtime/checked_commands.js +119 -0
- package/dist/agent/runtime/command_execution_policy.d.ts +26 -0
- package/dist/agent/runtime/command_execution_policy.js +91 -0
- package/dist/agent/runtime/command_jobs.d.ts +36 -18
- package/dist/agent/runtime/command_jobs.js +151 -13
- package/dist/agent/runtime/command_panes.d.ts +59 -0
- package/dist/agent/runtime/command_panes.js +398 -0
- package/dist/agent/runtime/command_wait.d.ts +19 -0
- package/dist/agent/runtime/command_wait.js +167 -0
- package/dist/agent/runtime/cpu_benchmark.d.ts +30 -0
- package/dist/agent/runtime/cpu_benchmark.js +126 -0
- package/dist/agent/runtime/delegated_evidence.d.ts +26 -0
- package/dist/agent/runtime/delegated_evidence.js +80 -0
- package/dist/agent/runtime/evidence.d.ts +127 -0
- package/dist/agent/runtime/evidence.js +468 -0
- package/dist/agent/runtime/execution_context.d.ts +7 -0
- package/dist/agent/runtime/execution_context.js +39 -3
- package/dist/agent/runtime/json_verifier_recipe.d.ts +8 -0
- package/dist/agent/runtime/json_verifier_recipe.js +66 -0
- package/dist/agent/runtime/measurements.d.ts +110 -0
- package/dist/agent/runtime/measurements.js +317 -0
- package/dist/agent/runtime/policy.d.ts +4 -0
- package/dist/agent/runtime/policy.js +31 -20
- package/dist/agent/runtime/provider_native_evidence.d.ts +20 -0
- package/dist/agent/runtime/provider_native_evidence.js +86 -0
- package/dist/agent/runtime/recovery.d.ts +2 -0
- package/dist/agent/runtime/recovery.js +45 -2
- package/dist/agent/runtime/remote_broker_transport.d.ts +10 -0
- package/dist/agent/runtime/remote_broker_transport.js +84 -0
- package/dist/agent/runtime/remote_deletion_receipts.d.ts +3 -0
- package/dist/agent/runtime/remote_deletion_receipts.js +43 -0
- package/dist/agent/runtime/remote_journal_replay.d.ts +13 -0
- package/dist/agent/runtime/remote_journal_replay.js +233 -0
- package/dist/agent/runtime/remote_mutation_journal.d.ts +25 -0
- package/dist/agent/runtime/remote_mutation_journal.js +208 -0
- package/dist/agent/runtime/remote_resource_endpoint.d.ts +7 -0
- package/dist/agent/runtime/remote_resource_endpoint.js +6 -0
- package/dist/agent/runtime/remote_resource_errors.d.ts +5 -0
- package/dist/agent/runtime/remote_resource_errors.js +9 -0
- package/dist/agent/runtime/remote_resource_script.d.ts +2 -0
- package/dist/agent/runtime/remote_resource_script.js +1009 -0
- package/dist/agent/runtime/remote_resources.d.ts +29 -0
- package/dist/agent/runtime/remote_resources.js +160 -0
- package/dist/agent/runtime/source_verification.d.ts +12 -0
- package/dist/agent/runtime/source_verification.js +210 -0
- package/dist/agent/runtime/source_verification_contract.d.ts +75 -0
- package/dist/agent/runtime/source_verification_contract.js +203 -0
- package/dist/agent/runtime/tool_checkpoints.d.ts +11 -0
- package/dist/agent/runtime/tool_checkpoints.js +26 -0
- package/dist/agent/runtime/tool_effects.d.ts +5 -0
- package/dist/agent/runtime/tool_progress.d.ts +12 -0
- package/dist/agent/runtime/tool_progress.js +76 -0
- package/dist/agent/runtime/tool_runtime.d.ts +16 -1
- package/dist/agent/runtime/tool_runtime.js +204 -10
- package/dist/agent/runtime/tracing.d.ts +1 -1
- package/dist/agent/runtime/verifier_capture.d.ts +9 -0
- package/dist/agent/runtime/verifier_capture.js +100 -0
- package/dist/agent/runtime/verifier_contract.d.ts +24 -0
- package/dist/agent/runtime/verifier_contract.js +41 -0
- package/dist/agent/subagent.d.ts +20 -10
- package/dist/agent/subagent.js +266 -29
- package/dist/agent/tool_response_directory.d.ts +24 -0
- package/dist/agent/tool_response_directory.js +61 -0
- package/dist/agent/tool_response_source.d.ts +8 -0
- package/dist/agent/tool_response_source.js +49 -0
- package/dist/agent/tool_response_store.d.ts +75 -4
- package/dist/agent/tool_response_store.js +583 -64
- package/dist/agent/tools/ask_user.d.ts +3 -1
- package/dist/agent/tools/ask_user.js +30 -11
- package/dist/agent/tools/background.js +1 -0
- package/dist/agent/tools/benchmark_cpu.d.ts +2 -0
- package/dist/agent/tools/benchmark_cpu.js +102 -0
- package/dist/agent/tools/compare_benchmarks.d.ts +2 -0
- package/dist/agent/tools/compare_benchmarks.js +53 -0
- package/dist/agent/tools/deferred.js +1 -0
- package/dist/agent/tools/delegate.js +18 -2
- package/dist/agent/tools/helper_requirements.d.ts +7 -0
- package/dist/agent/tools/helper_requirements.js +19 -0
- package/dist/agent/tools/index.d.ts +1 -0
- package/dist/agent/tools/index.js +3 -0
- package/dist/agent/tools/measurement_report.d.ts +22 -0
- package/dist/agent/tools/measurement_report.js +190 -0
- package/dist/agent/tools/pane.d.ts +0 -7
- package/dist/agent/tools/pane.js +5 -4
- package/dist/agent/tools/project_context.d.ts +3 -0
- package/dist/agent/tools/project_context.js +45 -0
- package/dist/agent/tools/recall_tool_response.d.ts +3 -5
- package/dist/agent/tools/recall_tool_response.js +19 -29
- package/dist/agent/tools/registry.d.ts +1 -0
- package/dist/agent/tools/registry.js +23 -0
- package/dist/agent/tools/related_sessions.d.ts +2 -0
- package/dist/agent/tools/related_sessions.js +156 -0
- package/dist/agent/tools/routine.js +13 -3
- package/dist/agent/tools/session_evidence.d.ts +3 -0
- package/dist/agent/tools/session_evidence.js +72 -0
- package/dist/agent/tools/shell.d.ts +4 -3
- package/dist/agent/tools/shell.js +174 -47
- package/dist/agent/tools/tool_search.js +3 -2
- package/dist/agent/tools/types.d.ts +28 -0
- package/dist/agent/tools/verify_artifact.d.ts +2 -0
- package/dist/agent/tools/verify_artifact.js +60 -0
- package/dist/agent/tools/workflow.js +1 -0
- package/dist/app/activity.d.ts +6 -1
- package/dist/app/activity.js +81 -12
- package/dist/app/agent_runner.d.ts +6 -1
- package/dist/app/agent_runner.js +34 -19
- package/dist/app/command_context.js +106 -10
- package/dist/app/controller.d.ts +15 -4
- package/dist/app/controller.js +29 -3
- package/dist/app/escape_stop.d.ts +11 -0
- package/dist/app/escape_stop.js +15 -0
- package/dist/app/liveness.d.ts +6 -4
- package/dist/app/liveness.js +13 -3
- package/dist/app/model_catalog_cache.d.ts +5 -0
- package/dist/app/model_catalog_cache.js +138 -0
- package/dist/app/ordered_transcript.js +43 -4
- package/dist/app/prompt_turn.d.ts +14 -0
- package/dist/app/prompt_turn.js +53 -0
- package/dist/app/recap.d.ts +3 -0
- package/dist/app/recap.js +9 -3
- package/dist/app/restart.d.ts +3 -2
- package/dist/app/restart.js +22 -7
- package/dist/app/rewind.d.ts +3 -3
- package/dist/app/rewind.js +3 -3
- package/dist/app/selection_completion.d.ts +17 -2
- package/dist/app/selection_completion.js +84 -9
- package/dist/app/session_manager.d.ts +38 -6
- package/dist/app/session_manager.js +263 -60
- package/dist/app/session_messages.d.ts +3 -0
- package/dist/app/session_messages.js +60 -0
- package/dist/app/task_scope_router.d.ts +20 -0
- package/dist/app/task_scope_router.js +173 -0
- package/dist/app/transcript_window.d.ts +2 -0
- package/dist/app/transcript_window.js +4 -0
- package/dist/app/workspace_admission_details.d.ts +3 -0
- package/dist/app/workspace_admission_details.js +26 -0
- package/dist/app/workspace_layout_controller.d.ts +11 -0
- package/dist/app/workspace_layout_controller.js +88 -0
- package/dist/app/workspace_monitor.d.ts +1 -0
- package/dist/app/workspace_monitor.js +216 -0
- package/dist/app/workspace_presets.d.ts +29 -0
- package/dist/app/workspace_presets.js +192 -0
- package/dist/attention/queue.d.ts +37 -0
- package/dist/attention/queue.js +183 -0
- package/dist/bench/rf_shared.d.ts +2 -0
- package/dist/board/binding.d.ts +42 -0
- package/dist/board/binding.js +38 -0
- package/dist/board/client.d.ts +42 -0
- package/dist/board/client.js +60 -0
- package/dist/cli.js +1 -1
- package/dist/cli_args.js +19 -17
- package/dist/cli_main.js +110 -13
- package/dist/cli_ratatui.d.ts +183 -16
- package/dist/cli_ratatui.js +3989 -1255
- package/dist/components/ActivityLine.d.ts +3 -2
- package/dist/components/ActivityLine.js +2 -2
- package/dist/components/InputArea.d.ts +19 -1
- package/dist/components/InputArea.js +264 -190
- package/dist/components/MessageList.d.ts +2 -0
- package/dist/components/MessageList.js +3 -1
- package/dist/components/SingleMessage.d.ts +2 -0
- package/dist/components/SingleMessage.js +4 -2
- package/dist/components/StatusBar.d.ts +4 -2
- package/dist/components/StatusBar.js +3 -1
- package/dist/conductor_branch.js +3 -0
- package/dist/config/api_retry.d.ts +6 -1
- package/dist/config/api_retry.js +19 -1
- package/dist/config/defaults.d.ts +2 -1
- package/dist/config/defaults.js +9 -2
- package/dist/config/message_display.d.ts +1 -0
- package/dist/config/message_display.js +17 -2
- package/dist/config/permissions.d.ts +21 -0
- package/dist/config/permissions.js +89 -0
- package/dist/config/resolved.d.ts +2 -2
- package/dist/config/types.d.ts +32 -5
- package/dist/config.d.ts +38 -4
- package/dist/config.js +290 -42
- package/dist/context/project_intelligence.d.ts +44 -0
- package/dist/context/project_intelligence.js +246 -0
- package/dist/desktop/cli_launch.d.ts +8 -0
- package/dist/desktop/cli_launch.js +6 -0
- package/dist/desktop/cli_shim.d.ts +8 -0
- package/dist/desktop/cli_shim.js +120 -0
- package/dist/desktop/runtime.d.ts +16 -0
- package/dist/desktop/runtime.js +161 -0
- package/dist/desktop/worker_launch.d.ts +7 -0
- package/dist/desktop/worker_launch.js +7 -0
- package/dist/diagnostics/cargo_mutants_selection.d.ts +27 -0
- package/dist/diagnostics/cargo_mutants_selection.js +48 -0
- package/dist/diagnostics/native_terminal_benchmark.d.ts +58 -0
- package/dist/diagnostics/native_terminal_benchmark.js +1247 -0
- package/dist/diagnostics/session_evidence_package.d.ts +40 -0
- package/dist/diagnostics/session_evidence_package.js +234 -0
- package/dist/diagnostics/session_export.d.ts +93 -0
- package/dist/diagnostics/session_export.js +634 -0
- package/dist/emulation/tool_adapters.js +48 -18
- package/dist/emulation/ztc_addendum.d.ts +1 -0
- package/dist/emulation/ztc_addendum.js +13 -12
- package/dist/environment/service_runner.d.ts +1 -0
- package/dist/environment/service_runner.js +68 -0
- package/dist/environment/store.d.ts +120 -0
- package/dist/environment/store.js +387 -0
- package/dist/events.d.ts +40 -1
- package/dist/events.js +10 -17
- package/dist/goal_contract.d.ts +25 -0
- package/dist/goal_contract.js +44 -0
- package/dist/headless.d.ts +31 -0
- package/dist/headless.js +578 -70
- package/dist/headless_cli.js +8 -0
- package/dist/headless_cli_args.d.ts +1 -0
- package/dist/headless_cli_args.js +1 -0
- package/dist/intake/cli.d.ts +1 -0
- package/dist/intake/cli.js +33 -0
- package/dist/intake/format.d.ts +2 -0
- package/dist/intake/format.js +14 -0
- package/dist/intake/notifications.d.ts +16 -0
- package/dist/intake/notifications.js +132 -0
- package/dist/intake/store.d.ts +56 -0
- package/dist/intake/store.js +238 -0
- package/dist/library.d.ts +8 -0
- package/dist/library.js +19 -10
- package/dist/mcp/client.d.ts +1 -0
- package/dist/mcp/client.js +20 -0
- package/dist/modes/builtin/plan.yaml +2 -2
- package/dist/modes/handlers.js +1 -1
- package/dist/onboarding/first_run.d.ts +3 -0
- package/dist/onboarding/first_run.js +33 -25
- package/dist/onboarding/setup.d.ts +32 -0
- package/dist/onboarding/setup.js +133 -0
- package/dist/onboarding/setup_agent_health.d.ts +17 -0
- package/dist/onboarding/setup_agent_health.js +79 -0
- package/dist/onboarding/setup_environment.d.ts +66 -0
- package/dist/onboarding/setup_environment.js +219 -0
- package/dist/onboarding/setup_workspace.d.ts +17 -0
- package/dist/onboarding/setup_workspace.js +129 -0
- package/dist/optimize/adapters/index.d.ts +4 -1
- package/dist/optimize/adapters/index.js +7 -0
- package/dist/optimize/adapters/kernelbench.js +14 -5
- package/dist/optimize/adapters/popcorn.js +3 -2
- package/dist/optimize/adapters/profile_benchmark.js +13 -7
- package/dist/optimize/adapters/rfbench.js +25 -14
- package/dist/optimize/adapters/types.d.ts +3 -0
- package/dist/optimize/adapters/zergpod.js +5 -2
- package/dist/optimize/baseline.js +10 -21
- package/dist/optimize/branch.js +9 -12
- package/dist/optimize/combine.js +2 -3
- package/dist/optimize/executor.js +34 -48
- package/dist/optimize/measurements.d.ts +55 -0
- package/dist/optimize/measurements.js +173 -0
- package/dist/optimize/results.d.ts +1 -0
- package/dist/optimize/results.js +11 -33
- package/dist/optimize/tge.js +2 -1
- package/dist/panes/initial_input.d.ts +19 -0
- package/dist/panes/initial_input.js +206 -0
- package/dist/panes/labels.d.ts +16 -0
- package/dist/panes/labels.js +42 -0
- package/dist/panes/model_catalog.d.ts +3 -3
- package/dist/panes/model_catalog.js +28 -4
- package/dist/panes/pane_registry.js +6 -2
- package/dist/panes/types.d.ts +3 -0
- package/dist/panes/ztc_launch.js +19 -0
- package/dist/plugin/optimization.d.ts +2 -0
- package/dist/plugin/optimization.js +1 -0
- package/dist/plugin/runtime.d.ts +2 -0
- package/dist/plugin/simulation.d.ts +6 -1
- package/dist/plugin/simulation_contract.d.ts +836 -1
- package/dist/plugin/simulation_documents.d.ts +22 -0
- package/dist/plugin/simulation_documents.js +1 -0
- package/dist/plugin/simulation_interventions.d.ts +103 -0
- package/dist/plugin/simulation_interventions.js +1 -0
- package/dist/plugin/simulation_living.d.ts +1 -0
- package/dist/plugin/simulation_living.js +3 -0
- package/dist/plugin/simulation_meetings.d.ts +121 -0
- package/dist/plugin/simulation_meetings.js +1 -0
- package/dist/plugin/simulation_story_review.d.ts +111 -0
- package/dist/plugin/simulation_story_review.js +1 -0
- package/dist/plugin/simulation_storyteller.d.ts +91 -0
- package/dist/plugin/simulation_storyteller.js +1 -0
- package/dist/plugin/simulation_structured.d.ts +2 -0
- package/dist/plugin/simulation_structured.js +2 -0
- package/dist/plugin/simulation_toolkit.d.ts +21 -0
- package/dist/plugin/simulation_toolkit.js +24 -0
- package/dist/prompts/tool_delegate_task.j2 +1 -1
- package/dist/remote-broker/Dockerfile +10 -0
- package/dist/remote-broker/archive.py +1227 -0
- package/dist/remote-broker/cutover.py +761 -0
- package/dist/remote-broker/requirements.txt +5 -0
- package/dist/remote-broker/resources.json +6 -0
- package/dist/remote-broker/resources.write-staging.example.json +18 -0
- package/dist/remote-broker/retained.py +686 -0
- package/dist/remote-broker/retained_cleanup.py +550 -0
- package/dist/remote-broker/server.py +648 -0
- package/dist/remote-broker/worker.py +1007 -0
- package/dist/router_auth.d.ts +31 -0
- package/dist/router_auth.js +155 -9
- package/dist/runbooks/catalog.d.ts +3 -0
- package/dist/runbooks/catalog.js +26 -0
- package/dist/runbooks/library.d.ts +11 -0
- package/dist/runbooks/library.js +95 -0
- package/dist/runbooks/package.d.ts +43 -0
- package/dist/runbooks/package.js +102 -0
- package/dist/session.d.ts +65 -13
- package/dist/session.js +548 -43
- package/dist/session_handoff.d.ts +25 -0
- package/dist/session_handoff.js +322 -0
- package/dist/session_handoff_contract.d.ts +89 -0
- package/dist/session_handoff_contract.js +179 -0
- package/dist/session_media.d.ts +41 -0
- package/dist/session_media.js +357 -0
- package/dist/session_ownership.d.ts +16 -0
- package/dist/session_ownership.js +221 -0
- package/dist/session_public.d.ts +10 -2
- package/dist/session_public.js +6 -1
- package/dist/simulation/dynamic_history.d.ts +42 -0
- package/dist/simulation/dynamic_history.js +173 -0
- package/dist/simulation/engines/swarm/pane_manager.js +7 -6
- package/dist/simulation/inference.d.ts +3 -1
- package/dist/simulation/inference.js +19 -1
- package/dist/simulation/intervention_generation.d.ts +27 -0
- package/dist/simulation/intervention_generation.js +246 -0
- package/dist/simulation/intervention_material.d.ts +4 -0
- package/dist/simulation/intervention_material.js +81 -0
- package/dist/simulation/living/context.d.ts +3130 -0
- package/dist/simulation/living/context.js +944 -0
- package/dist/simulation/living/definer.d.ts +12 -0
- package/dist/simulation/living/definer.js +223 -0
- package/dist/simulation/living/definition.d.ts +257 -0
- package/dist/simulation/living/definition.js +341 -0
- package/dist/simulation/living/frame.d.ts +259 -0
- package/dist/simulation/living/frame.js +223 -0
- package/dist/simulation/living/identifiers.d.ts +2 -0
- package/dist/simulation/living/identifiers.js +12 -0
- package/dist/simulation/living/index.d.ts +15 -0
- package/dist/simulation/living/index.js +15 -0
- package/dist/simulation/living/inference.d.ts +219 -0
- package/dist/simulation/living/inference.js +576 -0
- package/dist/simulation/living/mechanics.d.ts +54 -0
- package/dist/simulation/living/mechanics.js +588 -0
- package/dist/simulation/living/model-input.d.ts +25 -0
- package/dist/simulation/living/model-input.js +215 -0
- package/dist/simulation/living/open-world.d.ts +257 -0
- package/dist/simulation/living/open-world.js +102 -0
- package/dist/simulation/living/projection.d.ts +7 -0
- package/dist/simulation/living/projection.js +31 -0
- package/dist/simulation/living/recall.d.ts +17 -0
- package/dist/simulation/living/recall.js +25 -0
- package/dist/simulation/living/reduce.d.ts +15 -0
- package/dist/simulation/living/reduce.js +1853 -0
- package/dist/simulation/living/replay.d.ts +6 -0
- package/dist/simulation/living/replay.js +22 -0
- package/dist/simulation/living/runtime.d.ts +140 -0
- package/dist/simulation/living/runtime.js +3020 -0
- package/dist/simulation/living/shared-kinds.d.ts +24 -0
- package/dist/simulation/living/shared-kinds.js +96 -0
- package/dist/simulation/living/types.d.ts +2785 -0
- package/dist/simulation/living/types.js +669 -0
- package/dist/simulation/llm.d.ts +3 -1
- package/dist/simulation/llm.js +90 -30
- package/dist/simulation/memory/embedding.d.ts +1 -1
- package/dist/simulation/memory/embedding.js +9 -3
- package/dist/simulation/memory/filesystem.d.ts +2 -0
- package/dist/simulation/memory/filesystem.js +5 -1
- package/dist/simulation/memory/filesystem_file_tree.d.ts +4 -1
- package/dist/simulation/memory/filesystem_file_tree.js +41 -10
- package/dist/simulation/memory/filesystem_invariants.js +1 -2
- package/dist/simulation/memory/filesystem_types.d.ts +1 -0
- package/dist/simulation/memory/memory.d.ts +2 -0
- package/dist/simulation/memory/memory.js +5 -1
- package/dist/simulation/orchestration/intent.js +0 -2
- package/dist/simulation/orchestration/lifecycle.js +0 -2
- package/dist/simulation/plugin_host.d.ts +25 -1
- package/dist/simulation/plugin_host.js +334 -17
- package/dist/simulation/session_documents.d.ts +20 -0
- package/dist/simulation/session_documents.js +62 -0
- package/dist/simulation/session_input_validation.d.ts +43 -0
- package/dist/simulation/session_input_validation.js +364 -0
- package/dist/simulation/session_inputs.d.ts +32 -0
- package/dist/simulation/session_inputs.js +154 -0
- package/dist/simulation/telemetry.d.ts +11 -14
- package/dist/simulation/telemetry.js +177 -54
- package/dist/simulation/toolkit/bounded_turns.d.ts +13 -0
- package/dist/simulation/toolkit/bounded_turns.js +80 -0
- package/dist/simulation/toolkit/dynamic_agents.d.ts +25 -0
- package/dist/simulation/toolkit/dynamic_agents.js +218 -0
- package/dist/simulation/toolkit/dynamic_digest.d.ts +1 -0
- package/dist/simulation/toolkit/dynamic_digest.js +8 -0
- package/dist/simulation/toolkit/dynamic_documents.d.ts +7 -0
- package/dist/simulation/toolkit/dynamic_documents.js +71 -0
- package/dist/simulation/toolkit/dynamic_driver.d.ts +8 -0
- package/dist/simulation/toolkit/dynamic_driver.js +269 -0
- package/dist/simulation/toolkit/dynamic_game.d.ts +53 -0
- package/dist/simulation/toolkit/dynamic_game.js +805 -0
- package/dist/simulation/toolkit/dynamic_knowledge.d.ts +15 -0
- package/dist/simulation/toolkit/dynamic_knowledge.js +91 -0
- package/dist/simulation/toolkit/dynamic_mechanics.d.ts +22 -0
- package/dist/simulation/toolkit/dynamic_mechanics.js +910 -0
- package/dist/simulation/toolkit/dynamic_meetings.d.ts +239 -0
- package/dist/simulation/toolkit/dynamic_meetings.js +350 -0
- package/dist/simulation/toolkit/dynamic_progress.d.ts +131 -0
- package/dist/simulation/toolkit/dynamic_progress.js +214 -0
- package/dist/simulation/toolkit/dynamic_projection.d.ts +9 -0
- package/dist/simulation/toolkit/dynamic_projection.js +219 -0
- package/dist/simulation/toolkit/dynamic_runtime.d.ts +412 -0
- package/dist/simulation/toolkit/dynamic_runtime.js +6536 -0
- package/dist/simulation/toolkit/dynamic_state.d.ts +111 -0
- package/dist/simulation/toolkit/dynamic_state.js +1967 -0
- package/dist/simulation/toolkit/dynamic_story_review.d.ts +508 -0
- package/dist/simulation/toolkit/dynamic_story_review.js +333 -0
- package/dist/simulation/toolkit/dynamic_storyteller.d.ts +77 -0
- package/dist/simulation/toolkit/dynamic_storyteller.js +114 -0
- package/dist/simulation/toolkit/dynamic_storyteller_runtime.d.ts +26 -0
- package/dist/simulation/toolkit/dynamic_storyteller_runtime.js +126 -0
- package/dist/simulation/toolkit/dynamic_world.d.ts +22 -0
- package/dist/simulation/toolkit/dynamic_world.js +377 -0
- package/dist/simulation/toolkit/structured_output.d.ts +5 -0
- package/dist/simulation/toolkit/structured_output.js +321 -0
- package/dist/simulation/types/world.d.ts +2 -0
- package/dist/simulation/world/artifact_store.js +1 -1
- package/dist/simulation/world/memory_manager.d.ts +5 -0
- package/dist/simulation/world/memory_manager.js +17 -2
- package/dist/simulation/world/state.d.ts +3 -2
- package/dist/simulation/world/state.js +15 -5
- package/dist/skills/bundled/measurement-validation/SKILL.md +144 -0
- package/dist/terminal_attach.d.ts +1 -0
- package/dist/terminal_attach.js +113 -0
- package/dist/tool_progress.d.ts +3 -0
- package/dist/tool_progress.js +27 -0
- package/dist/types.d.ts +145 -10
- package/dist/types.js +3 -0
- package/dist/ui/core/input.d.ts +3 -0
- package/dist/ui/core/input_segments.d.ts +18 -1
- package/dist/ui/core/input_segments.js +131 -47
- package/dist/ui/core/input_state.d.ts +12 -0
- package/dist/ui/core/layout_yoga.d.ts +1 -1
- package/dist/ui/core/turn_metadata.d.ts +5 -0
- package/dist/ui/core/turn_metadata.js +22 -0
- package/dist/ui/ink/render.d.ts +2 -1
- package/dist/ui/ink/render.js +6 -5
- package/dist/ui/ratatui/headless_pane_manager.d.ts +14 -1
- package/dist/ui/ratatui/headless_pane_manager.js +293 -104
- package/dist/ui/ratatui/index.d.ts +1 -0
- package/dist/ui/ratatui/input.d.ts +4 -0
- package/dist/ui/ratatui/input.js +15 -0
- package/dist/ui/ratatui/ipc_path.d.ts +2 -0
- package/dist/ui/ratatui/ipc_path.js +29 -1
- package/dist/ui/ratatui/mux_persistence.d.ts +3 -1
- package/dist/ui/ratatui/mux_persistence.js +139 -1
- package/dist/ui/ratatui/pane_relay.d.ts +1 -1
- package/dist/ui/ratatui/pane_relay.js +55 -27
- package/dist/ui/ratatui/process.d.ts +17 -1
- package/dist/ui/ratatui/process.js +80 -7
- package/dist/ui/ratatui/protocol.d.ts +236 -1
- package/dist/ui/ratatui/protocol.js +90 -0
- package/dist/ui/scrollback.d.ts +2 -1
- package/dist/ui/scrollback.js +9 -4
- package/dist/ui/stream_typewriter.d.ts +8 -3
- package/dist/ui/stream_typewriter.js +30 -1
- package/dist/ui/streaming_scrollback.d.ts +1 -0
- package/dist/ui/streaming_scrollback.js +1 -1
- package/dist/ui/transcript_render_cache.js +10 -0
- package/dist/ui/views/activity_line.d.ts +5 -3
- package/dist/ui/views/activity_line.js +65 -7
- package/dist/ui/views/app.d.ts +6 -3
- package/dist/ui/views/app.js +17 -6
- package/dist/ui/views/completion_menu.js +10 -5
- package/dist/ui/views/input_area.d.ts +7 -3
- package/dist/ui/views/input_area.js +15 -9
- package/dist/ui/views/message_list.d.ts +4 -1
- package/dist/ui/views/message_list.js +89 -19
- package/dist/ui/views/queue_panel.d.ts +3 -1
- package/dist/ui/views/queue_panel.js +9 -2
- package/dist/ui/views/status_bar.d.ts +4 -2
- package/dist/ui/views/status_bar.js +28 -13
- package/dist/ui/views/theme.d.ts +2 -0
- package/dist/ui/views/theme.js +2 -0
- package/dist/ui/widgets/color_panel.js +18 -0
- package/dist/ui/widgets/form.js +8 -0
- package/dist/ui/widgets/log_view.js +7 -0
- package/dist/ui/widgets/navigator_panel.js +8 -0
- package/dist/ui/widgets/prompt_viewer.js +2 -0
- package/dist/ui/widgets/questionnaire.d.ts +2 -0
- package/dist/ui/widgets/questionnaire.js +79 -16
- package/dist/ui/widgets/resume_panel.d.ts +2 -0
- package/dist/ui/widgets/resume_panel.js +26 -15
- package/dist/ui/widgets/rewind_panel.d.ts +2 -2
- package/dist/ui/widgets/rewind_panel.js +4 -2
- package/dist/ui/widgets/select.js +16 -27
- package/dist/ui/widgets/settings_panel.js +16 -0
- package/dist/ui/widgets/status_line_panel.d.ts +1 -0
- package/dist/ui/widgets/status_line_panel.js +30 -4
- package/dist/ui/widgets/text_input.d.ts +2 -0
- package/dist/ui/widgets/text_input.js +15 -1
- package/dist/ui/widgets/types.d.ts +2 -0
- package/dist/usage_projection.d.ts +24 -0
- package/dist/usage_projection.js +130 -0
- package/dist/usage_snapshot.d.ts +36 -0
- package/dist/usage_snapshot.js +131 -0
- package/dist/utils/assistant_message.d.ts +2 -0
- package/dist/utils/assistant_message.js +5 -0
- package/dist/utils/atomic_write.d.ts +6 -2
- package/dist/utils/atomic_write.js +29 -4
- package/dist/utils/checked_shell.d.ts +25 -0
- package/dist/utils/checked_shell.js +107 -0
- package/dist/utils/compaction.d.ts +4 -1
- package/dist/utils/compaction.js +31 -7
- package/dist/utils/context.d.ts +4 -4
- package/dist/utils/context.js +14 -12
- package/dist/utils/deepseek.d.ts +2 -2
- package/dist/utils/deepseek.js +13 -5
- package/dist/utils/ephemeral.js +4 -0
- package/dist/utils/ephemeral_tools.d.ts +18 -0
- package/dist/utils/ephemeral_tools.js +97 -0
- package/dist/utils/jev_client.d.ts +4 -0
- package/dist/utils/jev_client.js +15 -0
- package/dist/utils/model_cache.d.ts +15 -0
- package/dist/utils/model_cache.js +143 -0
- package/dist/utils/models.d.ts +4 -2
- package/dist/utils/models.js +12 -16
- package/dist/utils/reasoning.d.ts +8 -1
- package/dist/utils/reasoning.js +31 -11
- package/dist/utils/router_catalog.d.ts +49 -0
- package/dist/utils/router_catalog.js +271 -0
- package/dist/utils/sandbox.d.ts +9 -0
- package/dist/utils/sandbox.js +118 -15
- package/dist/utils/shell.d.ts +7 -0
- package/dist/utils/shell.js +98 -34
- package/dist/utils/subprocess_env.d.ts +3 -0
- package/dist/utils/subprocess_env.js +27 -1
- package/dist/web/accounts_mirror.d.ts +3 -1
- package/dist/web/accounts_mirror.js +13 -9
- package/dist/web/child_publication.d.ts +17 -0
- package/dist/web/child_publication.js +45 -0
- package/dist/web/mirror_authenticator.d.ts +31 -11
- package/dist/web/mirror_authenticator.js +67 -17
- package/dist/web/mirror_client.d.ts +31 -4
- package/dist/web/mirror_client.js +189 -23
- package/dist/web/mirror_stream.d.ts +84 -22
- package/dist/web/mirror_stream.js +676 -95
- package/dist/web/observer_query_redaction.d.ts +6 -0
- package/dist/web/observer_query_redaction.js +58 -0
- package/dist/web/pane_registry.d.ts +2 -0
- package/dist/web/pane_registry.js +86 -22
- package/dist/web/tool_image.d.ts +6 -0
- package/dist/web/tool_image.js +17 -0
- package/dist/web/view_plugin.d.ts +14 -0
- package/dist/web/view_plugin.js +23 -1
- package/dist/worker/executor.js +31 -12
- package/dist/worker/index.d.ts +1 -0
- package/dist/worker/toolsets.js +3 -1
- package/dist/worker/types.d.ts +12 -1
- package/dist/workflows/registry.js +1 -0
- package/dist/workflows/registry_handle.d.ts +2 -0
- package/dist/workflows/runtime.d.ts +2 -0
- package/dist/workflows/runtime.js +1 -0
- package/dist/workspace_agent.d.ts +7 -0
- package/dist/workspace_agent.js +36 -0
- package/dist/workspace_collision.d.ts +76 -0
- package/dist/workspace_collision.js +688 -0
- package/dist/workspace_layout.d.ts +18 -0
- package/dist/workspace_layout.js +121 -0
- package/dist/workspace_runtime.d.ts +44 -0
- package/dist/workspace_runtime.js +82 -0
- package/dist/workspace_session_context.d.ts +62 -0
- package/dist/workspace_session_context.js +160 -0
- package/dist/zlm-embed.mjs +32518 -9615
- package/dist/zlm.js +1 -0
- package/node_modules/@zerg/model-contracts/LICENSE.md +375 -0
- package/node_modules/@zerg/model-contracts/README.md +90 -0
- package/node_modules/@zerg/model-contracts/index.d.ts +20 -0
- package/node_modules/@zerg/model-contracts/index.js +223 -0
- package/node_modules/@zerg/model-contracts/package.json +8 -0
- package/node_modules/glob/node_modules/brace-expansion/index.js +78 -22
- package/node_modules/glob/node_modules/brace-expansion/package.json +1 -1
- package/node_modules/readdir-glob/node_modules/brace-expansion/index.js +78 -22
- package/node_modules/readdir-glob/node_modules/brace-expansion/package.json +1 -1
- package/package.json +45 -24
- package/packages/model-contracts/LICENSE.md +375 -0
- package/packages/model-contracts/README.md +90 -0
- package/packages/model-contracts/index.d.ts +20 -0
- package/packages/model-contracts/index.js +223 -0
- package/packages/model-contracts/package.json +8 -0
- package/remote-broker/Dockerfile +10 -0
- package/remote-broker/README.md +308 -0
- package/remote-broker/archive.py +1227 -0
- package/remote-broker/cutover.py +761 -0
- package/remote-broker/requirements.txt +5 -0
- package/remote-broker/resources.example.json +6 -0
- package/remote-broker/resources.write-staging.example.json +18 -0
- package/remote-broker/retained.py +686 -0
- package/remote-broker/retained_cleanup.py +550 -0
- package/remote-broker/server.py +648 -0
- package/scripts/build-remote-broker.mjs +13 -0
- package/scripts/ensure-local-packages.mjs +6 -0
- package/scripts/node-pty-helpers.mjs +103 -0
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
"""Authenticated fixed-operation receiver; requires a read-only execution image."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import hmac
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import socket
|
|
10
|
+
import stat
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import threading
|
|
14
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Literal
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
19
|
+
|
|
20
|
+
L = logging.getLogger(__name__)
|
|
21
|
+
MAXIMUM_BYTES = 8 * 1024 * 1024
|
|
22
|
+
MAXIMUM_CONCURRENT_REQUESTS = 16
|
|
23
|
+
INSTALLATION = Path(__file__).resolve().parent
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MountTopologyChangedError(ValueError):
|
|
27
|
+
"""A stable operator-facing refusal without request or filesystem contents."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RequestNotDispatchedError(ValueError):
|
|
31
|
+
"""Preparation failed before the worker could run."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FilesystemRegion(BaseModel):
|
|
35
|
+
model_config = ConfigDict(extra="forbid", strict=True, frozen=True)
|
|
36
|
+
version: Literal[1] = 1
|
|
37
|
+
device: str = Field(pattern=r"^[0-9]+:[0-9]+$")
|
|
38
|
+
path: str = Field(min_length=1, max_length=4096)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MountEntry(BaseModel):
|
|
42
|
+
model_config = ConfigDict(extra="forbid", strict=True, frozen=True)
|
|
43
|
+
mount_id: int = Field(gt=0)
|
|
44
|
+
device: str
|
|
45
|
+
root: str
|
|
46
|
+
mount_point: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def read_mount_snapshot() -> bytes:
|
|
50
|
+
with Path("/proc/self/mountinfo").open("rb") as stream:
|
|
51
|
+
data = stream.read(MAXIMUM_BYTES + 1)
|
|
52
|
+
if not data or len(data) > MAXIMUM_BYTES:
|
|
53
|
+
raise ValueError("Host mount topology is empty or exceeds its bound")
|
|
54
|
+
return data
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def decode_mount_path(value: str) -> str:
|
|
58
|
+
if re.search(r"\\(?!040|011|012|134)", value):
|
|
59
|
+
raise ValueError("Unsupported host mount path escape")
|
|
60
|
+
path = re.sub(r"\\(040|011|012|134)", lambda match: chr(int(match[1], 8)), value)
|
|
61
|
+
if not os.path.isabs(path) or os.path.normpath(path) != path or "\x00" in path:
|
|
62
|
+
raise ValueError("Host mount paths must be canonical and absolute")
|
|
63
|
+
return path
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def parse_mount_snapshot(data: bytes) -> dict[int, MountEntry]:
|
|
67
|
+
entries: dict[int, MountEntry] = {}
|
|
68
|
+
for line in data.decode("utf-8").splitlines():
|
|
69
|
+
before, separator, after = line.partition(" - ")
|
|
70
|
+
fields = before.split()
|
|
71
|
+
if (
|
|
72
|
+
not separator
|
|
73
|
+
or len(fields) < 6
|
|
74
|
+
or len(after.split()) < 3
|
|
75
|
+
or not re.fullmatch(r"[0-9]+", fields[0])
|
|
76
|
+
or not re.fullmatch(r"[0-9]+:[0-9]+", fields[2])
|
|
77
|
+
):
|
|
78
|
+
raise ValueError("Malformed host mount topology")
|
|
79
|
+
entry = MountEntry(
|
|
80
|
+
mount_id=int(fields[0]),
|
|
81
|
+
device=fields[2],
|
|
82
|
+
root=decode_mount_path(fields[3]),
|
|
83
|
+
mount_point=decode_mount_path(fields[4]),
|
|
84
|
+
)
|
|
85
|
+
if entry.mount_id in entries:
|
|
86
|
+
raise ValueError("Duplicate host mount identity")
|
|
87
|
+
entries[entry.mount_id] = entry
|
|
88
|
+
if not entries:
|
|
89
|
+
raise ValueError("Host mount topology is empty")
|
|
90
|
+
return entries
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class Endpoint(BaseModel):
|
|
94
|
+
model_config = ConfigDict(extra="forbid", strict=True, frozen=True)
|
|
95
|
+
host: str = Field(min_length=1, max_length=253)
|
|
96
|
+
user: str = Field(min_length=1, max_length=100)
|
|
97
|
+
port: int = Field(ge=1, le=65535)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Resource(BaseModel):
|
|
101
|
+
model_config = ConfigDict(extra="forbid", strict=True, frozen=True)
|
|
102
|
+
id: str = Field(min_length=1, max_length=100, pattern=r"^[a-zA-Z0-9._:-]+$")
|
|
103
|
+
root: str = Field(min_length=1, max_length=4096)
|
|
104
|
+
endpoint: Endpoint
|
|
105
|
+
# Operator configuration contract; request fields cannot supply this policy.
|
|
106
|
+
write_staging_directory: str | None = Field(default=None, max_length=4096)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class Configuration(BaseModel):
|
|
110
|
+
model_config = ConfigDict(extra="forbid", strict=True, frozen=True)
|
|
111
|
+
version: Literal[1]
|
|
112
|
+
resources: list[Resource] = Field(min_length=1, max_length=100)
|
|
113
|
+
receipt_archive_index: bool = False
|
|
114
|
+
legacy_write_staging_directories: list[str] = Field(
|
|
115
|
+
default_factory=list, max_length=100
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class Request(BaseModel):
|
|
120
|
+
# Unknown model fields have no authority and are never forwarded to the worker.
|
|
121
|
+
model_config = ConfigDict(extra="ignore", strict=True)
|
|
122
|
+
operation: Literal[
|
|
123
|
+
"status",
|
|
124
|
+
"claim",
|
|
125
|
+
"transfer",
|
|
126
|
+
"stop",
|
|
127
|
+
"read",
|
|
128
|
+
"list",
|
|
129
|
+
"stat",
|
|
130
|
+
"write",
|
|
131
|
+
"delete",
|
|
132
|
+
"receipt",
|
|
133
|
+
"ack",
|
|
134
|
+
"reconcile",
|
|
135
|
+
"resolve",
|
|
136
|
+
]
|
|
137
|
+
root: str = Field(max_length=4096)
|
|
138
|
+
resource_id: str = Field(max_length=100)
|
|
139
|
+
owner_id: str = Field(min_length=1, max_length=200)
|
|
140
|
+
endpoint: Endpoint
|
|
141
|
+
client_journal: str | None = Field(default=None, max_length=4096)
|
|
142
|
+
path: str | None = Field(default=None, max_length=4096)
|
|
143
|
+
content: str | None = Field(default=None, max_length=1048576)
|
|
144
|
+
dry_run: bool = False
|
|
145
|
+
tool_call_id: str | None = Field(default=None, max_length=200)
|
|
146
|
+
operation_id: str | None = Field(default=None, max_length=36)
|
|
147
|
+
receipt_id: str | None = Field(default=None, max_length=36)
|
|
148
|
+
observation_token: str | None = Field(default=None, max_length=64)
|
|
149
|
+
expected_generation: int | None = None
|
|
150
|
+
target_owner_id: str | None = Field(default=None, max_length=200)
|
|
151
|
+
ttl_seconds: int | float | None = None
|
|
152
|
+
original_operation: Literal["write", "delete"] | None = None
|
|
153
|
+
original_owner_id: str | None = Field(default=None, max_length=200)
|
|
154
|
+
original_tool_call_id: str | None = Field(default=None, max_length=200)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class ImmutableResourceBroker:
|
|
158
|
+
def __init__(self) -> None:
|
|
159
|
+
self._init_execution_system()
|
|
160
|
+
self._init_resource_system()
|
|
161
|
+
self._init_mount_system()
|
|
162
|
+
|
|
163
|
+
def _init_execution_system(self) -> None:
|
|
164
|
+
if not sys.flags.isolated or os.geteuid() == 0:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
"Broker requires isolated Python and a non-root service user"
|
|
167
|
+
)
|
|
168
|
+
self.interpreter = Path(sys.executable).resolve()
|
|
169
|
+
self.worker = INSTALLATION / "worker.py"
|
|
170
|
+
self.configuration_path = INSTALLATION / "resources.json"
|
|
171
|
+
trusted = [
|
|
172
|
+
INSTALLATION,
|
|
173
|
+
self.worker,
|
|
174
|
+
self.configuration_path,
|
|
175
|
+
self.interpreter,
|
|
176
|
+
Path(sys.prefix),
|
|
177
|
+
]
|
|
178
|
+
for path in trusted:
|
|
179
|
+
if not os.statvfs(path).f_flag & os.ST_RDONLY:
|
|
180
|
+
raise ValueError(
|
|
181
|
+
"Broker requires a read-only execution image and policy"
|
|
182
|
+
)
|
|
183
|
+
self.trusted_devices = {path.stat().st_dev for path in trusted}
|
|
184
|
+
self.token = os.environ.get("ZTC_BROKER_TOKEN", "")
|
|
185
|
+
if not re.fullmatch(r"[!-~]{32,4096}", self.token):
|
|
186
|
+
raise ValueError(
|
|
187
|
+
"Broker requires a credential of 32 to 4096 ASCII characters"
|
|
188
|
+
)
|
|
189
|
+
self.root_identities: dict[str, tuple[int, int]] = {}
|
|
190
|
+
|
|
191
|
+
def _init_resource_system(self) -> None:
|
|
192
|
+
config = Configuration.model_validate_json(self.configuration_path.read_text())
|
|
193
|
+
self.receipt_archive_index = config.receipt_archive_index
|
|
194
|
+
self.resources = {resource.id: resource for resource in config.resources}
|
|
195
|
+
if len(self.resources) != len(config.resources):
|
|
196
|
+
raise ValueError("Broker resource IDs must be unique")
|
|
197
|
+
self.deletion_staging = Path("/data/.ztc-delete-quarantine")
|
|
198
|
+
self.deletion_staging.mkdir(mode=0o700, exist_ok=True)
|
|
199
|
+
private = self.deletion_staging.lstat()
|
|
200
|
+
if (
|
|
201
|
+
not stat.S_ISDIR(private.st_mode)
|
|
202
|
+
or private.st_uid != os.geteuid()
|
|
203
|
+
or private.st_mode & 0o077
|
|
204
|
+
):
|
|
205
|
+
raise ValueError(
|
|
206
|
+
"Broker deletion staging must be private to its service UID"
|
|
207
|
+
)
|
|
208
|
+
self.deletion_staging_identity = (private.st_dev, private.st_ino)
|
|
209
|
+
for directory in [Path("/data"), self.deletion_staging]:
|
|
210
|
+
descriptor = os.open(
|
|
211
|
+
directory, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW
|
|
212
|
+
)
|
|
213
|
+
try:
|
|
214
|
+
os.fsync(descriptor)
|
|
215
|
+
finally:
|
|
216
|
+
os.close(descriptor)
|
|
217
|
+
for resource in config.resources:
|
|
218
|
+
root = Path(resource.root)
|
|
219
|
+
if (
|
|
220
|
+
not root.is_absolute()
|
|
221
|
+
or str(root.resolve()) != resource.root
|
|
222
|
+
or not root.is_relative_to("/data")
|
|
223
|
+
or root.is_relative_to(self.deletion_staging)
|
|
224
|
+
):
|
|
225
|
+
raise ValueError(
|
|
226
|
+
"Broker writable roots must be canonical directories beneath /data"
|
|
227
|
+
)
|
|
228
|
+
root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
229
|
+
info = root.stat()
|
|
230
|
+
if not stat.S_ISDIR(info.st_mode) or info.st_dev in self.trusted_devices:
|
|
231
|
+
raise ValueError(
|
|
232
|
+
"Broker data must use a separate filesystem "
|
|
233
|
+
"from its executor and policy"
|
|
234
|
+
)
|
|
235
|
+
self.root_identities[resource.id] = (info.st_dev, info.st_ino)
|
|
236
|
+
self.write_staging_directories: dict[str, Path] = {}
|
|
237
|
+
self.trusted_staging_identities: dict[str, tuple[int, int]] = {
|
|
238
|
+
str(self.deletion_staging): self.deletion_staging_identity
|
|
239
|
+
}
|
|
240
|
+
for resource in config.resources:
|
|
241
|
+
self.write_staging_directories[resource.id] = (
|
|
242
|
+
Path(resource.write_staging_directory)
|
|
243
|
+
if resource.write_staging_directory is not None
|
|
244
|
+
else self.deletion_staging
|
|
245
|
+
)
|
|
246
|
+
for path in [
|
|
247
|
+
*self.write_staging_directories.values(),
|
|
248
|
+
*map(Path, config.legacy_write_staging_directories),
|
|
249
|
+
]:
|
|
250
|
+
if (
|
|
251
|
+
not path.is_absolute()
|
|
252
|
+
or str(path.resolve(strict=True)) != str(path)
|
|
253
|
+
or not path.is_relative_to("/data")
|
|
254
|
+
):
|
|
255
|
+
raise ValueError(
|
|
256
|
+
"Write staging must be an existing canonical /data directory"
|
|
257
|
+
)
|
|
258
|
+
private = path.lstat()
|
|
259
|
+
if (
|
|
260
|
+
not stat.S_ISDIR(private.st_mode)
|
|
261
|
+
or private.st_uid != os.geteuid()
|
|
262
|
+
or private.st_mode & 0o077
|
|
263
|
+
or private.st_dev in self.trusted_devices
|
|
264
|
+
):
|
|
265
|
+
raise ValueError(
|
|
266
|
+
"Write staging must be private to the broker on its data filesystem"
|
|
267
|
+
)
|
|
268
|
+
self.trusted_staging_identities[str(path)] = (
|
|
269
|
+
private.st_dev,
|
|
270
|
+
private.st_ino,
|
|
271
|
+
)
|
|
272
|
+
os.makedirs("/state", mode=0o700, exist_ok=True)
|
|
273
|
+
|
|
274
|
+
def _init_mount_system(self) -> None:
|
|
275
|
+
snapshot = read_mount_snapshot()
|
|
276
|
+
entries = parse_mount_snapshot(snapshot)
|
|
277
|
+
self.mountinfo_sha256 = hashlib.sha256(snapshot).hexdigest()
|
|
278
|
+
self.filesystem_regions: dict[str, FilesystemRegion] = {}
|
|
279
|
+
self.resource_mount_ids: dict[str, int] = {}
|
|
280
|
+
for resource in self.resources.values():
|
|
281
|
+
root = Path(resource.root)
|
|
282
|
+
if any(
|
|
283
|
+
Path(entry.mount_point) != root
|
|
284
|
+
and Path(entry.mount_point).is_relative_to(root)
|
|
285
|
+
for entry in entries.values()
|
|
286
|
+
):
|
|
287
|
+
raise ValueError("Nested mounts require proven overlap before serving")
|
|
288
|
+
descriptor = os.open(root, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
|
289
|
+
try:
|
|
290
|
+
info = os.fstat(descriptor)
|
|
291
|
+
details = Path(f"/proc/self/fdinfo/{descriptor}").read_text()
|
|
292
|
+
match = re.search(r"^mnt_id:\s*([0-9]+)$", details, re.MULTILINE)
|
|
293
|
+
if match is None or int(match[1]) not in entries:
|
|
294
|
+
raise ValueError("Host root has no proven mount identity")
|
|
295
|
+
entry = entries[int(match[1])]
|
|
296
|
+
if (
|
|
297
|
+
(info.st_dev, info.st_ino) != self.root_identities[resource.id]
|
|
298
|
+
or entry.device
|
|
299
|
+
!= f"{os.major(info.st_dev)}:{os.minor(info.st_dev)}"
|
|
300
|
+
or not root.is_relative_to(entry.mount_point)
|
|
301
|
+
):
|
|
302
|
+
raise ValueError("Host root does not match its mount identity")
|
|
303
|
+
path = str(Path(entry.root) / root.relative_to(entry.mount_point))
|
|
304
|
+
self.filesystem_regions[resource.id] = FilesystemRegion(
|
|
305
|
+
device=entry.device, path=path
|
|
306
|
+
)
|
|
307
|
+
self.resource_mount_ids[resource.id] = entry.mount_id
|
|
308
|
+
finally:
|
|
309
|
+
os.close(descriptor)
|
|
310
|
+
staging_mount_ids: dict[str, int] = {}
|
|
311
|
+
for path, expected in self.trusted_staging_identities.items():
|
|
312
|
+
location = Path(path)
|
|
313
|
+
if any(
|
|
314
|
+
Path(entry.mount_point) != location
|
|
315
|
+
and Path(entry.mount_point).is_relative_to(location)
|
|
316
|
+
for entry in entries.values()
|
|
317
|
+
):
|
|
318
|
+
raise ValueError("Nested private staging mounts are unsupported")
|
|
319
|
+
descriptor = os.open(location, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
|
320
|
+
try:
|
|
321
|
+
info = os.fstat(descriptor)
|
|
322
|
+
details = Path(f"/proc/self/fdinfo/{descriptor}").read_text()
|
|
323
|
+
match = re.search(r"^mnt_id:\s*([0-9]+)$", details, re.MULTILINE)
|
|
324
|
+
if match is None or int(match[1]) not in entries:
|
|
325
|
+
raise ValueError("Write staging has no proven mount identity")
|
|
326
|
+
entry = entries[int(match[1])]
|
|
327
|
+
if (
|
|
328
|
+
(info.st_dev, info.st_ino) != expected
|
|
329
|
+
or entry.device
|
|
330
|
+
!= f"{os.major(info.st_dev)}:{os.minor(info.st_dev)}"
|
|
331
|
+
or not location.is_relative_to(entry.mount_point)
|
|
332
|
+
):
|
|
333
|
+
raise ValueError("Write staging does not match its mount identity")
|
|
334
|
+
physical_path = str(
|
|
335
|
+
Path(entry.root) / location.relative_to(entry.mount_point)
|
|
336
|
+
)
|
|
337
|
+
if any(
|
|
338
|
+
region.device == entry.device
|
|
339
|
+
and os.path.commonpath([physical_path, region.path])
|
|
340
|
+
in [physical_path, region.path]
|
|
341
|
+
for region in self.filesystem_regions.values()
|
|
342
|
+
):
|
|
343
|
+
raise ValueError(
|
|
344
|
+
"Private staging overlaps a configured resource through its filesystem region"
|
|
345
|
+
)
|
|
346
|
+
staging_mount_ids[path] = entry.mount_id
|
|
347
|
+
finally:
|
|
348
|
+
os.close(descriptor)
|
|
349
|
+
for resource in self.resources.values():
|
|
350
|
+
if resource.write_staging_directory is not None and (
|
|
351
|
+
staging_mount_ids[str(self.write_staging_directories[resource.id])]
|
|
352
|
+
!= self.resource_mount_ids[resource.id]
|
|
353
|
+
):
|
|
354
|
+
raise ValueError(
|
|
355
|
+
"Configured write staging must share the exact resource mount"
|
|
356
|
+
)
|
|
357
|
+
self._validate_mount_system()
|
|
358
|
+
|
|
359
|
+
def _validate_mount_system(self) -> None:
|
|
360
|
+
if hashlib.sha256(read_mount_snapshot()).hexdigest() != self.mountinfo_sha256:
|
|
361
|
+
raise MountTopologyChangedError(
|
|
362
|
+
"Host mount topology changed; restart with reviewed policy"
|
|
363
|
+
)
|
|
364
|
+
for resource in self.resources.values():
|
|
365
|
+
if str(Path(resource.root).resolve()) != resource.root:
|
|
366
|
+
raise ValueError("Host broker resource canonical path changed")
|
|
367
|
+
info = os.stat(resource.root)
|
|
368
|
+
if (info.st_dev, info.st_ino) != self.root_identities[resource.id]:
|
|
369
|
+
raise ValueError("Host broker resource identity changed")
|
|
370
|
+
for path, expected in self.trusted_staging_identities.items():
|
|
371
|
+
private = Path(path).lstat()
|
|
372
|
+
if (
|
|
373
|
+
str(Path(path).resolve()) != path
|
|
374
|
+
or not stat.S_ISDIR(private.st_mode)
|
|
375
|
+
or (private.st_dev, private.st_ino) != expected
|
|
376
|
+
or private.st_uid != os.geteuid()
|
|
377
|
+
or private.st_mode & 0o077
|
|
378
|
+
):
|
|
379
|
+
raise ValueError(
|
|
380
|
+
"Host broker private staging identity or permissions changed"
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
def _prepare_request(self, data: dict[str, Any]) -> str:
|
|
384
|
+
request = Request.model_validate(data)
|
|
385
|
+
resource = self.resources.get(request.resource_id)
|
|
386
|
+
if (
|
|
387
|
+
resource is None
|
|
388
|
+
or request.root != resource.root
|
|
389
|
+
or request.endpoint != resource.endpoint
|
|
390
|
+
):
|
|
391
|
+
raise ValueError("Request does not match a host-configured broker resource")
|
|
392
|
+
info = os.stat(resource.root)
|
|
393
|
+
identity = (info.st_dev, info.st_ino)
|
|
394
|
+
if identity != self.root_identities[resource.id]:
|
|
395
|
+
raise ValueError("Host broker resource identity changed")
|
|
396
|
+
self._validate_mount_system()
|
|
397
|
+
payload = request.model_dump(exclude_unset=True)
|
|
398
|
+
payload["host_receipt_archive_index"] = self.receipt_archive_index
|
|
399
|
+
payload["host_root_identity"] = list(identity)
|
|
400
|
+
payload["host_mountinfo_sha256"] = self.mountinfo_sha256
|
|
401
|
+
payload["host_filesystem_region"] = self.filesystem_regions[
|
|
402
|
+
resource.id
|
|
403
|
+
].model_dump()
|
|
404
|
+
payload["host_resource_roots"] = {
|
|
405
|
+
item.root: list(self.root_identities[item.id])
|
|
406
|
+
for item in self.resources.values()
|
|
407
|
+
}
|
|
408
|
+
private = self.deletion_staging.lstat()
|
|
409
|
+
if (private.st_dev, private.st_ino) != self.deletion_staging_identity:
|
|
410
|
+
raise ValueError("Host broker deletion staging identity changed")
|
|
411
|
+
payload["host_delete_staging"] = str(self.deletion_staging)
|
|
412
|
+
payload["host_delete_staging_identity"] = list(self.deletion_staging_identity)
|
|
413
|
+
write_staging = str(self.write_staging_directories[resource.id])
|
|
414
|
+
payload["host_write_staging"] = write_staging
|
|
415
|
+
payload["host_write_staging_identity"] = list(
|
|
416
|
+
self.trusted_staging_identities[write_staging]
|
|
417
|
+
)
|
|
418
|
+
payload["host_write_staging_directories"] = {
|
|
419
|
+
path: list(identity)
|
|
420
|
+
for path, identity in self.trusted_staging_identities.items()
|
|
421
|
+
}
|
|
422
|
+
return json.dumps(payload)
|
|
423
|
+
|
|
424
|
+
def execute_request(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
425
|
+
try:
|
|
426
|
+
payload = self._prepare_request(data)
|
|
427
|
+
except (ValueError, OSError) as error:
|
|
428
|
+
raise RequestNotDispatchedError(
|
|
429
|
+
str(error)
|
|
430
|
+
if isinstance(error, MountTopologyChangedError)
|
|
431
|
+
else "Request refused by immutable broker policy"
|
|
432
|
+
) from error
|
|
433
|
+
# No shell, PATH lookup, account startup or writable Python search path.
|
|
434
|
+
# Neither credentials nor caller environment enter the worker process.
|
|
435
|
+
try:
|
|
436
|
+
result = subprocess.run(
|
|
437
|
+
[str(self.interpreter), "-I", "-B", str(self.worker)],
|
|
438
|
+
input=payload,
|
|
439
|
+
capture_output=True,
|
|
440
|
+
text=True,
|
|
441
|
+
cwd=str(INSTALLATION),
|
|
442
|
+
env={"HOME": "/state", "LANG": "C.UTF-8"},
|
|
443
|
+
timeout=30,
|
|
444
|
+
check=False,
|
|
445
|
+
)
|
|
446
|
+
except (OSError, ValueError):
|
|
447
|
+
return {
|
|
448
|
+
"ok": False,
|
|
449
|
+
"error": "Broker execution interrupted; outcome may be unresolved",
|
|
450
|
+
}
|
|
451
|
+
except subprocess.TimeoutExpired:
|
|
452
|
+
return {
|
|
453
|
+
"ok": False,
|
|
454
|
+
"error": "Broker execution timed out; outcome may be unresolved",
|
|
455
|
+
}
|
|
456
|
+
try:
|
|
457
|
+
if len(result.stdout.encode()) > MAXIMUM_BYTES:
|
|
458
|
+
return {
|
|
459
|
+
"ok": False,
|
|
460
|
+
"error": "Broker execution exceeded its response bound",
|
|
461
|
+
}
|
|
462
|
+
envelope = json.loads(result.stdout)
|
|
463
|
+
except (ValueError, RecursionError):
|
|
464
|
+
return {
|
|
465
|
+
"ok": False,
|
|
466
|
+
"error": "Broker execution did not return a complete receipt",
|
|
467
|
+
}
|
|
468
|
+
if not isinstance(envelope, dict) or not isinstance(envelope.get("ok"), bool):
|
|
469
|
+
return {"ok": False, "error": "Invalid broker execution envelope"}
|
|
470
|
+
if result.returncode != 0 and envelope["ok"]:
|
|
471
|
+
return {
|
|
472
|
+
"ok": False,
|
|
473
|
+
"error": "Broker execution failed before acknowledgement",
|
|
474
|
+
}
|
|
475
|
+
return envelope
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
class BrokerServer(ThreadingHTTPServer):
|
|
479
|
+
def __init__(self, broker: ImmutableResourceBroker, port: int) -> None:
|
|
480
|
+
self.broker = broker
|
|
481
|
+
self.request_slots = threading.BoundedSemaphore(MAXIMUM_CONCURRENT_REQUESTS)
|
|
482
|
+
super().__init__(("0.0.0.0", port), BrokerHandler)
|
|
483
|
+
|
|
484
|
+
def process_request(
|
|
485
|
+
self, request: socket.socket, client_address: tuple[str, int]
|
|
486
|
+
) -> None:
|
|
487
|
+
# Admission happens in the accept loop, before a handler or header timer
|
|
488
|
+
# exists. Waiting here would let incomplete peers block every new client.
|
|
489
|
+
if not self.request_slots.acquire(blocking=False):
|
|
490
|
+
encoded = json.dumps(
|
|
491
|
+
{"ok": False, "error": "Broker request capacity reached"}
|
|
492
|
+
).encode()
|
|
493
|
+
response = (
|
|
494
|
+
"HTTP/1.1 503 Service Unavailable\r\n"
|
|
495
|
+
"Content-Type: application/json\r\n"
|
|
496
|
+
f"Content-Length: {len(encoded)}\r\n"
|
|
497
|
+
"Cache-Control: no-store\r\n"
|
|
498
|
+
"Connection: close\r\n\r\n"
|
|
499
|
+
).encode() + encoded
|
|
500
|
+
try:
|
|
501
|
+
request.settimeout(1)
|
|
502
|
+
request.sendall(response)
|
|
503
|
+
except OSError:
|
|
504
|
+
L.debug("Overloaded broker peer disconnected")
|
|
505
|
+
finally:
|
|
506
|
+
self.shutdown_request(request)
|
|
507
|
+
return
|
|
508
|
+
try:
|
|
509
|
+
super().process_request(request, client_address)
|
|
510
|
+
except BaseException:
|
|
511
|
+
self.request_slots.release()
|
|
512
|
+
raise
|
|
513
|
+
|
|
514
|
+
def process_request_thread(
|
|
515
|
+
self, request: socket.socket, client_address: tuple[str, int]
|
|
516
|
+
) -> None:
|
|
517
|
+
try:
|
|
518
|
+
super().process_request_thread(request, client_address)
|
|
519
|
+
finally:
|
|
520
|
+
self.request_slots.release()
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
class BrokerHandler(BaseHTTPRequestHandler):
|
|
524
|
+
server: BrokerServer
|
|
525
|
+
|
|
526
|
+
def setup(self) -> None:
|
|
527
|
+
super().setup()
|
|
528
|
+
self.connection.settimeout(5)
|
|
529
|
+
self.header_timer: threading.Timer | None = None
|
|
530
|
+
|
|
531
|
+
def handle_one_request(self) -> None:
|
|
532
|
+
# Socket read timeouts bound silence only. An absolute deadline also
|
|
533
|
+
# bounds unfinished request lines/headers that continue receiving bytes.
|
|
534
|
+
self.header_timer = threading.Timer(5, self._expire_headers)
|
|
535
|
+
self.header_timer.daemon = True
|
|
536
|
+
self.header_timer.start()
|
|
537
|
+
try:
|
|
538
|
+
super().handle_one_request()
|
|
539
|
+
finally:
|
|
540
|
+
self.header_timer.cancel()
|
|
541
|
+
|
|
542
|
+
def parse_request(self) -> bool:
|
|
543
|
+
try:
|
|
544
|
+
return super().parse_request()
|
|
545
|
+
finally:
|
|
546
|
+
# Complete headers enter the ordinary authenticated/body path,
|
|
547
|
+
# whose read timeout and framing bounds remain independent.
|
|
548
|
+
if self.header_timer is not None:
|
|
549
|
+
self.header_timer.cancel()
|
|
550
|
+
|
|
551
|
+
def _expire_headers(self) -> None:
|
|
552
|
+
try:
|
|
553
|
+
self.connection.shutdown(socket.SHUT_RDWR)
|
|
554
|
+
except OSError:
|
|
555
|
+
pass # The peer or request cleanup already closed this connection.
|
|
556
|
+
|
|
557
|
+
def log_message(self, format: str, *args: Any) -> None:
|
|
558
|
+
# Request paths, headers, payloads and credentials are never logged.
|
|
559
|
+
L.debug("Broker HTTP request completed")
|
|
560
|
+
|
|
561
|
+
def send_envelope(self, status: int, envelope: dict[str, Any]) -> None:
|
|
562
|
+
encoded = json.dumps(envelope).encode()
|
|
563
|
+
self.send_response(status)
|
|
564
|
+
self.send_header("Content-Type", "application/json")
|
|
565
|
+
self.send_header("Content-Length", str(len(encoded)))
|
|
566
|
+
self.send_header("Cache-Control", "no-store")
|
|
567
|
+
self.end_headers()
|
|
568
|
+
self.wfile.write(encoded)
|
|
569
|
+
|
|
570
|
+
def do_POST(self) -> None:
|
|
571
|
+
credential = self.headers.get("Authorization", "").encode()
|
|
572
|
+
if not hmac.compare_digest(
|
|
573
|
+
credential, ("Bearer " + self.server.broker.token).encode()
|
|
574
|
+
):
|
|
575
|
+
self.send_envelope(
|
|
576
|
+
401, {"ok": False, "error": "Broker authentication required"}
|
|
577
|
+
)
|
|
578
|
+
return
|
|
579
|
+
if (
|
|
580
|
+
self.path != "/v1/resources"
|
|
581
|
+
or self.headers.get("Transfer-Encoding") is not None
|
|
582
|
+
):
|
|
583
|
+
self.send_envelope(
|
|
584
|
+
400, {"ok": False, "error": "Invalid broker route or framing"}
|
|
585
|
+
)
|
|
586
|
+
return
|
|
587
|
+
length = self.headers.get("Content-Length", "")
|
|
588
|
+
if (
|
|
589
|
+
not length.isdecimal()
|
|
590
|
+
or len(length) > 9
|
|
591
|
+
or not 0 < int(length) <= MAXIMUM_BYTES
|
|
592
|
+
):
|
|
593
|
+
self.send_envelope(
|
|
594
|
+
413, {"ok": False, "error": "Broker request exceeds its bound"}
|
|
595
|
+
)
|
|
596
|
+
return
|
|
597
|
+
data: dict[str, Any] = {}
|
|
598
|
+
try:
|
|
599
|
+
raw = self.rfile.read(int(length))
|
|
600
|
+
if len(raw) != int(length):
|
|
601
|
+
raise ValueError("Incomplete broker request")
|
|
602
|
+
parsed = json.loads(raw)
|
|
603
|
+
if not isinstance(parsed, dict):
|
|
604
|
+
raise ValueError("Broker request must be an object")
|
|
605
|
+
data = parsed
|
|
606
|
+
except (ValueError, OSError, RecursionError):
|
|
607
|
+
# Body parsing cannot dispatch a worker or recover a trusted request.
|
|
608
|
+
self.send_envelope(
|
|
609
|
+
200,
|
|
610
|
+
{"ok": False, "error": "Request refused by immutable broker policy"},
|
|
611
|
+
)
|
|
612
|
+
return
|
|
613
|
+
try:
|
|
614
|
+
envelope = self.server.broker.execute_request(data)
|
|
615
|
+
except RequestNotDispatchedError as error:
|
|
616
|
+
# Only preparation can establish this proof. Other execution errors
|
|
617
|
+
# must not release a client's reservation after a possible effect.
|
|
618
|
+
envelope = {
|
|
619
|
+
"ok": False,
|
|
620
|
+
"error": str(error),
|
|
621
|
+
"admitted": False,
|
|
622
|
+
"operation_id": data.get("operation_id"),
|
|
623
|
+
}
|
|
624
|
+
except (ValueError, OSError, RecursionError):
|
|
625
|
+
envelope = {
|
|
626
|
+
"ok": False,
|
|
627
|
+
"error": "Broker execution failed; outcome may be unresolved",
|
|
628
|
+
}
|
|
629
|
+
self.send_envelope(200, envelope)
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def main() -> None:
|
|
633
|
+
logging.basicConfig(level=logging.INFO)
|
|
634
|
+
try:
|
|
635
|
+
broker = ImmutableResourceBroker()
|
|
636
|
+
port = int(os.environ.get("ZTC_BROKER_PORT", "8787"))
|
|
637
|
+
if not 1 <= port <= 65535:
|
|
638
|
+
raise ValueError("Invalid broker port")
|
|
639
|
+
with BrokerServer(broker, port) as server:
|
|
640
|
+
L.info("Immutable resource broker ready on port %d", port)
|
|
641
|
+
server.serve_forever()
|
|
642
|
+
except (ValueError, OSError) as error:
|
|
643
|
+
L.error("Broker startup refused: %s", error)
|
|
644
|
+
raise SystemExit(1) from error
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
if __name__ == "__main__":
|
|
648
|
+
main()
|