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
|
@@ -14,6 +14,8 @@ import re
|
|
|
14
14
|
import tempfile
|
|
15
15
|
from typing import Iterator, Literal, Mapping, get_args
|
|
16
16
|
|
|
17
|
+
from .domain.role import RoleCatalogError, role_for_duty
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
AgentAudience = Literal[
|
|
19
21
|
"lead",
|
|
@@ -79,6 +81,15 @@ _TOP_LEVEL_KEYS = {
|
|
|
79
81
|
"prompt",
|
|
80
82
|
"digests",
|
|
81
83
|
}
|
|
84
|
+
_V2_TOP_LEVEL_KEYS = (_TOP_LEVEL_KEYS - {"workerId"}) | {
|
|
85
|
+
"executionIdentityVersion",
|
|
86
|
+
"invocationRef",
|
|
87
|
+
"participantRef",
|
|
88
|
+
"roleExecutionRef",
|
|
89
|
+
"executionLabel",
|
|
90
|
+
"dutyId",
|
|
91
|
+
"attempt",
|
|
92
|
+
}
|
|
82
93
|
_NESTED_KEYS = {
|
|
83
94
|
"contractSource": {"mode", "runManifestPath", "dutyRootPath"},
|
|
84
95
|
"modelAssignment": {
|
|
@@ -130,6 +141,24 @@ class AgentModelAssignment:
|
|
|
130
141
|
host_model_value: str | None
|
|
131
142
|
|
|
132
143
|
|
|
144
|
+
@dataclass(frozen=True)
|
|
145
|
+
class InvocationExecutionIdentity:
|
|
146
|
+
participant_ref: str
|
|
147
|
+
role_execution_ref: str
|
|
148
|
+
execution_label: str
|
|
149
|
+
duty_id: str
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass(frozen=True)
|
|
153
|
+
class InvocationMetadataIdentity:
|
|
154
|
+
invocation_ref: str
|
|
155
|
+
participant_ref: str
|
|
156
|
+
role_execution_ref: str
|
|
157
|
+
execution_label: str
|
|
158
|
+
duty_id: str
|
|
159
|
+
attempt: int
|
|
160
|
+
|
|
161
|
+
|
|
133
162
|
@dataclass(frozen=True)
|
|
134
163
|
class AgentInstructionSource:
|
|
135
164
|
kind: Literal["project", "runtime"]
|
|
@@ -158,6 +187,11 @@ class AgentInvocationRequest:
|
|
|
158
187
|
prompt_path: Path
|
|
159
188
|
metadata_path: Path
|
|
160
189
|
dispatch_kind: str
|
|
190
|
+
participant_ref: str | None
|
|
191
|
+
role_execution_ref: str | None
|
|
192
|
+
duty_id: str | None
|
|
193
|
+
invocation_ref: str | None
|
|
194
|
+
attempt: int
|
|
161
195
|
# Republish a prompt this invocation id already wrote, but only while no
|
|
162
196
|
# dispatch has used it. See `_publish_or_reuse`.
|
|
163
197
|
replace_undispatched: bool = False
|
|
@@ -178,6 +212,10 @@ class PreparedAgentInvocation:
|
|
|
178
212
|
duty_digest: str
|
|
179
213
|
instruction_digest: str
|
|
180
214
|
prompt_digest: str
|
|
215
|
+
participant_ref: str | None = None
|
|
216
|
+
role_execution_ref: str | None = None
|
|
217
|
+
invocation_ref: str | None = None
|
|
218
|
+
attempt: int = 1
|
|
181
219
|
|
|
182
220
|
|
|
183
221
|
@dataclass(frozen=True)
|
|
@@ -189,6 +227,15 @@ class MaterializedStandaloneResult:
|
|
|
189
227
|
result_envelope_digest: str
|
|
190
228
|
|
|
191
229
|
|
|
230
|
+
@dataclass(frozen=True)
|
|
231
|
+
class RetryAgentInvocation:
|
|
232
|
+
invocation_id: str
|
|
233
|
+
invocation_ref: str
|
|
234
|
+
prompt_path: Path
|
|
235
|
+
metadata_path: Path
|
|
236
|
+
attempt: int
|
|
237
|
+
|
|
238
|
+
|
|
192
239
|
@dataclass(frozen=True)
|
|
193
240
|
class VerifiedStandaloneResult:
|
|
194
241
|
purpose: str
|
|
@@ -228,6 +275,201 @@ def agent_model_assignment_from_payload(payload: object) -> AgentModelAssignment
|
|
|
228
275
|
return assignment
|
|
229
276
|
|
|
230
277
|
|
|
278
|
+
def invocation_execution_identity_from_manifest(
|
|
279
|
+
manifest: Mapping[str, object],
|
|
280
|
+
*,
|
|
281
|
+
assignment: AgentModelAssignment,
|
|
282
|
+
assignment_ref: str,
|
|
283
|
+
duty_id: str,
|
|
284
|
+
role_execution_ref: str | None = None,
|
|
285
|
+
) -> InvocationExecutionIdentity | None:
|
|
286
|
+
"""Resolve v2 refs from Task 8 without duplicating them in model assignments."""
|
|
287
|
+
schema = manifest.get("schemaVersion")
|
|
288
|
+
identity_version = manifest.get("executionIdentityVersion")
|
|
289
|
+
if schema in (None, 1, "1", "1.0") and identity_version is None:
|
|
290
|
+
return None
|
|
291
|
+
if schema != "2.0" or identity_version != 2:
|
|
292
|
+
raise AgentInvocationError("run manifest mixes v1 and v2 execution identity")
|
|
293
|
+
try:
|
|
294
|
+
role = role_for_duty(duty_id)
|
|
295
|
+
except RoleCatalogError as exc:
|
|
296
|
+
raise AgentInvocationError(str(exc)) from exc
|
|
297
|
+
role_executions = manifest.get("roleExecutions")
|
|
298
|
+
candidates = [
|
|
299
|
+
row for row in role_executions
|
|
300
|
+
if isinstance(row, Mapping)
|
|
301
|
+
and row.get("role") == role
|
|
302
|
+
and row.get("provider") == assignment.provider
|
|
303
|
+
and _role_execution_matches_assignment(row, assignment)
|
|
304
|
+
] if isinstance(role_executions, list) else []
|
|
305
|
+
if role_execution_ref is None:
|
|
306
|
+
selected = _select_role_execution_candidate(
|
|
307
|
+
manifest,
|
|
308
|
+
candidates,
|
|
309
|
+
assignment_ref=assignment_ref,
|
|
310
|
+
role=role,
|
|
311
|
+
assignment=assignment,
|
|
312
|
+
)
|
|
313
|
+
else:
|
|
314
|
+
selected = next(
|
|
315
|
+
(
|
|
316
|
+
row
|
|
317
|
+
for row in candidates
|
|
318
|
+
if row.get("roleExecutionRef") == role_execution_ref
|
|
319
|
+
),
|
|
320
|
+
None,
|
|
321
|
+
)
|
|
322
|
+
if selected is None:
|
|
323
|
+
raise AgentInvocationError(
|
|
324
|
+
"v2 execution identity does not match a canonical role execution"
|
|
325
|
+
)
|
|
326
|
+
return InvocationExecutionIdentity(
|
|
327
|
+
participant_ref=_required_metadata_string(selected, "participantRef"),
|
|
328
|
+
role_execution_ref=_required_metadata_string(selected, "roleExecutionRef"),
|
|
329
|
+
execution_label=_required_metadata_string(selected, "executionLabel"),
|
|
330
|
+
duty_id=duty_id,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def v2_role_assignment_authority_errors(
|
|
335
|
+
manifest: Mapping[str, object],
|
|
336
|
+
*,
|
|
337
|
+
role_execution_ref: str,
|
|
338
|
+
participant_ref: str,
|
|
339
|
+
assignment: AgentModelAssignment,
|
|
340
|
+
) -> list[str]:
|
|
341
|
+
"""Compare one v2 role and participant with the provider-neutral assignment."""
|
|
342
|
+
role_executions = manifest.get("roleExecutions")
|
|
343
|
+
execution = next((
|
|
344
|
+
row for row in role_executions
|
|
345
|
+
if isinstance(row, Mapping)
|
|
346
|
+
and row.get("roleExecutionRef") == role_execution_ref
|
|
347
|
+
), None) if isinstance(role_executions, list) else None
|
|
348
|
+
participants = manifest.get("participantAssignments")
|
|
349
|
+
participant = next((
|
|
350
|
+
row for row in participants
|
|
351
|
+
if isinstance(row, Mapping)
|
|
352
|
+
and row.get("participantRef") == participant_ref
|
|
353
|
+
), None) if isinstance(participants, list) else None
|
|
354
|
+
if not isinstance(execution, Mapping) or not isinstance(participant, Mapping):
|
|
355
|
+
return ["v2 assignment does not reference canonical execution identity"]
|
|
356
|
+
errors: list[str] = []
|
|
357
|
+
if participant.get("hostModelValue") != assignment.host_model_value:
|
|
358
|
+
errors.append(
|
|
359
|
+
"participant hostModelValue does not match invocation metadata"
|
|
360
|
+
)
|
|
361
|
+
if (
|
|
362
|
+
participant.get("hostRuntime") != assignment.host_runtime
|
|
363
|
+
or (
|
|
364
|
+
manifest.get("leadRuntime") is not None
|
|
365
|
+
and participant.get("hostRuntime") != manifest.get("leadRuntime")
|
|
366
|
+
)
|
|
367
|
+
):
|
|
368
|
+
errors.append(
|
|
369
|
+
"participant hostRuntime does not match invocation metadata"
|
|
370
|
+
)
|
|
371
|
+
binding = execution.get("binding")
|
|
372
|
+
binding_matches = (
|
|
373
|
+
isinstance(binding, Mapping)
|
|
374
|
+
and binding.get("runner") == assignment.runner
|
|
375
|
+
and binding.get("resolvedExecutionValue")
|
|
376
|
+
== assignment.model_execution_value
|
|
377
|
+
and binding.get("hostModelValue") == assignment.host_model_value
|
|
378
|
+
and binding.get("hostModelValue") == participant.get("hostModelValue")
|
|
379
|
+
and binding.get("workerWriteCapability")
|
|
380
|
+
== participant.get("workerWriteCapability")
|
|
381
|
+
and participant.get("runner") == binding.get("runner")
|
|
382
|
+
) or (
|
|
383
|
+
binding is None
|
|
384
|
+
and assignment.model_execution_value == "unknown"
|
|
385
|
+
and participant.get("runner") == assignment.runner
|
|
386
|
+
)
|
|
387
|
+
if (
|
|
388
|
+
execution.get("participantRef") != participant_ref
|
|
389
|
+
or execution.get("provider") != assignment.provider
|
|
390
|
+
or execution.get("provider") != participant.get("provider")
|
|
391
|
+
or execution.get("modelRef") != participant.get("modelRef")
|
|
392
|
+
or execution.get("modelId") != assignment.model
|
|
393
|
+
or execution.get("modelId") != participant.get("modelId")
|
|
394
|
+
or not binding_matches
|
|
395
|
+
):
|
|
396
|
+
errors.append("v2 assignment does not match role execution authority")
|
|
397
|
+
return errors
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _role_execution_matches_assignment(
|
|
401
|
+
row: Mapping[str, object], assignment: AgentModelAssignment,
|
|
402
|
+
) -> bool:
|
|
403
|
+
binding = row.get("binding")
|
|
404
|
+
if isinstance(binding, Mapping):
|
|
405
|
+
return binding.get("resolvedExecutionValue") == assignment.model_execution_value
|
|
406
|
+
return assignment.model_execution_value == "unknown"
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _select_role_execution_candidate(
|
|
410
|
+
manifest: Mapping[str, object],
|
|
411
|
+
candidates: list[Mapping[str, object]],
|
|
412
|
+
*,
|
|
413
|
+
assignment_ref: str,
|
|
414
|
+
role: str,
|
|
415
|
+
assignment: AgentModelAssignment,
|
|
416
|
+
) -> Mapping[str, object] | None:
|
|
417
|
+
if len(candidates) == 1:
|
|
418
|
+
return candidates[0]
|
|
419
|
+
if not assignment_ref.startswith("initial/"):
|
|
420
|
+
return None
|
|
421
|
+
worker_id = assignment_ref.partition("/")[2]
|
|
422
|
+
workers = manifest.get("workerAssignments")
|
|
423
|
+
matching_workers = [
|
|
424
|
+
row for row in workers
|
|
425
|
+
if isinstance(row, Mapping)
|
|
426
|
+
and row.get("role") == role
|
|
427
|
+
and row.get("provider") == assignment.provider
|
|
428
|
+
and row.get("modelExecutionValue") == assignment.model_execution_value
|
|
429
|
+
] if isinstance(workers, list) else []
|
|
430
|
+
worker_index = next((
|
|
431
|
+
index for index, row in enumerate(matching_workers)
|
|
432
|
+
if row.get("workerId") == worker_id
|
|
433
|
+
), None)
|
|
434
|
+
ordered = sorted(candidates, key=lambda row: int(row.get("ordinal", 0)))
|
|
435
|
+
if worker_index is None or worker_index >= len(ordered):
|
|
436
|
+
return None
|
|
437
|
+
return ordered[worker_index]
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _required_metadata_string(
|
|
441
|
+
payload: Mapping[str, object], key: str,
|
|
442
|
+
) -> str:
|
|
443
|
+
value = payload.get(key)
|
|
444
|
+
if not isinstance(value, str) or not value:
|
|
445
|
+
raise AgentInvocationError(f"v2 execution identity field is invalid: {key}")
|
|
446
|
+
return value
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def invocation_metadata_identity(
|
|
450
|
+
payload: Mapping[str, object],
|
|
451
|
+
) -> InvocationMetadataIdentity | None:
|
|
452
|
+
"""Read one exact metadata version without guessing from partial fields."""
|
|
453
|
+
schema = payload.get("schemaVersion")
|
|
454
|
+
identity_version = payload.get("executionIdentityVersion")
|
|
455
|
+
if schema == 1 and identity_version is None:
|
|
456
|
+
if not _metadata_schema_is_exact(payload):
|
|
457
|
+
raise AgentInvocationError("agent invocation metadata schema is invalid")
|
|
458
|
+
return None
|
|
459
|
+
if schema != "2.0" or identity_version != 2:
|
|
460
|
+
raise AgentInvocationError("agent invocation metadata mixes v1 and v2 identity")
|
|
461
|
+
if not _metadata_schema_is_exact(payload):
|
|
462
|
+
raise AgentInvocationError("v2 execution identity metadata is invalid")
|
|
463
|
+
return InvocationMetadataIdentity(
|
|
464
|
+
invocation_ref=payload["invocationRef"],
|
|
465
|
+
participant_ref=payload["participantRef"],
|
|
466
|
+
role_execution_ref=payload["roleExecutionRef"],
|
|
467
|
+
execution_label=payload["executionLabel"],
|
|
468
|
+
duty_id=payload["dutyId"],
|
|
469
|
+
attempt=payload["attempt"],
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
|
|
231
473
|
@dataclass(frozen=True)
|
|
232
474
|
class _MaterializedInvocation:
|
|
233
475
|
prepared: PreparedAgentInvocation
|
|
@@ -307,18 +549,132 @@ def prepare_agent_invocation(
|
|
|
307
549
|
expected_worker_id=request.worker_id,
|
|
308
550
|
expected_assignment_ref=request.assignment_ref,
|
|
309
551
|
expected_audience=request.audience,
|
|
552
|
+
expected_participant_ref=request.participant_ref,
|
|
553
|
+
expected_role_execution_ref=request.role_execution_ref,
|
|
554
|
+
expected_invocation_ref=request.invocation_ref,
|
|
310
555
|
)
|
|
311
556
|
if errors:
|
|
312
557
|
raise AgentInvocationError("; ".join(errors))
|
|
313
558
|
return materialized.prepared
|
|
314
559
|
|
|
315
560
|
|
|
561
|
+
def materialize_retry_invocation(
|
|
562
|
+
metadata_path: Path,
|
|
563
|
+
*,
|
|
564
|
+
project_root: Path,
|
|
565
|
+
attempt: int,
|
|
566
|
+
) -> RetryAgentInvocation:
|
|
567
|
+
"""Publish an attempt-specific prompt/metadata pair for one v2 retry."""
|
|
568
|
+
metadata = _load_json_object(metadata_path, "agent invocation metadata")
|
|
569
|
+
identity = invocation_metadata_identity(metadata)
|
|
570
|
+
if identity is None:
|
|
571
|
+
raise AgentInvocationError("v1 invocation metadata cannot be retried as v2")
|
|
572
|
+
if attempt != identity.attempt + 1:
|
|
573
|
+
raise AgentInvocationError(
|
|
574
|
+
f"retry attempt must be {identity.attempt + 1}"
|
|
575
|
+
)
|
|
576
|
+
source = metadata.get("contractSource")
|
|
577
|
+
prompt_spec = metadata.get("prompt")
|
|
578
|
+
if not isinstance(source, dict) or not isinstance(prompt_spec, dict):
|
|
579
|
+
raise AgentInvocationError("retry invocation metadata is incomplete")
|
|
580
|
+
manifest_path = _project_path(
|
|
581
|
+
project_root, source.get("runManifestPath"), must_exist=True
|
|
582
|
+
)
|
|
583
|
+
prompt_path = _project_path(
|
|
584
|
+
project_root, prompt_spec.get("path"), must_exist=True
|
|
585
|
+
)
|
|
586
|
+
retry_prompt_path = prompt_path.with_name(
|
|
587
|
+
f"{prompt_path.stem}-attempt-{attempt}{prompt_path.suffix}"
|
|
588
|
+
)
|
|
589
|
+
retry_metadata_path = retry_prompt_path.with_name(
|
|
590
|
+
retry_prompt_path.name + ".meta.json"
|
|
591
|
+
)
|
|
592
|
+
retry_invocation_id = f"{metadata['invocationId']}-attempt-{attempt}"
|
|
593
|
+
retry_metadata = dict(metadata)
|
|
594
|
+
retry_metadata["invocationId"] = retry_invocation_id
|
|
595
|
+
retry_metadata["attempt"] = attempt
|
|
596
|
+
retry_metadata["prompt"] = {
|
|
597
|
+
"path": _project_relative(
|
|
598
|
+
retry_prompt_path, project_root, must_exist=False
|
|
599
|
+
)
|
|
600
|
+
}
|
|
601
|
+
manifest = _load_json_object(manifest_path, "run manifest")
|
|
602
|
+
contract = manifest.get("agentContract")
|
|
603
|
+
if not isinstance(contract, dict):
|
|
604
|
+
raise AgentInvocationError("run manifest has no agent contract")
|
|
605
|
+
reservation_root = _project_path(
|
|
606
|
+
project_root,
|
|
607
|
+
contract.get("invocationReservationRootPath"),
|
|
608
|
+
must_exist=True,
|
|
609
|
+
)
|
|
610
|
+
reservation = {
|
|
611
|
+
"schemaVersion": "2.0",
|
|
612
|
+
"executionIdentityVersion": 2,
|
|
613
|
+
"invocationId": retry_invocation_id,
|
|
614
|
+
"assignmentRef": metadata["assignmentRef"],
|
|
615
|
+
"audience": metadata["audience"],
|
|
616
|
+
"dispatchKind": metadata["dispatchKind"],
|
|
617
|
+
"promptPath": retry_metadata["prompt"]["path"],
|
|
618
|
+
"metadataPath": _project_relative(
|
|
619
|
+
retry_metadata_path, project_root, must_exist=False
|
|
620
|
+
),
|
|
621
|
+
"invocationRef": identity.invocation_ref,
|
|
622
|
+
"participantRef": identity.participant_ref,
|
|
623
|
+
"roleExecutionRef": identity.role_execution_ref,
|
|
624
|
+
"dutyId": identity.duty_id,
|
|
625
|
+
"attempt": attempt,
|
|
626
|
+
}
|
|
627
|
+
with _exclusive_lock(reservation_root / "publish.lock"):
|
|
628
|
+
_publish_or_reuse_exact(
|
|
629
|
+
reservation_root / f"{retry_invocation_id}.json",
|
|
630
|
+
_pretty_json(reservation),
|
|
631
|
+
)
|
|
632
|
+
with _exclusive_lock(_prompt_lock_path(retry_prompt_path)):
|
|
633
|
+
_publish_or_reuse_exact(retry_prompt_path, prompt_path.read_bytes())
|
|
634
|
+
_publish_or_reuse_exact(
|
|
635
|
+
retry_metadata_path, _pretty_json(retry_metadata)
|
|
636
|
+
)
|
|
637
|
+
errors = verify_agent_invocation(
|
|
638
|
+
retry_metadata_path,
|
|
639
|
+
project_root=project_root,
|
|
640
|
+
expected_run_manifest_path=manifest_path,
|
|
641
|
+
expected_assignment=agent_model_assignment_from_payload(
|
|
642
|
+
metadata["modelAssignment"]
|
|
643
|
+
),
|
|
644
|
+
expected_invocation_id=retry_invocation_id,
|
|
645
|
+
expected_assignment_ref=str(metadata["assignmentRef"]),
|
|
646
|
+
expected_audience=str(metadata["audience"]),
|
|
647
|
+
expected_participant_ref=identity.participant_ref,
|
|
648
|
+
expected_role_execution_ref=identity.role_execution_ref,
|
|
649
|
+
expected_invocation_ref=identity.invocation_ref,
|
|
650
|
+
)
|
|
651
|
+
if errors:
|
|
652
|
+
raise AgentInvocationError("; ".join(errors))
|
|
653
|
+
return RetryAgentInvocation(
|
|
654
|
+
invocation_id=retry_invocation_id,
|
|
655
|
+
invocation_ref=identity.invocation_ref,
|
|
656
|
+
prompt_path=retry_prompt_path,
|
|
657
|
+
metadata_path=retry_metadata_path,
|
|
658
|
+
attempt=attempt,
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
|
|
316
662
|
def compose_agent_prompt(request: AgentInvocationRequest) -> str:
|
|
317
663
|
"""Render and validate a prompt candidate without publishing artifacts."""
|
|
318
664
|
_validate_request(request)
|
|
319
665
|
return _materialize(request).prompt_bytes.decode("utf-8")
|
|
320
666
|
|
|
321
667
|
|
|
668
|
+
def compose_unbound_run_prompt(request: AgentInvocationRequest) -> bytes:
|
|
669
|
+
"""Compose v2 run prompt bytes before a dynamic role identity is reserved."""
|
|
670
|
+
_validate_common_request(request)
|
|
671
|
+
_validate_unbound_v2_run_request(request)
|
|
672
|
+
common = load_common_duty_contract(request.duty_root)
|
|
673
|
+
catalog = load_duty_catalog(request.duty_root)
|
|
674
|
+
duty = catalog[request.audience]
|
|
675
|
+
return _render_prompt(request, common, duty).encode("utf-8")
|
|
676
|
+
|
|
677
|
+
|
|
322
678
|
def verify_agent_invocation(
|
|
323
679
|
metadata_path: Path,
|
|
324
680
|
*,
|
|
@@ -329,6 +685,9 @@ def verify_agent_invocation(
|
|
|
329
685
|
expected_worker_id: str | None = None,
|
|
330
686
|
expected_assignment_ref: str | None = None,
|
|
331
687
|
expected_audience: AgentAudience | None = None,
|
|
688
|
+
expected_participant_ref: str | None = None,
|
|
689
|
+
expected_role_execution_ref: str | None = None,
|
|
690
|
+
expected_invocation_ref: str | None = None,
|
|
332
691
|
) -> list[str]:
|
|
333
692
|
"""Return every deterministic violation found in a published invocation."""
|
|
334
693
|
metadata = _read_metadata(metadata_path)
|
|
@@ -340,6 +699,9 @@ def verify_agent_invocation(
|
|
|
340
699
|
expected_worker_id=expected_worker_id,
|
|
341
700
|
expected_assignment_ref=expected_assignment_ref,
|
|
342
701
|
expected_audience=expected_audience,
|
|
702
|
+
expected_participant_ref=expected_participant_ref,
|
|
703
|
+
expected_role_execution_ref=expected_role_execution_ref,
|
|
704
|
+
expected_invocation_ref=expected_invocation_ref,
|
|
343
705
|
)
|
|
344
706
|
errors.extend(_verify_prompt_file(metadata, project_root))
|
|
345
707
|
errors.extend(_verify_assignment(metadata, expected_assignment))
|
|
@@ -618,6 +980,8 @@ def _materialize(request: AgentInvocationRequest) -> _MaterializedInvocation:
|
|
|
618
980
|
common = load_common_duty_contract(request.duty_root)
|
|
619
981
|
catalog = load_duty_catalog(request.duty_root)
|
|
620
982
|
duty = catalog[request.audience]
|
|
983
|
+
if _request_identity_version(request) == 2 and request.duty_id != duty.id:
|
|
984
|
+
raise AgentInvocationError("v2 execution identity duty does not match audience")
|
|
621
985
|
prompt_bytes = _render_prompt(request, common, duty).encode("utf-8")
|
|
622
986
|
digests = _invocation_digests(request, duty, prompt_bytes)
|
|
623
987
|
metadata = _metadata_payload(request, duty, digests)
|
|
@@ -635,6 +999,10 @@ def _materialize(request: AgentInvocationRequest) -> _MaterializedInvocation:
|
|
|
635
999
|
duty_digest=digests["dutyDigest"],
|
|
636
1000
|
instruction_digest=digests["instructionDigest"],
|
|
637
1001
|
prompt_digest=digests["promptDigest"],
|
|
1002
|
+
participant_ref=request.participant_ref,
|
|
1003
|
+
role_execution_ref=request.role_execution_ref,
|
|
1004
|
+
invocation_ref=request.invocation_ref,
|
|
1005
|
+
attempt=request.attempt,
|
|
638
1006
|
)
|
|
639
1007
|
reservation_root, reservation = _reservation_spec(request)
|
|
640
1008
|
return _MaterializedInvocation(
|
|
@@ -648,6 +1016,14 @@ def _materialize(request: AgentInvocationRequest) -> _MaterializedInvocation:
|
|
|
648
1016
|
|
|
649
1017
|
|
|
650
1018
|
def _validate_request(request: AgentInvocationRequest) -> None:
|
|
1019
|
+
_validate_common_request(request)
|
|
1020
|
+
if request.run_manifest_path is None:
|
|
1021
|
+
_validate_standalone_identity(request)
|
|
1022
|
+
else:
|
|
1023
|
+
_validate_run_identity(request)
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
def _validate_common_request(request: AgentInvocationRequest) -> None:
|
|
651
1027
|
if request.audience not in _SUPPORTED_AUDIENCES:
|
|
652
1028
|
raise AgentInvocationError(f"unknown duty audience: {request.audience}")
|
|
653
1029
|
adjacent = request.prompt_path.with_name(request.prompt_path.name + ".meta.json")
|
|
@@ -658,10 +1034,35 @@ def _validate_request(request: AgentInvocationRequest) -> None:
|
|
|
658
1034
|
_project_relative(request.duty_root, request.project_root, must_exist=True)
|
|
659
1035
|
_project_relative(request.prompt_path, request.project_root, must_exist=False)
|
|
660
1036
|
_project_relative(request.metadata_path, request.project_root, must_exist=False)
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
def _validate_unbound_v2_run_request(request: AgentInvocationRequest) -> None:
|
|
1040
|
+
if request.run_manifest_path is None or request.purpose is not None:
|
|
1041
|
+
raise AgentInvocationError("unbound prompt requires a run invocation")
|
|
1042
|
+
if _request_identity_version(request) != 2:
|
|
1043
|
+
raise AgentInvocationError("unbound prompt requires v2 execution identity")
|
|
1044
|
+
if not request.assignment_ref:
|
|
1045
|
+
raise AgentInvocationError("run assignment reference is required")
|
|
1046
|
+
if any((
|
|
1047
|
+
request.participant_ref,
|
|
1048
|
+
request.role_execution_ref,
|
|
1049
|
+
request.duty_id,
|
|
1050
|
+
request.invocation_ref,
|
|
1051
|
+
)):
|
|
1052
|
+
raise AgentInvocationError("unbound prompt cannot carry execution identity")
|
|
1053
|
+
if not request.worker_id or not _SLUG_RE.fullmatch(request.worker_id):
|
|
1054
|
+
raise AgentInvocationError("run worker ID must be a non-empty slug")
|
|
1055
|
+
suffix = request.assignment_ref.partition("/")[2]
|
|
1056
|
+
if suffix and request.assignment_ref.split("/", 1)[0] in {"initial", "reverify"}:
|
|
1057
|
+
if suffix != request.worker_id:
|
|
1058
|
+
raise AgentInvocationError(
|
|
1059
|
+
"run worker ID does not match assignment reference"
|
|
1060
|
+
)
|
|
1061
|
+
manifest = _load_json_object(request.run_manifest_path, "run manifest")
|
|
1062
|
+
contract = manifest.get("agentContract")
|
|
1063
|
+
if not isinstance(contract, Mapping):
|
|
1064
|
+
raise AgentInvocationError("run manifest has no agent contract")
|
|
1065
|
+
_validate_manifest_contract(request, manifest, contract)
|
|
665
1066
|
|
|
666
1067
|
|
|
667
1068
|
def _validate_assignment(assignment: AgentModelAssignment) -> None:
|
|
@@ -697,6 +1098,13 @@ def _validate_standalone_identity(request: AgentInvocationRequest) -> None:
|
|
|
697
1098
|
raise AgentInvocationError("standalone invocation ID must be a slug")
|
|
698
1099
|
if request.worker_id is not None or request.assignment_ref is not None:
|
|
699
1100
|
raise AgentInvocationError("standalone invocation cannot bind a run worker")
|
|
1101
|
+
if any((
|
|
1102
|
+
request.participant_ref,
|
|
1103
|
+
request.role_execution_ref,
|
|
1104
|
+
request.duty_id,
|
|
1105
|
+
request.invocation_ref,
|
|
1106
|
+
)):
|
|
1107
|
+
raise AgentInvocationError("standalone invocation cannot bind v2 execution identity")
|
|
700
1108
|
root = (
|
|
701
1109
|
request.project_root
|
|
702
1110
|
/ ".okstra"
|
|
@@ -712,8 +1120,6 @@ def _validate_standalone_identity(request: AgentInvocationRequest) -> None:
|
|
|
712
1120
|
def _validate_run_identity(request: AgentInvocationRequest) -> None:
|
|
713
1121
|
if request.purpose is not None:
|
|
714
1122
|
raise AgentInvocationError("run invocation cannot define standalone purpose")
|
|
715
|
-
if not request.worker_id or not _SLUG_RE.fullmatch(request.worker_id):
|
|
716
|
-
raise AgentInvocationError("run worker ID must be a non-empty slug")
|
|
717
1123
|
if not request.assignment_ref:
|
|
718
1124
|
raise AgentInvocationError("run assignment reference is required")
|
|
719
1125
|
_project_relative(
|
|
@@ -721,12 +1127,74 @@ def _validate_run_identity(request: AgentInvocationRequest) -> None:
|
|
|
721
1127
|
request.project_root,
|
|
722
1128
|
must_exist=True,
|
|
723
1129
|
)
|
|
1130
|
+
if _request_identity_version(request) == 2:
|
|
1131
|
+
_validate_v2_run_identity(request)
|
|
1132
|
+
return
|
|
1133
|
+
if any((
|
|
1134
|
+
request.participant_ref,
|
|
1135
|
+
request.role_execution_ref,
|
|
1136
|
+
request.duty_id,
|
|
1137
|
+
request.invocation_ref,
|
|
1138
|
+
)):
|
|
1139
|
+
raise AgentInvocationError("v1 run invocation cannot bind v2 execution identity")
|
|
1140
|
+
if not request.worker_id or not _SLUG_RE.fullmatch(request.worker_id):
|
|
1141
|
+
raise AgentInvocationError("run worker ID must be a non-empty slug")
|
|
724
1142
|
suffix = request.assignment_ref.partition("/")[2]
|
|
725
1143
|
if suffix and request.assignment_ref.split("/", 1)[0] in {"initial", "reverify"}:
|
|
726
1144
|
if suffix != request.worker_id:
|
|
727
1145
|
raise AgentInvocationError("run worker ID does not match assignment reference")
|
|
728
1146
|
|
|
729
1147
|
|
|
1148
|
+
def _request_identity_version(request: AgentInvocationRequest) -> int:
|
|
1149
|
+
if request.run_manifest_path is None:
|
|
1150
|
+
return 1
|
|
1151
|
+
manifest = _load_json_object(request.run_manifest_path, "run manifest")
|
|
1152
|
+
schema = manifest.get("schemaVersion")
|
|
1153
|
+
identity = manifest.get("executionIdentityVersion")
|
|
1154
|
+
if schema == "2.0" and identity == 2:
|
|
1155
|
+
return 2
|
|
1156
|
+
if schema == "2.0" or identity is not None:
|
|
1157
|
+
raise AgentInvocationError("run manifest mixes v1 and v2 execution identity")
|
|
1158
|
+
if schema not in (None, 1, "1", "1.0"):
|
|
1159
|
+
raise AgentInvocationError("run manifest execution identity version is unsupported")
|
|
1160
|
+
return 1
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
def _validate_v2_run_identity(request: AgentInvocationRequest) -> None:
|
|
1164
|
+
fields = (
|
|
1165
|
+
request.participant_ref,
|
|
1166
|
+
request.role_execution_ref,
|
|
1167
|
+
request.duty_id,
|
|
1168
|
+
request.invocation_ref,
|
|
1169
|
+
)
|
|
1170
|
+
if any(not isinstance(value, str) or not value.strip() for value in fields):
|
|
1171
|
+
raise AgentInvocationError("v2 execution identity references are required")
|
|
1172
|
+
if request.invocation_ref != request.invocation_id:
|
|
1173
|
+
raise AgentInvocationError("v2 execution identity invocation reference is invalid")
|
|
1174
|
+
if (
|
|
1175
|
+
not isinstance(request.attempt, int)
|
|
1176
|
+
or isinstance(request.attempt, bool)
|
|
1177
|
+
or request.attempt < 1
|
|
1178
|
+
):
|
|
1179
|
+
raise AgentInvocationError("v2 execution identity attempt must be positive")
|
|
1180
|
+
manifest = _load_json_object(request.run_manifest_path, "run manifest")
|
|
1181
|
+
identity = invocation_execution_identity_from_manifest(
|
|
1182
|
+
manifest,
|
|
1183
|
+
assignment=request.assignment,
|
|
1184
|
+
assignment_ref=request.assignment_ref,
|
|
1185
|
+
duty_id=request.duty_id,
|
|
1186
|
+
role_execution_ref=request.role_execution_ref,
|
|
1187
|
+
)
|
|
1188
|
+
if (
|
|
1189
|
+
identity is None
|
|
1190
|
+
or identity.participant_ref != request.participant_ref
|
|
1191
|
+
or identity.role_execution_ref != request.role_execution_ref
|
|
1192
|
+
):
|
|
1193
|
+
raise AgentInvocationError(
|
|
1194
|
+
"v2 execution identity does not match a canonical role execution"
|
|
1195
|
+
)
|
|
1196
|
+
|
|
1197
|
+
|
|
730
1198
|
def _render_prompt(
|
|
731
1199
|
request: AgentInvocationRequest,
|
|
732
1200
|
common: DutyContract,
|
|
@@ -799,7 +1267,7 @@ def _metadata_payload(
|
|
|
799
1267
|
if request.run_manifest_path is not None
|
|
800
1268
|
else None
|
|
801
1269
|
)
|
|
802
|
-
|
|
1270
|
+
payload: dict[str, object] = {
|
|
803
1271
|
"schemaVersion": 1,
|
|
804
1272
|
"invocationId": request.invocation_id,
|
|
805
1273
|
"audience": request.audience,
|
|
@@ -832,6 +1300,31 @@ def _metadata_payload(
|
|
|
832
1300
|
},
|
|
833
1301
|
"digests": dict(digests),
|
|
834
1302
|
}
|
|
1303
|
+
if _request_identity_version(request) == 2:
|
|
1304
|
+
manifest = _load_json_object(request.run_manifest_path, "run manifest")
|
|
1305
|
+
identity = invocation_execution_identity_from_manifest(
|
|
1306
|
+
manifest,
|
|
1307
|
+
assignment=request.assignment,
|
|
1308
|
+
assignment_ref=request.assignment_ref,
|
|
1309
|
+
duty_id=request.duty_id,
|
|
1310
|
+
role_execution_ref=request.role_execution_ref,
|
|
1311
|
+
)
|
|
1312
|
+
if identity is None:
|
|
1313
|
+
raise AgentInvocationError(
|
|
1314
|
+
"v2 execution identity does not match a canonical role execution"
|
|
1315
|
+
)
|
|
1316
|
+
payload.update({
|
|
1317
|
+
"schemaVersion": "2.0",
|
|
1318
|
+
"executionIdentityVersion": 2,
|
|
1319
|
+
"invocationRef": request.invocation_ref,
|
|
1320
|
+
"participantRef": request.participant_ref,
|
|
1321
|
+
"roleExecutionRef": request.role_execution_ref,
|
|
1322
|
+
"executionLabel": identity.execution_label,
|
|
1323
|
+
"dutyId": request.duty_id,
|
|
1324
|
+
"attempt": request.attempt,
|
|
1325
|
+
})
|
|
1326
|
+
payload.pop("workerId")
|
|
1327
|
+
return payload
|
|
835
1328
|
|
|
836
1329
|
|
|
837
1330
|
def _reservation_spec(
|
|
@@ -850,7 +1343,7 @@ def _reservation_spec(
|
|
|
850
1343
|
reservation_value,
|
|
851
1344
|
must_exist=False,
|
|
852
1345
|
)
|
|
853
|
-
reservation = {
|
|
1346
|
+
reservation: dict[str, object] = {
|
|
854
1347
|
"schemaVersion": 1,
|
|
855
1348
|
"invocationId": request.invocation_id,
|
|
856
1349
|
"workerId": request.worker_id,
|
|
@@ -868,6 +1361,17 @@ def _reservation_spec(
|
|
|
868
1361
|
must_exist=False,
|
|
869
1362
|
),
|
|
870
1363
|
}
|
|
1364
|
+
if _request_identity_version(request) == 2:
|
|
1365
|
+
reservation.update({
|
|
1366
|
+
"schemaVersion": "2.0",
|
|
1367
|
+
"executionIdentityVersion": 2,
|
|
1368
|
+
"invocationRef": request.invocation_ref,
|
|
1369
|
+
"participantRef": request.participant_ref,
|
|
1370
|
+
"roleExecutionRef": request.role_execution_ref,
|
|
1371
|
+
"dutyId": request.duty_id,
|
|
1372
|
+
"attempt": request.attempt,
|
|
1373
|
+
})
|
|
1374
|
+
reservation.pop("workerId")
|
|
871
1375
|
return reservation_root, reservation
|
|
872
1376
|
|
|
873
1377
|
|
|
@@ -894,7 +1398,9 @@ def _validate_manifest_contract(
|
|
|
894
1398
|
if isinstance(assignments, dict)
|
|
895
1399
|
else None
|
|
896
1400
|
)
|
|
897
|
-
if manifest_assignment != _assignment_payload(
|
|
1401
|
+
if _model_assignment_projection(manifest_assignment) != _assignment_payload(
|
|
1402
|
+
request.assignment
|
|
1403
|
+
):
|
|
898
1404
|
raise AgentInvocationError("model assignment does not match run manifest")
|
|
899
1405
|
_validate_authorized_prompt_path(request, contract)
|
|
900
1406
|
|
|
@@ -1114,6 +1620,14 @@ def _prompt_lock_path(prompt_path: Path) -> Path:
|
|
|
1114
1620
|
return prompt_path.with_name(prompt_path.name + ".publish.lock")
|
|
1115
1621
|
|
|
1116
1622
|
|
|
1623
|
+
def _publish_or_reuse_exact(path: Path, body: bytes) -> None:
|
|
1624
|
+
if path.exists():
|
|
1625
|
+
if path.read_bytes() != body:
|
|
1626
|
+
raise _existing_conflict(f"published path differs: {path}")
|
|
1627
|
+
return
|
|
1628
|
+
_publish_exclusive(path, body)
|
|
1629
|
+
|
|
1630
|
+
|
|
1117
1631
|
def _publish_exclusive(path: Path, body: bytes, *, replace: bool = False) -> None:
|
|
1118
1632
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
1119
1633
|
temp_path: Path | None = None
|
|
@@ -1150,13 +1664,20 @@ def _verify_expected_identity(
|
|
|
1150
1664
|
expected_worker_id: str | None,
|
|
1151
1665
|
expected_assignment_ref: str | None,
|
|
1152
1666
|
expected_audience: AgentAudience | None,
|
|
1667
|
+
expected_participant_ref: str | None,
|
|
1668
|
+
expected_role_execution_ref: str | None,
|
|
1669
|
+
expected_invocation_ref: str | None,
|
|
1153
1670
|
) -> list[str]:
|
|
1154
1671
|
expected = {
|
|
1155
1672
|
"invocationId": expected_invocation_id,
|
|
1156
|
-
"workerId": expected_worker_id,
|
|
1157
1673
|
"assignmentRef": expected_assignment_ref,
|
|
1158
1674
|
"audience": expected_audience,
|
|
1675
|
+
"participantRef": expected_participant_ref,
|
|
1676
|
+
"roleExecutionRef": expected_role_execution_ref,
|
|
1677
|
+
"invocationRef": expected_invocation_ref,
|
|
1159
1678
|
}
|
|
1679
|
+
if metadata.get("schemaVersion") == 1:
|
|
1680
|
+
expected["workerId"] = expected_worker_id
|
|
1160
1681
|
errors = []
|
|
1161
1682
|
for key, value in expected.items():
|
|
1162
1683
|
if value is not None and metadata.get(key) != value:
|
|
@@ -1371,6 +1892,8 @@ def _verify_run_manifest_contract(
|
|
|
1371
1892
|
if not isinstance(allowed, list) or metadata["audience"] not in allowed:
|
|
1372
1893
|
errors.append("audience is not allowed by run manifest")
|
|
1373
1894
|
errors.extend(_verify_manifest_assignment(metadata, manifest))
|
|
1895
|
+
if metadata.get("executionIdentityVersion") == 2:
|
|
1896
|
+
errors.extend(_verify_manifest_execution_identity(metadata, manifest))
|
|
1374
1897
|
return errors
|
|
1375
1898
|
|
|
1376
1899
|
|
|
@@ -1382,11 +1905,39 @@ def _verify_manifest_assignment(
|
|
|
1382
1905
|
reference = metadata["assignmentRef"]
|
|
1383
1906
|
if not isinstance(assignments, dict) or reference not in assignments:
|
|
1384
1907
|
return ["assignment reference is missing from run manifest"]
|
|
1385
|
-
if assignments[reference] != metadata["modelAssignment"]:
|
|
1908
|
+
if _model_assignment_projection(assignments[reference]) != metadata["modelAssignment"]:
|
|
1386
1909
|
return ["model assignment does not match run manifest"]
|
|
1387
1910
|
return []
|
|
1388
1911
|
|
|
1389
1912
|
|
|
1913
|
+
def _verify_manifest_execution_identity(
|
|
1914
|
+
metadata: Mapping[str, object],
|
|
1915
|
+
manifest: Mapping[str, object],
|
|
1916
|
+
) -> list[str]:
|
|
1917
|
+
if metadata["dutyContract"]["id"] != metadata["dutyId"]:
|
|
1918
|
+
return ["v2 duty identity does not match duty contract"]
|
|
1919
|
+
try:
|
|
1920
|
+
identity = invocation_execution_identity_from_manifest(
|
|
1921
|
+
manifest,
|
|
1922
|
+
assignment=agent_model_assignment_from_payload(
|
|
1923
|
+
metadata["modelAssignment"]
|
|
1924
|
+
),
|
|
1925
|
+
assignment_ref=str(metadata["assignmentRef"]),
|
|
1926
|
+
duty_id=str(metadata["dutyId"]),
|
|
1927
|
+
role_execution_ref=str(metadata["roleExecutionRef"]),
|
|
1928
|
+
)
|
|
1929
|
+
except AgentInvocationError as exc:
|
|
1930
|
+
return [str(exc)]
|
|
1931
|
+
if (
|
|
1932
|
+
identity is None
|
|
1933
|
+
or identity.participant_ref != metadata["participantRef"]
|
|
1934
|
+
or identity.role_execution_ref != metadata["roleExecutionRef"]
|
|
1935
|
+
or identity.execution_label != metadata["executionLabel"]
|
|
1936
|
+
):
|
|
1937
|
+
return ["v2 identity does not match a canonical role execution"]
|
|
1938
|
+
return []
|
|
1939
|
+
|
|
1940
|
+
|
|
1390
1941
|
def _verify_duty_snapshot(
|
|
1391
1942
|
metadata: Mapping[str, object],
|
|
1392
1943
|
project_root: Path,
|
|
@@ -1473,23 +2024,57 @@ def _verify_reservation(
|
|
|
1473
2024
|
return ["invocation reservation is missing or invalid"]
|
|
1474
2025
|
prompt = metadata["prompt"]
|
|
1475
2026
|
assert isinstance(prompt, dict)
|
|
1476
|
-
expected = {
|
|
2027
|
+
expected: dict[str, object] = {
|
|
1477
2028
|
"schemaVersion": 1,
|
|
1478
2029
|
"invocationId": metadata["invocationId"],
|
|
1479
|
-
"workerId": metadata["workerId"],
|
|
1480
2030
|
"assignmentRef": metadata["assignmentRef"],
|
|
1481
2031
|
"audience": metadata["audience"],
|
|
1482
2032
|
"dispatchKind": metadata["dispatchKind"],
|
|
1483
2033
|
"promptPath": prompt["path"],
|
|
1484
2034
|
"metadataPath": f"{prompt['path']}.meta.json",
|
|
1485
2035
|
}
|
|
2036
|
+
if metadata.get("executionIdentityVersion") == 2:
|
|
2037
|
+
expected.update({
|
|
2038
|
+
"schemaVersion": "2.0",
|
|
2039
|
+
"executionIdentityVersion": 2,
|
|
2040
|
+
"invocationRef": metadata["invocationRef"],
|
|
2041
|
+
"participantRef": metadata["participantRef"],
|
|
2042
|
+
"roleExecutionRef": metadata["roleExecutionRef"],
|
|
2043
|
+
"dutyId": metadata["dutyId"],
|
|
2044
|
+
"attempt": metadata["attempt"],
|
|
2045
|
+
})
|
|
2046
|
+
else:
|
|
2047
|
+
expected["workerId"] = metadata["workerId"]
|
|
1486
2048
|
if actual != expected:
|
|
1487
2049
|
return ["invocation reservation does not match invocation metadata"]
|
|
1488
2050
|
return []
|
|
1489
2051
|
|
|
1490
2052
|
|
|
1491
2053
|
def _metadata_schema_is_exact(metadata: Mapping[str, object]) -> bool:
|
|
1492
|
-
|
|
2054
|
+
schema = metadata.get("schemaVersion")
|
|
2055
|
+
if schema == 1:
|
|
2056
|
+
if set(metadata) != _TOP_LEVEL_KEYS:
|
|
2057
|
+
return False
|
|
2058
|
+
elif schema == "2.0":
|
|
2059
|
+
if (
|
|
2060
|
+
set(metadata) != _V2_TOP_LEVEL_KEYS
|
|
2061
|
+
or metadata.get("executionIdentityVersion") != 2
|
|
2062
|
+
or not all(
|
|
2063
|
+
isinstance(metadata.get(key), str) and metadata[key]
|
|
2064
|
+
for key in (
|
|
2065
|
+
"invocationRef",
|
|
2066
|
+
"participantRef",
|
|
2067
|
+
"roleExecutionRef",
|
|
2068
|
+
"executionLabel",
|
|
2069
|
+
"dutyId",
|
|
2070
|
+
)
|
|
2071
|
+
)
|
|
2072
|
+
or not isinstance(metadata.get("attempt"), int)
|
|
2073
|
+
or isinstance(metadata.get("attempt"), bool)
|
|
2074
|
+
or metadata["attempt"] < 1
|
|
2075
|
+
):
|
|
2076
|
+
return False
|
|
2077
|
+
else:
|
|
1493
2078
|
return False
|
|
1494
2079
|
for key, expected_keys in _NESTED_KEYS.items():
|
|
1495
2080
|
value = metadata.get(key)
|
|
@@ -1509,6 +2094,15 @@ def _metadata_schema_is_exact(metadata: Mapping[str, object]) -> bool:
|
|
|
1509
2094
|
}
|
|
1510
2095
|
|
|
1511
2096
|
|
|
2097
|
+
def _model_assignment_projection(payload: object) -> dict[str, object] | None:
|
|
2098
|
+
if not isinstance(payload, Mapping):
|
|
2099
|
+
return None
|
|
2100
|
+
keys = _NESTED_KEYS["modelAssignment"]
|
|
2101
|
+
if not keys.issubset(payload):
|
|
2102
|
+
return None
|
|
2103
|
+
return {key: payload[key] for key in keys}
|
|
2104
|
+
|
|
2105
|
+
|
|
1512
2106
|
def _digest_paths(value: object, prefix: str = "") -> set[str]:
|
|
1513
2107
|
if not isinstance(value, dict):
|
|
1514
2108
|
return set()
|