okstra 0.186.7 → 0.188.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 +1 -1
- package/dist/cli-registry.d.mts +88 -1
- package/dist/cli-registry.mjs +68 -111
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.mjs +0 -1
- package/dist/commands/execute/render-bundle.mjs.map +1 -1
- package/dist/commands/execute/run.mjs +8 -3
- package/dist/commands/execute/run.mjs.map +1 -1
- package/dist/commands/lifecycle/check-project.mjs +1 -14
- package/dist/commands/lifecycle/check-project.mjs.map +1 -1
- package/dist/commands/lifecycle/config.mjs +38 -40
- package/dist/commands/lifecycle/config.mjs.map +1 -1
- package/dist/commands/lifecycle/doctor.d.mts +22 -7
- package/dist/commands/lifecycle/doctor.mjs +77 -49
- package/dist/commands/lifecycle/doctor.mjs.map +1 -1
- package/dist/commands/lifecycle/install.d.mts +12 -10
- package/dist/commands/lifecycle/install.mjs +107 -40
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/dist/commands/lifecycle/paths.mjs +8 -3
- package/dist/commands/lifecycle/paths.mjs.map +1 -1
- package/dist/commands/lifecycle/preflight.mjs +2 -1
- package/dist/commands/lifecycle/preflight.mjs.map +1 -1
- package/dist/commands/lifecycle/setup.mjs +22 -36
- package/dist/commands/lifecycle/setup.mjs.map +1 -1
- package/dist/commands/lifecycle/uninstall.d.mts +4 -2
- package/dist/commands/lifecycle/uninstall.mjs +59 -15
- package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
- package/dist/commands/memory/memory.mjs +7 -1
- package/dist/commands/memory/memory.mjs.map +1 -1
- package/dist/lib/helper-scripts.d.mts +1 -1
- package/dist/lib/helper-scripts.mjs +10 -18
- package/dist/lib/helper-scripts.mjs.map +1 -1
- package/dist/lib/host-config.d.mts +72 -0
- package/dist/lib/host-config.mjs +404 -0
- package/dist/lib/host-config.mjs.map +1 -0
- package/dist/lib/host-registry-client.d.mts +2 -2
- package/dist/lib/host-registry-client.mjs +0 -3
- package/dist/lib/host-registry-client.mjs.map +1 -1
- package/dist/lib/install-assets.d.mts +1 -0
- package/dist/lib/install-assets.mjs +4 -0
- package/dist/lib/install-assets.mjs.map +1 -1
- package/dist/lib/proc.d.mts +2 -0
- package/dist/lib/proc.mjs +12 -0
- package/dist/lib/proc.mjs.map +1 -1
- package/dist/lib/python-command.d.mts +2 -0
- package/dist/lib/python-command.mjs +52 -0
- package/dist/lib/python-command.mjs.map +1 -0
- package/dist/lib/python-helper.d.mts +2 -5
- package/dist/lib/python-helper.mjs +3 -52
- package/dist/lib/python-helper.mjs.map +1 -1
- package/dist/lib/runtime-payload.d.mts +23 -0
- package/dist/lib/runtime-payload.mjs +57 -0
- package/dist/lib/runtime-payload.mjs.map +1 -0
- package/dist/lib/types.d.mts +24 -13
- package/docs/architecture/storage-model.md +21 -5
- package/docs/architecture.md +39 -38
- package/docs/cli.md +55 -76
- package/docs/coding-rules.md +295 -0
- package/docs/container.md +10 -36
- package/docs/contributor-change-matrix.md +1 -1
- package/docs/for-ai/skills/okstra-code-review.md +0 -1
- package/docs/for-ai/skills/okstra-container-build.md +8 -41
- package/docs/for-ai/skills/okstra-inspect.md +4 -4
- package/docs/for-ai/skills/okstra-manager.md +2 -2
- package/docs/for-ai/skills/okstra-rollup.md +0 -1
- package/docs/for-ai/skills/okstra-run.md +3 -3
- package/docs/for-ai/skills/okstra-user-response.md +0 -1
- package/docs/performance-improvement-plan-v2.md +1 -1
- package/docs/project-structure-overview.md +62 -62
- package/docs/task-process/README.md +3 -3
- package/docs/task-process/common-flow.md +1 -1
- package/docs/task-process/error-analysis.md +3 -3
- package/docs/task-process/implementation-planning.md +3 -1
- package/docs/task-process/release-handoff.md +1 -1
- package/package.json +3 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +6 -11
- package/runtime/agents/workers/report-writer-worker.md +1 -1
- package/runtime/bin/lib/okstra/cli.sh +4 -0
- package/runtime/bin/lib/okstra/globals.sh +2 -0
- package/runtime/bin/lib/okstra/interactive.sh +26 -173
- package/runtime/bin/lib/okstra/project-resolver.sh +23 -61
- package/runtime/bin/lib/okstra/usage.sh +3 -3
- package/runtime/bin/okstra-compact-reminder.sh +1 -5
- package/runtime/bin/okstra-error-log.py +19 -2
- package/runtime/bin/okstra-import-check.py +26 -0
- package/runtime/bin/okstra-inject-report-index.py +5 -4
- package/runtime/bin/okstra-provider-exec.py +22 -17
- package/runtime/bin/okstra-render-final-report.py +16 -6
- package/runtime/bin/okstra-render-report-views.py +47 -22
- package/runtime/bin/okstra-report-translate.py +30 -18
- package/runtime/bin/okstra-spawn-followups.py +4 -0
- package/runtime/bin/okstra-token-usage.py +3 -0
- package/runtime/bin/okstra.sh +4 -2
- package/runtime/bin/okstra_bootstrap.py +58 -0
- package/runtime/prompts/coding-preflight/overview.md +1 -1
- package/runtime/prompts/duties/planning-worker.md +1 -1
- package/runtime/prompts/launch.template.md +35 -9
- package/runtime/prompts/lead/adapters/cmux.md +3 -3
- package/runtime/prompts/lead/convergence.md +99 -88
- package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
- package/runtime/prompts/lead/plan-body-verification.md +65 -46
- package/runtime/prompts/lead/report-writer.md +35 -5
- package/runtime/prompts/lead/team-contract.md +4 -1
- package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +3 -3
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
- package/runtime/prompts/profiles/_implementation-executor.md +3 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
- package/runtime/prompts/profiles/error-analysis.md +4 -3
- package/runtime/prompts/profiles/final-verification.md +2 -2
- package/runtime/prompts/profiles/implementation-planning.md +22 -17
- package/runtime/prompts/profiles/implementation.md +3 -2
- package/runtime/prompts/profiles/requirements-discovery.md +2 -1
- package/runtime/prompts/wizard/prompts.ko.json +29 -8
- package/runtime/python/okstra_ctl/__init__.py +6 -27
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +63 -22
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +20 -21
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
- package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
- package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
- package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +16 -2
- package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
- package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +228 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +605 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
- package/runtime/python/okstra_ctl/approval_decisions.py +287 -10
- package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
- package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
- package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
- package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
- package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
- package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
- package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
- package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
- package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
- package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
- package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
- package/runtime/python/okstra_ctl/cmux.py +38 -31
- package/runtime/python/okstra_ctl/code_review_target.py +163 -1
- package/runtime/python/okstra_ctl/conformance.py +23 -0
- package/runtime/python/okstra_ctl/container.py +38 -421
- package/runtime/python/okstra_ctl/context_cost.py +32 -4
- package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
- package/runtime/python/okstra_ctl/convergence.py +107 -16
- package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
- package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
- package/runtime/python/okstra_ctl/convergence_store.py +5 -29
- package/runtime/python/okstra_ctl/design_prep.py +108 -12
- package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
- package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
- package/runtime/python/okstra_ctl/dispatch_core.py +236 -49
- package/runtime/python/okstra_ctl/dispatch_state.py +417 -41
- package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
- package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
- package/runtime/python/okstra_ctl/domain/worker_presentation.py +28 -1
- package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
- package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
- package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
- package/runtime/python/okstra_ctl/error_log_core.py +1 -1
- package/runtime/python/okstra_ctl/error_report.py +14 -0
- package/runtime/python/okstra_ctl/error_zip.py +18 -2
- package/runtime/python/okstra_ctl/exact_coverage.py +5 -0
- package/runtime/python/okstra_ctl/execution_identity.py +100 -7
- package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
- package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +319 -17
- package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
- package/runtime/python/okstra_ctl/handoff.py +24 -1
- package/runtime/python/okstra_ctl/ids.py +6 -16
- package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
- package/runtime/python/okstra_ctl/implementation_options.py +32 -13
- package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
- package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
- package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
- package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
- package/runtime/python/okstra_ctl/index.py +2 -2
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
- package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
- package/runtime/python/okstra_ctl/json_boundary.py +10 -0
- package/runtime/python/okstra_ctl/listing.py +0 -91
- package/runtime/python/okstra_ctl/locks.py +5 -19
- package/runtime/python/okstra_ctl/log_report.py +14 -0
- package/runtime/python/okstra_ctl/manager_cli.py +22 -3
- package/runtime/python/okstra_ctl/manager_launch.py +4 -8
- package/runtime/python/okstra_ctl/material.py +2 -2
- package/runtime/python/okstra_ctl/migrate.py +23 -1
- package/runtime/python/okstra_ctl/model_cli.py +19 -5
- package/runtime/python/okstra_ctl/model_discovery.py +46 -47
- package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
- package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
- package/runtime/python/okstra_ctl/model_io/references.py +370 -0
- package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
- package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
- package/runtime/python/okstra_ctl/model_pool.py +10 -6
- package/runtime/python/okstra_ctl/models.py +8 -8
- package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
- package/runtime/python/okstra_ctl/next_phase.py +121 -69
- package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
- package/runtime/python/okstra_ctl/pane_title.py +11 -8
- package/runtime/python/okstra_ctl/path_hints.py +36 -11
- package/runtime/python/okstra_ctl/paths.py +76 -8
- package/runtime/python/okstra_ctl/plan_items.py +132 -16
- package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
- package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
- package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
- package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
- package/runtime/python/okstra_ctl/prepare_error.py +20 -0
- package/runtime/python/okstra_ctl/prior_planning.py +157 -0
- package/runtime/python/okstra_ctl/profile_show.py +18 -1
- package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
- package/runtime/python/okstra_ctl/recap.py +22 -1
- package/runtime/python/okstra_ctl/reconcile.py +84 -0
- package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
- package/runtime/python/okstra_ctl/render.py +98 -32
- package/runtime/python/okstra_ctl/render_final_report.py +4 -8
- package/runtime/python/okstra_ctl/report_assembly.py +297 -7
- package/runtime/python/okstra_ctl/report_contract.py +30 -7
- package/runtime/python/okstra_ctl/report_finalize.py +213 -16
- package/runtime/python/okstra_ctl/report_html/common.py +2 -2
- package/runtime/python/okstra_ctl/report_html/render.py +8 -12
- package/runtime/python/okstra_ctl/report_language.py +8 -4
- package/runtime/python/okstra_ctl/report_narrative.py +220 -34
- package/runtime/python/okstra_ctl/report_projections.py +8 -2
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +393 -1
- package/runtime/python/okstra_ctl/report_translation.py +2 -35
- package/runtime/python/okstra_ctl/report_view_artifacts.py +42 -2
- package/runtime/python/okstra_ctl/report_views.py +4 -22
- package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
- package/runtime/python/okstra_ctl/rollup.py +13 -0
- package/runtime/python/okstra_ctl/run.py +639 -138
- package/runtime/python/okstra_ctl/run_audit.py +13 -0
- package/runtime/python/okstra_ctl/run_index_row.py +0 -12
- package/runtime/python/okstra_ctl/seeding.py +21 -11
- package/runtime/python/okstra_ctl/sequence.py +1 -1
- package/runtime/python/okstra_ctl/session.py +64 -14
- package/runtime/python/okstra_ctl/set_work_status.py +18 -0
- package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
- package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
- package/runtime/python/okstra_ctl/stage_map.py +57 -11
- package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
- package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
- package/runtime/python/okstra_ctl/stage_targets.py +3 -12
- package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
- package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
- package/runtime/python/okstra_ctl/task_target.py +4 -16
- package/runtime/python/okstra_ctl/team.py +49 -2
- package/runtime/python/okstra_ctl/time_report.py +17 -2
- package/runtime/python/okstra_ctl/usage_report.py +11 -0
- package/runtime/python/okstra_ctl/user_response.py +55 -218
- package/runtime/python/okstra_ctl/user_response_values.py +242 -0
- package/runtime/python/okstra_ctl/validation_contract.py +3 -0
- package/runtime/python/okstra_ctl/verdict_blocks.py +28 -8
- package/runtime/python/okstra_ctl/verification_target.py +68 -0
- package/runtime/python/okstra_ctl/wizard.py +457 -92
- package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
- package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
- package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
- package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
- package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
- package/runtime/python/okstra_ctl/worker_request.py +2 -1
- package/runtime/python/okstra_ctl/worker_runner.py +58 -17
- package/runtime/python/okstra_ctl/worker_state.py +16 -1
- package/runtime/python/okstra_ctl/workflow.py +18 -1
- package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
- package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
- package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
- package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
- package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
- package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
- package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
- package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
- package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
- package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
- package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
- package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
- package/runtime/python/okstra_ctl/write_policy.py +90 -213
- package/runtime/python/okstra_project/__init__.py +0 -4
- package/runtime/python/okstra_project/dirs.py +2 -2
- package/runtime/python/okstra_project/phase_pointer.py +84 -0
- package/runtime/python/okstra_project/slug.py +24 -0
- package/runtime/python/okstra_project/state.py +22 -230
- package/runtime/python/okstra_token_usage/antigravity.py +50 -12
- package/runtime/python/okstra_token_usage/claude.py +9 -1
- package/runtime/python/okstra_token_usage/cli.py +3 -1
- package/runtime/python/okstra_token_usage/collect.py +171 -36
- package/runtime/python/okstra_token_usage/cursor.py +2 -0
- package/runtime/python/okstra_token_usage/grok.py +1 -5
- package/runtime/python/okstra_token_usage/paths.py +26 -0
- package/runtime/python/okstra_token_usage/pricing.py +23 -8
- package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
- package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
- package/runtime/schemas/final-report-v2.0.schema.json +9 -26
- package/runtime/schemas/final-report-v3.0.schema.json +173 -41
- package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
- package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
- package/runtime/skills/okstra-container-build/SKILL.md +16 -47
- package/runtime/skills/okstra-inspect/facets/history.md +1 -1
- package/runtime/skills/okstra-inspect/facets/status.md +7 -6
- package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
- package/runtime/skills/okstra-run/SKILL.md +7 -5
- package/runtime/templates/report-writer-prompt-preamble.md +3 -1
- package/runtime/templates/reports/brief.template.md +6 -2
- package/runtime/templates/reports/error-analysis-input.template.md +2 -0
- package/runtime/templates/reports/final-verification-input.template.md +2 -0
- package/runtime/templates/reports/implementation-input.template.md +7 -1
- package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
- package/runtime/templates/reports/quick-input.template.md +2 -0
- package/runtime/templates/reports/release-handoff-input.template.md +6 -1
- package/runtime/templates/reports/report.js +20 -3
- package/runtime/templates/reports/schedule.template.md +2 -0
- package/runtime/templates/reports/settings.template.json +0 -10
- package/runtime/templates/reports/task-brief.template.md +3 -0
- package/runtime/templates/reports/user-response.template.md +7 -3
- package/runtime/validators/checks/fixtures-01.py +57 -0
- package/runtime/validators/checks/fixtures-02.py +565 -0
- package/runtime/validators/checks/runners-01.py +108 -0
- package/runtime/validators/checks/validate-assets-01.py +60 -0
- package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
- package/runtime/validators/checks/validate-tasks-01.py +46 -0
- package/runtime/validators/checks/validate-tasks-02.py +85 -0
- package/runtime/validators/checks/validate-tasks-03.py +61 -0
- package/runtime/validators/checks/validate-tasks-04.py +118 -0
- package/runtime/validators/forbidden_actions.py +73 -3
- package/runtime/validators/lib/common.sh +5 -0
- package/runtime/validators/lib/fixtures.sh +7 -591
- package/runtime/validators/lib/paths.sh +13 -0
- package/runtime/validators/lib/runners.sh +6 -104
- package/runtime/validators/lib/summary.sh +1 -1
- package/runtime/validators/lib/validate-assets.sh +6 -56
- package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
- package/runtime/validators/lib/validate-tasks.sh +9 -294
- package/runtime/validators/validate-implementation-plan-stages.py +4 -4
- package/runtime/validators/validate-run.py +1363 -2643
- package/runtime/validators/validate-workflow.sh +56 -16
- package/runtime/validators/validate_improvement_report.py +2 -1
- package/runtime/validators/validate_session_conformance.py +295 -49
- package/dist/commands/execute/agent-prompt.d.mts +0 -1
- package/dist/commands/execute/agent-prompt.mjs +0 -24
- package/dist/commands/execute/agent-prompt.mjs.map +0 -1
- package/dist/commands/execute/codex-dispatch.d.mts +0 -3
- package/dist/commands/execute/codex-dispatch.mjs +0 -6
- package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
- package/dist/commands/execute/codex-run.d.mts +0 -3
- package/dist/commands/execute/codex-run.mjs +0 -62
- package/dist/commands/execute/codex-run.mjs.map +0 -1
- package/dist/commands/execute/convergence.d.mts +0 -1
- package/dist/commands/execute/convergence.mjs +0 -38
- package/dist/commands/execute/convergence.mjs.map +0 -1
- package/dist/commands/execute/error-log.d.mts +0 -1
- package/dist/commands/execute/error-log.mjs +0 -18
- package/dist/commands/execute/error-log.mjs.map +0 -1
- package/dist/commands/execute/git-reconcile.d.mts +0 -1
- package/dist/commands/execute/git-reconcile.mjs +0 -30
- package/dist/commands/execute/git-reconcile.mjs.map +0 -1
- package/dist/commands/execute/handoff.d.mts +0 -1
- package/dist/commands/execute/handoff.mjs +0 -31
- package/dist/commands/execute/handoff.mjs.map +0 -1
- package/dist/commands/execute/incremental-carry.d.mts +0 -1
- package/dist/commands/execute/incremental-carry.mjs +0 -20
- package/dist/commands/execute/incremental-carry.mjs.map +0 -1
- package/dist/commands/execute/incremental-scope.d.mts +0 -1
- package/dist/commands/execute/incremental-scope.mjs +0 -29
- package/dist/commands/execute/incremental-scope.mjs.map +0 -1
- package/dist/commands/execute/integrate-stages.d.mts +0 -1
- package/dist/commands/execute/integrate-stages.mjs +0 -24
- package/dist/commands/execute/integrate-stages.mjs.map +0 -1
- package/dist/commands/execute/pane-title.d.mts +0 -1
- package/dist/commands/execute/pane-title.mjs +0 -20
- package/dist/commands/execute/pane-title.mjs.map +0 -1
- package/dist/commands/execute/plan-items.d.mts +0 -1
- package/dist/commands/execute/plan-items.mjs +0 -9
- package/dist/commands/execute/plan-items.mjs.map +0 -1
- package/dist/commands/execute/plan-validate.d.mts +0 -1
- package/dist/commands/execute/plan-validate.mjs +0 -68
- package/dist/commands/execute/plan-validate.mjs.map +0 -1
- package/dist/commands/execute/plan-verify.d.mts +0 -1
- package/dist/commands/execute/plan-verify.mjs +0 -43
- package/dist/commands/execute/plan-verify.mjs.map +0 -1
- package/dist/commands/execute/spawn-followups.d.mts +0 -1
- package/dist/commands/execute/spawn-followups.mjs +0 -22
- package/dist/commands/execute/spawn-followups.mjs.map +0 -1
- package/dist/commands/execute/team.d.mts +0 -3
- package/dist/commands/execute/team.mjs +0 -66
- package/dist/commands/execute/team.mjs.map +0 -1
- package/dist/commands/execute/token-usage.d.mts +0 -1
- package/dist/commands/execute/token-usage.mjs +0 -19
- package/dist/commands/execute/token-usage.mjs.map +0 -1
- package/dist/commands/execute/worker-audit-check.d.mts +0 -1
- package/dist/commands/execute/worker-audit-check.mjs +0 -34
- package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
- package/dist/commands/execute/worker-dispatch.d.mts +0 -7
- package/dist/commands/execute/worker-dispatch.mjs +0 -64
- package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
- package/dist/commands/execute/worker-state.d.mts +0 -1
- package/dist/commands/execute/worker-state.mjs +0 -28
- package/dist/commands/execute/worker-state.mjs.map +0 -1
- package/dist/commands/execute/worktree-lookup.d.mts +0 -1
- package/dist/commands/execute/worktree-lookup.mjs +0 -92
- package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
- package/dist/commands/execute/worktree-status.d.mts +0 -1
- package/dist/commands/execute/worktree-status.mjs +0 -121
- package/dist/commands/execute/worktree-status.mjs.map +0 -1
- package/dist/commands/inspect/code-review.d.mts +0 -1
- package/dist/commands/inspect/code-review.mjs +0 -32
- package/dist/commands/inspect/code-review.mjs.map +0 -1
- package/dist/commands/inspect/container.d.mts +0 -1
- package/dist/commands/inspect/container.mjs +0 -25
- package/dist/commands/inspect/container.mjs.map +0 -1
- package/dist/commands/inspect/context-cost.d.mts +0 -1
- package/dist/commands/inspect/context-cost.mjs +0 -25
- package/dist/commands/inspect/context-cost.mjs.map +0 -1
- package/dist/commands/inspect/design-prep.d.mts +0 -1
- package/dist/commands/inspect/design-prep.mjs +0 -22
- package/dist/commands/inspect/design-prep.mjs.map +0 -1
- package/dist/commands/inspect/error-report.d.mts +0 -1
- package/dist/commands/inspect/error-report.mjs +0 -25
- package/dist/commands/inspect/error-report.mjs.map +0 -1
- package/dist/commands/inspect/error-zip.d.mts +0 -1
- package/dist/commands/inspect/error-zip.mjs +0 -24
- package/dist/commands/inspect/error-zip.mjs.map +0 -1
- package/dist/commands/inspect/log-report.d.mts +0 -1
- package/dist/commands/inspect/log-report.mjs +0 -26
- package/dist/commands/inspect/log-report.mjs.map +0 -1
- package/dist/commands/inspect/model-io.d.mts +0 -1
- package/dist/commands/inspect/model-io.mjs +0 -25
- package/dist/commands/inspect/model-io.mjs.map +0 -1
- package/dist/commands/inspect/profile-show.d.mts +0 -1
- package/dist/commands/inspect/profile-show.mjs +0 -28
- package/dist/commands/inspect/profile-show.mjs.map +0 -1
- package/dist/commands/inspect/recap.d.mts +0 -1
- package/dist/commands/inspect/recap.mjs +0 -30
- package/dist/commands/inspect/recap.mjs.map +0 -1
- package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
- package/dist/commands/inspect/resolve-task-key.mjs +0 -24
- package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
- package/dist/commands/inspect/rollup.d.mts +0 -1
- package/dist/commands/inspect/rollup.mjs +0 -25
- package/dist/commands/inspect/rollup.mjs.map +0 -1
- package/dist/commands/inspect/run-audit.d.mts +0 -1
- package/dist/commands/inspect/run-audit.mjs +0 -25
- package/dist/commands/inspect/run-audit.mjs.map +0 -1
- package/dist/commands/inspect/set-work-status.d.mts +0 -1
- package/dist/commands/inspect/set-work-status.mjs +0 -29
- package/dist/commands/inspect/set-work-status.mjs.map +0 -1
- package/dist/commands/inspect/stage-map.d.mts +0 -1
- package/dist/commands/inspect/stage-map.mjs +0 -131
- package/dist/commands/inspect/stage-map.mjs.map +0 -1
- package/dist/commands/inspect/task-list.d.mts +0 -1
- package/dist/commands/inspect/task-list.mjs +0 -149
- package/dist/commands/inspect/task-list.mjs.map +0 -1
- package/dist/commands/inspect/task-show.d.mts +0 -1
- package/dist/commands/inspect/task-show.mjs +0 -108
- package/dist/commands/inspect/task-show.mjs.map +0 -1
- package/dist/commands/inspect/time-report.d.mts +0 -1
- package/dist/commands/inspect/time-report.mjs +0 -24
- package/dist/commands/inspect/time-report.mjs.map +0 -1
- package/dist/commands/inspect/usage-report.d.mts +0 -1
- package/dist/commands/inspect/usage-report.mjs +0 -23
- package/dist/commands/inspect/usage-report.mjs.map +0 -1
- package/dist/commands/inspect/user-response.d.mts +0 -1
- package/dist/commands/inspect/user-response.mjs +0 -35
- package/dist/commands/inspect/user-response.mjs.map +0 -1
- package/dist/commands/inspect/worker-liveness.d.mts +0 -1
- package/dist/commands/inspect/worker-liveness.mjs +0 -45
- package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
- package/dist/commands/lifecycle/contract-check.d.mts +0 -1
- package/dist/commands/lifecycle/contract-check.mjs +0 -18
- package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
- package/dist/commands/lifecycle/migrate.d.mts +0 -1
- package/dist/commands/lifecycle/migrate.mjs +0 -30
- package/dist/commands/lifecycle/migrate.mjs.map +0 -1
- package/dist/commands/lifecycle/model.d.mts +0 -1
- package/dist/commands/lifecycle/model.mjs +0 -22
- package/dist/commands/lifecycle/model.mjs.map +0 -1
- package/dist/commands/manager.d.mts +0 -3
- package/dist/commands/manager.mjs +0 -50
- package/dist/commands/manager.mjs.map +0 -1
- package/dist/commands/report/agent-activity.d.mts +0 -1
- package/dist/commands/report/agent-activity.mjs +0 -20
- package/dist/commands/report/agent-activity.mjs.map +0 -1
- package/dist/commands/report/approval-decision.d.mts +0 -1
- package/dist/commands/report/approval-decision.mjs +0 -21
- package/dist/commands/report/approval-decision.mjs.map +0 -1
- package/dist/commands/report/design-snapshot.d.mts +0 -1
- package/dist/commands/report/design-snapshot.mjs +0 -19
- package/dist/commands/report/design-snapshot.mjs.map +0 -1
- package/dist/commands/report/finalize.d.mts +0 -4
- package/dist/commands/report/finalize.mjs +0 -64
- package/dist/commands/report/finalize.mjs.map +0 -1
- package/dist/commands/report/inject-report-index.d.mts +0 -1
- package/dist/commands/report/inject-report-index.mjs +0 -21
- package/dist/commands/report/inject-report-index.mjs.map +0 -1
- package/dist/commands/report/render-final-report.d.mts +0 -1
- package/dist/commands/report/render-final-report.mjs +0 -23
- package/dist/commands/report/render-final-report.mjs.map +0 -1
- package/dist/commands/report/render-views.d.mts +0 -1
- package/dist/commands/report/render-views.mjs +0 -27
- package/dist/commands/report/render-views.mjs.map +0 -1
- package/dist/commands/report/translate.d.mts +0 -1
- package/dist/commands/report/translate.mjs +0 -33
- package/dist/commands/report/translate.mjs.map +0 -1
- package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
- package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
- package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
- package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
- package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
- package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
- package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
- package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
- package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
- package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
- package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
- package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
- package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
- package/runtime/bin/okstra-central.sh +0 -152
- package/runtime/bin/okstra-incremental-carry.py +0 -10
- package/runtime/bin/okstra-incremental-scope.py +0 -10
- package/runtime/bin/okstra-team-reconcile.sh +0 -36
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
- package/runtime/python/okstra_ctl/batch.py +0 -60
- package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
- package/runtime/python/okstra_ctl/container_registry.py +0 -72
- package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
- package/runtime/python/okstra_ctl/resolver.py +0 -54
- package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
- package/runtime/python/okstra_ctl/tmux.py +0 -134
- package/runtime/python/okstra_ctl/worktree.py +0 -1099
|
@@ -26,10 +26,10 @@ import uuid
|
|
|
26
26
|
from dataclasses import dataclass
|
|
27
27
|
from datetime import datetime, timezone
|
|
28
28
|
from pathlib import Path
|
|
29
|
-
from typing import Any, Callable, Mapping, Sequence
|
|
29
|
+
from typing import Any, Callable, Iterable, Mapping, Sequence
|
|
30
30
|
|
|
31
31
|
from . import cmux
|
|
32
|
-
from .
|
|
32
|
+
from .agent.invocation import (
|
|
33
33
|
AgentInvocationError,
|
|
34
34
|
AgentModelAssignment,
|
|
35
35
|
InvocationMetadataIdentity,
|
|
@@ -39,7 +39,11 @@ from .agent_invocation import (
|
|
|
39
39
|
verify_agent_invocation,
|
|
40
40
|
)
|
|
41
41
|
from .domain.role import RoleCatalogError, role_for_duty
|
|
42
|
-
from .execution_identity import
|
|
42
|
+
from .execution_identity import ( # noqa: F401 — build_dispatch_id 재노출
|
|
43
|
+
Attempt,
|
|
44
|
+
Invocation,
|
|
45
|
+
build_dispatch_id,
|
|
46
|
+
)
|
|
43
47
|
from .execution_manifest import (
|
|
44
48
|
finish_attempt_mutation,
|
|
45
49
|
read_execution_manifest,
|
|
@@ -52,6 +56,7 @@ from .worker_prompt_body import REPORT_WRITER_WORKER_ID
|
|
|
52
56
|
from .worker_prompt_contract import (
|
|
53
57
|
PromptRecord,
|
|
54
58
|
validate_initial_prompt_records,
|
|
59
|
+
validate_prompt_model_header,
|
|
55
60
|
validate_reverify_prompt,
|
|
56
61
|
)
|
|
57
62
|
from .worker_runner import LIVE, QUIET
|
|
@@ -65,6 +70,7 @@ from .wrapper_status import (
|
|
|
65
70
|
from .write_policy import (
|
|
66
71
|
build_invocation_write_contract,
|
|
67
72
|
planned_paths_from_run_manifest,
|
|
73
|
+
task_root_from_run_manifest,
|
|
68
74
|
write_policy_from_payload,
|
|
69
75
|
)
|
|
70
76
|
from .convergence_store import write_json_atomic
|
|
@@ -188,6 +194,7 @@ class WorkerJob:
|
|
|
188
194
|
execution_label: str = ""
|
|
189
195
|
duty_id: str = ""
|
|
190
196
|
invocation_ref: str = ""
|
|
197
|
+
result_aliases: tuple[Path, ...] = ()
|
|
191
198
|
attempt: int = 0
|
|
192
199
|
|
|
193
200
|
@property
|
|
@@ -352,13 +359,6 @@ def resolve_required_path(
|
|
|
352
359
|
return resolve_project_path(project_root, require_string(manifest, key))
|
|
353
360
|
|
|
354
361
|
|
|
355
|
-
def worker_prompt_path(manifest: Mapping[str, Any], worker_id: str) -> str:
|
|
356
|
-
paths = manifest.get("workerPromptPathByWorkerId")
|
|
357
|
-
if not isinstance(paths, Mapping):
|
|
358
|
-
raise DispatchError("run manifest has no workerPromptPathByWorkerId object")
|
|
359
|
-
return require_string(paths, worker_id)
|
|
360
|
-
|
|
361
|
-
|
|
362
362
|
def worktree_path(
|
|
363
363
|
manifest: Mapping[str, Any], active_context: Mapping[str, Any]
|
|
364
364
|
) -> str:
|
|
@@ -505,6 +505,35 @@ _AGENT_ENFORCEMENT_MODES = frozenset({
|
|
|
505
505
|
})
|
|
506
506
|
|
|
507
507
|
|
|
508
|
+
def _require_prompt_model_matches_assignment(
|
|
509
|
+
metadata_path: Path, assignment: Any
|
|
510
|
+
) -> None:
|
|
511
|
+
"""디스패치 직전, 프롬프트가 이 배정의 모델을 가리키는지 확인한다.
|
|
512
|
+
|
|
513
|
+
메타데이터는 프롬프트 옆에 `<prompt>.meta.json` 으로 놓인다. 읽을 수 없으면
|
|
514
|
+
조용히 통과한다 — 이 검사는 어긋남을 앞당겨 잡는 것이지, 프롬프트를 못 읽는
|
|
515
|
+
사정을 새 차단 사유로 만드는 것이 아니다. 그 사정은 이미 다른 검사가 본다.
|
|
516
|
+
"""
|
|
517
|
+
prompt_path = metadata_path.with_name(
|
|
518
|
+
metadata_path.name.removesuffix(".meta.json")
|
|
519
|
+
)
|
|
520
|
+
if prompt_path == metadata_path or not prompt_path.is_file():
|
|
521
|
+
return
|
|
522
|
+
expected = str(getattr(assignment, "model_execution_value", "") or "").strip()
|
|
523
|
+
if not expected:
|
|
524
|
+
return
|
|
525
|
+
try:
|
|
526
|
+
text = prompt_path.read_text(encoding="utf-8")
|
|
527
|
+
except OSError:
|
|
528
|
+
return
|
|
529
|
+
errors = validate_prompt_model_header(text, expected)
|
|
530
|
+
if errors:
|
|
531
|
+
raise DispatchError(
|
|
532
|
+
f"agent dispatch prompt does not name this assignment's model: "
|
|
533
|
+
+ "; ".join(errors)
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
|
|
508
537
|
def record_verified_agent_dispatch(
|
|
509
538
|
*,
|
|
510
539
|
project_root: Path,
|
|
@@ -577,6 +606,7 @@ def record_verified_agent_dispatch(
|
|
|
577
606
|
)
|
|
578
607
|
if errors:
|
|
579
608
|
raise DispatchError("agent invocation verification failed: " + "; ".join(errors))
|
|
609
|
+
_require_prompt_model_matches_assignment(metadata_path, assignment)
|
|
580
610
|
if (
|
|
581
611
|
enforcement_mode == "host-native-spec-link-gate"
|
|
582
612
|
and assignment.runner != "native-session"
|
|
@@ -691,9 +721,6 @@ def record_verified_agent_dispatch(
|
|
|
691
721
|
dispatch_kind=dispatch_kind,
|
|
692
722
|
round=_execution_dispatch_round(dispatch_kind),
|
|
693
723
|
input_digest=prompt_digest,
|
|
694
|
-
write_policy=write_policy.to_payload(),
|
|
695
|
-
write_policy_digest=write_policy.digest,
|
|
696
|
-
write_enforcement=write_enforcement.to_payload(),
|
|
697
724
|
),
|
|
698
725
|
Attempt(
|
|
699
726
|
invocation_ref=execution_identity.invocation_ref,
|
|
@@ -705,6 +732,9 @@ def record_verified_agent_dispatch(
|
|
|
705
732
|
error_path=None,
|
|
706
733
|
change_summary={},
|
|
707
734
|
evidence_seal_ref=None,
|
|
735
|
+
write_policy=write_policy.to_payload(),
|
|
736
|
+
write_policy_digest=write_policy.digest,
|
|
737
|
+
write_enforcement=write_enforcement.to_payload(),
|
|
708
738
|
),
|
|
709
739
|
task_key=require_string(manifest, "taskKey"),
|
|
710
740
|
)
|
|
@@ -833,6 +863,7 @@ def _agent_write_contract(
|
|
|
833
863
|
project_root=project_root,
|
|
834
864
|
worktree=worktree,
|
|
835
865
|
artifact_paths=artifacts,
|
|
866
|
+
task_root=task_root_from_run_manifest(project_root, authority),
|
|
836
867
|
maximum_precision=maximum_precision,
|
|
837
868
|
planned_paths=planned[0],
|
|
838
869
|
planned_paths_declared=planned[1],
|
|
@@ -954,6 +985,25 @@ def _project_or_absolute(project_root: Path, value: str) -> Path:
|
|
|
954
985
|
return path if path.is_absolute() else project_root / path
|
|
955
986
|
|
|
956
987
|
|
|
988
|
+
def prompt_anchor_values(
|
|
989
|
+
prompt_path: Path, labels: Iterable[str],
|
|
990
|
+
) -> dict[str, str]:
|
|
991
|
+
"""`**<label>:** <value>` 앵커 줄을 라벨별로 읽는다. 첫 번째 값이 이긴다.
|
|
992
|
+
|
|
993
|
+
앵커는 materialize 가 문서 머리에 쓰고, 본문(리드 지시문)이 같은 라벨을
|
|
994
|
+
예시로 되풀이할 수 있다. `_prompt_write_paths` 는 마지막 값을 취하는데
|
|
995
|
+
그것은 write-policy 의 허용 집합을 넓힐 뿐이지만, 여기서 읽는 값은 워커가
|
|
996
|
+
실제로 쓰는 경로의 판정 기준이므로 머리의 것이어야 한다.
|
|
997
|
+
"""
|
|
998
|
+
values: dict[str, str] = {}
|
|
999
|
+
for line in prompt_path.read_text(encoding="utf-8").splitlines():
|
|
1000
|
+
for label in labels:
|
|
1001
|
+
prefix = f"**{label}:** "
|
|
1002
|
+
if label not in values and line.startswith(prefix):
|
|
1003
|
+
values[label] = line.removeprefix(prefix).strip()
|
|
1004
|
+
return values
|
|
1005
|
+
|
|
1006
|
+
|
|
957
1007
|
def _execution_dispatch_round(dispatch_kind: str) -> int:
|
|
958
1008
|
prefix = "reverify-r"
|
|
959
1009
|
if not dispatch_kind.startswith(prefix):
|
|
@@ -1017,6 +1067,9 @@ def link_agent_dispatch_result(
|
|
|
1017
1067
|
f"agent result link requires exactly one dispatch record: {dispatch_id}"
|
|
1018
1068
|
)
|
|
1019
1069
|
dispatch = dispatches[0]
|
|
1070
|
+
_require_recorded_result_path(
|
|
1071
|
+
project_root, dispatch, resolved_result, result_relative
|
|
1072
|
+
)
|
|
1020
1073
|
link: dict[str, Any] = {
|
|
1021
1074
|
"dispatchId": dispatch_id,
|
|
1022
1075
|
"resultPath": result_relative,
|
|
@@ -1081,20 +1134,21 @@ def _finish_native_agent_attempt(
|
|
|
1081
1134
|
if identity is None:
|
|
1082
1135
|
return
|
|
1083
1136
|
execution = read_execution_manifest(manifest_path)
|
|
1084
|
-
|
|
1137
|
+
attempt = next(
|
|
1085
1138
|
(
|
|
1086
|
-
row for row in execution.
|
|
1139
|
+
row for row in execution.attempts
|
|
1087
1140
|
if row.invocation_ref == identity["invocation_ref"]
|
|
1141
|
+
and row.attempt == identity["attempt"]
|
|
1088
1142
|
),
|
|
1089
1143
|
None,
|
|
1090
1144
|
)
|
|
1091
|
-
if
|
|
1092
|
-
raise DispatchError("agent result has no canonical
|
|
1093
|
-
if dispatch.get("writePolicyDigest") !=
|
|
1145
|
+
if attempt is None:
|
|
1146
|
+
raise DispatchError("agent result has no canonical attempt policy")
|
|
1147
|
+
if dispatch.get("writePolicyDigest") != attempt.write_policy_digest:
|
|
1094
1148
|
raise DispatchError("agent dispatch write policy digest changed")
|
|
1095
|
-
if dispatch.get("writeEnforcement") !=
|
|
1149
|
+
if dispatch.get("writeEnforcement") != attempt.write_enforcement:
|
|
1096
1150
|
raise DispatchError("agent dispatch write enforcement changed")
|
|
1097
|
-
policy = write_policy_from_payload(
|
|
1151
|
+
policy = write_policy_from_payload(attempt.write_policy)
|
|
1098
1152
|
status, summary = _native_mutation_result(
|
|
1099
1153
|
project_root=project_root,
|
|
1100
1154
|
dispatch=dispatch,
|
|
@@ -1290,6 +1344,127 @@ def reject_agent_dispatch_result(
|
|
|
1290
1344
|
return dict(row)
|
|
1291
1345
|
|
|
1292
1346
|
|
|
1347
|
+
def _require_recorded_result_path(
|
|
1348
|
+
project_root: Path,
|
|
1349
|
+
dispatch: Mapping[str, Any],
|
|
1350
|
+
resolved_result: Path,
|
|
1351
|
+
result_relative: str,
|
|
1352
|
+
) -> None:
|
|
1353
|
+
"""링크되는 결과가 이 디스패치가 실제로 기다린 산출물인가.
|
|
1354
|
+
|
|
1355
|
+
비교 대상을 여기서 다시 계산하지 않는다. `record-dispatch` 가 이미
|
|
1356
|
+
`completionPaths` 로 그 목록을 적어 두었고(리포트 작성자는 narrative 와
|
|
1357
|
+
포인터 기록 둘), 그것이 이 디스패치가 완료로 인정하는 경로의 정본이다.
|
|
1358
|
+
|
|
1359
|
+
이 대조가 없으면 잘못된 경로를 링크해도 그 자리에서는 통과하고, 조립이
|
|
1360
|
+
`required input is missing` 으로 죽을 때에야 드러난다 — 그때는 워커가 이미
|
|
1361
|
+
돌고 난 뒤다. 실측(2026-08-26): 리드가 지어낸 seq(`-002.md`)를 넘겼고
|
|
1362
|
+
매니페스트의 선언은 `-004.md` 였다. 여기서 막으면 워커를 다시 돌릴 필요 없이
|
|
1363
|
+
같은 명령을 옳은 인자로 다시 부르면 된다.
|
|
1364
|
+
|
|
1365
|
+
`completionPaths` 를 기록하지 않은 행은 판정하지 않는다 — 목록이 없으면
|
|
1366
|
+
무엇이 정당한지 알 수 없고, 모르면서 막는 것은 추측이다.
|
|
1367
|
+
"""
|
|
1368
|
+
recorded = dispatch.get("completionPaths")
|
|
1369
|
+
if not isinstance(recorded, list) or not recorded:
|
|
1370
|
+
return
|
|
1371
|
+
allowed = {
|
|
1372
|
+
resolve_project_path(project_root, str(value)).resolve()
|
|
1373
|
+
for value in recorded
|
|
1374
|
+
if isinstance(value, str) and value.strip()
|
|
1375
|
+
}
|
|
1376
|
+
if not allowed or resolved_result in allowed:
|
|
1377
|
+
return
|
|
1378
|
+
expected = ", ".join(sorted(_relative_project_path(project_root, path) for path in allowed))
|
|
1379
|
+
raise DispatchError(
|
|
1380
|
+
f"agent result link is not one of this dispatch's completion paths: "
|
|
1381
|
+
f"linked {result_relative}, expected one of {expected}"
|
|
1382
|
+
)
|
|
1383
|
+
|
|
1384
|
+
|
|
1385
|
+
def abandon_agent_dispatch_attempt(
|
|
1386
|
+
*,
|
|
1387
|
+
project_root: Path,
|
|
1388
|
+
run_manifest_path: Path,
|
|
1389
|
+
invocation_ref: str,
|
|
1390
|
+
reason: str,
|
|
1391
|
+
) -> dict[str, Any]:
|
|
1392
|
+
"""Close a started attempt whose worker died without producing a result.
|
|
1393
|
+
|
|
1394
|
+
Every other path that closes an attempt needs something the dead worker
|
|
1395
|
+
never made: `link-result` needs its result file, and
|
|
1396
|
+
`_abandon_unstarted_attempt` only runs when the dispatch itself failed
|
|
1397
|
+
before the process existed. A worker that started and then died — its host
|
|
1398
|
+
killed, its pane closed, an import error before any log — leaves the attempt
|
|
1399
|
+
`started` forever, and `_validate_next_attempt` then refuses every retry with
|
|
1400
|
+
`previous attempt is not terminal`. The run has to be re-rendered from
|
|
1401
|
+
scratch to make progress.
|
|
1402
|
+
|
|
1403
|
+
**Only a `source-readonly` attempt may be abandoned.** The manifest accepts a
|
|
1404
|
+
following attempt only after `failed-no-mutation`, so closing an attempt is
|
|
1405
|
+
also an assertion that nothing was written. For a read-only worker that
|
|
1406
|
+
assertion is true by policy — it never held permission to write — so no
|
|
1407
|
+
guess is involved. For a `project-mutation` attempt it would be a guess, and
|
|
1408
|
+
a wrong one lets the retry run on top of a half-mutated tree, which is the
|
|
1409
|
+
invariant this manifest exists to protect. That case is refused here and
|
|
1410
|
+
belongs to the mutation audit.
|
|
1411
|
+
"""
|
|
1412
|
+
if not reason.strip():
|
|
1413
|
+
raise DispatchError(
|
|
1414
|
+
"an abandonment reason is required — the ledger has to say why an "
|
|
1415
|
+
"attempt was closed without a result"
|
|
1416
|
+
)
|
|
1417
|
+
project_root = project_root.resolve()
|
|
1418
|
+
manifest_path = resolve_project_path(
|
|
1419
|
+
project_root, str(run_manifest_path)
|
|
1420
|
+
).resolve(strict=True)
|
|
1421
|
+
manifest = load_json_object(manifest_path, "run manifest")
|
|
1422
|
+
execution = read_execution_manifest(manifest_path)
|
|
1423
|
+
attempts = [
|
|
1424
|
+
row for row in execution.attempts if row.invocation_ref == invocation_ref
|
|
1425
|
+
]
|
|
1426
|
+
if not attempts:
|
|
1427
|
+
raise DispatchError(f"no attempt for invocation {invocation_ref}")
|
|
1428
|
+
row = max(attempts, key=lambda item: item.attempt)
|
|
1429
|
+
if row.finished_at is not None:
|
|
1430
|
+
raise DispatchError(
|
|
1431
|
+
f"attempt {invocation_ref} #{row.attempt} is already terminal "
|
|
1432
|
+
f"({row.status})"
|
|
1433
|
+
)
|
|
1434
|
+
mode = _attempt_source_mode(row)
|
|
1435
|
+
if mode != "source-readonly":
|
|
1436
|
+
raise DispatchError(
|
|
1437
|
+
f"attempt {invocation_ref} #{row.attempt} runs under sourcePolicy "
|
|
1438
|
+
f"`{mode}` — only a source-readonly attempt may be abandoned, "
|
|
1439
|
+
"because closing one asserts that nothing was written. Resolve a "
|
|
1440
|
+
"mutating attempt through its mutation audit."
|
|
1441
|
+
)
|
|
1442
|
+
finish_attempt_mutation(
|
|
1443
|
+
manifest_path,
|
|
1444
|
+
invocation_ref=invocation_ref,
|
|
1445
|
+
attempt=row.attempt,
|
|
1446
|
+
finished_at=utc_now(),
|
|
1447
|
+
status="failed-no-mutation",
|
|
1448
|
+
result_path=None,
|
|
1449
|
+
error_path=None,
|
|
1450
|
+
change_summary={"abandonedReason": reason.strip()},
|
|
1451
|
+
task_key=require_string(manifest, "taskKey"),
|
|
1452
|
+
)
|
|
1453
|
+
return {
|
|
1454
|
+
"invocationRef": invocation_ref,
|
|
1455
|
+
"attempt": row.attempt,
|
|
1456
|
+
"status": "failed-no-mutation",
|
|
1457
|
+
"reason": reason.strip(),
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
|
|
1461
|
+
def _attempt_source_mode(row: Any) -> str:
|
|
1462
|
+
policy = getattr(row, "write_policy", None)
|
|
1463
|
+
source = policy.get("sourcePolicy") if isinstance(policy, Mapping) else None
|
|
1464
|
+
mode = source.get("mode") if isinstance(source, Mapping) else None
|
|
1465
|
+
return str(mode or "unknown")
|
|
1466
|
+
|
|
1467
|
+
|
|
1293
1468
|
def _relative_project_path(project_root: Path, path: Path) -> str:
|
|
1294
1469
|
try:
|
|
1295
1470
|
return path.resolve(strict=False).relative_to(project_root).as_posix()
|
|
@@ -1307,6 +1482,63 @@ def worker_state(team_state: Mapping[str, Any], worker_id: str) -> Mapping[str,
|
|
|
1307
1482
|
raise DispatchError(f"team-state has no workerId={worker_id}")
|
|
1308
1483
|
|
|
1309
1484
|
|
|
1485
|
+
def _task_project_root(team_state_path: Path) -> Path | None:
|
|
1486
|
+
"""team-state 경로에서 프로젝트 루트를 되짚는다 (`.okstra` 를 기준점으로).
|
|
1487
|
+
|
|
1488
|
+
stage run 은 `runs/<phase>/stage-N/state/` 로 한 층이 더 깊으므로 부모를
|
|
1489
|
+
세는 방식은 빗나간다. 기준점을 못 찾으면 None — 예상 밖 배치에서 판정을
|
|
1490
|
+
포기하는 쪽이, 실행을 통째로 막는 쪽보다 낫다. 최종 검증이 같은 모순을
|
|
1491
|
+
다시 잡는다.
|
|
1492
|
+
"""
|
|
1493
|
+
for parent in team_state_path.resolve().parents:
|
|
1494
|
+
if parent.name == ".okstra":
|
|
1495
|
+
return parent.parent
|
|
1496
|
+
return None
|
|
1497
|
+
|
|
1498
|
+
|
|
1499
|
+
class CompletedWithoutResultError(DispatchError):
|
|
1500
|
+
"""명부의 `resultPath` 파일 없이 `completed` 로 적으려 했다.
|
|
1501
|
+
|
|
1502
|
+
`_finish_attempt` 가 이 거절을 데이터로 돌려세우기 위해 구분한다 — 디스패치
|
|
1503
|
+
자신의 산출물은 있는데 명부가 가리키는 파일이 다른 경우다.
|
|
1504
|
+
"""
|
|
1505
|
+
|
|
1506
|
+
|
|
1507
|
+
def _reject_completed_without_result(
|
|
1508
|
+
team_state_path: Path, worker_id: str, worker: Mapping[str, Any]
|
|
1509
|
+
) -> None:
|
|
1510
|
+
"""산출물이 없는 역할을 완료로 적지 못하게 한다.
|
|
1511
|
+
|
|
1512
|
+
okstra 는 디스패치 결과를 스스로 기록한다. 그 원장이 두 시도 모두 `error`
|
|
1513
|
+
라고 적은 역할이 명부에는 `completed` 로 남을 수 있었다 — 명부 상태가
|
|
1514
|
+
원장에서 파생되지 않기 때문이다. 모순은 phase 끝 `validate-run` 이
|
|
1515
|
+
"completed but worker result file is missing" 으로 잡지만, 그때는 워커를
|
|
1516
|
+
다시 돌릴 수 없어 실행 전체가 막힌다.
|
|
1517
|
+
|
|
1518
|
+
조인 대신 명부 자신의 `resultPath` 를 본다. 그 값이 없는 역할(critic scope
|
|
1519
|
+
등)은 판정 대상이 아니다.
|
|
1520
|
+
|
|
1521
|
+
실측(2026-08-27, `fontsninja-v3-site` `dev-10341`): 명부 5건 중 `resultPath`
|
|
1522
|
+
를 가진 4건에서 3건은 파일이 있었고, grok-planner 만 없었다.
|
|
1523
|
+
"""
|
|
1524
|
+
relative = str(worker.get("resultPath") or "").strip()
|
|
1525
|
+
if not relative:
|
|
1526
|
+
return
|
|
1527
|
+
candidate = Path(relative)
|
|
1528
|
+
if not candidate.is_absolute():
|
|
1529
|
+
project_root = _task_project_root(team_state_path)
|
|
1530
|
+
if project_root is None:
|
|
1531
|
+
return
|
|
1532
|
+
candidate = project_root / candidate
|
|
1533
|
+
if candidate.is_file():
|
|
1534
|
+
return
|
|
1535
|
+
raise CompletedWithoutResultError(
|
|
1536
|
+
f"worker {worker_id} cannot be recorded completed: its result "
|
|
1537
|
+
f"{relative} does not exist. Record the terminal status the dispatch "
|
|
1538
|
+
"actually reached (`error` / `timeout` / `not-run`) with a reason."
|
|
1539
|
+
)
|
|
1540
|
+
|
|
1541
|
+
|
|
1310
1542
|
def transition_worker_status(
|
|
1311
1543
|
team_state_path: Path,
|
|
1312
1544
|
worker_id: str,
|
|
@@ -1332,6 +1564,10 @@ def transition_worker_status(
|
|
|
1332
1564
|
raise DispatchError(f"team-state workers must be an array: {team_state_path}")
|
|
1333
1565
|
for worker in workers:
|
|
1334
1566
|
if isinstance(worker, dict) and worker.get("workerId") == worker_id:
|
|
1567
|
+
if status == "completed":
|
|
1568
|
+
_reject_completed_without_result(
|
|
1569
|
+
team_state_path, worker_id, worker
|
|
1570
|
+
)
|
|
1335
1571
|
worker["status"] = status
|
|
1336
1572
|
worker["reason"] = reason
|
|
1337
1573
|
if status == "in-progress":
|
|
@@ -1400,25 +1636,6 @@ def _has_recorded_team_create(team_state: Mapping[str, Any]) -> bool:
|
|
|
1400
1636
|
|
|
1401
1637
|
# --- job facts ----------------------------------------------------------------
|
|
1402
1638
|
|
|
1403
|
-
def build_dispatch_id(invocation_id: str, attempt: int) -> str:
|
|
1404
|
-
"""The only place Python assembles a dispatch id.
|
|
1405
|
-
|
|
1406
|
-
Two generators drifted: this module pinned the attempt to 1 while
|
|
1407
|
-
`dispatch_core` used the real one, so a re-dispatch took the first attempt's
|
|
1408
|
-
id, team-state kept the same row twice, and `validate-run` failed that run
|
|
1409
|
-
with `agent dispatch ID is duplicated`.
|
|
1410
|
-
|
|
1411
|
-
Prose mirrors of this shape live outside Python, where no guard reaches
|
|
1412
|
-
them: `prompts/lead/convergence.md`, `prompts/lead/report-writer.md`,
|
|
1413
|
-
`prompts/lead/plan-body-verification.md` and the five
|
|
1414
|
-
`adapters/hosts/*/relay.md` each spell an `--dispatch-id` argument out for
|
|
1415
|
-
the lead. They agree with this function only because
|
|
1416
|
-
`record_verified_agent_dispatch` records the first attempt and no other;
|
|
1417
|
-
changing that convention has to change them too.
|
|
1418
|
-
"""
|
|
1419
|
-
return f"{invocation_id}:attempt-{attempt}"
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
1639
|
def dispatch_mode(jobs: Sequence[WorkerJob]) -> str:
|
|
1423
1640
|
backends = {job.backend for job in jobs}
|
|
1424
1641
|
if len(backends) == 1:
|
|
@@ -1427,7 +1644,18 @@ def dispatch_mode(jobs: Sequence[WorkerJob]) -> str:
|
|
|
1427
1644
|
|
|
1428
1645
|
|
|
1429
1646
|
def missing_completion_paths(job: WorkerJob) -> tuple[Path, ...]:
|
|
1430
|
-
|
|
1647
|
+
missing: list[Path] = []
|
|
1648
|
+
for path in job.completion_paths:
|
|
1649
|
+
if path.is_file():
|
|
1650
|
+
continue
|
|
1651
|
+
# reports seq 와 workerResults seq 가 갈라지면 워커는 다른 쪽
|
|
1652
|
+
# 파일명으로 쓴다. 둘 중 하나가 있으면 산출물은 있는 것이다.
|
|
1653
|
+
if path in {job.result_path, job.worker_result_path} and any(
|
|
1654
|
+
alias.is_file() for alias in job.result_aliases
|
|
1655
|
+
):
|
|
1656
|
+
continue
|
|
1657
|
+
missing.append(path)
|
|
1658
|
+
return tuple(missing)
|
|
1431
1659
|
|
|
1432
1660
|
|
|
1433
1661
|
def dispatch_session_id(provider: str) -> str:
|
|
@@ -1475,7 +1703,7 @@ def worker_session_ids(
|
|
|
1475
1703
|
for record in dispatches if isinstance(dispatches, list) else []:
|
|
1476
1704
|
if not isinstance(record, Mapping):
|
|
1477
1705
|
continue
|
|
1478
|
-
if worker_id is not None and record
|
|
1706
|
+
if worker_id is not None and _dispatch_worker_key(record) != worker_id:
|
|
1479
1707
|
continue
|
|
1480
1708
|
session_id = str(record.get("sessionId") or "").strip()
|
|
1481
1709
|
if session_id and session_id not in session_ids:
|
|
@@ -1483,6 +1711,27 @@ def worker_session_ids(
|
|
|
1483
1711
|
return session_ids
|
|
1484
1712
|
|
|
1485
1713
|
|
|
1714
|
+
def _dispatch_worker_key(record: Mapping[str, Any]) -> str:
|
|
1715
|
+
"""The `workers[]` row a dispatch record belongs to.
|
|
1716
|
+
|
|
1717
|
+
A v1 record names it as `workerId`. A v2 record carries execution identity
|
|
1718
|
+
instead and no `workerId` (`_dispatch_record`), so its row is the one the
|
|
1719
|
+
roster keys off the assignment ref's last segment — `critic/scope` is the
|
|
1720
|
+
row `scope`, `initial/claude-analyser` the row `claude-analyser` — the same
|
|
1721
|
+
projection `v2_worker_state_key` makes for the jobs file. Matching on
|
|
1722
|
+
`workerId` alone found no v2 record, so a pane worker's session id was never
|
|
1723
|
+
read and every v2 pane worker stayed `unavailable` (observed 2026-09-02,
|
|
1724
|
+
dev-10626 r04: the critic's session was on disk with 79 usage records).
|
|
1725
|
+
"""
|
|
1726
|
+
worker_id = str(record.get("workerId") or "").strip()
|
|
1727
|
+
if worker_id:
|
|
1728
|
+
return worker_id
|
|
1729
|
+
assignment_ref = record.get("assignmentRef")
|
|
1730
|
+
if not isinstance(assignment_ref, str):
|
|
1731
|
+
return ""
|
|
1732
|
+
return assignment_ref.rsplit("/", 1)[-1].strip()
|
|
1733
|
+
|
|
1734
|
+
|
|
1486
1735
|
def dispatch_result_path(
|
|
1487
1736
|
worker_id: str,
|
|
1488
1737
|
worker_result_path: Path,
|
|
@@ -1534,6 +1783,32 @@ def dispatch_completion_paths(
|
|
|
1534
1783
|
return (data_json, worker_result_path)
|
|
1535
1784
|
|
|
1536
1785
|
|
|
1786
|
+
def _plan_verify_result_aliases(
|
|
1787
|
+
worker_result_path: Path, manifest: Mapping[str, Any],
|
|
1788
|
+
) -> tuple[Path, ...]:
|
|
1789
|
+
"""reports seq 파일의 workerResults seq 짝.
|
|
1790
|
+
|
|
1791
|
+
plan-verify 결과 파일명만 해당한다. 다른 산출물의 seq 를 바꾸면 안 된다.
|
|
1792
|
+
"""
|
|
1793
|
+
if "-plan-verify-r" not in worker_result_path.name:
|
|
1794
|
+
return ()
|
|
1795
|
+
sequences = manifest.get("runSequencesByCategory")
|
|
1796
|
+
if not isinstance(sequences, Mapping):
|
|
1797
|
+
return ()
|
|
1798
|
+
reports = str(sequences.get("reports") or "").strip()
|
|
1799
|
+
workers = str(sequences.get("workerResults") or "").strip()
|
|
1800
|
+
if not reports or not workers or reports == workers:
|
|
1801
|
+
return ()
|
|
1802
|
+
name = worker_result_path.name
|
|
1803
|
+
if name.endswith(f"-{reports}.md"):
|
|
1804
|
+
other = name[: -len(f"-{reports}.md")] + f"-{workers}.md"
|
|
1805
|
+
elif name.endswith(f"-{workers}.md"):
|
|
1806
|
+
other = name[: -len(f"-{workers}.md")] + f"-{reports}.md"
|
|
1807
|
+
else:
|
|
1808
|
+
return ()
|
|
1809
|
+
return (worker_result_path.with_name(other),)
|
|
1810
|
+
|
|
1811
|
+
|
|
1537
1812
|
def validate_initial_prompts(
|
|
1538
1813
|
manifest: Mapping[str, Any], jobs: Sequence[WorkerJob]
|
|
1539
1814
|
) -> None:
|
|
@@ -1893,10 +2168,82 @@ def worker_jobs_from_file(
|
|
|
1893
2168
|
for item in workers
|
|
1894
2169
|
if isinstance(item, Mapping)
|
|
1895
2170
|
]
|
|
2171
|
+
_validate_jobs_file_prompt_anchors(jobs)
|
|
1896
2172
|
_validate_v2_jobs_file_authority(manifest, jobs)
|
|
1897
2173
|
return jobs
|
|
1898
2174
|
|
|
1899
2175
|
|
|
2176
|
+
def _normalized_path(path: Path) -> Path:
|
|
2177
|
+
return Path(os.path.normpath(path))
|
|
2178
|
+
|
|
2179
|
+
|
|
2180
|
+
def _validate_jobs_file_prompt_anchors(jobs: Sequence[WorkerJob]) -> None:
|
|
2181
|
+
"""jobs-file 의 결과 경로가 프롬프트 앵커와 같은 파일인지 본다.
|
|
2182
|
+
|
|
2183
|
+
워커는 프롬프트의 `**Result Path:**` 에 쓰고(포인터를 가진 report-writer 는
|
|
2184
|
+
`**Worker Result Path:**` 에 포인터를), 수집기는 jobs-file 의
|
|
2185
|
+
`workerResultPath` 를 기다린다. 두 값은 리드가 따로 적으므로 어긋날 수 있고,
|
|
2186
|
+
어긋나면 워커가 결과를 다 써도 `required worker artifact was not produced`
|
|
2187
|
+
로 끝나 같은 프롬프트가 한 번 더 돈다. 실측(2026-09-02, fontsninja-v3-site
|
|
2188
|
+
dev-10626-1 error-analysis r04): 검증 디스패치 9건 중 8건이 이 형태였고
|
|
2189
|
+
재시도 6건이 같은 이유로 실패했다.
|
|
2190
|
+
|
|
2191
|
+
report-writer 는 한 가지를 더 본다 — 프롬프트의 `**Result Path:**`(서술문)가
|
|
2192
|
+
`dispatch_result_path` 가 정한 조립 입력과 같은가. 다르면 서술문은 써지지만
|
|
2193
|
+
`report-finalize` 가 읽는 자리에는 아무것도 없다.
|
|
2194
|
+
|
|
2195
|
+
프롬프트 파일이 아직 없는 잡은 건너뛴다. 실제 디스패치는 같은 파일을
|
|
2196
|
+
write-policy 검사(`_prompt_write_paths`)가 반드시 읽으므로 빠져나갈 자리가
|
|
2197
|
+
없고, 단위 테스트의 빈 경로만 여기서 면제된다.
|
|
2198
|
+
"""
|
|
2199
|
+
errors: list[str] = []
|
|
2200
|
+
for job in jobs:
|
|
2201
|
+
if not job.prompt_path.is_file():
|
|
2202
|
+
continue
|
|
2203
|
+
anchors = prompt_anchor_values(
|
|
2204
|
+
job.prompt_path, ("Result Path", "Worker Result Path"),
|
|
2205
|
+
)
|
|
2206
|
+
label = (
|
|
2207
|
+
"Worker Result Path"
|
|
2208
|
+
if anchors.get("Worker Result Path")
|
|
2209
|
+
else "Result Path"
|
|
2210
|
+
)
|
|
2211
|
+
declared = anchors.get(label)
|
|
2212
|
+
if not declared:
|
|
2213
|
+
continue
|
|
2214
|
+
declared_path = _normalized_path(
|
|
2215
|
+
_project_or_absolute(job.project_root, declared)
|
|
2216
|
+
)
|
|
2217
|
+
accepted = {
|
|
2218
|
+
_normalized_path(path)
|
|
2219
|
+
for path in (job.worker_result_path, *job.result_aliases)
|
|
2220
|
+
}
|
|
2221
|
+
if declared_path not in accepted:
|
|
2222
|
+
errors.append(
|
|
2223
|
+
f"{job.worker_id}: workerResultPath {job.worker_result_path} "
|
|
2224
|
+
f"differs from the prompt's **{label}:** {declared_path}"
|
|
2225
|
+
)
|
|
2226
|
+
narrative = anchors.get("Result Path")
|
|
2227
|
+
if (
|
|
2228
|
+
label == "Worker Result Path"
|
|
2229
|
+
and narrative
|
|
2230
|
+
and _normalized_path(job.result_path)
|
|
2231
|
+
!= _normalized_path(job.worker_result_path)
|
|
2232
|
+
and _normalized_path(_project_or_absolute(job.project_root, narrative))
|
|
2233
|
+
!= _normalized_path(job.result_path)
|
|
2234
|
+
):
|
|
2235
|
+
errors.append(
|
|
2236
|
+
f"{job.worker_id}: the prompt's **Result Path:** {narrative} is "
|
|
2237
|
+
f"not the result this run assembles from ({job.result_path})"
|
|
2238
|
+
)
|
|
2239
|
+
if errors:
|
|
2240
|
+
raise DispatchError(
|
|
2241
|
+
"jobs file result paths differ from the prompt anchors — the worker "
|
|
2242
|
+
"writes where the prompt says, so the jobs file must name that path: "
|
|
2243
|
+
+ "; ".join(errors)
|
|
2244
|
+
)
|
|
2245
|
+
|
|
2246
|
+
|
|
1900
2247
|
def _worker_job_from_file(
|
|
1901
2248
|
project_root: Path,
|
|
1902
2249
|
item: Mapping[str, Any],
|
|
@@ -1909,6 +2256,14 @@ def _worker_job_from_file(
|
|
|
1909
2256
|
resolve_wrapper: Callable[[str], Path],
|
|
1910
2257
|
default_provider: Callable[[str], str],
|
|
1911
2258
|
) -> WorkerJob:
|
|
2259
|
+
# 빠진 필드를 한꺼번에 댄다. 하나씩 거절하던 동안 세 워커짜리 파일 하나를
|
|
2260
|
+
# 통과시키는 데 dry-run 세 번이 들었다(2026-09-02 실측).
|
|
2261
|
+
missing = _missing_required_strings(item)
|
|
2262
|
+
if missing:
|
|
2263
|
+
raise DispatchError(
|
|
2264
|
+
"jobs file worker is missing required string fields: "
|
|
2265
|
+
+ ", ".join(missing)
|
|
2266
|
+
)
|
|
1912
2267
|
identity = worker_execution_identity(item)
|
|
1913
2268
|
worker_id = (
|
|
1914
2269
|
require_string(item, "workerId")
|
|
@@ -1937,6 +2292,7 @@ def _worker_job_from_file(
|
|
|
1937
2292
|
completion_paths = dispatch_completion_paths(
|
|
1938
2293
|
worker_id, worker_result_path, manifest, project_root
|
|
1939
2294
|
)
|
|
2295
|
+
result_aliases = _plan_verify_result_aliases(worker_result_path, manifest)
|
|
1940
2296
|
digests = item.get("digests")
|
|
1941
2297
|
digest_values = digests if isinstance(digests, Mapping) else {}
|
|
1942
2298
|
host_model_value = item.get("hostModelValue")
|
|
@@ -1985,10 +2341,30 @@ def _worker_job_from_file(
|
|
|
1985
2341
|
prompt_digest=string_value(digest_values.get("promptDigest")),
|
|
1986
2342
|
host_model_value=host_model_value,
|
|
1987
2343
|
enforcement_mode=string_value(item.get("enforcementMode")),
|
|
2344
|
+
result_aliases=result_aliases,
|
|
1988
2345
|
**(identity or {}),
|
|
1989
2346
|
)
|
|
1990
2347
|
|
|
1991
2348
|
|
|
2349
|
+
_V1_JOB_REQUIRED_STRINGS = (
|
|
2350
|
+
"workerId", "promptPath", "workerResultPath", "modelExecutionValue", "role",
|
|
2351
|
+
)
|
|
2352
|
+
_V2_JOB_REQUIRED_STRINGS = (
|
|
2353
|
+
"provider", "assignmentRef", "participantRef", "roleExecutionRef",
|
|
2354
|
+
"executionLabel", "dutyId", "invocationRef", "promptPath",
|
|
2355
|
+
"workerResultPath", "modelExecutionValue", "role",
|
|
2356
|
+
)
|
|
2357
|
+
|
|
2358
|
+
|
|
2359
|
+
def _missing_required_strings(item: Mapping[str, Any]) -> list[str]:
|
|
2360
|
+
keys = (
|
|
2361
|
+
_V2_JOB_REQUIRED_STRINGS
|
|
2362
|
+
if item.get("schemaVersion") == "2.0"
|
|
2363
|
+
else _V1_JOB_REQUIRED_STRINGS
|
|
2364
|
+
)
|
|
2365
|
+
return [key for key in keys if not string_value(item.get(key))]
|
|
2366
|
+
|
|
2367
|
+
|
|
1992
2368
|
def worker_execution_identity(
|
|
1993
2369
|
item: Mapping[str, Any],
|
|
1994
2370
|
) -> dict[str, Any] | None:
|