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
|
@@ -6,10 +6,18 @@ import hashlib
|
|
|
6
6
|
import json
|
|
7
7
|
from typing import Any, Mapping
|
|
8
8
|
|
|
9
|
+
from .execution_identity import ExecutionManifest
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
WORKING_SCHEMA_VERSION = "1.0"
|
|
13
|
+
V2_WORKING_SCHEMA_VERSION = "2.0"
|
|
14
|
+
EXECUTION_IDENTITY_VERSION = 2
|
|
11
15
|
FINAL_SCHEMA_VERSION = "1.3"
|
|
12
16
|
_AUDIENCES = {"analysis", "report-writer"}
|
|
17
|
+
_AUDIENCE_SOURCE_ROLES = {
|
|
18
|
+
"analysis": frozenset({"analyser", "designer", "planner", "verifier"}),
|
|
19
|
+
"report-writer": frozenset({"report-writer"}),
|
|
20
|
+
}
|
|
13
21
|
_VERIFICATION_MODES = {"lightweight", "full-reanalysis"}
|
|
14
22
|
_CLASSIFICATION_COUNT_KEYS = {
|
|
15
23
|
"full-consensus": "fullConsensus",
|
|
@@ -52,19 +60,27 @@ def grouped_input_digest(grouped_input: Mapping[str, Any]) -> str:
|
|
|
52
60
|
return hashlib.sha256(encoded).hexdigest()
|
|
53
61
|
|
|
54
62
|
|
|
55
|
-
def seed_working_state(
|
|
63
|
+
def seed_working_state(
|
|
64
|
+
grouped_input: Mapping[str, Any],
|
|
65
|
+
*,
|
|
66
|
+
execution_manifest: ExecutionManifest | None = None,
|
|
67
|
+
) -> dict[str, Any]:
|
|
56
68
|
"""Validate grouped findings, classify Round 0, and create the queue."""
|
|
57
69
|
source = _object(grouped_input, "grouped input")
|
|
58
|
-
|
|
59
|
-
raise ConvergenceContractError("grouped input schemaVersion must be 1.0")
|
|
70
|
+
identity_version = _grouped_execution_identity_version(source)
|
|
60
71
|
task_key = _required_string(source, "taskKey", "grouped input")
|
|
61
72
|
config = _parse_config(source.get("config"))
|
|
62
|
-
workers = _parse_workers(source.get("workers"))
|
|
73
|
+
workers = _parse_workers(source.get("workers"), identity_version)
|
|
74
|
+
_validate_worker_execution_identity(
|
|
75
|
+
workers,
|
|
76
|
+
execution_manifest,
|
|
77
|
+
identity_version,
|
|
78
|
+
)
|
|
63
79
|
analysis_workers = [
|
|
64
80
|
worker["workerId"] for worker in workers if worker["audience"] == "analysis"
|
|
65
81
|
]
|
|
66
82
|
state = {
|
|
67
|
-
"schemaVersion":
|
|
83
|
+
"schemaVersion": source["schemaVersion"],
|
|
68
84
|
"taskKey": task_key,
|
|
69
85
|
"groupsDigest": grouped_input_digest(source),
|
|
70
86
|
"config": config,
|
|
@@ -74,6 +90,15 @@ def seed_working_state(grouped_input: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
74
90
|
"roundHistory": [],
|
|
75
91
|
"stopReason": None,
|
|
76
92
|
}
|
|
93
|
+
if identity_version == EXECUTION_IDENTITY_VERSION:
|
|
94
|
+
state.update({
|
|
95
|
+
"executionIdentityVersion": EXECUTION_IDENTITY_VERSION,
|
|
96
|
+
"runManifestPath": _required_string(
|
|
97
|
+
source,
|
|
98
|
+
"runManifestPath",
|
|
99
|
+
"grouped input",
|
|
100
|
+
),
|
|
101
|
+
})
|
|
77
102
|
if not config["enabled"] or len(analysis_workers) < 2:
|
|
78
103
|
state["stopReason"] = "auto-disabled"
|
|
79
104
|
return state
|
|
@@ -141,16 +166,30 @@ def plan_next_round(state: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
141
166
|
if not isinstance(worker, Mapping) or worker.get("audience") != "analysis":
|
|
142
167
|
continue
|
|
143
168
|
worker_id = _required_string(worker, "workerId", "working state worker")
|
|
169
|
+
worker_participant_ref = _worker_participant_ref(worker)
|
|
144
170
|
eligible = [
|
|
145
171
|
finding_id
|
|
146
172
|
for finding_id in queue
|
|
147
|
-
if
|
|
173
|
+
if _worker_is_independent_from_finding(
|
|
174
|
+
worker_id,
|
|
175
|
+
worker_participant_ref,
|
|
176
|
+
findings[finding_id],
|
|
177
|
+
workers,
|
|
178
|
+
)
|
|
148
179
|
]
|
|
149
180
|
if eligible:
|
|
150
|
-
dispatches.append({
|
|
181
|
+
dispatches.append({
|
|
182
|
+
**_worker_execution_refs(worker),
|
|
183
|
+
"worker": worker_id,
|
|
184
|
+
"findingIds": eligible,
|
|
185
|
+
})
|
|
151
186
|
else:
|
|
152
187
|
skipped.append(
|
|
153
|
-
{
|
|
188
|
+
{
|
|
189
|
+
**_worker_execution_refs(worker),
|
|
190
|
+
"worker": worker_id,
|
|
191
|
+
"reason": "no items to verify",
|
|
192
|
+
}
|
|
154
193
|
)
|
|
155
194
|
return {
|
|
156
195
|
"schemaVersion": WORKING_SCHEMA_VERSION,
|
|
@@ -162,6 +201,45 @@ def plan_next_round(state: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
162
201
|
}
|
|
163
202
|
|
|
164
203
|
|
|
204
|
+
def _worker_participant_ref(worker: Mapping[str, Any]) -> str | None:
|
|
205
|
+
value = worker.get("participantRef")
|
|
206
|
+
return value if isinstance(value, str) and value else None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _worker_is_independent_from_finding(
|
|
210
|
+
worker_id: str,
|
|
211
|
+
participant_ref: str | None,
|
|
212
|
+
finding: Mapping[str, Any],
|
|
213
|
+
workers: list[Any],
|
|
214
|
+
) -> bool:
|
|
215
|
+
origin_worker = finding.get("originWorker")
|
|
216
|
+
if participant_ref is None:
|
|
217
|
+
return origin_worker != worker_id
|
|
218
|
+
origin = next(
|
|
219
|
+
(
|
|
220
|
+
row
|
|
221
|
+
for row in workers
|
|
222
|
+
if isinstance(row, Mapping) and row.get("workerId") == origin_worker
|
|
223
|
+
),
|
|
224
|
+
None,
|
|
225
|
+
)
|
|
226
|
+
origin_participant_ref = (
|
|
227
|
+
_worker_participant_ref(origin) if origin is not None else None
|
|
228
|
+
)
|
|
229
|
+
return origin_participant_ref != participant_ref
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _worker_execution_refs(worker: Mapping[str, Any]) -> dict[str, str]:
|
|
233
|
+
participant_ref = _worker_participant_ref(worker)
|
|
234
|
+
source_ref = worker.get("sourceRoleExecutionRef")
|
|
235
|
+
if participant_ref is None or not isinstance(source_ref, str) or not source_ref:
|
|
236
|
+
return {}
|
|
237
|
+
return {
|
|
238
|
+
"participantRef": participant_ref,
|
|
239
|
+
"sourceRoleExecutionRef": source_ref,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
|
|
165
243
|
def classify_collaborative_round(
|
|
166
244
|
votes: Mapping[str, Mapping[str, Any]],
|
|
167
245
|
) -> str | None:
|
|
@@ -304,7 +382,10 @@ def apply_round_results(
|
|
|
304
382
|
}
|
|
305
383
|
for row in expected_plan["dispatches"]
|
|
306
384
|
]
|
|
307
|
-
skipped =
|
|
385
|
+
skipped = [
|
|
386
|
+
{"worker": row["worker"], "reason": row["reason"]}
|
|
387
|
+
for row in expected_plan["skippedWorkers"]
|
|
388
|
+
]
|
|
308
389
|
for row in dispatch_history:
|
|
309
390
|
if row["status"] != "completed":
|
|
310
391
|
skipped.append(
|
|
@@ -457,13 +538,40 @@ def finalize_working_state(state: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
457
538
|
return final
|
|
458
539
|
|
|
459
540
|
|
|
541
|
+
def _working_execution_identity_version(
|
|
542
|
+
state: Mapping[str, Any],
|
|
543
|
+
errors: list[str],
|
|
544
|
+
) -> int | None:
|
|
545
|
+
schema_version = state.get("schemaVersion")
|
|
546
|
+
identity_version = state.get("executionIdentityVersion")
|
|
547
|
+
run_manifest_path = state.get("runManifestPath")
|
|
548
|
+
if schema_version == WORKING_SCHEMA_VERSION:
|
|
549
|
+
if (
|
|
550
|
+
"executionIdentityVersion" in state
|
|
551
|
+
or "runManifestPath" in state
|
|
552
|
+
):
|
|
553
|
+
errors.append(
|
|
554
|
+
"working schemaVersion 1.0 v1 state forbids v2 identity fields"
|
|
555
|
+
)
|
|
556
|
+
return 1
|
|
557
|
+
if schema_version == V2_WORKING_SCHEMA_VERSION:
|
|
558
|
+
if identity_version != EXECUTION_IDENTITY_VERSION:
|
|
559
|
+
errors.append(
|
|
560
|
+
"working execution identity version must be 2 for schemaVersion 2.0"
|
|
561
|
+
)
|
|
562
|
+
if not _nonempty_string(run_manifest_path):
|
|
563
|
+
errors.append("working runManifestPath must be a non-empty string")
|
|
564
|
+
return EXECUTION_IDENTITY_VERSION
|
|
565
|
+
errors.append(f"unsupported working schemaVersion: {schema_version!r}")
|
|
566
|
+
return None
|
|
567
|
+
|
|
568
|
+
|
|
460
569
|
def validate_working_state(state: Mapping[str, Any]) -> list[str]:
|
|
461
570
|
"""Return structural and transition errors for a working-state object."""
|
|
462
571
|
errors: list[str] = []
|
|
463
572
|
if not isinstance(state, Mapping):
|
|
464
573
|
return ["working state must be an object"]
|
|
465
|
-
|
|
466
|
-
errors.append("working schemaVersion must be 1.0")
|
|
574
|
+
identity_version = _working_execution_identity_version(state, errors)
|
|
467
575
|
if not _nonempty_string(state.get("taskKey")):
|
|
468
576
|
errors.append("taskKey must be a non-empty string")
|
|
469
577
|
digest = state.get("groupsDigest")
|
|
@@ -497,6 +605,24 @@ def validate_working_state(state: Mapping[str, Any]) -> list[str]:
|
|
|
497
605
|
"use 'analysis'; only the report author uses 'report-writer'. "
|
|
498
606
|
f"Allowed: {sorted(_AUDIENCES)}."
|
|
499
607
|
)
|
|
608
|
+
participant_ref = worker.get("participantRef")
|
|
609
|
+
source_ref = worker.get("sourceRoleExecutionRef")
|
|
610
|
+
has_execution_fields = (
|
|
611
|
+
"participantRef" in worker
|
|
612
|
+
or "sourceRoleExecutionRef" in worker
|
|
613
|
+
)
|
|
614
|
+
if identity_version == 1 and has_execution_fields:
|
|
615
|
+
errors.append(
|
|
616
|
+
f"workers[{index}] v1 row forbids execution identity references"
|
|
617
|
+
)
|
|
618
|
+
elif identity_version == EXECUTION_IDENTITY_VERSION and (
|
|
619
|
+
not _nonempty_string(participant_ref)
|
|
620
|
+
or not _nonempty_string(source_ref)
|
|
621
|
+
):
|
|
622
|
+
errors.append(
|
|
623
|
+
f"workers[{index}] must carry participantRef and "
|
|
624
|
+
"sourceRoleExecutionRef together"
|
|
625
|
+
)
|
|
500
626
|
findings = state.get("findings")
|
|
501
627
|
if not isinstance(findings, list):
|
|
502
628
|
errors.append("findings must be an array")
|
|
@@ -1788,8 +1914,10 @@ def _parse_vote(
|
|
|
1788
1914
|
def _validate_plannable_state(
|
|
1789
1915
|
state: Mapping[str, Any],
|
|
1790
1916
|
) -> tuple[list[str], dict[str, Mapping[str, Any]], list[Mapping[str, Any]]]:
|
|
1791
|
-
|
|
1792
|
-
|
|
1917
|
+
version_errors: list[str] = []
|
|
1918
|
+
_working_execution_identity_version(state, version_errors)
|
|
1919
|
+
if version_errors:
|
|
1920
|
+
raise ConvergenceContractError("; ".join(version_errors))
|
|
1793
1921
|
queue_value = state.get("queueFindingIds")
|
|
1794
1922
|
if not isinstance(queue_value, list) or not all(
|
|
1795
1923
|
isinstance(item, str) and item for item in queue_value
|
|
@@ -1833,6 +1961,41 @@ def _validate_plannable_state(
|
|
|
1833
1961
|
return queue, findings, history
|
|
1834
1962
|
|
|
1835
1963
|
|
|
1964
|
+
def _grouped_execution_identity_version(source: Mapping[str, Any]) -> int:
|
|
1965
|
+
schema_version = source.get("schemaVersion")
|
|
1966
|
+
identity_version = source.get("executionIdentityVersion")
|
|
1967
|
+
workers = source.get("workers")
|
|
1968
|
+
worker_rows = workers if isinstance(workers, list) else []
|
|
1969
|
+
has_worker_identity = any(
|
|
1970
|
+
isinstance(worker, Mapping)
|
|
1971
|
+
and (
|
|
1972
|
+
"participantRef" in worker
|
|
1973
|
+
or "sourceRoleExecutionRef" in worker
|
|
1974
|
+
)
|
|
1975
|
+
for worker in worker_rows
|
|
1976
|
+
)
|
|
1977
|
+
if schema_version == WORKING_SCHEMA_VERSION:
|
|
1978
|
+
if (
|
|
1979
|
+
"executionIdentityVersion" in source
|
|
1980
|
+
or "runManifestPath" in source
|
|
1981
|
+
or has_worker_identity
|
|
1982
|
+
):
|
|
1983
|
+
raise ConvergenceContractError(
|
|
1984
|
+
"v1 convergence groups forbid v2 execution identity fields"
|
|
1985
|
+
)
|
|
1986
|
+
return 1
|
|
1987
|
+
if schema_version == V2_WORKING_SCHEMA_VERSION:
|
|
1988
|
+
if identity_version != EXECUTION_IDENTITY_VERSION:
|
|
1989
|
+
raise ConvergenceContractError(
|
|
1990
|
+
"v2 convergence groups require executionIdentityVersion 2"
|
|
1991
|
+
)
|
|
1992
|
+
_required_string(source, "runManifestPath", "grouped input")
|
|
1993
|
+
return EXECUTION_IDENTITY_VERSION
|
|
1994
|
+
raise ConvergenceContractError(
|
|
1995
|
+
f"unsupported grouped input schemaVersion: {schema_version!r}"
|
|
1996
|
+
)
|
|
1997
|
+
|
|
1998
|
+
|
|
1836
1999
|
def _parse_config(value: Any) -> dict[str, Any]:
|
|
1837
2000
|
config = _object(value, "config")
|
|
1838
2001
|
enabled = config.get("enabled")
|
|
@@ -1865,7 +2028,7 @@ def _parse_config(value: Any) -> dict[str, Any]:
|
|
|
1865
2028
|
}
|
|
1866
2029
|
|
|
1867
2030
|
|
|
1868
|
-
def _parse_workers(value: Any) -> list[dict[str, str]]:
|
|
2031
|
+
def _parse_workers(value: Any, identity_version: int) -> list[dict[str, str]]:
|
|
1869
2032
|
if not isinstance(value, list):
|
|
1870
2033
|
raise ConvergenceContractError("workers must be an array")
|
|
1871
2034
|
workers: list[dict[str, str]] = []
|
|
@@ -1885,10 +2048,80 @@ def _parse_workers(value: Any) -> list[dict[str, str]]:
|
|
|
1885
2048
|
if worker_id in seen:
|
|
1886
2049
|
raise ConvergenceContractError(f"duplicate workerId: {worker_id}")
|
|
1887
2050
|
seen.add(worker_id)
|
|
1888
|
-
|
|
2051
|
+
parsed = {"workerId": worker_id, "audience": audience}
|
|
2052
|
+
participant_ref = worker.get("participantRef")
|
|
2053
|
+
source_ref = worker.get("sourceRoleExecutionRef")
|
|
2054
|
+
has_execution_refs = (
|
|
2055
|
+
"participantRef" in worker
|
|
2056
|
+
or "sourceRoleExecutionRef" in worker
|
|
2057
|
+
)
|
|
2058
|
+
if identity_version == 1 and has_execution_refs:
|
|
2059
|
+
raise ConvergenceContractError(
|
|
2060
|
+
"v1 convergence workers forbid execution identity references"
|
|
2061
|
+
)
|
|
2062
|
+
if identity_version == EXECUTION_IDENTITY_VERSION and (
|
|
2063
|
+
not _nonempty_string(participant_ref)
|
|
2064
|
+
or not _nonempty_string(source_ref)
|
|
2065
|
+
):
|
|
2066
|
+
raise ConvergenceContractError(
|
|
2067
|
+
"v2 convergence workers require execution identity references: "
|
|
2068
|
+
f"workers[{index}] must carry participantRef and "
|
|
2069
|
+
"sourceRoleExecutionRef"
|
|
2070
|
+
)
|
|
2071
|
+
if identity_version == EXECUTION_IDENTITY_VERSION:
|
|
2072
|
+
parsed.update({
|
|
2073
|
+
"participantRef": str(participant_ref),
|
|
2074
|
+
"sourceRoleExecutionRef": str(source_ref),
|
|
2075
|
+
})
|
|
2076
|
+
workers.append(parsed)
|
|
1889
2077
|
return workers
|
|
1890
2078
|
|
|
1891
2079
|
|
|
2080
|
+
def _validate_worker_execution_identity(
|
|
2081
|
+
workers: list[dict[str, str]],
|
|
2082
|
+
execution_manifest: ExecutionManifest | None,
|
|
2083
|
+
identity_version: int,
|
|
2084
|
+
) -> None:
|
|
2085
|
+
if identity_version == 1:
|
|
2086
|
+
if execution_manifest is not None:
|
|
2087
|
+
raise ConvergenceContractError(
|
|
2088
|
+
"v1 convergence forbids execution manifest authority; "
|
|
2089
|
+
"omit --run-manifest"
|
|
2090
|
+
)
|
|
2091
|
+
return
|
|
2092
|
+
if execution_manifest is None:
|
|
2093
|
+
raise ConvergenceContractError(
|
|
2094
|
+
"v2 convergence requires execution manifest authority; "
|
|
2095
|
+
"pass --run-manifest"
|
|
2096
|
+
)
|
|
2097
|
+
if execution_manifest.legacy:
|
|
2098
|
+
raise ConvergenceContractError(
|
|
2099
|
+
"v2 convergence workers require v2 execution manifest"
|
|
2100
|
+
)
|
|
2101
|
+
roles = {
|
|
2102
|
+
role.role_execution_ref: role
|
|
2103
|
+
for role in execution_manifest.role_executions
|
|
2104
|
+
}
|
|
2105
|
+
for worker in workers:
|
|
2106
|
+
source_ref = worker["sourceRoleExecutionRef"]
|
|
2107
|
+
source = roles.get(source_ref)
|
|
2108
|
+
if source is None:
|
|
2109
|
+
raise ConvergenceContractError(
|
|
2110
|
+
f"unknown sourceRoleExecutionRef: {source_ref}"
|
|
2111
|
+
)
|
|
2112
|
+
if source.participant_ref != worker["participantRef"]:
|
|
2113
|
+
raise ConvergenceContractError(
|
|
2114
|
+
f"worker {worker['workerId']} participantRef does not match "
|
|
2115
|
+
f"sourceRoleExecutionRef: {source_ref}"
|
|
2116
|
+
)
|
|
2117
|
+
allowed_roles = _AUDIENCE_SOURCE_ROLES[worker["audience"]]
|
|
2118
|
+
if source.role not in allowed_roles:
|
|
2119
|
+
raise ConvergenceContractError(
|
|
2120
|
+
f"{worker['audience']} audience source role is not allowed: "
|
|
2121
|
+
f"{source.role}"
|
|
2122
|
+
)
|
|
2123
|
+
|
|
2124
|
+
|
|
1892
2125
|
def _parse_groups(
|
|
1893
2126
|
value: Any,
|
|
1894
2127
|
analysis_workers: list[str],
|
|
@@ -2,13 +2,28 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
|
-
import hashlib
|
|
6
5
|
import os
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
import tempfile
|
|
9
8
|
from typing import Any, Mapping
|
|
10
9
|
|
|
11
10
|
from .convergence_engine import ConvergenceContractError
|
|
11
|
+
from .domain.role import RoleCatalogError, role_for_duty
|
|
12
|
+
from .execution_identity import ExecutionManifestError, Invocation, RoleExecution
|
|
13
|
+
from .execution_manifest import (
|
|
14
|
+
read_execution_manifest,
|
|
15
|
+
reserve_derived_role_invocation,
|
|
16
|
+
)
|
|
17
|
+
from .worker_request import verifier_extra_dirs
|
|
18
|
+
from .write_policy import build_invocation_write_contract
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
DYNAMIC_VERIFIER_SOURCE_ROLES = frozenset({
|
|
22
|
+
"analyser",
|
|
23
|
+
"designer",
|
|
24
|
+
"planner",
|
|
25
|
+
"verifier",
|
|
26
|
+
})
|
|
12
27
|
|
|
13
28
|
|
|
14
29
|
def load_json_object(path: Path) -> dict[str, Any]:
|
|
@@ -62,6 +77,160 @@ def write_final_state_atomic(
|
|
|
62
77
|
write_json_atomic(path, payload)
|
|
63
78
|
|
|
64
79
|
|
|
80
|
+
def reserve_dynamic_verifier(
|
|
81
|
+
manifest_path: Path,
|
|
82
|
+
source_role_execution_ref: str,
|
|
83
|
+
duty_id: str,
|
|
84
|
+
round_number: int,
|
|
85
|
+
task_key: str,
|
|
86
|
+
*,
|
|
87
|
+
input_digest: str,
|
|
88
|
+
invocation_ref: str | None = None,
|
|
89
|
+
artifact_paths: tuple[Path, ...],
|
|
90
|
+
) -> tuple[RoleExecution, Invocation]:
|
|
91
|
+
"""Reserve one provider-neutral verifier identity for a logical round."""
|
|
92
|
+
if round_number < 1:
|
|
93
|
+
raise ExecutionManifestError("dynamic verifier round must be positive")
|
|
94
|
+
manifest_path = Path(manifest_path).resolve()
|
|
95
|
+
manifest = read_execution_manifest(manifest_path)
|
|
96
|
+
if manifest.legacy:
|
|
97
|
+
raise ExecutionManifestError("cannot reserve a dynamic verifier in v1 data")
|
|
98
|
+
_validate_dynamic_verifier_request(
|
|
99
|
+
manifest.role_executions,
|
|
100
|
+
source_role_execution_ref,
|
|
101
|
+
duty_id,
|
|
102
|
+
)
|
|
103
|
+
authority = load_json_object(manifest_path)
|
|
104
|
+
if authority.get("taskKey") not in (None, task_key):
|
|
105
|
+
raise ExecutionManifestError("dynamic verifier taskKey does not match manifest")
|
|
106
|
+
project_root = _manifest_project_root(authority, manifest_path)
|
|
107
|
+
task_manifest_path = _manifest_authority_path(
|
|
108
|
+
authority,
|
|
109
|
+
"taskManifestPath",
|
|
110
|
+
project_root,
|
|
111
|
+
)
|
|
112
|
+
run_ref = _manifest_run_ref(authority, manifest_path, project_root)
|
|
113
|
+
|
|
114
|
+
def invocation_factory(verifier: RoleExecution) -> Invocation:
|
|
115
|
+
selected_ref = invocation_ref or (
|
|
116
|
+
f"{verifier.role_execution_ref}-{duty_id}-r{round_number:03d}"
|
|
117
|
+
)
|
|
118
|
+
capability = (
|
|
119
|
+
verifier.binding.worker_write_capability
|
|
120
|
+
if verifier.binding is not None
|
|
121
|
+
else None
|
|
122
|
+
)
|
|
123
|
+
if capability is None:
|
|
124
|
+
raise ExecutionManifestError(
|
|
125
|
+
"dynamic verifier has no runner write capability"
|
|
126
|
+
)
|
|
127
|
+
try:
|
|
128
|
+
policy, enforcement = build_invocation_write_contract(
|
|
129
|
+
role="verifier",
|
|
130
|
+
project_root=project_root,
|
|
131
|
+
worktree=None,
|
|
132
|
+
artifact_paths=artifact_paths,
|
|
133
|
+
maximum_precision=capability.max_boundary_precision,
|
|
134
|
+
auxiliary_roots=verifier_extra_dirs("verifier"),
|
|
135
|
+
validated_auxiliary_roots=verifier_extra_dirs("verifier"),
|
|
136
|
+
)
|
|
137
|
+
except (OSError, ValueError) as exc:
|
|
138
|
+
raise ExecutionManifestError(
|
|
139
|
+
f"dynamic verifier write policy is invalid: {exc}"
|
|
140
|
+
) from exc
|
|
141
|
+
return Invocation(
|
|
142
|
+
invocation_ref=selected_ref,
|
|
143
|
+
participant_ref=verifier.participant_ref,
|
|
144
|
+
role_execution_ref=verifier.role_execution_ref,
|
|
145
|
+
source_invocation_ref=None,
|
|
146
|
+
recovery_ref=None,
|
|
147
|
+
duty_id=duty_id,
|
|
148
|
+
dispatch_kind=f"reverify-r{round_number}",
|
|
149
|
+
round=round_number,
|
|
150
|
+
input_digest=input_digest,
|
|
151
|
+
write_policy=policy.to_payload(),
|
|
152
|
+
write_policy_digest=policy.digest,
|
|
153
|
+
write_enforcement=enforcement.to_payload(),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return reserve_derived_role_invocation(
|
|
157
|
+
manifest_path,
|
|
158
|
+
source_role_execution_ref=source_role_execution_ref,
|
|
159
|
+
role="verifier",
|
|
160
|
+
invocation_factory=invocation_factory,
|
|
161
|
+
task_key=task_key,
|
|
162
|
+
task_manifest_path=task_manifest_path,
|
|
163
|
+
run_ref=run_ref,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _validate_dynamic_verifier_request(
|
|
168
|
+
roles: tuple[RoleExecution, ...],
|
|
169
|
+
source_role_execution_ref: str,
|
|
170
|
+
duty_id: str,
|
|
171
|
+
) -> None:
|
|
172
|
+
try:
|
|
173
|
+
duty_role = role_for_duty(duty_id)
|
|
174
|
+
except RoleCatalogError as exc:
|
|
175
|
+
raise ExecutionManifestError(f"unknown verifier duty: {duty_id}") from exc
|
|
176
|
+
if duty_role != "verifier":
|
|
177
|
+
raise ExecutionManifestError(f"dynamic verifier duty is not verifier: {duty_id}")
|
|
178
|
+
source = next(
|
|
179
|
+
(
|
|
180
|
+
role
|
|
181
|
+
for role in roles
|
|
182
|
+
if role.role_execution_ref == source_role_execution_ref
|
|
183
|
+
),
|
|
184
|
+
None,
|
|
185
|
+
)
|
|
186
|
+
if source is not None and source.role not in DYNAMIC_VERIFIER_SOURCE_ROLES:
|
|
187
|
+
raise ExecutionManifestError(
|
|
188
|
+
f"dynamic verifier source role is not eligible: {source.role}"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _manifest_project_root(
|
|
193
|
+
manifest: Mapping[str, Any],
|
|
194
|
+
manifest_path: Path,
|
|
195
|
+
) -> Path:
|
|
196
|
+
value = manifest.get("projectRoot")
|
|
197
|
+
if not isinstance(value, str) or not value.strip():
|
|
198
|
+
raise ExecutionManifestError("run manifest has no projectRoot")
|
|
199
|
+
project_root = Path(value).resolve()
|
|
200
|
+
if not manifest_path.is_relative_to(project_root):
|
|
201
|
+
raise ExecutionManifestError("run manifest resolves outside projectRoot")
|
|
202
|
+
return project_root
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _manifest_authority_path(
|
|
206
|
+
manifest: Mapping[str, Any],
|
|
207
|
+
key: str,
|
|
208
|
+
project_root: Path,
|
|
209
|
+
) -> Path:
|
|
210
|
+
value = manifest.get(key)
|
|
211
|
+
if not isinstance(value, str) or not value.strip():
|
|
212
|
+
raise ExecutionManifestError(f"run manifest has no {key}")
|
|
213
|
+
path = (project_root / value).resolve()
|
|
214
|
+
if not path.is_relative_to(project_root):
|
|
215
|
+
raise ExecutionManifestError(f"run manifest {key} escapes projectRoot")
|
|
216
|
+
return path
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _manifest_run_ref(
|
|
220
|
+
manifest: Mapping[str, Any],
|
|
221
|
+
manifest_path: Path,
|
|
222
|
+
project_root: Path,
|
|
223
|
+
) -> str:
|
|
224
|
+
value = manifest.get("runManifestPath")
|
|
225
|
+
if not isinstance(value, str) or not value.strip():
|
|
226
|
+
raise ExecutionManifestError("run manifest has no runManifestPath")
|
|
227
|
+
if (project_root / value).resolve() != manifest_path:
|
|
228
|
+
raise ExecutionManifestError(
|
|
229
|
+
"runManifestPath does not identify the execution manifest"
|
|
230
|
+
)
|
|
231
|
+
return value
|
|
232
|
+
|
|
233
|
+
|
|
65
234
|
def _validate_final_replacement(
|
|
66
235
|
path: Path,
|
|
67
236
|
migration: Mapping[str, Any] | None,
|