okstra 0.174.0 → 0.176.0
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 +18 -9
- package/bin/okstra +8 -4
- package/dist/cli-registry.d.mts +16 -0
- package/dist/cli-registry.mjs +560 -0
- package/dist/cli-registry.mjs.map +1 -0
- package/dist/commands/execute/agent-prompt.d.mts +1 -0
- package/{src → dist}/commands/execute/agent-prompt.mjs +10 -11
- package/dist/commands/execute/agent-prompt.mjs.map +1 -0
- package/dist/commands/execute/codex-dispatch.d.mts +3 -0
- package/dist/commands/execute/codex-dispatch.mjs +6 -0
- package/dist/commands/execute/codex-dispatch.mjs.map +1 -0
- package/dist/commands/execute/codex-run.d.mts +3 -0
- package/{src → dist}/commands/execute/codex-run.mjs +30 -37
- package/dist/commands/execute/codex-run.mjs.map +1 -0
- package/dist/commands/execute/convergence.d.mts +1 -0
- package/{src → dist}/commands/execute/convergence.mjs +15 -16
- package/dist/commands/execute/convergence.mjs.map +1 -0
- package/dist/commands/execute/error-log.d.mts +1 -0
- package/{src → dist}/commands/execute/error-log.mjs +2 -3
- package/dist/commands/execute/error-log.mjs.map +1 -0
- package/dist/commands/execute/git-reconcile.d.mts +1 -0
- package/{src → dist}/commands/execute/git-reconcile.mjs +10 -11
- package/dist/commands/execute/git-reconcile.mjs.map +1 -0
- package/dist/commands/execute/handoff.d.mts +1 -0
- package/{src → dist}/commands/execute/handoff.mjs +10 -11
- package/dist/commands/execute/handoff.mjs.map +1 -0
- package/dist/commands/execute/incremental-carry.d.mts +1 -0
- package/{src → dist}/commands/execute/incremental-carry.mjs +2 -3
- package/dist/commands/execute/incremental-carry.mjs.map +1 -0
- package/dist/commands/execute/incremental-scope.d.mts +1 -0
- package/{src → dist}/commands/execute/incremental-scope.mjs +2 -3
- package/dist/commands/execute/incremental-scope.mjs.map +1 -0
- package/dist/commands/execute/integrate-stages.d.mts +1 -0
- package/{src → dist}/commands/execute/integrate-stages.mjs +10 -11
- package/dist/commands/execute/integrate-stages.mjs.map +1 -0
- package/dist/commands/execute/pane-title.d.mts +1 -0
- package/dist/commands/execute/pane-title.mjs +20 -0
- package/dist/commands/execute/pane-title.mjs.map +1 -0
- package/dist/commands/execute/plan-items.d.mts +1 -0
- package/dist/commands/execute/plan-items.mjs +9 -0
- package/dist/commands/execute/plan-items.mjs.map +1 -0
- package/dist/commands/execute/plan-validate.d.mts +1 -0
- package/dist/commands/execute/plan-validate.mjs +68 -0
- package/dist/commands/execute/plan-validate.mjs.map +1 -0
- package/dist/commands/execute/plan-verify.d.mts +1 -0
- package/{src → dist}/commands/execute/plan-verify.mjs +13 -16
- package/dist/commands/execute/plan-verify.mjs.map +1 -0
- package/dist/commands/execute/render-bundle.d.mts +3 -0
- package/{src → dist}/commands/execute/render-bundle.mjs +65 -61
- package/dist/commands/execute/render-bundle.mjs.map +1 -0
- package/dist/commands/execute/run.d.mts +24 -0
- package/dist/commands/execute/run.mjs +196 -0
- package/dist/commands/execute/run.mjs.map +1 -0
- package/dist/commands/execute/spawn-followups.d.mts +1 -0
- package/{src → dist}/commands/execute/spawn-followups.mjs +6 -7
- package/dist/commands/execute/spawn-followups.mjs.map +1 -0
- package/dist/commands/execute/team.d.mts +3 -0
- package/{src → dist}/commands/execute/team.mjs +29 -36
- package/dist/commands/execute/team.mjs.map +1 -0
- package/dist/commands/execute/token-usage.d.mts +1 -0
- package/{src → dist}/commands/execute/token-usage.mjs +2 -3
- package/dist/commands/execute/token-usage.mjs.map +1 -0
- package/dist/commands/execute/wizard.d.mts +8 -0
- package/dist/commands/execute/wizard.mjs +191 -0
- package/dist/commands/execute/wizard.mjs.map +1 -0
- package/dist/commands/execute/worker-audit-check.d.mts +1 -0
- package/{src → dist}/commands/execute/worker-audit-check.mjs +11 -12
- package/dist/commands/execute/worker-audit-check.mjs.map +1 -0
- package/dist/commands/execute/worker-dispatch.d.mts +7 -0
- package/{src → dist}/commands/execute/worker-dispatch.mjs +34 -46
- package/dist/commands/execute/worker-dispatch.mjs.map +1 -0
- package/dist/commands/execute/worker-state.d.mts +1 -0
- package/{src → dist}/commands/execute/worker-state.mjs +15 -16
- package/dist/commands/execute/worker-state.mjs.map +1 -0
- package/dist/commands/execute/worktree-lookup.d.mts +1 -0
- package/{src → dist}/commands/execute/worktree-lookup.mjs +38 -37
- package/dist/commands/execute/worktree-lookup.mjs.map +1 -0
- package/dist/commands/execute/worktree-status.d.mts +1 -0
- package/{src → dist}/commands/execute/worktree-status.mjs +52 -46
- package/dist/commands/execute/worktree-status.mjs.map +1 -0
- package/dist/commands/inspect/code-review.d.mts +1 -0
- package/{src → dist}/commands/inspect/code-review.mjs +16 -19
- package/dist/commands/inspect/code-review.mjs.map +1 -0
- package/dist/commands/inspect/container.d.mts +1 -0
- package/{src → dist}/commands/inspect/container.mjs +11 -13
- package/dist/commands/inspect/container.mjs.map +1 -0
- package/dist/commands/inspect/context-cost.d.mts +1 -0
- package/{src → dist}/commands/inspect/context-cost.mjs +11 -13
- package/dist/commands/inspect/context-cost.mjs.map +1 -0
- package/dist/commands/inspect/design-prep.d.mts +1 -0
- package/{src → dist}/commands/inspect/design-prep.mjs +10 -11
- package/dist/commands/inspect/design-prep.mjs.map +1 -0
- package/dist/commands/inspect/error-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/error-report.mjs +11 -13
- package/dist/commands/inspect/error-report.mjs.map +1 -0
- package/dist/commands/inspect/error-zip.d.mts +1 -0
- package/{src → dist}/commands/inspect/error-zip.mjs +11 -12
- package/dist/commands/inspect/error-zip.mjs.map +1 -0
- package/dist/commands/inspect/log-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/log-report.mjs +11 -13
- package/dist/commands/inspect/log-report.mjs.map +1 -0
- package/dist/commands/inspect/profile-show.d.mts +1 -0
- package/{src → dist}/commands/inspect/profile-show.mjs +10 -11
- package/dist/commands/inspect/profile-show.mjs.map +1 -0
- package/dist/commands/inspect/recap.d.mts +1 -0
- package/{src → dist}/commands/inspect/recap.mjs +11 -13
- package/dist/commands/inspect/recap.mjs.map +1 -0
- package/dist/commands/inspect/resolve-task-key.d.mts +1 -0
- package/{src → dist}/commands/inspect/resolve-task-key.mjs +11 -13
- package/dist/commands/inspect/resolve-task-key.mjs.map +1 -0
- package/dist/commands/inspect/rollup.d.mts +1 -0
- package/{src → dist}/commands/inspect/rollup.mjs +11 -13
- package/dist/commands/inspect/rollup.mjs.map +1 -0
- package/dist/commands/inspect/run-audit.d.mts +1 -0
- package/{src → dist}/commands/inspect/run-audit.mjs +11 -12
- package/dist/commands/inspect/run-audit.mjs.map +1 -0
- package/dist/commands/inspect/set-work-status.d.mts +1 -0
- package/{src → dist}/commands/inspect/set-work-status.mjs +11 -13
- package/dist/commands/inspect/set-work-status.mjs.map +1 -0
- package/dist/commands/inspect/stage-map.d.mts +1 -0
- package/dist/commands/inspect/stage-map.mjs +110 -0
- package/dist/commands/inspect/stage-map.mjs.map +1 -0
- package/dist/commands/inspect/task-list.d.mts +1 -0
- package/dist/commands/inspect/task-list.mjs +103 -0
- package/dist/commands/inspect/task-list.mjs.map +1 -0
- package/dist/commands/inspect/task-show.d.mts +1 -0
- package/dist/commands/inspect/task-show.mjs +108 -0
- package/dist/commands/inspect/task-show.mjs.map +1 -0
- package/dist/commands/inspect/time-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/time-report.mjs +11 -13
- package/dist/commands/inspect/time-report.mjs.map +1 -0
- package/dist/commands/inspect/usage-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/usage-report.mjs +11 -13
- package/dist/commands/inspect/usage-report.mjs.map +1 -0
- package/dist/commands/inspect/user-response.d.mts +1 -0
- package/{src → dist}/commands/inspect/user-response.mjs +10 -11
- package/dist/commands/inspect/user-response.mjs.map +1 -0
- package/dist/commands/inspect/worker-liveness.d.mts +1 -0
- package/{src → dist}/commands/inspect/worker-liveness.mjs +11 -13
- package/dist/commands/inspect/worker-liveness.mjs.map +1 -0
- package/dist/commands/lifecycle/check-project.d.mts +21 -0
- package/dist/commands/lifecycle/check-project.mjs +175 -0
- package/dist/commands/lifecycle/check-project.mjs.map +1 -0
- package/dist/commands/lifecycle/config.d.mts +1 -0
- package/dist/commands/lifecycle/config.mjs +369 -0
- package/dist/commands/lifecycle/config.mjs.map +1 -0
- package/dist/commands/lifecycle/doctor.d.mts +10 -0
- package/dist/commands/lifecycle/doctor.mjs +317 -0
- package/dist/commands/lifecycle/doctor.mjs.map +1 -0
- package/dist/commands/lifecycle/install.d.mts +17 -0
- package/dist/commands/lifecycle/install.mjs +1077 -0
- package/dist/commands/lifecycle/install.mjs.map +1 -0
- package/dist/commands/lifecycle/migrate.d.mts +1 -0
- package/{src → dist}/commands/lifecycle/migrate.mjs +10 -11
- package/dist/commands/lifecycle/migrate.mjs.map +1 -0
- package/dist/commands/lifecycle/model.d.mts +1 -0
- package/dist/commands/lifecycle/model.mjs +22 -0
- package/dist/commands/lifecycle/model.mjs.map +1 -0
- package/dist/commands/lifecycle/paths.d.mts +1 -0
- package/dist/commands/lifecycle/paths.mjs +102 -0
- package/dist/commands/lifecycle/paths.mjs.map +1 -0
- package/dist/commands/lifecycle/preflight.d.mts +1 -0
- package/dist/commands/lifecycle/preflight.mjs +124 -0
- package/dist/commands/lifecycle/preflight.mjs.map +1 -0
- package/dist/commands/lifecycle/setup.d.mts +1 -0
- package/dist/commands/lifecycle/setup.mjs +297 -0
- package/dist/commands/lifecycle/setup.mjs.map +1 -0
- package/dist/commands/lifecycle/uninstall.d.mts +4 -0
- package/dist/commands/lifecycle/uninstall.mjs +262 -0
- package/dist/commands/lifecycle/uninstall.mjs.map +1 -0
- package/dist/commands/manager.d.mts +3 -0
- package/{src → dist}/commands/manager.mjs +25 -32
- package/dist/commands/manager.mjs.map +1 -0
- package/dist/commands/memory/memory.d.mts +1 -0
- package/dist/commands/memory/memory.mjs +569 -0
- package/dist/commands/memory/memory.mjs.map +1 -0
- package/dist/commands/pr/pr.d.mts +33 -0
- package/dist/commands/pr/pr.mjs +296 -0
- package/dist/commands/pr/pr.mjs.map +1 -0
- package/dist/commands/report/agent-activity.d.mts +1 -0
- package/{src → dist}/commands/report/agent-activity.mjs +11 -12
- package/dist/commands/report/agent-activity.mjs.map +1 -0
- package/dist/commands/report/finalize.d.mts +4 -0
- package/{src → dist}/commands/report/finalize.mjs +25 -32
- package/dist/commands/report/finalize.mjs.map +1 -0
- package/dist/commands/report/inject-report-index.d.mts +1 -0
- package/{src → dist}/commands/report/inject-report-index.mjs +6 -7
- package/dist/commands/report/inject-report-index.mjs.map +1 -0
- package/dist/commands/report/render-final-report.d.mts +1 -0
- package/{src → dist}/commands/report/render-final-report.mjs +6 -7
- package/dist/commands/report/render-final-report.mjs.map +1 -0
- package/dist/commands/report/render-views.d.mts +1 -0
- package/{src → dist}/commands/report/render-views.mjs +6 -7
- package/dist/commands/report/render-views.mjs.map +1 -0
- package/dist/commands/report/translate.d.mts +1 -0
- package/{src → dist}/commands/report/translate.mjs +6 -7
- package/dist/commands/report/translate.mjs.map +1 -0
- package/dist/lib/helper-scripts.d.mts +3 -0
- package/{src → dist}/lib/helper-scripts.mjs +11 -14
- package/dist/lib/helper-scripts.mjs.map +1 -0
- package/dist/lib/host-registry-client.d.mts +25 -0
- package/dist/lib/host-registry-client.mjs +152 -0
- package/dist/lib/host-registry-client.mjs.map +1 -0
- package/dist/lib/install-assets.d.mts +8 -0
- package/{src → dist}/lib/install-assets.mjs +17 -20
- package/dist/lib/install-assets.mjs.map +1 -0
- package/dist/lib/okstra-dirs.d.mts +4 -0
- package/{src → dist}/lib/okstra-dirs.mjs +3 -6
- package/dist/lib/okstra-dirs.mjs.map +1 -0
- package/dist/lib/paths.d.mts +4 -0
- package/dist/lib/paths.mjs +78 -0
- package/dist/lib/paths.mjs.map +1 -0
- package/dist/lib/proc.d.mts +3 -0
- package/dist/lib/proc.mjs +31 -0
- package/dist/lib/proc.mjs.map +1 -0
- package/dist/lib/python-helper.d.mts +8 -0
- package/dist/lib/python-helper.mjs +123 -0
- package/dist/lib/python-helper.mjs.map +1 -0
- package/dist/lib/runtime-manifest.d.mts +7 -0
- package/dist/lib/runtime-manifest.mjs +73 -0
- package/dist/lib/runtime-manifest.mjs.map +1 -0
- package/dist/lib/skill-catalog.d.mts +4 -0
- package/dist/lib/skill-catalog.mjs +50 -0
- package/dist/lib/skill-catalog.mjs.map +1 -0
- package/dist/lib/types.d.mts +129 -0
- package/dist/lib/types.mjs +2 -0
- package/dist/lib/types.mjs.map +1 -0
- package/dist/lib/version.d.mts +2 -0
- package/dist/lib/version.mjs +27 -0
- package/dist/lib/version.mjs.map +1 -0
- package/docs/architecture/storage-model.md +6 -2
- package/docs/architecture.md +29 -20
- package/docs/cli.md +40 -16
- package/docs/contributor-change-matrix.md +2 -2
- package/docs/for-ai/skills/okstra-inspect.md +2 -3
- package/docs/for-ai/skills/okstra-rollup.md +1 -0
- package/docs/project-structure-overview.md +72 -62
- package/docs/task-process/README.md +12 -8
- package/docs/task-process/common-flow.md +13 -16
- package/docs/task-process/error-analysis.md +9 -10
- package/docs/task-process/final-verification.md +7 -7
- package/docs/task-process/implementation-planning.md +9 -9
- package/docs/task-process/implementation.md +6 -6
- package/docs/task-process/release-handoff.md +8 -7
- package/docs/task-process/requirements-discovery.md +8 -8
- package/package.json +11 -5
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +2 -1
- package/runtime/agents/workers/report-writer-worker.md +3 -1
- package/runtime/agents/workers/translator-worker.md +3 -1
- package/runtime/bin/lib/okstra/cli.sh +12 -0
- package/runtime/bin/lib/okstra/globals.sh +3 -0
- package/runtime/bin/lib/okstra/interactive.sh +12 -6
- package/runtime/bin/lib/okstra/usage.sh +7 -6
- package/runtime/bin/okstra-error-log.py +27 -0
- package/runtime/bin/okstra-provider-exec.py +217 -5
- package/runtime/bin/okstra-spawn-followups.py +4 -2
- package/runtime/bin/okstra.sh +10 -0
- package/runtime/prompts/launch.template.md +2 -2
- package/runtime/prompts/lead/context-loader.md +1 -2
- package/runtime/prompts/lead/convergence.md +27 -18
- package/runtime/prompts/lead/okstra-lead-contract.md +6 -5
- package/runtime/prompts/lead/plan-body-verification.md +10 -3
- package/runtime/prompts/lead/report-writer.md +15 -1
- package/runtime/prompts/lead/team-contract.md +17 -13
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +0 -3
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -7
- package/runtime/prompts/profiles/change-impact-analysis.md +20 -0
- package/runtime/prompts/profiles/error-analysis.md +27 -1
- package/runtime/prompts/profiles/feature-analysis.md +20 -0
- package/runtime/prompts/profiles/final-verification.md +20 -1
- package/runtime/prompts/profiles/implementation-option-selection.md +20 -0
- package/runtime/prompts/profiles/implementation-planning.md +26 -0
- package/runtime/prompts/profiles/implementation.md +19 -0
- package/runtime/prompts/profiles/improvement-discovery.md +21 -1
- package/runtime/prompts/profiles/project-analysis.md +20 -0
- package/runtime/prompts/profiles/release-handoff.md +4 -0
- package/runtime/prompts/profiles/requirements-discovery.md +27 -1
- package/runtime/prompts/wizard/prompts.ko.json +35 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/cli_wrapper.py +8 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +8 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/native_team.py +13 -0
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +25 -5
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +4 -1
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +79 -11
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +95 -25
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +59 -14
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +55 -9
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +51 -12
- package/runtime/python/okstra_ctl/adapters/runtime/__init__.py +1 -0
- package/runtime/python/okstra_ctl/adapters/runtime/assembly.py +27 -0
- package/runtime/python/okstra_ctl/adapters/runtime/cli_wrapper.py +49 -0
- package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +74 -0
- package/runtime/python/okstra_ctl/agent_activity.py +5 -1
- package/runtime/python/okstra_ctl/agent_invocation.py +608 -14
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +249 -50
- package/runtime/python/okstra_ctl/application/open_worker.py +29 -0
- package/runtime/python/okstra_ctl/application/resolve_assignment.py +53 -12
- package/runtime/python/okstra_ctl/assignment_environment.py +102 -0
- package/runtime/python/okstra_ctl/assignment_resolver.py +790 -0
- package/runtime/python/okstra_ctl/attempt_evidence.py +342 -0
- package/runtime/python/okstra_ctl/convergence.py +409 -3
- package/runtime/python/okstra_ctl/convergence_engine.py +248 -15
- package/runtime/python/okstra_ctl/convergence_store.py +170 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +1204 -155
- package/runtime/python/okstra_ctl/dispatch_state.py +696 -11
- package/runtime/python/okstra_ctl/doctor.py +14 -0
- package/runtime/python/okstra_ctl/domain/host.py +34 -1
- package/runtime/python/okstra_ctl/domain/provider.py +212 -6
- package/runtime/python/okstra_ctl/domain/role.py +125 -0
- package/runtime/python/okstra_ctl/domain/wizard/interaction.py +18 -0
- package/runtime/python/okstra_ctl/domain/worker_exec.py +34 -1
- package/runtime/python/okstra_ctl/domain/worker_role.py +13 -3
- package/runtime/python/okstra_ctl/domain/worker_runtime.py +44 -0
- package/runtime/python/okstra_ctl/domain/worker_stream.py +24 -0
- package/runtime/python/okstra_ctl/error_log_write.py +9 -0
- package/runtime/python/okstra_ctl/error_report.py +21 -1
- package/runtime/python/okstra_ctl/execution_identity.py +529 -0
- package/runtime/python/okstra_ctl/execution_manifest.py +1281 -0
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +619 -0
- package/runtime/python/okstra_ctl/implementation_outcome.py +21 -5
- package/runtime/python/okstra_ctl/index.py +6 -2
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +43 -3
- package/runtime/python/okstra_ctl/legacy_model_selection.py +578 -0
- package/runtime/python/okstra_ctl/manager_sync.py +4 -1
- package/runtime/python/okstra_ctl/model_cli.py +278 -0
- package/runtime/python/okstra_ctl/model_defaults.py +46 -0
- package/runtime/python/okstra_ctl/model_discovery.py +26 -7
- package/runtime/python/okstra_ctl/model_pool.py +501 -0
- package/runtime/python/okstra_ctl/models.py +47 -27
- package/runtime/python/okstra_ctl/mutation_recovery.py +293 -0
- package/runtime/python/okstra_ctl/next_phase.py +236 -0
- package/runtime/python/okstra_ctl/pane_title.py +151 -0
- package/runtime/python/okstra_ctl/ports/host_model.py +48 -17
- package/runtime/python/okstra_ctl/ports/worker_dispatch.py +4 -0
- package/runtime/python/okstra_ctl/ports/worker_runtime.py +26 -0
- package/runtime/python/okstra_ctl/recap.py +4 -1
- package/runtime/python/okstra_ctl/registry/provider_registry.py +8 -4
- package/runtime/python/okstra_ctl/render.py +124 -57
- package/runtime/python/okstra_ctl/render_final_report.py +19 -0
- package/runtime/python/okstra_ctl/report_contract.py +39 -0
- package/runtime/python/okstra_ctl/report_finalize.py +16 -0
- package/runtime/python/okstra_ctl/report_html/render.py +1 -0
- package/runtime/python/okstra_ctl/role_requirements.py +210 -0
- package/runtime/python/okstra_ctl/rollup.py +4 -1
- package/runtime/python/okstra_ctl/run.py +1210 -258
- package/runtime/python/okstra_ctl/run_index_row.py +7 -1
- package/runtime/python/okstra_ctl/stage_fix_carry.py +17 -1
- package/runtime/python/okstra_ctl/team.py +14 -18
- package/runtime/python/okstra_ctl/time_report.py +38 -4
- package/runtime/python/okstra_ctl/usage_identity.py +86 -0
- package/runtime/python/okstra_ctl/usage_report.py +15 -3
- package/runtime/python/okstra_ctl/wizard.py +1449 -66
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +68 -65
- package/runtime/python/okstra_ctl/worker_prompt_body.py +18 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +40 -9
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +16 -1
- package/runtime/python/okstra_ctl/worker_request.py +7 -1
- package/runtime/python/okstra_ctl/worker_runner.py +130 -5
- package/runtime/python/okstra_ctl/workers.py +7 -5
- package/runtime/python/okstra_ctl/workflow.py +18 -32
- package/runtime/python/okstra_ctl/worktree.py +3 -3
- package/runtime/python/okstra_ctl/worktree_registry.py +5 -4
- package/runtime/python/okstra_ctl/write_policy.py +571 -0
- package/runtime/python/okstra_project/state.py +54 -6
- package/runtime/schemas/convergence-groups-v2.0.schema.json +128 -0
- package/runtime/schemas/execution-manifest-v2.schema.json +340 -0
- package/runtime/schemas/final-report-v2.0.schema.json +68 -5
- package/runtime/skills/okstra-inspect/facets/recap.md +2 -0
- package/runtime/skills/okstra-inspect/facets/report.md +1 -1
- package/runtime/skills/okstra-inspect/facets/status.md +15 -13
- package/runtime/skills/okstra-rollup/SKILL.md +1 -0
- package/runtime/skills/okstra-run/SKILL.md +14 -14
- package/runtime/templates/implementation-worker-preamble.md +3 -18
- package/runtime/templates/project-docs/task-index.template.md +0 -1
- package/runtime/templates/report-writer-prompt-preamble.md +2 -0
- package/runtime/templates/reports/final-report-v2.template.md +7 -1
- package/runtime/templates/reports/html/base.template.html +21 -0
- package/runtime/templates/reports/html/i18n/en.json +4 -0
- package/runtime/templates/reports/html/i18n/ko.json +4 -0
- package/runtime/templates/reports/html/macros/forms.html +5 -4
- package/runtime/templates/reports/html/tasks/final-verification.template.html +1 -1
- package/runtime/templates/reports/html/tasks/implementation.template.html +1 -1
- package/runtime/templates/worker-prompt-preamble.md +6 -35
- package/runtime/validators/lib/fixtures.sh +49 -16
- package/runtime/validators/validate-run.py +486 -134
- package/runtime/validators/validate_session_conformance.py +25 -7
- package/src/cli-registry.mjs +0 -539
- package/src/commands/execute/codex-dispatch.mjs +0 -10
- package/src/commands/execute/plan-items.mjs +0 -9
- package/src/commands/execute/plan-validate.mjs +0 -68
- package/src/commands/execute/run.mjs +0 -208
- package/src/commands/execute/wizard.mjs +0 -167
- package/src/commands/inspect/stage-map.mjs +0 -98
- package/src/commands/inspect/task-list.mjs +0 -97
- package/src/commands/inspect/task-show.mjs +0 -100
- package/src/commands/lifecycle/check-project.mjs +0 -187
- package/src/commands/lifecycle/config.mjs +0 -385
- package/src/commands/lifecycle/doctor.mjs +0 -308
- package/src/commands/lifecycle/install.mjs +0 -1170
- package/src/commands/lifecycle/paths.mjs +0 -104
- package/src/commands/lifecycle/preflight.mjs +0 -125
- package/src/commands/lifecycle/setup.mjs +0 -333
- package/src/commands/lifecycle/uninstall.mjs +0 -279
- package/src/commands/memory/memory.mjs +0 -524
- package/src/commands/pr/pr.mjs +0 -261
- package/src/lib/host-registry-client.mjs +0 -176
- package/src/lib/paths.mjs +0 -86
- package/src/lib/proc.mjs +0 -31
- package/src/lib/python-helper.mjs +0 -142
- package/src/lib/runtime-manifest.mjs +0 -70
- package/src/lib/skill-catalog.mjs +0 -53
- package/src/lib/version.mjs +0 -20
- /package/{src → dist}/commands/pr/default.md +0 -0
|
@@ -32,8 +32,18 @@ from . import cmux
|
|
|
32
32
|
from .agent_invocation import (
|
|
33
33
|
AgentInvocationError,
|
|
34
34
|
agent_model_assignment_from_payload,
|
|
35
|
+
invocation_metadata_identity,
|
|
36
|
+
v2_role_assignment_authority_errors,
|
|
35
37
|
verify_agent_invocation,
|
|
36
38
|
)
|
|
39
|
+
from .domain.role import RoleCatalogError, role_for_duty
|
|
40
|
+
from .execution_identity import Attempt, Invocation
|
|
41
|
+
from .execution_manifest import (
|
|
42
|
+
finish_attempt_mutation,
|
|
43
|
+
read_execution_manifest,
|
|
44
|
+
record_invocation_attempt,
|
|
45
|
+
)
|
|
46
|
+
from .execution_mutation_audit import ExecutionMutationAudit, MutationSnapshot
|
|
37
47
|
from .final_report_paths import (
|
|
38
48
|
final_report_data_path,
|
|
39
49
|
final_report_markdown_path,
|
|
@@ -45,6 +55,15 @@ from .worker_prompt_contract import (
|
|
|
45
55
|
validate_reverify_prompt,
|
|
46
56
|
)
|
|
47
57
|
from .worker_runner import LIVE, QUIET
|
|
58
|
+
from .worker_request import verifier_extra_dirs
|
|
59
|
+
from .worker_artifact_paths import audit_sidecar_rel
|
|
60
|
+
from .wrapper_status import status_path_for_prompt
|
|
61
|
+
from .write_policy import (
|
|
62
|
+
build_invocation_write_contract,
|
|
63
|
+
planned_paths_from_run_manifest,
|
|
64
|
+
write_policy_from_payload,
|
|
65
|
+
)
|
|
66
|
+
from .convergence_store import write_json_atomic
|
|
48
67
|
|
|
49
68
|
BACKEND_CLI_WRAPPER = "cli-wrapper"
|
|
50
69
|
BACKEND_CMUX_PANE = "cmux-pane"
|
|
@@ -104,6 +123,10 @@ class DispatchError(Exception):
|
|
|
104
123
|
"""Raised when a worker dispatch request cannot be executed."""
|
|
105
124
|
|
|
106
125
|
|
|
126
|
+
class IncompletePromptWriteArtifacts(DispatchError):
|
|
127
|
+
"""Raised when a worker prompt omits the standard artifact headers."""
|
|
128
|
+
|
|
129
|
+
|
|
107
130
|
@dataclass(frozen=True)
|
|
108
131
|
class WorkerJob:
|
|
109
132
|
worker_id: str
|
|
@@ -137,6 +160,12 @@ class WorkerJob:
|
|
|
137
160
|
# process and leaves no `agentName` behind, so without this its usage cannot
|
|
138
161
|
# be attributed at all.
|
|
139
162
|
session_id: str = ""
|
|
163
|
+
participant_ref: str = ""
|
|
164
|
+
role_execution_ref: str = ""
|
|
165
|
+
execution_label: str = ""
|
|
166
|
+
duty_id: str = ""
|
|
167
|
+
invocation_ref: str = ""
|
|
168
|
+
attempt: int = 0
|
|
140
169
|
|
|
141
170
|
@property
|
|
142
171
|
def command(self) -> list[str]:
|
|
@@ -152,6 +181,11 @@ class WorkerJob:
|
|
|
152
181
|
"--presentation",
|
|
153
182
|
self._presentation(),
|
|
154
183
|
]
|
|
184
|
+
if self.has_execution_identity:
|
|
185
|
+
argv.extend([
|
|
186
|
+
"--invocation-metadata",
|
|
187
|
+
str(self.prompt_metadata_path),
|
|
188
|
+
])
|
|
155
189
|
if self.session_id:
|
|
156
190
|
argv += ["--session-id", self.session_id]
|
|
157
191
|
return argv
|
|
@@ -164,7 +198,6 @@ class WorkerJob:
|
|
|
164
198
|
|
|
165
199
|
def to_payload(self) -> dict[str, Any]:
|
|
166
200
|
payload = {
|
|
167
|
-
"workerId": self.worker_id,
|
|
168
201
|
"provider": self.provider,
|
|
169
202
|
"backend": self.backend,
|
|
170
203
|
"modelExecutionValue": self.model_execution_value,
|
|
@@ -178,6 +211,19 @@ class WorkerJob:
|
|
|
178
211
|
"dispatchKind": self.dispatch_kind,
|
|
179
212
|
"command": self.command,
|
|
180
213
|
}
|
|
214
|
+
if self.has_execution_identity:
|
|
215
|
+
payload.update({
|
|
216
|
+
"schemaVersion": "2.0",
|
|
217
|
+
"executionIdentityVersion": 2,
|
|
218
|
+
"participantRef": self.participant_ref,
|
|
219
|
+
"roleExecutionRef": self.role_execution_ref,
|
|
220
|
+
"executionLabel": self.execution_label,
|
|
221
|
+
"dutyId": self.duty_id,
|
|
222
|
+
"invocationRef": self.invocation_ref,
|
|
223
|
+
"attempt": self.attempt,
|
|
224
|
+
})
|
|
225
|
+
else:
|
|
226
|
+
payload.update({"schemaVersion": 1, "workerId": self.worker_id})
|
|
181
227
|
if self.invocation_id:
|
|
182
228
|
payload.update({
|
|
183
229
|
"invocationId": self.invocation_id,
|
|
@@ -190,6 +236,22 @@ class WorkerJob:
|
|
|
190
236
|
})
|
|
191
237
|
return payload
|
|
192
238
|
|
|
239
|
+
@property
|
|
240
|
+
def has_execution_identity(self) -> bool:
|
|
241
|
+
values = (
|
|
242
|
+
self.participant_ref,
|
|
243
|
+
self.role_execution_ref,
|
|
244
|
+
self.execution_label,
|
|
245
|
+
self.duty_id,
|
|
246
|
+
self.invocation_ref,
|
|
247
|
+
)
|
|
248
|
+
present = tuple(bool(value) for value in values)
|
|
249
|
+
if any(present) and not all(present):
|
|
250
|
+
raise DispatchError("worker job has partial v2 execution identity")
|
|
251
|
+
if all(present) and self.attempt < 1:
|
|
252
|
+
raise DispatchError("worker job v2 attempt must be positive")
|
|
253
|
+
return all(present)
|
|
254
|
+
|
|
193
255
|
@property
|
|
194
256
|
def digests(self) -> dict[str, str]:
|
|
195
257
|
return {
|
|
@@ -438,9 +500,22 @@ def record_verified_agent_dispatch(
|
|
|
438
500
|
assignment = agent_model_assignment_from_payload(assignments.get(assignment_ref))
|
|
439
501
|
except AgentInvocationError as exc:
|
|
440
502
|
raise DispatchError(str(exc)) from exc
|
|
441
|
-
|
|
503
|
+
try:
|
|
504
|
+
execution_identity = invocation_metadata_identity(metadata)
|
|
505
|
+
except AgentInvocationError as exc:
|
|
506
|
+
raise DispatchError(str(exc)) from exc
|
|
507
|
+
worker_id = (
|
|
508
|
+
require_string(metadata, "workerId")
|
|
509
|
+
if execution_identity is None
|
|
510
|
+
else None
|
|
511
|
+
)
|
|
442
512
|
audience = require_string(metadata, "audience")
|
|
443
513
|
invocation_id = require_string(metadata, "invocationId")
|
|
514
|
+
if (
|
|
515
|
+
execution_identity is not None
|
|
516
|
+
and execution_identity.invocation_ref != invocation_id
|
|
517
|
+
):
|
|
518
|
+
raise DispatchError("v2 invocation reference does not match invocation ID")
|
|
444
519
|
errors = verify_agent_invocation(
|
|
445
520
|
metadata_path,
|
|
446
521
|
project_root=project_root,
|
|
@@ -450,6 +525,15 @@ def record_verified_agent_dispatch(
|
|
|
450
525
|
expected_worker_id=worker_id,
|
|
451
526
|
expected_assignment_ref=assignment_ref,
|
|
452
527
|
expected_audience=audience,
|
|
528
|
+
expected_participant_ref=(
|
|
529
|
+
execution_identity.participant_ref if execution_identity else None
|
|
530
|
+
),
|
|
531
|
+
expected_role_execution_ref=(
|
|
532
|
+
execution_identity.role_execution_ref if execution_identity else None
|
|
533
|
+
),
|
|
534
|
+
expected_invocation_ref=(
|
|
535
|
+
execution_identity.invocation_ref if execution_identity else None
|
|
536
|
+
),
|
|
453
537
|
)
|
|
454
538
|
if errors:
|
|
455
539
|
raise DispatchError("agent invocation verification failed: " + "; ".join(errors))
|
|
@@ -478,12 +562,15 @@ def record_verified_agent_dispatch(
|
|
|
478
562
|
if not isinstance(prompt, Mapping):
|
|
479
563
|
raise DispatchError("agent invocation prompt is missing")
|
|
480
564
|
prompt_path = require_string(prompt, "path")
|
|
481
|
-
#
|
|
482
|
-
#
|
|
483
|
-
|
|
565
|
+
# Identity-bearing records carry the stored attempt. A path without
|
|
566
|
+
# identity still records the first attempt; the conflict guard below
|
|
567
|
+
# refuses a second write under that id.
|
|
568
|
+
dispatch_id = build_dispatch_id(
|
|
569
|
+
invocation_id,
|
|
570
|
+
execution_identity.attempt if execution_identity else 1,
|
|
571
|
+
)
|
|
484
572
|
record = {
|
|
485
573
|
"dispatchId": dispatch_id,
|
|
486
|
-
"workerId": worker_id,
|
|
487
574
|
"audience": audience,
|
|
488
575
|
"invocationId": invocation_id,
|
|
489
576
|
"assignmentRef": assignment_ref,
|
|
@@ -496,7 +583,99 @@ def record_verified_agent_dispatch(
|
|
|
496
583
|
"promptDeliveryVerified": False,
|
|
497
584
|
"status": "dispatched",
|
|
498
585
|
}
|
|
586
|
+
if execution_identity is None:
|
|
587
|
+
record["workerId"] = worker_id
|
|
588
|
+
else:
|
|
589
|
+
record.update({
|
|
590
|
+
"schemaVersion": "2.0",
|
|
591
|
+
"executionIdentityVersion": 2,
|
|
592
|
+
"invocationRef": execution_identity.invocation_ref,
|
|
593
|
+
"participantRef": execution_identity.participant_ref,
|
|
594
|
+
"roleExecutionRef": execution_identity.role_execution_ref,
|
|
595
|
+
"executionLabel": execution_identity.execution_label,
|
|
596
|
+
"dutyId": execution_identity.duty_id,
|
|
597
|
+
"attempt": execution_identity.attempt,
|
|
598
|
+
"dispatchKind": require_string(metadata, "dispatchKind"),
|
|
599
|
+
})
|
|
499
600
|
team_state_path = resolve_required_path(project_root, manifest, "teamStatePath")
|
|
601
|
+
write_policy = None
|
|
602
|
+
write_enforcement = None
|
|
603
|
+
mutation_snapshot_path = metadata_path.with_suffix(
|
|
604
|
+
metadata_path.suffix + ".mutation-audit.json"
|
|
605
|
+
)
|
|
606
|
+
if execution_identity is not None:
|
|
607
|
+
write_policy, write_enforcement = _agent_write_contract(
|
|
608
|
+
project_root,
|
|
609
|
+
run_manifest_path,
|
|
610
|
+
manifest,
|
|
611
|
+
execution_identity.role_execution_ref,
|
|
612
|
+
resolve_project_path(project_root, prompt_path),
|
|
613
|
+
assignment_ref,
|
|
614
|
+
)
|
|
615
|
+
record.update({
|
|
616
|
+
"writePolicyDigest": write_policy.digest,
|
|
617
|
+
"writeEnforcement": write_enforcement.to_payload(),
|
|
618
|
+
"mutationAuditSnapshotPath": (
|
|
619
|
+
_relative_project_path(project_root, mutation_snapshot_path)
|
|
620
|
+
if write_enforcement.mutation_audit == "batch"
|
|
621
|
+
else ""
|
|
622
|
+
),
|
|
623
|
+
})
|
|
624
|
+
existing_state = load_json_object(team_state_path, "team-state")
|
|
625
|
+
existing = [
|
|
626
|
+
item
|
|
627
|
+
for item in existing_state.get("agentDispatches") or []
|
|
628
|
+
if isinstance(item, Mapping) and item.get("dispatchId") == dispatch_id
|
|
629
|
+
]
|
|
630
|
+
if existing:
|
|
631
|
+
if len(existing) != 1 or dict(existing[0]) != record:
|
|
632
|
+
raise DispatchError(f"agent dispatch ID conflicts: {dispatch_id}")
|
|
633
|
+
return record
|
|
634
|
+
if execution_identity is not None:
|
|
635
|
+
dispatch_kind = require_string(metadata, "dispatchKind")
|
|
636
|
+
prompt_digest = require_string(digests, "promptDigest")
|
|
637
|
+
if write_policy is None or write_enforcement is None:
|
|
638
|
+
raise DispatchError("agent write contract was not prepared")
|
|
639
|
+
record_invocation_attempt(
|
|
640
|
+
run_manifest_path,
|
|
641
|
+
Invocation(
|
|
642
|
+
invocation_ref=execution_identity.invocation_ref,
|
|
643
|
+
participant_ref=execution_identity.participant_ref,
|
|
644
|
+
role_execution_ref=execution_identity.role_execution_ref,
|
|
645
|
+
source_invocation_ref=None,
|
|
646
|
+
recovery_ref=None,
|
|
647
|
+
duty_id=execution_identity.duty_id,
|
|
648
|
+
dispatch_kind=dispatch_kind,
|
|
649
|
+
round=_execution_dispatch_round(dispatch_kind),
|
|
650
|
+
input_digest=prompt_digest,
|
|
651
|
+
write_policy=write_policy.to_payload(),
|
|
652
|
+
write_policy_digest=write_policy.digest,
|
|
653
|
+
write_enforcement=write_enforcement.to_payload(),
|
|
654
|
+
),
|
|
655
|
+
Attempt(
|
|
656
|
+
invocation_ref=execution_identity.invocation_ref,
|
|
657
|
+
attempt=execution_identity.attempt,
|
|
658
|
+
started_at=utc_now(),
|
|
659
|
+
finished_at=None,
|
|
660
|
+
status="started",
|
|
661
|
+
result_path=None,
|
|
662
|
+
error_path=None,
|
|
663
|
+
change_summary={},
|
|
664
|
+
evidence_seal_ref=None,
|
|
665
|
+
),
|
|
666
|
+
task_key=require_string(manifest, "taskKey"),
|
|
667
|
+
)
|
|
668
|
+
if write_enforcement.mutation_audit == "batch":
|
|
669
|
+
snapshot = ExecutionMutationAudit().snapshot(
|
|
670
|
+
(write_policy,),
|
|
671
|
+
orchestrator_paths=(
|
|
672
|
+
run_manifest_path,
|
|
673
|
+
team_state_path,
|
|
674
|
+
Path(f"{team_state_path}.lock"),
|
|
675
|
+
mutation_snapshot_path,
|
|
676
|
+
),
|
|
677
|
+
)
|
|
678
|
+
write_json_atomic(mutation_snapshot_path, snapshot.to_payload())
|
|
500
679
|
with _team_state_lock(team_state_path):
|
|
501
680
|
team_state = load_json_object(team_state_path, "team-state")
|
|
502
681
|
dispatches = team_state.setdefault("agentDispatches", [])
|
|
@@ -515,13 +694,191 @@ def record_verified_agent_dispatch(
|
|
|
515
694
|
return record
|
|
516
695
|
|
|
517
696
|
|
|
697
|
+
def _agent_write_contract(
|
|
698
|
+
project_root: Path,
|
|
699
|
+
manifest_path: Path,
|
|
700
|
+
authority: Mapping[str, Any],
|
|
701
|
+
role_execution_ref: str,
|
|
702
|
+
prompt_path: Path,
|
|
703
|
+
assignment_ref: str,
|
|
704
|
+
):
|
|
705
|
+
execution_manifest = read_execution_manifest(manifest_path)
|
|
706
|
+
execution = next(
|
|
707
|
+
(
|
|
708
|
+
row for row in execution_manifest.role_executions
|
|
709
|
+
if row.role_execution_ref == role_execution_ref
|
|
710
|
+
),
|
|
711
|
+
None,
|
|
712
|
+
)
|
|
713
|
+
capability = (
|
|
714
|
+
execution.binding.worker_write_capability
|
|
715
|
+
if execution is not None and execution.binding is not None
|
|
716
|
+
else None
|
|
717
|
+
)
|
|
718
|
+
if execution is None:
|
|
719
|
+
raise DispatchError("agent write policy has no role execution")
|
|
720
|
+
if execution.role == "leader":
|
|
721
|
+
artifacts = _leader_artifact_paths(project_root, authority)
|
|
722
|
+
worktree = None
|
|
723
|
+
maximum_precision = "none"
|
|
724
|
+
elif capability is not None:
|
|
725
|
+
try:
|
|
726
|
+
artifacts, worktree = _prompt_write_paths(project_root, prompt_path)
|
|
727
|
+
except IncompletePromptWriteArtifacts:
|
|
728
|
+
artifacts, worktree = _manifest_worker_write_paths(
|
|
729
|
+
project_root, authority, assignment_ref, prompt_path
|
|
730
|
+
)
|
|
731
|
+
maximum_precision = capability.max_boundary_precision
|
|
732
|
+
else:
|
|
733
|
+
raise DispatchError("agent write policy has no runner capability")
|
|
734
|
+
try:
|
|
735
|
+
return build_invocation_write_contract(
|
|
736
|
+
role=execution.role,
|
|
737
|
+
project_root=project_root,
|
|
738
|
+
worktree=worktree,
|
|
739
|
+
artifact_paths=artifacts,
|
|
740
|
+
maximum_precision=maximum_precision,
|
|
741
|
+
planned_paths=(
|
|
742
|
+
planned_paths_from_run_manifest(project_root, authority)
|
|
743
|
+
if execution.role == "implementer"
|
|
744
|
+
else ()
|
|
745
|
+
),
|
|
746
|
+
auxiliary_roots=verifier_extra_dirs(execution.role),
|
|
747
|
+
validated_auxiliary_roots=verifier_extra_dirs(execution.role),
|
|
748
|
+
)
|
|
749
|
+
except (OSError, ValueError) as exc:
|
|
750
|
+
raise DispatchError(f"agent write policy is invalid: {exc}") from exc
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
def _leader_artifact_paths(
|
|
754
|
+
project_root: Path, authority: Mapping[str, Any]
|
|
755
|
+
) -> tuple[Path, ...]:
|
|
756
|
+
value = authority.get("taskRootPath") or authority.get("runDirectoryPath")
|
|
757
|
+
if not isinstance(value, str) or not value.strip():
|
|
758
|
+
raise DispatchError("leader write policy has no artifact root")
|
|
759
|
+
artifact_home = project_root / ".okstra"
|
|
760
|
+
if not artifact_home.is_dir():
|
|
761
|
+
raise DispatchError("leader write policy artifact home does not exist")
|
|
762
|
+
return (artifact_home,)
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def _manifest_worker_write_paths(
|
|
766
|
+
project_root: Path,
|
|
767
|
+
authority: Mapping[str, Any],
|
|
768
|
+
assignment_ref: str,
|
|
769
|
+
prompt_path: Path,
|
|
770
|
+
) -> tuple[tuple[Path, ...], Path | None]:
|
|
771
|
+
prefix = "initial/"
|
|
772
|
+
if not assignment_ref.startswith(prefix):
|
|
773
|
+
raise DispatchError("agent prompt write artifact headers are incomplete")
|
|
774
|
+
worker_id = assignment_ref.removeprefix(prefix)
|
|
775
|
+
team_state_path = resolve_required_path(
|
|
776
|
+
project_root, authority, "teamStatePath"
|
|
777
|
+
)
|
|
778
|
+
team_state = load_json_object(team_state_path, "team-state")
|
|
779
|
+
matches = [
|
|
780
|
+
row for row in team_state.get("workers") or []
|
|
781
|
+
if isinstance(row, Mapping) and row.get("workerId") == worker_id
|
|
782
|
+
]
|
|
783
|
+
if len(matches) != 1:
|
|
784
|
+
raise DispatchError("agent prompt has no unique worker artifact authority")
|
|
785
|
+
result = resolve_project_path(
|
|
786
|
+
project_root, require_string(matches[0], "resultPath")
|
|
787
|
+
)
|
|
788
|
+
paths = {
|
|
789
|
+
*dispatch_completion_paths(worker_id, result, authority, project_root),
|
|
790
|
+
result,
|
|
791
|
+
Path(audit_sidecar_rel(str(result))),
|
|
792
|
+
status_path_for_prompt(prompt_path),
|
|
793
|
+
prompt_path.with_suffix(prompt_path.suffix + ".log"),
|
|
794
|
+
}
|
|
795
|
+
active_value = authority.get("activeRunContextPath")
|
|
796
|
+
active = (
|
|
797
|
+
load_json_object(
|
|
798
|
+
resolve_project_path(project_root, active_value),
|
|
799
|
+
"active run context",
|
|
800
|
+
)
|
|
801
|
+
if isinstance(active_value, str) and active_value
|
|
802
|
+
else {}
|
|
803
|
+
)
|
|
804
|
+
error_logs = active.get("errorLogs")
|
|
805
|
+
sidecars = (
|
|
806
|
+
error_logs.get("sidecarsByWorkerId")
|
|
807
|
+
if isinstance(error_logs, Mapping)
|
|
808
|
+
else None
|
|
809
|
+
)
|
|
810
|
+
error_value = sidecars.get(worker_id) if isinstance(sidecars, Mapping) else None
|
|
811
|
+
if isinstance(error_value, str) and error_value:
|
|
812
|
+
paths.add(resolve_project_path(project_root, error_value))
|
|
813
|
+
worktree_value = worktree_path(authority, active)
|
|
814
|
+
worktree = Path(worktree_value) if worktree_value else None
|
|
815
|
+
return tuple(sorted(paths, key=str)), worktree
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
def _prompt_write_paths(
|
|
819
|
+
project_root: Path, prompt_path: Path
|
|
820
|
+
) -> tuple[tuple[Path, ...], Path | None]:
|
|
821
|
+
try:
|
|
822
|
+
lines = prompt_path.read_text(encoding="utf-8").splitlines()
|
|
823
|
+
except (OSError, UnicodeError) as exc:
|
|
824
|
+
raise DispatchError("agent prompt cannot be read for write policy") from exc
|
|
825
|
+
values: dict[str, str] = {}
|
|
826
|
+
labels = {
|
|
827
|
+
"Result Path",
|
|
828
|
+
"Worker Result Path",
|
|
829
|
+
"Audit sidecar path",
|
|
830
|
+
"Errors sidecar path",
|
|
831
|
+
"Worktree",
|
|
832
|
+
}
|
|
833
|
+
for line in lines:
|
|
834
|
+
for label in labels:
|
|
835
|
+
prefix = f"**{label}:** "
|
|
836
|
+
if line.startswith(prefix):
|
|
837
|
+
values[label] = line.removeprefix(prefix).strip()
|
|
838
|
+
required = {"Result Path", "Audit sidecar path", "Errors sidecar path"}
|
|
839
|
+
if not required <= values.keys():
|
|
840
|
+
raise IncompletePromptWriteArtifacts(
|
|
841
|
+
"agent prompt write artifact headers are incomplete"
|
|
842
|
+
)
|
|
843
|
+
artifact_values = [values[label] for label in sorted(required)]
|
|
844
|
+
if "Worker Result Path" in values:
|
|
845
|
+
artifact_values.append(values["Worker Result Path"])
|
|
846
|
+
artifacts = {
|
|
847
|
+
_project_or_absolute(project_root, value) for value in artifact_values
|
|
848
|
+
}
|
|
849
|
+
artifacts.add(status_path_for_prompt(prompt_path))
|
|
850
|
+
artifacts.add(prompt_path.with_suffix(prompt_path.suffix + ".log"))
|
|
851
|
+
worktree_value = values.get("Worktree")
|
|
852
|
+
worktree = (
|
|
853
|
+
_project_or_absolute(project_root, worktree_value)
|
|
854
|
+
if worktree_value
|
|
855
|
+
else None
|
|
856
|
+
)
|
|
857
|
+
return tuple(sorted(artifacts, key=str)), worktree
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
def _project_or_absolute(project_root: Path, value: str) -> Path:
|
|
861
|
+
path = Path(value)
|
|
862
|
+
return path if path.is_absolute() else project_root / path
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
def _execution_dispatch_round(dispatch_kind: str) -> int:
|
|
866
|
+
prefix = "reverify-r"
|
|
867
|
+
if not dispatch_kind.startswith(prefix):
|
|
868
|
+
return 1
|
|
869
|
+
suffix = dispatch_kind[len(prefix):]
|
|
870
|
+
if not suffix.isdigit() or int(suffix) < 1:
|
|
871
|
+
raise DispatchError(f"invalid reverify dispatch kind: {dispatch_kind}")
|
|
872
|
+
return int(suffix)
|
|
873
|
+
|
|
874
|
+
|
|
518
875
|
def link_agent_dispatch_result(
|
|
519
876
|
*,
|
|
520
877
|
project_root: Path,
|
|
521
878
|
run_manifest_path: Path,
|
|
522
879
|
dispatch_id: str,
|
|
523
880
|
result_path: Path,
|
|
524
|
-
) -> dict[str,
|
|
881
|
+
) -> dict[str, Any]:
|
|
525
882
|
"""Link an existing result artifact to exactly one verified dispatch."""
|
|
526
883
|
project_root = project_root.resolve()
|
|
527
884
|
manifest_path = resolve_project_path(
|
|
@@ -538,7 +895,20 @@ def link_agent_dispatch_result(
|
|
|
538
895
|
raise DispatchError("agent result link must stay inside the current run")
|
|
539
896
|
result_relative = _relative_project_path(project_root, resolved_result)
|
|
540
897
|
team_state_path = resolve_required_path(project_root, manifest, "teamStatePath")
|
|
541
|
-
|
|
898
|
+
preview = load_json_object(team_state_path, "team-state")
|
|
899
|
+
native_dispatches = [
|
|
900
|
+
item for item in preview.get("agentDispatches") or []
|
|
901
|
+
if isinstance(item, Mapping) and item.get("dispatchId") == dispatch_id
|
|
902
|
+
]
|
|
903
|
+
if len(native_dispatches) == 1:
|
|
904
|
+
_finish_native_agent_attempt(
|
|
905
|
+
project_root=project_root,
|
|
906
|
+
manifest_path=manifest_path,
|
|
907
|
+
manifest=manifest,
|
|
908
|
+
dispatch=native_dispatches[0],
|
|
909
|
+
result_path=resolved_result,
|
|
910
|
+
result_relative=result_relative,
|
|
911
|
+
)
|
|
542
912
|
with _team_state_lock(team_state_path):
|
|
543
913
|
team_state = load_json_object(team_state_path, "team-state")
|
|
544
914
|
dispatches = [
|
|
@@ -554,6 +924,21 @@ def link_agent_dispatch_result(
|
|
|
554
924
|
raise DispatchError(
|
|
555
925
|
f"agent result link requires exactly one dispatch record: {dispatch_id}"
|
|
556
926
|
)
|
|
927
|
+
dispatch = dispatches[0]
|
|
928
|
+
link: dict[str, Any] = {
|
|
929
|
+
"dispatchId": dispatch_id,
|
|
930
|
+
"resultPath": result_relative,
|
|
931
|
+
}
|
|
932
|
+
identity = worker_execution_identity(dispatch)
|
|
933
|
+
if identity is not None:
|
|
934
|
+
link.update({
|
|
935
|
+
"participantRef": identity["participant_ref"],
|
|
936
|
+
"roleExecutionRef": identity["role_execution_ref"],
|
|
937
|
+
"executionLabel": identity["execution_label"],
|
|
938
|
+
"dutyId": identity["duty_id"],
|
|
939
|
+
"invocationRef": identity["invocation_ref"],
|
|
940
|
+
"attempt": identity["attempt"],
|
|
941
|
+
})
|
|
557
942
|
links = team_state.setdefault("agentResultLinks", [])
|
|
558
943
|
if not isinstance(links, list):
|
|
559
944
|
raise DispatchError("team-state agentResultLinks must be an array")
|
|
@@ -591,6 +976,137 @@ def link_agent_dispatch_result(
|
|
|
591
976
|
return link
|
|
592
977
|
|
|
593
978
|
|
|
979
|
+
def _finish_native_agent_attempt(
|
|
980
|
+
*,
|
|
981
|
+
project_root: Path,
|
|
982
|
+
manifest_path: Path,
|
|
983
|
+
manifest: Mapping[str, Any],
|
|
984
|
+
dispatch: Mapping[str, Any],
|
|
985
|
+
result_path: Path,
|
|
986
|
+
result_relative: str,
|
|
987
|
+
) -> None:
|
|
988
|
+
identity = worker_execution_identity(dispatch)
|
|
989
|
+
if identity is None:
|
|
990
|
+
return
|
|
991
|
+
execution = read_execution_manifest(manifest_path)
|
|
992
|
+
invocation = next(
|
|
993
|
+
(
|
|
994
|
+
row for row in execution.invocations
|
|
995
|
+
if row.invocation_ref == identity["invocation_ref"]
|
|
996
|
+
),
|
|
997
|
+
None,
|
|
998
|
+
)
|
|
999
|
+
if invocation is None:
|
|
1000
|
+
raise DispatchError("agent result has no canonical invocation policy")
|
|
1001
|
+
if dispatch.get("writePolicyDigest") != invocation.write_policy_digest:
|
|
1002
|
+
raise DispatchError("agent dispatch write policy digest changed")
|
|
1003
|
+
if dispatch.get("writeEnforcement") != invocation.write_enforcement:
|
|
1004
|
+
raise DispatchError("agent dispatch write enforcement changed")
|
|
1005
|
+
policy = write_policy_from_payload(invocation.write_policy)
|
|
1006
|
+
snapshot_value = string_value(dispatch.get("mutationAuditSnapshotPath"))
|
|
1007
|
+
if snapshot_value:
|
|
1008
|
+
snapshot_payload = load_json_object(
|
|
1009
|
+
resolve_project_path(project_root, snapshot_value),
|
|
1010
|
+
"mutation audit snapshot",
|
|
1011
|
+
)
|
|
1012
|
+
try:
|
|
1013
|
+
snapshot = MutationSnapshot.from_payload(snapshot_payload)
|
|
1014
|
+
except (KeyError, TypeError, ValueError) as exc:
|
|
1015
|
+
raise DispatchError("mutation audit snapshot is invalid") from exc
|
|
1016
|
+
result = ExecutionMutationAudit().compare(
|
|
1017
|
+
snapshot,
|
|
1018
|
+
(policy,),
|
|
1019
|
+
out_of_plan_edits=_native_out_of_plan_paths(result_path),
|
|
1020
|
+
attempt_succeeded=True,
|
|
1021
|
+
result_present=True,
|
|
1022
|
+
)
|
|
1023
|
+
summary = result.change_summary()
|
|
1024
|
+
status = result.status
|
|
1025
|
+
else:
|
|
1026
|
+
status = "ok"
|
|
1027
|
+
summary = {
|
|
1028
|
+
"mutationStatus": "ok",
|
|
1029
|
+
"changedPaths": [],
|
|
1030
|
+
"sourceChanged": False,
|
|
1031
|
+
"gitChanged": False,
|
|
1032
|
+
"attribution": "call",
|
|
1033
|
+
"retryAllowed": False,
|
|
1034
|
+
"violations": [],
|
|
1035
|
+
"beforeDigest": "",
|
|
1036
|
+
"afterDigest": "",
|
|
1037
|
+
}
|
|
1038
|
+
_close_native_attempt(
|
|
1039
|
+
manifest_path,
|
|
1040
|
+
manifest,
|
|
1041
|
+
identity,
|
|
1042
|
+
status,
|
|
1043
|
+
result_relative,
|
|
1044
|
+
summary,
|
|
1045
|
+
)
|
|
1046
|
+
if status != "ok":
|
|
1047
|
+
violations = summary.get("violations")
|
|
1048
|
+
detail = "; ".join(violations) if isinstance(violations, list) else status
|
|
1049
|
+
raise DispatchError(f"agent mutation audit failed: {detail or status}")
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
def _close_native_attempt(
|
|
1053
|
+
manifest_path: Path,
|
|
1054
|
+
manifest: Mapping[str, Any],
|
|
1055
|
+
identity: Mapping[str, Any],
|
|
1056
|
+
status: str,
|
|
1057
|
+
result_path: str,
|
|
1058
|
+
summary: Mapping[str, Any],
|
|
1059
|
+
) -> None:
|
|
1060
|
+
execution = read_execution_manifest(manifest_path)
|
|
1061
|
+
existing = next(
|
|
1062
|
+
(
|
|
1063
|
+
row for row in execution.attempts
|
|
1064
|
+
if row.invocation_ref == identity["invocation_ref"]
|
|
1065
|
+
and row.attempt == identity["attempt"]
|
|
1066
|
+
),
|
|
1067
|
+
None,
|
|
1068
|
+
)
|
|
1069
|
+
if existing is not None and existing.finished_at is not None:
|
|
1070
|
+
if (
|
|
1071
|
+
existing.status == status
|
|
1072
|
+
and existing.result_path == result_path
|
|
1073
|
+
and dict(existing.change_summary) == dict(summary)
|
|
1074
|
+
):
|
|
1075
|
+
return
|
|
1076
|
+
raise DispatchError("agent attempt terminal mutation result drift")
|
|
1077
|
+
finish_attempt_mutation(
|
|
1078
|
+
manifest_path,
|
|
1079
|
+
invocation_ref=identity["invocation_ref"],
|
|
1080
|
+
attempt=identity["attempt"],
|
|
1081
|
+
finished_at=utc_now(),
|
|
1082
|
+
status=status,
|
|
1083
|
+
result_path=result_path,
|
|
1084
|
+
error_path=None,
|
|
1085
|
+
change_summary=summary,
|
|
1086
|
+
task_key=require_string(manifest, "taskKey"),
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
def _native_out_of_plan_paths(result_path: Path) -> tuple[str, ...]:
|
|
1091
|
+
if result_path.suffix != ".json":
|
|
1092
|
+
return ()
|
|
1093
|
+
try:
|
|
1094
|
+
payload = json.loads(result_path.read_text(encoding="utf-8"))
|
|
1095
|
+
except (OSError, UnicodeError, json.JSONDecodeError):
|
|
1096
|
+
return ()
|
|
1097
|
+
implementation = payload.get("implementation") if isinstance(payload, Mapping) else None
|
|
1098
|
+
rows = implementation.get("outOfPlanEdits") if isinstance(implementation, Mapping) else None
|
|
1099
|
+
if not isinstance(rows, list):
|
|
1100
|
+
return ()
|
|
1101
|
+
return tuple(
|
|
1102
|
+
str(row["file"])
|
|
1103
|
+
for row in rows
|
|
1104
|
+
if isinstance(row, Mapping)
|
|
1105
|
+
and isinstance(row.get("file"), str)
|
|
1106
|
+
and row["file"]
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
|
|
594
1110
|
def reject_agent_dispatch_result(
|
|
595
1111
|
*,
|
|
596
1112
|
project_root: Path,
|
|
@@ -1045,6 +1561,121 @@ def _validate_agent_invocations(
|
|
|
1045
1561
|
raise DispatchError("agent invocation contract: " + "; ".join(errors))
|
|
1046
1562
|
|
|
1047
1563
|
|
|
1564
|
+
def _v2_job_authority_errors(
|
|
1565
|
+
job: WorkerJob,
|
|
1566
|
+
metadata: Mapping[str, Any],
|
|
1567
|
+
manifest: Mapping[str, Any],
|
|
1568
|
+
) -> list[str]:
|
|
1569
|
+
try:
|
|
1570
|
+
identity = invocation_metadata_identity(metadata)
|
|
1571
|
+
except AgentInvocationError as exc:
|
|
1572
|
+
return [str(exc)]
|
|
1573
|
+
if identity is None:
|
|
1574
|
+
return ["jobs file v2 identity has v1 invocation metadata"]
|
|
1575
|
+
errors: list[str] = []
|
|
1576
|
+
if job.invocation_ref != identity.invocation_ref:
|
|
1577
|
+
errors.append("jobs file invocationRef does not match invocation metadata")
|
|
1578
|
+
if (
|
|
1579
|
+
job.participant_ref != identity.participant_ref
|
|
1580
|
+
or job.role_execution_ref != identity.role_execution_ref
|
|
1581
|
+
or job.execution_label != identity.execution_label
|
|
1582
|
+
or job.duty_id != identity.duty_id
|
|
1583
|
+
or job.attempt != identity.attempt
|
|
1584
|
+
):
|
|
1585
|
+
errors.append("jobs file v2 identity does not match invocation metadata")
|
|
1586
|
+
prompt = metadata.get("prompt")
|
|
1587
|
+
metadata_prompt_value = (
|
|
1588
|
+
string_value(prompt.get("path")) if isinstance(prompt, Mapping) else ""
|
|
1589
|
+
)
|
|
1590
|
+
metadata_prompt_path = resolve_project_path(
|
|
1591
|
+
job.project_root, metadata_prompt_value
|
|
1592
|
+
)
|
|
1593
|
+
if (
|
|
1594
|
+
not metadata_prompt_value
|
|
1595
|
+
or job.prompt_path.resolve() != metadata_prompt_path.resolve()
|
|
1596
|
+
):
|
|
1597
|
+
errors.append("jobs file promptPath does not match invocation metadata")
|
|
1598
|
+
role_executions = manifest.get("roleExecutions")
|
|
1599
|
+
execution = next((
|
|
1600
|
+
row for row in role_executions
|
|
1601
|
+
if isinstance(row, Mapping)
|
|
1602
|
+
and row.get("roleExecutionRef") == identity.role_execution_ref
|
|
1603
|
+
), None) if isinstance(role_executions, list) else None
|
|
1604
|
+
participants = manifest.get("participantAssignments")
|
|
1605
|
+
participant = next((
|
|
1606
|
+
row for row in participants
|
|
1607
|
+
if isinstance(row, Mapping)
|
|
1608
|
+
and row.get("participantRef") == identity.participant_ref
|
|
1609
|
+
), None) if isinstance(participants, list) else None
|
|
1610
|
+
assignment = metadata.get("modelAssignment")
|
|
1611
|
+
try:
|
|
1612
|
+
duty_role = role_for_duty(job.duty_id)
|
|
1613
|
+
except RoleCatalogError:
|
|
1614
|
+
duty_role = ""
|
|
1615
|
+
if isinstance(assignment, Mapping):
|
|
1616
|
+
try:
|
|
1617
|
+
parsed_assignment = agent_model_assignment_from_payload(assignment)
|
|
1618
|
+
except AgentInvocationError as exc:
|
|
1619
|
+
errors.append(str(exc))
|
|
1620
|
+
else:
|
|
1621
|
+
errors.extend(
|
|
1622
|
+
"jobs file " + error
|
|
1623
|
+
for error in v2_role_assignment_authority_errors(
|
|
1624
|
+
manifest,
|
|
1625
|
+
role_execution_ref=identity.role_execution_ref,
|
|
1626
|
+
participant_ref=identity.participant_ref,
|
|
1627
|
+
assignment=parsed_assignment,
|
|
1628
|
+
)
|
|
1629
|
+
)
|
|
1630
|
+
if (
|
|
1631
|
+
not isinstance(execution, Mapping)
|
|
1632
|
+
or not isinstance(participant, Mapping)
|
|
1633
|
+
or not isinstance(assignment, Mapping)
|
|
1634
|
+
or execution.get("participantRef") != job.participant_ref
|
|
1635
|
+
or execution.get("executionLabel") != job.execution_label
|
|
1636
|
+
or execution.get("role") != job.role
|
|
1637
|
+
or execution.get("role") != duty_role
|
|
1638
|
+
or execution.get("provider") != job.provider
|
|
1639
|
+
or assignment.get("modelExecutionValue") != job.model_execution_value
|
|
1640
|
+
or metadata.get("dispatchKind") != job.dispatch_kind
|
|
1641
|
+
):
|
|
1642
|
+
errors.append("jobs file v2 identity does not match role execution authority")
|
|
1643
|
+
return errors
|
|
1644
|
+
|
|
1645
|
+
|
|
1646
|
+
def _validate_v2_jobs_file_authority(
|
|
1647
|
+
manifest: Mapping[str, Any], jobs: Sequence[WorkerJob]
|
|
1648
|
+
) -> None:
|
|
1649
|
+
if (
|
|
1650
|
+
manifest.get("schemaVersion") != "2.0"
|
|
1651
|
+
or manifest.get("executionIdentityVersion") != 2
|
|
1652
|
+
):
|
|
1653
|
+
return
|
|
1654
|
+
_validate_agent_invocations(manifest, jobs)
|
|
1655
|
+
errors: list[str] = []
|
|
1656
|
+
for job in jobs:
|
|
1657
|
+
metadata = load_json_object(job.prompt_metadata_path, "agent metadata")
|
|
1658
|
+
errors.extend(
|
|
1659
|
+
f"{job.worker_id}: {error}"
|
|
1660
|
+
for error in _v2_job_authority_errors(job, metadata, manifest)
|
|
1661
|
+
)
|
|
1662
|
+
if errors:
|
|
1663
|
+
raise DispatchError("jobs file v2 authority: " + "; ".join(errors))
|
|
1664
|
+
role_executions = {
|
|
1665
|
+
string_value(row.get("roleExecutionRef")): row
|
|
1666
|
+
for row in manifest.get("roleExecutions") or []
|
|
1667
|
+
if isinstance(row, Mapping)
|
|
1668
|
+
}
|
|
1669
|
+
if any(
|
|
1670
|
+
isinstance(role_executions.get(job.role_execution_ref), Mapping)
|
|
1671
|
+
and role_executions[job.role_execution_ref].get("role") == "translator"
|
|
1672
|
+
for job in jobs
|
|
1673
|
+
):
|
|
1674
|
+
raise DispatchError(
|
|
1675
|
+
"jobs file translator must use canonical reservation dispatch"
|
|
1676
|
+
)
|
|
1677
|
+
|
|
1678
|
+
|
|
1048
1679
|
def worker_jobs_from_file(
|
|
1049
1680
|
project_root: Path,
|
|
1050
1681
|
jobs_file: Path,
|
|
@@ -1064,7 +1695,7 @@ def worker_jobs_from_file(
|
|
|
1064
1695
|
workers = payload.get("workers")
|
|
1065
1696
|
if not isinstance(workers, list):
|
|
1066
1697
|
raise DispatchError("jobs file workers must be an array")
|
|
1067
|
-
|
|
1698
|
+
jobs = [
|
|
1068
1699
|
_worker_job_from_file(
|
|
1069
1700
|
project_root,
|
|
1070
1701
|
item,
|
|
@@ -1078,6 +1709,8 @@ def worker_jobs_from_file(
|
|
|
1078
1709
|
for item in workers
|
|
1079
1710
|
if isinstance(item, Mapping)
|
|
1080
1711
|
]
|
|
1712
|
+
_validate_v2_jobs_file_authority(manifest, jobs)
|
|
1713
|
+
return jobs
|
|
1081
1714
|
|
|
1082
1715
|
|
|
1083
1716
|
def _worker_job_from_file(
|
|
@@ -1091,8 +1724,17 @@ def _worker_job_from_file(
|
|
|
1091
1724
|
resolve_wrapper: Callable[[str], Path],
|
|
1092
1725
|
default_provider: Callable[[str], str],
|
|
1093
1726
|
) -> WorkerJob:
|
|
1094
|
-
|
|
1095
|
-
|
|
1727
|
+
identity = worker_execution_identity(item)
|
|
1728
|
+
worker_id = (
|
|
1729
|
+
require_string(item, "workerId")
|
|
1730
|
+
if identity is None
|
|
1731
|
+
else v2_worker_state_key(item)
|
|
1732
|
+
)
|
|
1733
|
+
provider = (
|
|
1734
|
+
string_value(item.get("provider")) or default_provider(worker_id)
|
|
1735
|
+
if identity is None
|
|
1736
|
+
else require_string(item, "provider")
|
|
1737
|
+
)
|
|
1096
1738
|
prompt_path = resolve_project_path(
|
|
1097
1739
|
project_root, require_string(item, "promptPath")
|
|
1098
1740
|
)
|
|
@@ -1148,9 +1790,52 @@ def _worker_job_from_file(
|
|
|
1148
1790
|
prompt_digest=string_value(digest_values.get("promptDigest")),
|
|
1149
1791
|
host_model_value=host_model_value,
|
|
1150
1792
|
enforcement_mode=string_value(item.get("enforcementMode")),
|
|
1793
|
+
**(identity or {}),
|
|
1151
1794
|
)
|
|
1152
1795
|
|
|
1153
1796
|
|
|
1797
|
+
def worker_execution_identity(
|
|
1798
|
+
item: Mapping[str, Any],
|
|
1799
|
+
) -> dict[str, Any] | None:
|
|
1800
|
+
identity_keys = {
|
|
1801
|
+
"executionIdentityVersion",
|
|
1802
|
+
"participantRef",
|
|
1803
|
+
"roleExecutionRef",
|
|
1804
|
+
"executionLabel",
|
|
1805
|
+
"dutyId",
|
|
1806
|
+
"invocationRef",
|
|
1807
|
+
}
|
|
1808
|
+
schema = item.get("schemaVersion")
|
|
1809
|
+
has_v2_fields = bool(identity_keys.intersection(item))
|
|
1810
|
+
if schema in (None, 1, "1", "1.0"):
|
|
1811
|
+
if has_v2_fields:
|
|
1812
|
+
raise DispatchError("jobs file worker mixes v1 and v2 identity")
|
|
1813
|
+
return None
|
|
1814
|
+
if schema != "2.0" or item.get("executionIdentityVersion") != 2:
|
|
1815
|
+
raise DispatchError("jobs file worker has unsupported identity version")
|
|
1816
|
+
if "workerId" in item:
|
|
1817
|
+
raise DispatchError("jobs file worker mixes v1 and v2 identity")
|
|
1818
|
+
attempt = item.get("attempt")
|
|
1819
|
+
if not isinstance(attempt, int) or isinstance(attempt, bool) or attempt < 1:
|
|
1820
|
+
raise DispatchError("jobs file v2 attempt must be positive")
|
|
1821
|
+
return {
|
|
1822
|
+
"participant_ref": require_string(item, "participantRef"),
|
|
1823
|
+
"role_execution_ref": require_string(item, "roleExecutionRef"),
|
|
1824
|
+
"execution_label": require_string(item, "executionLabel"),
|
|
1825
|
+
"duty_id": require_string(item, "dutyId"),
|
|
1826
|
+
"invocation_ref": require_string(item, "invocationRef"),
|
|
1827
|
+
"attempt": attempt,
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
|
|
1831
|
+
def v2_worker_state_key(item: Mapping[str, Any]) -> str:
|
|
1832
|
+
assignment_ref = require_string(item, "assignmentRef")
|
|
1833
|
+
worker_id = assignment_ref.rsplit("/", 1)[-1]
|
|
1834
|
+
if not worker_id:
|
|
1835
|
+
raise DispatchError("jobs file v2 assignmentRef has no worker projection")
|
|
1836
|
+
return worker_id
|
|
1837
|
+
|
|
1838
|
+
|
|
1154
1839
|
def utc_now() -> str:
|
|
1155
1840
|
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
1156
1841
|
|