okstra 0.206.0 → 0.207.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 +3 -3
- package/dist/cli-registry.mjs +7 -1
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/lifecycle/install.mjs +1 -1
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/docs/architecture/storage-model.md +1 -0
- package/docs/architecture.md +40 -16
- package/docs/cli.md +17 -15
- package/docs/contributor-change-matrix.md +3 -2
- package/docs/performance-improvement-plan-v2.md +1 -1
- package/docs/project-structure-overview.md +43 -20
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/operations/code-review.json +1 -1
- package/runtime/bin/lib/okstra/usage.sh +3 -3
- package/runtime/bin/okstra-compact-reminder.sh +1 -1
- package/runtime/bin/okstra-spawn-followups.py +2 -2
- package/runtime/prompts/duties/direction-selection-worker.json +1 -1
- package/runtime/prompts/launch.template.md +2 -2
- package/runtime/prompts/lead/adapters/cmux.md +4 -3
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +44 -12
- package/runtime/prompts/lead/okstra-lead-contract.md +44 -73
- package/runtime/prompts/lead/phase-routing.md +64 -0
- package/runtime/prompts/lead/report-writer.md +10 -8
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/profiles/_clarification-recommendation.md +4 -4
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +2 -2
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/forbidden-actions.json +0 -94
- package/runtime/prompts/wizard/prompts.ko.json +2 -1
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +5 -5
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +3 -2
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +17 -26
- package/runtime/python/okstra_ctl/agent/prompt_cli/batch.py +183 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +60 -10
- package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +1 -1
- package/runtime/python/okstra_ctl/agent/prompt_cli/jobs.py +21 -4
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +10 -1
- package/runtime/python/okstra_ctl/analysis_inputs.py +0 -39
- package/runtime/python/okstra_ctl/analysis_scope.py +31 -0
- package/runtime/python/okstra_ctl/approval_decisions.py +32 -2
- package/runtime/python/okstra_ctl/asset_roots.py +19 -0
- package/runtime/python/okstra_ctl/assignment_resolver.py +8 -0
- package/runtime/python/okstra_ctl/blocking_checks.py +7 -0
- package/runtime/python/okstra_ctl/code_review_target.py +92 -6
- package/runtime/python/okstra_ctl/consumers.py +12 -0
- package/runtime/python/okstra_ctl/dispatch_checkpoints.py +121 -0
- package/runtime/python/okstra_ctl/dispatch_core.py +54 -32
- package/runtime/python/okstra_ctl/dispatch_state.py +34 -5
- package/runtime/python/okstra_ctl/doctor.py +2 -1
- package/runtime/python/okstra_ctl/domain/provider.py +5 -0
- package/runtime/python/okstra_ctl/domain/worker_presentation.py +21 -2
- package/runtime/python/okstra_ctl/domain/write_policy.py +2 -1
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +46 -9
- package/runtime/python/okstra_ctl/handoff.py +11 -466
- package/runtime/python/okstra_ctl/handoff_error.py +5 -0
- package/runtime/python/okstra_ctl/implementation_direction.py +0 -477
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +13 -1
- package/runtime/python/okstra_ctl/lead_progress.py +33 -1
- package/runtime/python/okstra_ctl/manager_view.py +26 -19
- package/runtime/python/okstra_ctl/model_io/lines.py +21 -4
- package/runtime/python/okstra_ctl/models.py +4 -1
- package/runtime/python/okstra_ctl/operation_invocation.py +11 -2
- package/runtime/python/okstra_ctl/option_comparison.py +3 -165
- package/runtime/python/okstra_ctl/option_votes.py +3 -191
- package/runtime/python/okstra_ctl/paths.py +8 -6
- package/runtime/python/okstra_ctl/phases/catalog.py +56 -12
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/boundary.json +11 -0
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/entry.py +39 -0
- package/runtime/python/okstra_ctl/{report_html/view_models/change_impact_analysis.py → phases/change_impact_analysis/report.py} +3 -3
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/spec.md +26 -0
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/validation.py +23 -0
- package/runtime/python/okstra_ctl/phases/error_analysis/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/error_analysis/boundary.json +9 -0
- package/runtime/{prompts/profiles/error-analysis.md → python/okstra_ctl/phases/error_analysis/profile.md} +2 -2
- package/runtime/python/okstra_ctl/{report_html/view_models/error_analysis.py → phases/error_analysis/report.py} +9 -8
- package/runtime/{templates/reports → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/error_analysis/spec.md +118 -0
- package/runtime/python/okstra_ctl/phases/error_analysis/validation.py +241 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/entry.py +63 -0
- package/runtime/python/okstra_ctl/{report_html/view_models/feature_analysis.py → phases/feature_analysis/report.py} +12 -5
- package/runtime/python/okstra_ctl/phases/feature_analysis/spec.md +22 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/validation.py +27 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/wizard.py +95 -0
- package/runtime/python/okstra_ctl/phases/final_verification/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/final_verification/profile.md +2 -2
- package/runtime/{templates/reports → python/okstra_ctl/phases/final_verification/report_assets}/final-verification-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/final_verification/spec.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/implementation/boundary.json +17 -0
- package/runtime/python/okstra_ctl/{implementation_stage.py → phases/implementation/entry.py} +22 -10
- package/runtime/{prompts/host-orchestration/implementation.md → python/okstra_ctl/phases/implementation/host-rules.md} +1 -1
- package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-deliverable.md +1 -1
- package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-executor.md +4 -3
- package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-verifier.md +18 -7
- package/runtime/{prompts/profiles/implementation.md → python/okstra_ctl/phases/implementation/profile.md} +5 -5
- package/runtime/python/okstra_ctl/{report_html/view_models/implementation.py → phases/implementation/report.py} +3 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/implementation/report_assets}/implementation-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation/spec.md +238 -0
- package/runtime/python/okstra_ctl/phases/implementation/validation.py +205 -0
- package/runtime/python/okstra_ctl/phases/implementation/wizard.py +39 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +80 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/boundary.json +10 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/comparison.py +168 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +27 -0
- package/runtime/{prompts/profiles/implementation-option-selection.md → python/okstra_ctl/phases/implementation_option_selection/profile.md} +3 -3
- package/runtime/python/okstra_ctl/{report_html/view_models/implementation_option_selection.py → phases/implementation_option_selection/report.py} +2 -2
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md +83 -0
- package/runtime/python/okstra_ctl/{implementation_options.py → phases/implementation_option_selection/validation.py} +3 -3
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/votes.py +194 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +2345 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/boundary.json +12 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/entry.py +161 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/guidance.py +178 -0
- package/runtime/{prompts/lead → python/okstra_ctl/phases/implementation_planning/instructions}/plan-body-verification.md +61 -51
- package/runtime/python/okstra_ctl/phases/implementation_planning/plan_body.py +3295 -0
- package/runtime/{prompts/profiles/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/profile.md} +74 -25
- package/runtime/python/okstra_ctl/phases/implementation_planning/report.py +237 -0
- package/runtime/{templates/reports → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning-input.template.md +2 -2
- package/runtime/python/okstra_ctl/phases/implementation_planning/spec.md +204 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/validation.py +597 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/wizard.py +166 -0
- package/runtime/python/okstra_ctl/phases/improvement_discovery/boundary.json +12 -0
- package/runtime/python/okstra_ctl/{improvement_lenses.py → phases/improvement_discovery/lenses.py} +1 -6
- package/runtime/{prompts/profiles/improvement-discovery.md → python/okstra_ctl/phases/improvement_discovery/profile.md} +5 -5
- package/runtime/python/okstra_ctl/{report_html/view_models/improvement_discovery.py → phases/improvement_discovery/report.py} +3 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery-input.template.md +1 -2
- package/runtime/python/okstra_ctl/phases/improvement_discovery/spec.md +29 -0
- package/runtime/{validators/validate_improvement_report.py → python/okstra_ctl/phases/improvement_discovery/validation.py} +5 -14
- package/runtime/python/okstra_ctl/phases/project_analysis/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/project_analysis/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/project_analysis/entry.py +11 -0
- package/runtime/python/okstra_ctl/{report_html/view_models/project_analysis.py → phases/project_analysis/report.py} +3 -3
- package/runtime/python/okstra_ctl/phases/project_analysis/spec.md +33 -0
- package/runtime/python/okstra_ctl/phases/project_analysis/validation.py +55 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/boundary.json +17 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/entry.py +147 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/operations.py +446 -0
- package/runtime/{prompts/profiles/release-handoff.md → python/okstra_ctl/phases/release_handoff/profile.md} +3 -3
- package/runtime/python/okstra_ctl/{report_html/view_models/release_handoff.py → phases/release_handoff/report.py} +3 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +233 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/wizard.py +84 -0
- package/runtime/python/okstra_ctl/phases/requirements_discovery/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/requirements_discovery/boundary.json +9 -0
- package/runtime/{prompts/profiles/requirements-discovery.md → python/okstra_ctl/phases/requirements_discovery/profile.md} +2 -3
- package/runtime/python/okstra_ctl/{report_html/view_models/requirements_discovery.py → phases/requirements_discovery/report.py} +3 -3
- package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +132 -0
- package/runtime/{validators/validate_fanout.py → python/okstra_ctl/phases/requirements_discovery/validation.py} +11 -12
- package/runtime/python/okstra_ctl/phases/technical_verification/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/boundary.json +9 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/entry.py +100 -0
- package/runtime/{prompts/profiles/technical-verification.md → python/okstra_ctl/phases/technical_verification/profile.md} +2 -2
- package/runtime/python/okstra_ctl/{report_html/view_models/technical_verification.py → phases/technical_verification/report.py} +2 -2
- package/runtime/python/okstra_ctl/phases/technical_verification/spec.md +37 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/validation.py +90 -0
- package/runtime/python/okstra_ctl/plan_approval.py +70 -0
- package/runtime/python/okstra_ctl/plan_items_cli.py +2 -2130
- package/runtime/python/okstra_ctl/process_group.py +118 -0
- package/runtime/python/okstra_ctl/profile_show.py +3 -3
- package/runtime/python/okstra_ctl/render.py +15 -4
- package/runtime/python/okstra_ctl/report_assembly.py +28 -92
- package/runtime/python/okstra_ctl/report_finalize.py +106 -2
- package/runtime/python/okstra_ctl/report_html/context_links.py +1 -1
- package/runtime/python/okstra_ctl/report_projections.py +1 -36
- package/runtime/python/okstra_ctl/report_routing.py +23 -0
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +4 -73
- package/runtime/python/okstra_ctl/report_validation_identity.py +38 -0
- package/runtime/python/okstra_ctl/report_views.py +1 -1
- package/runtime/python/okstra_ctl/run.py +68 -350
- package/runtime/python/okstra_ctl/run_artifact_prune.py +200 -0
- package/runtime/python/okstra_ctl/stage_map.py +13 -0
- package/runtime/python/okstra_ctl/team.py +108 -9
- package/runtime/python/okstra_ctl/technical_verification_facts.py +52 -0
- package/runtime/python/okstra_ctl/wizard/__init__.py +31 -31
- package/runtime/python/okstra_ctl/wizard/api.py +18 -0
- package/runtime/python/okstra_ctl/wizard/outcome.py +3 -12
- package/runtime/python/okstra_ctl/wizard/registry.py +20 -12
- package/runtime/python/okstra_ctl/wizard/steps_analysis.py +0 -97
- package/runtime/python/okstra_ctl/wizard/steps_options.py +8 -0
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +10 -263
- package/runtime/python/okstra_ctl/wizard/steps_roles.py +2 -1
- package/runtime/python/okstra_ctl/work_categories.py +1 -1
- package/runtime/python/okstra_ctl/worker_dispatch.py +44 -3
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +36 -0
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +19 -0
- package/runtime/python/okstra_ctl/worker_runner.py +21 -3
- package/runtime/python/okstra_ctl/workflow.py +26 -143
- package/runtime/python/okstra_ctl/write_policy.py +57 -7
- package/runtime/python/okstra_project/dirs.py +14 -0
- package/runtime/python/okstra_project/resolver.py +2 -1
- package/runtime/schemas/execution-manifest-v2.schema.json +2 -1
- package/runtime/skills/okstra-brief-gen/SKILL.md +3 -3
- package/runtime/skills/okstra-code-review/SKILL.md +70 -32
- package/runtime/skills/okstra-code-review/references/review-calibration.md +26 -6
- package/runtime/skills/okstra-run/SKILL.md +3 -3
- package/runtime/templates/manager/view.template.html +18 -1
- package/runtime/templates/reports/quick-input.template.md +1 -1
- package/runtime/templates/reports/task-brief.template.md +1 -1
- package/runtime/validators/validate-brief.py +2 -2
- package/runtime/validators/validate-run.py +299 -3940
- package/runtime/validators/validate_analysis_report.py +14 -126
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +0 -147
- package/runtime/python/okstra_ctl/technical_verification.py +0 -195
- /package/runtime/{prompts/profiles/change-impact-analysis.json → python/okstra_ctl/phases/change_impact_analysis/profile.json} +0 -0
- /package/runtime/{prompts/profiles/change-impact-analysis.md → python/okstra_ctl/phases/change_impact_analysis/profile.md} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis-input.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles/error-analysis.json → python/okstra_ctl/phases/error_analysis/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles/feature-analysis.json → python/okstra_ctl/phases/feature_analysis/profile.json} +0 -0
- /package/runtime/{prompts/profiles/feature-analysis.md → python/okstra_ctl/phases/feature_analysis/profile.md} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis-input.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-diff-review.md +0 -0
- /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-self-check.md +0 -0
- /package/runtime/{prompts/profiles/implementation.json → python/okstra_ctl/phases/implementation/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.md +0 -0
- /package/runtime/{prompts/profiles/implementation-option-selection.json → python/okstra_ctl/phases/implementation_option_selection/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.md +0 -0
- /package/runtime/{prompts/host-orchestration/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/host-rules.md} +0 -0
- /package/runtime/{prompts/profiles/implementation-planning.json → python/okstra_ctl/phases/implementation_planning/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.md +0 -0
- /package/runtime/{prompts/profiles/improvement-discovery.json → python/okstra_ctl/phases/improvement_discovery/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.md +0 -0
- /package/runtime/{prompts/profiles/project-analysis.json → python/okstra_ctl/phases/project_analysis/profile.json} +0 -0
- /package/runtime/{prompts/profiles/project-analysis.md → python/okstra_ctl/phases/project_analysis/profile.md} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis-input.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles/release-handoff.json → python/okstra_ctl/phases/release_handoff/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.md +0 -0
- /package/runtime/python/okstra_ctl/{fanout.py → phases/requirements_discovery/fanout.py} +0 -0
- /package/runtime/{prompts/profiles/requirements-discovery.json → python/okstra_ctl/phases/requirements_discovery/profile.json} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/requirements_discovery/report_assets}/fan-out-unit.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.md +0 -0
- /package/runtime/{prompts/profiles/technical-verification.json → python/okstra_ctl/phases/technical_verification/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.md +0 -0
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## When to Use
|
|
4
4
|
|
|
5
|
+
Use the absolute common-contract paths in the lead prompt's `Okstra Runtime Resources` block. The `prompts/lead/...` references below are relative to the same asset root that owns this instruction, not to this file's directory or the current project. That root contains `prompts/` beside one package layout: `scripts/okstra_ctl` (source), `python/okstra_ctl` (build), or `lib/python/okstra_ctl` (installed). Read `<asset-root>/prompts/lead/<name>.md` when only a root-relative reference is available; do not search another installation.
|
|
6
|
+
|
|
5
7
|
- ONLY when the run's task-type is `implementation-planning` — no other task-type runs this round
|
|
6
|
-
- As a Phase 6 sub-step, AFTER the Report writer worker's draft is reviewed — see
|
|
7
|
-
- Companion to
|
|
8
|
+
- As a Phase 6 sub-step, AFTER the Report writer worker's draft is reviewed — see `prompts/lead/okstra-lead-contract.md` "Phase 6 sub-step: Plan-body verification". Do NOT read this file during Phase 5.5 finding convergence; it is not part of that loop.
|
|
9
|
+
- Companion to `prompts/lead/convergence.md`: finding convergence reconciles worker **findings** (`F-*`); this contract verifies the **consolidated plan body** (`P-*`) authored by the Report writer worker. The two queues are disjoint — see "MUTUAL EXCLUSION" below.
|
|
8
10
|
|
|
9
11
|
This contract defines a **second, independent** verification round that fires only for `task-type = implementation-planning`. The round verifies the *consolidated plan* that the report-writer worker has authored, not the worker findings that were already reconciled earlier.
|
|
10
12
|
|
|
@@ -14,7 +16,7 @@ Plan-body verification runs **after** finding convergence and **after** the repo
|
|
|
14
16
|
|
|
15
17
|
```
|
|
16
18
|
Phase 4 workers produce independent analyses (Findings F-001…)
|
|
17
|
-
→ Phase 5.5 FINDING convergence (
|
|
19
|
+
→ Phase 5.5 FINDING convergence (`prompts/lead/convergence.md`, sections "Convergence Algorithm" through "Convergence State Artifact")
|
|
18
20
|
→ Phase 6 report-writer authors report-writer-narrative Markdown (consolidated Option Candidates / Stepwise Execution Order / Dependency / Validation Checklist / Rollback)
|
|
19
21
|
→ okstra plan-items prepare + prompt + validate-prepared creates and projects the deterministic P-* queue
|
|
20
22
|
→ PLAN-BODY VERIFICATION ROUND ← this contract
|
|
@@ -29,11 +31,11 @@ Plan-body verification MUST NOT replace, precede, or be conflated with the Phase
|
|
|
29
31
|
|
|
30
32
|
## MUTUAL EXCLUSION (BLOCKING)
|
|
31
33
|
|
|
32
|
-
The finding queue (Phase 5.5,
|
|
34
|
+
The finding queue (Phase 5.5, `prompts/lead/convergence.md`) and the plan-item queue (this contract) are **disjoint**:
|
|
33
35
|
|
|
34
36
|
- A finding-convergence reverify prompt MUST NOT contain any `P-*` item.
|
|
35
37
|
- A plan-body verification prompt MUST NOT contain any `F-*` finding.
|
|
36
|
-
- The two rounds write to **different state files**: `runs/<task-type>/state/convergence-<task-type>-<seq>.json` (findings, see
|
|
38
|
+
- The two rounds write to **different state files**: `runs/<task-type>/state/convergence-<task-type>-<seq>.json` (findings, see `prompts/lead/convergence.md` §"Convergence State Artifact") vs. `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (plan items, see §"`plan-body-verification.json` schema").
|
|
37
39
|
- Aggregation logic (verdict counting, classification) MUST NOT carry votes from one queue into the other.
|
|
38
40
|
|
|
39
41
|
**Enforced:** `_validate_queue_mutual_exclusion` in `validators/validate-run.py` fails a `P-*` id in the convergence state's `findings[]` or `dispatches[].findingIds`, an `F-*`/`C-*` id in the plan-body state's `planItems[]`, and a `### <id>` heading from the wrong namespace in either round's prompts.
|
|
@@ -49,11 +51,11 @@ Plan-body verification is configured under `convergence.planBodyVerification` in
|
|
|
49
51
|
| `enabled` | `true` | If `false`, the round is skipped and the approval gate is not blocked by this round (legacy behaviour). |
|
|
50
52
|
| `maxRounds` | `1` | Upper bound. Plan-body verification is consistency / completeness checking, not fact checking — additional rounds rarely help. Range 1–3. |
|
|
51
53
|
| `selfFixMaxRounds` | `1` | Fixed limit, not configurable: one automatic report-writer rewrite at most. Skip when nothing is fixable. `plan_items_cli._record_self_fixes`, `validate-run._validate_self_fix_grouping`, and session activity validation enforce the limit. |
|
|
52
|
-
| `gating` | `true` | If `true` (default), `majority-disagree` blocks approval. If `false`, the round is advisory-only until an objective verification defect requires repair. Prepare emits `true` because the plan does not exist yet. After the report-writer draft, `okstra plan-items prepare` (and `seed`) flip it to `false` when `designPreparation.mode` is `no-design-inputs` and the Stage Map has exactly one row. That path keeps extraction and one analyser verification round and does not run the self-fix loop or a sweep batch while it remains advisory. `apply-verdicts` and `complete-round` promote it to `gating=true` when `requires_plan_repair` finds factual `b`/`c`/`e` defects or factual `f` requirement-coverage defects. Explicit `claimKind: judgement` and rollback items retain advisory treatment. Correct the affected items using the existing bounded self-fix and re-verification procedure; do not carry an unexecutable check into implementation as accepted evidence. Environment-only `UNVERIFIABLE` remains a non-result, not a factual code rejection. Critic corrections are exempt from the analyser round limit and need not concern an even split. Record their actual round numbers and preserve completed history; they are not planner rewrites. Two-or-more stages, a PREP item, or non-empty `designPreparation.items` keep `gating=true`. `--no-plan-verification` is the separate manual opt-out (`enabled=false`). **Enforced:** `okstra_ctl.plan_items.advisory_plan_body_gating`, `
|
|
54
|
+
| `gating` | `true` | If `true` (default), `majority-disagree` blocks approval. If `false`, the round is advisory-only until an objective verification defect requires repair. Prepare emits `true` because the plan does not exist yet. After the report-writer draft, `okstra plan-items prepare` (and `seed`) flip it to `false` when `designPreparation.mode` is `no-design-inputs` and the Stage Map has exactly one row. That path keeps extraction and one analyser verification round and does not run the self-fix loop or a sweep batch while it remains advisory. `apply-verdicts` and `complete-round` promote it to `gating=true` when `requires_plan_repair` finds factual `b`/`c`/`e` defects or factual `f` requirement-coverage defects. Explicit `claimKind: judgement` and rollback items retain advisory treatment. Correct the affected items using the existing bounded self-fix and re-verification procedure; do not carry an unexecutable check into implementation as accepted evidence. Environment-only `UNVERIFIABLE` remains a non-result, not a factual code rejection. Critic corrections are exempt from the analyser round limit and need not concern an even split. Record their actual round numbers and preserve completed history; they are not planner rewrites. Two-or-more stages, a PREP item, or non-empty `designPreparation.items` keep `gating=true`. `--no-plan-verification` is the separate manual opt-out (`enabled=false`). **Enforced:** `okstra_ctl.plan_items.advisory_plan_body_gating`, `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_advisory_plan_body_gating`. The remaining advisory exemption is enforced by the `gating is False and not requires_plan_repair(pbv)` condition in `_recompute_plan_body_gate`, `_gate_blocking_causes`, `_validate_plan_body_clarification_matching`, and `_validate_self_fix_before_clarification` — an advisory round neither demands a `blocks=approval` row nor an exhausted self-fix budget. |
|
|
53
55
|
|
|
54
56
|
Default values are emitted into the manifest by `scripts/okstra_ctl/render.py` (`_build_convergence_block`). The ctx knob `OKSTRA_PLAN_VERIFICATION=false` flips `planBodyVerification.enabled` to false. `gating=false` is not that opt-out: extraction and one round still run.
|
|
55
57
|
|
|
56
|
-
The shared Majority definition and the auto-disable rule (fewer than 2 analyser workers → advisory `gating=false` path) are owned by
|
|
58
|
+
The shared Majority definition and the auto-disable rule (fewer than 2 analyser workers → advisory `gating=false` path) are owned by `prompts/lead/convergence.md` §"Convergence Algorithm" / §"Configuration" and apply here unchanged.
|
|
57
59
|
|
|
58
60
|
## Plan-item extraction (Round 0 equivalent)
|
|
59
61
|
|
|
@@ -103,9 +105,9 @@ For legacy candidate-comparison plans, `4.5.2 Trade-off Matrix` and `4.5.3 Recom
|
|
|
103
105
|
|
|
104
106
|
Each plan item inherits the `[TICKETID: ...]` tag of its source section (per the standard ticket-tagging contract).
|
|
105
107
|
|
|
106
|
-
For every detector-produced `(stage, kind)`, extract exactly one plan item named `P-Prep-S<stage>-<kind>`. The V1 detector in `scripts/okstra_ctl/design_surfaces.py` owns the set: extraction consumes its output and never reruns a free-form requirements or keyword analysis. The worker receives the stage trigger evidence and its single `designSurfaceCoverage` row, plus only the referenced `designPreparation` PREP items needed to judge that row. `okstra_ctl.plan_items.expected_plan_item_ids()` and `validators/validate-run.py` enforce the exact ID set. The coverage rows themselves are detector-owned: `scripts/okstra_ctl/design_snapshot.py` `build_design_snapshot` writes one row per detected `(stage, kind)` with its trigger evidence, and `scripts/okstra_ctl/
|
|
108
|
+
For every detector-produced `(stage, kind)`, extract exactly one plan item named `P-Prep-S<stage>-<kind>`. The V1 detector in `scripts/okstra_ctl/design_surfaces.py` owns the set: extraction consumes its output and never reruns a free-form requirements or keyword analysis. The worker receives the stage trigger evidence and its single `designSurfaceCoverage` row, plus only the referenced `designPreparation` PREP items needed to judge that row. `okstra_ctl.plan_items.expected_plan_item_ids()` and `validators/validate-run.py` enforce the exact ID set. The coverage rows themselves are detector-owned: `scripts/okstra_ctl/design_snapshot.py` `build_design_snapshot` writes one row per detected `(stage, kind)` with its trigger evidence, and `scripts/okstra_ctl/phases/implementation_planning/report.py` `project_design` reruns the detector against the plan and raises `ReportProjectionError` unless the snapshot's rows and evidence match it exactly — so a missing, duplicated, or evidence-drifted row stops the report at projection, before the plan is judged.
|
|
107
109
|
|
|
108
|
-
When extracting each item, lead also captures a **`subject`** — a plain one-line label (≤12 words) describing *what that item is* in the reader's terms, e.g. `P-Opt-1` → "Option A: split upload v2 into a new module", `P-Step-1.1` → "Stage 1 Step 2: regression-check with `npm run test:v2`". This is a label-capture, not new analysis. The `subject` is what §5.5.9 renders as the per-item heading so the reader knows *what* each AGREE/DISAGREE is about without cross-referencing §4.5; a bare `P-*` ID with no subject is a contract violation. **Enforced:** `
|
|
110
|
+
When extracting each item, lead also captures a **`subject`** — a plain one-line label (≤12 words) describing *what that item is* in the reader's terms, e.g. `P-Opt-1` → "Option A: split upload v2 into a new module", `P-Step-1.1` → "Stage 1 Step 2: regression-check with `npm run test:v2`". This is a label-capture, not new analysis. The `subject` is what §5.5.9 renders as the per-item heading so the reader knows *what* each AGREE/DISAGREE is about without cross-referencing §4.5; a bare `P-*` ID with no subject is a contract violation. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_plan_item_subject_substance` fails a subject that is a placeholder — under 3 chars, equal to the item id, or shaped like a bare `P-*` id.
|
|
109
111
|
|
|
110
112
|
## Fact and judgement (BLOCKING)
|
|
111
113
|
|
|
@@ -141,7 +143,7 @@ The last row is deliberate. Verdicts recorded before this field existed are not
|
|
|
141
143
|
re-judged in hindsight, so adoption only ever relaxes: a claim earns the quorum
|
|
142
144
|
route by declaring itself, never loses a block by staying silent.
|
|
143
145
|
|
|
144
|
-
**Enforced:** `
|
|
146
|
+
**Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_single_vote_block_survives`, with
|
|
145
147
|
the probes in `scripts/okstra_ctl/claim_reproduction.py`.
|
|
146
148
|
|
|
147
149
|
## What the gate asks (BLOCKING)
|
|
@@ -200,7 +202,7 @@ stage, a disposition recorded without its confirmation.
|
|
|
200
202
|
|
|
201
203
|
**Enforced:** `scripts/okstra_ctl/plan_items.py` `_item_block` assigns the block
|
|
202
204
|
at extraction, `okstra plan-items seed` carries it onto the row, and
|
|
203
|
-
`
|
|
205
|
+
`scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_plan_item_gate_class` downgrades a `record`
|
|
204
206
|
blocker to `has-dissent`. `_independent_coverage_blockers` is the separate
|
|
205
207
|
channel that keeps a genuine gap blocking.
|
|
206
208
|
|
|
@@ -229,7 +231,7 @@ something is outstanding. `gate.items[]` carries the bucket for each item; a
|
|
|
229
231
|
scoped-out defect and a real consensus would otherwise look identical in the
|
|
230
232
|
record.
|
|
231
233
|
|
|
232
|
-
**Enforced:** `
|
|
234
|
+
**Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_stage_scope_bucket` and
|
|
233
235
|
`_plan_item_gate_class`, which `_recompute_plan_body_gate` and
|
|
234
236
|
`_gate_summary_item` both call — the scoping cannot apply to the gate value and
|
|
235
237
|
not to the summary the round protocol records.
|
|
@@ -250,7 +252,7 @@ The verdict tokens `AGREE` / `DISAGREE` / `SUPPLEMENT` are reused, but their mea
|
|
|
250
252
|
- **fixability** (DISAGREE-only, required): each `DISAGREE(<kind>)` judges whether the defect can be fixed using only the code you have now + this plan draft + the brief.
|
|
251
253
|
- `planner-fixable` — resolved by correcting the plan itself without external information (abbreviated paths, prose commands, placeholders, requirement-coverage remapping, citing a non-existent stage, etc.).
|
|
252
254
|
- `needs-user-input` — the fix requires an open user clarification (infrastructure / contract decision) or external information.
|
|
253
|
-
One-line criterion: "Can this defect be fixed using only the code I have now + the plan + the brief?" — if so, `planner-fixable`. **Enforced:** `
|
|
255
|
+
One-line criterion: "Can this defect be fixed using only the code I have now + the plan + the brief?" — if so, `planner-fixable`. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_disagree_has_fixability` fails, on a run where the verification round ran (`roundCount >= 1`), any `DISAGREE` verdict whose `fixability` is missing or not one of the allowed values (`planner-fixable` / `needs-user-input`) — so that `_validate_self_fix_before_clarification` cannot let a mislabelled planner-fixable defect be promoted without a self-fix.
|
|
254
256
|
|
|
255
257
|
`P-Prep-S<stage>-<kind>` applies the same verdict tokens and adds these disposition checks:
|
|
256
258
|
|
|
@@ -259,7 +261,7 @@ The verdict tokens `AGREE` / `DISAGREE` / `SUPPLEMENT` are reused, but their mea
|
|
|
259
261
|
- `not-applicable`: AGREE only when the rationale is consistent with the stage action; otherwise DISAGREE with `fixability` (`planner-fixable` when the plan can supply the missing contract, `needs-user-input` only for genuinely external facts).
|
|
260
262
|
- A declared `blocked` item is not itself a plan-body failure. Missing or duplicate coverage, an empty proposal, a mismatched reference, or an unjustified disposition is a failure and receives `DISAGREE(<kind>)` with `fixability`.
|
|
261
263
|
|
|
262
|
-
`P-Var-<N>` applies the same verdict tokens to the variation-point analysis and is **majority-gated**: exactly like the `b` / `c` / `e` kinds, only a `majority-disagree` blocks the gate, and a single `DISAGREE` does not block on its own. Whether a behavior has two implementations, and whether the plan extracted the right interface for it, is a judgement about the design — it lacks the concrete certainty of kind `a`, where the verifier can point at two spelled-out references that contradict each other. So a P-Var defect is raised under a majority-gated kind (`b` when the extraction decision or its seam is not implementable as written, `b` likewise when a declared "no variation point" is contradicted by evidence the plan itself carries, `e` when it contradicts the recommended option) and never as kind `a`, which would single-vote-block on a judgement call. **Enforced:** `
|
|
264
|
+
`P-Var-<N>` applies the same verdict tokens to the variation-point analysis and is **majority-gated**: exactly like the `b` / `c` / `e` kinds, only a `majority-disagree` blocks the gate, and a single `DISAGREE` does not block on its own. Whether a behavior has two implementations, and whether the plan extracted the right interface for it, is a judgement about the design — it lacks the concrete certainty of kind `a`, where the verifier can point at two spelled-out references that contradict each other. So a P-Var defect is raised under a majority-gated kind (`b` when the extraction decision or its seam is not implementable as written, `b` likewise when a declared "no variation point" is contradicted by evidence the plan itself carries, `e` when it contradicts the recommended option) and never as kind `a`, which would single-vote-block on a judgement call. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_is_variation_point_item` excludes `P-Var-*` from both kind-`a` gating paths (`_classify_plan_item_gate`, `_is_correctness_critical`), so a mis-tagged `DISAGREE(a)` on one still needs a majority to block.
|
|
263
265
|
|
|
264
266
|
DISAGREE on a `P-Var-*` item means one of:
|
|
265
267
|
|
|
@@ -267,7 +269,7 @@ DISAGREE on a `P-Var-*` item means one of:
|
|
|
267
269
|
- **the extraction decision violates OCP** — the plan branches on resource identity (one `if` / `switch` arm per implementation) instead of extracting the interface the second implementation plugs into, so adding the next implementation means editing the same call site again;
|
|
268
270
|
- **the declared test seam is not actually injectable** — `extractionDecision.coveredBy` or the recommended option's `testSeams[].injectedAs` names no construction or wiring point a test can replace, so the seam exists on paper but nothing can be substituted at it.
|
|
269
271
|
|
|
270
|
-
The hexagonal rule that an extracted point must declare `interfaceKind: "port"` is already machine-checked by `
|
|
272
|
+
The hexagonal rule that an extracted point must declare `interfaceKind: "port"` is already machine-checked by `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_variation_point_analysis` (it fires only for a project whose `architecture.style` is `hexagonal`). Do not re-run that mechanical check as a verdict; spend the judgement on placement and semantics instead — a point extracted as a port whose domain rule leaked into the adapter passes the validator and is still wrong.
|
|
271
273
|
|
|
272
274
|
`P-Dir-1` carries the same YAGNI judgement as the legacy option item, but its comparison source is the selected-direction snapshot rather than a trade-off matrix. A new abstraction, configuration knob, widened interface, file, or stage with no original-requirement link is a hidden direction change and receives `DISAGREE(e)` on `P-Dir-1`.
|
|
273
275
|
|
|
@@ -277,7 +279,7 @@ The hexagonal rule that an extracted point must declare `interfaceKind: "port"`
|
|
|
277
279
|
- **a configuration knob with one value** — a flag, option object, or env-var switch that every planned call site passes identically;
|
|
278
280
|
- **a widened signature** — a step that adds an optional parameter no planned call site supplies.
|
|
279
281
|
|
|
280
|
-
The scope-provenance gate cannot reach these. It resolves the source of a *requirement row* and the citation of a *stage*, so unrequested work smuggled inside a legitimately-sourced stage passes it clean — the gate's own stated limit (`
|
|
282
|
+
The scope-provenance gate cannot reach these. It resolves the source of a *requirement row* and the citation of a *stage*, so unrequested work smuggled inside a legitimately-sourced stage passes it clean — the gate's own stated limit (`scripts/okstra_ctl/phases/implementation_planning/profile.md` "Scope provenance" → "The reach of this gate — do not over-trust it"). This verdict is that gate's missing half and the only plan-side judgement that can block on it, so a `P-Opt` item's AGREE asserts the option is free of unrequested work — not merely that it is executable. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_classify_plan_item_gate` — kind `e` sits in neither `_SINGLE_VOTE_BLOCKING_KINDS` nor `_ADVISORY_ONLY_KINDS`, so a `majority-disagree` on a `P-Opt-*` item blocks approval exactly like any other majority-gated defect, and one lone dissent does not.
|
|
281
283
|
|
|
282
284
|
The semantic checks above are the plan-body enforcement layer for the schema-valid structures; `validators/validate-run.py` enforces detector coverage and references, while this worker verdict decides whether the content is implementable.
|
|
283
285
|
|
|
@@ -312,7 +314,7 @@ which is what sends the item into a self-fix round that can only paper over it.
|
|
|
312
314
|
|
|
313
315
|
## Mode constraint
|
|
314
316
|
|
|
315
|
-
Plan-body verification only supports **lightweight mode** (defined in
|
|
317
|
+
Plan-body verification only supports **lightweight mode** (defined in `prompts/lead/convergence.md` §"Verification Mode"). `full-reanalysis` is not meaningful here because the "original source materials" for a plan item are the worker's own analysis plus the lead-mediated synthesis — there is no independent ground truth to re-read. The manifest's top-level `verificationMode` is ignored for this round; lightweight is always used.
|
|
316
318
|
|
|
317
319
|
**Enforced:** `scripts/okstra_ctl/convergence_engine.py` `_parse_config` rejects a `verificationMode` outside `_VERIFICATION_MODES`, and the plan-body round is seeded with `lightweight` rather than the manifest value.
|
|
318
320
|
|
|
@@ -322,18 +324,18 @@ For selected-direction `P-Req-*` rows, `status: externally-tracked` explicitly a
|
|
|
322
324
|
|
|
323
325
|
## Adversarial plan-body posture
|
|
324
326
|
|
|
325
|
-
When `config.adversarial == true` (the default for `implementation-planning`; see
|
|
327
|
+
When `config.adversarial == true` (the default for `implementation-planning`; see `prompts/lead/convergence.md` §"Configuration"), the plan-body round runs with an **adversarial posture**. The classification rules and gate arithmetic in §"Round protocol" are UNCHANGED — `majority-disagree` blocks approval, and that class now includes a blocking-kind minority dissent so a 2-AGREE / 1-DISAGREE on `b` / `c` / `e` is not passed silently. Advisory `dissent-isolated` (`DISAGREE(d)`, `P-Rb-*`) still does not block. Adversarial mode changes only *how each verifier evaluates an item*:
|
|
326
328
|
|
|
327
329
|
- The burden of proof sits on the plan: an item earns `AGREE` only if the verifier actively tried to break it and could not.
|
|
328
330
|
- The verifier MUST open the file paths / symbols / commands the item cites and confirm they exist and are **defined** as written. This is the one allowed widening of the lightweight "judge from internal consistency and stated commands / paths" rule — confirming the existence of cited paths is not "re-analyzing the original requirements". The widening stops at *definition*: a build/test command's **execution success** is out of scope here, because the planning worktree has no dependencies installed (§"Planning-time environment gap"). Confirm the script is declared; do not treat its failure to run as evidence against the plan.
|
|
329
331
|
- If a cited path / command / validation signal cannot be confirmed, the verifier responds `DISAGREE(<kind>)` with the applicable breakage kind (a–f); uncertainty resolves toward DISAGREE, not AGREE.
|
|
330
|
-
- **Single-vote-blocking kinds.** A reproduced `DISAGREE(a)` (cited path/symbol mismatch) on any item other than a `P-Var-*` one, or a reproduced `DISAGREE(f)` on a `P-Req-*` item, blocks on that one vote unless the assigned critic settles the analyser split with `AGREE` / `SUPPLEMENT`. The critic can correct the original factual objection; the original vote remains in the audit history. **Enforced:** `
|
|
332
|
+
- **Single-vote-blocking kinds.** A reproduced `DISAGREE(a)` (cited path/symbol mismatch) on any item other than a `P-Var-*` one, or a reproduced `DISAGREE(f)` on a `P-Req-*` item, blocks on that one vote unless the assigned critic settles the analyser split with `AGREE` / `SUPPLEMENT`. The critic can correct the original factual objection; the original vote remains in the audit history. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_single_vote_block_survives`. Kinds `b` / `c` / `e` do not auto-block on one unreproduced vote, but a blocking-kind minority with ≥2 participating votes is still `majority-disagree` and goes to the user — the majority does not silently pass it. **Rollback ordering (`d`) never blocks.** Because `a` is reserved for a concrete contradiction between two spelled-out references, an abbreviated path is raised as `b`, never `a`. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_classify_plan_item_gate`.
|
|
331
333
|
|
|
332
|
-
Plan-body verification stays **lightweight** even under this posture — the `verificationMode = "full-reanalysis"` forcing in
|
|
334
|
+
Plan-body verification stays **lightweight** even under this posture — the `verificationMode = "full-reanalysis"` forcing in `prompts/lead/convergence.md` §"Adversarial Verification Mode" applies to finding convergence only (see §"Mode constraint"); the adversarial posture here only changes verifier behaviour, not the mode. This raises verification *quality* (active refutation, plan-side burden). A reproduced fact (`a`, or `f` on P-Req) still blocks on one confirmed vote until the critic corrects that judgement. A blocking-kind minority (`b`/`c`/`e`) with ≥2 participating votes goes to the user rather than passing as `has-dissent`. Rollback ordering (`d`) is advisory and never blocks. A lone surviving `DISAGREE` whose peer returned a non-result does NOT block — a worker failure must not make the gate stricter than a healthy roster would.
|
|
333
335
|
|
|
334
336
|
## Round protocol (single round at default `maxRounds=1`)
|
|
335
337
|
|
|
336
|
-
Every plan-body verification and self-fix instruction passes
|
|
338
|
+
Every plan-body verification and self-fix instruction passes `prompts/lead/okstra-lead-contract.md` "Worker instruction quality gate" before dispatch. The lead names the exact plan-item IDs, current defects or dissent, authoritative paths, requested changes or verdict fields, allowed literals, and completion conditions for that round.
|
|
337
339
|
|
|
338
340
|
Before each verifier call, write one task-instructions file under the current
|
|
339
341
|
run's `state/` directory and run `okstra agent-prompt materialize` with
|
|
@@ -361,7 +363,15 @@ dispatch, and `okstra_ctl.worker_prompt_policy.is_verification_dispatch_kind`
|
|
|
361
363
|
routes the kind through the reverify prompt plan. Before this kind existed
|
|
362
364
|
(2026-09-09, dev-10642 implementation-planning 001) every attempt to dispatch the
|
|
363
365
|
round was refused and `planBodyVerification.roundCount` stayed 0 while the gate
|
|
364
|
-
read `passed`.
|
|
366
|
+
read `passed`. The reverse direction is
|
|
367
|
+
enforced too: `okstra_ctl.worker_prompt_contract.validate_plan_verify_dispatch_identity`,
|
|
368
|
+
run by `okstra agent-prompt materialize` and again by `okstra team dispatch`,
|
|
369
|
+
refuses a `plan-items prompt` queue under any kind other than `plan-verify-r<N>`,
|
|
370
|
+
and a `plan-verify-r<N>` dispatch whose result file name lacks
|
|
371
|
+
`-plan-verify-r<N>-`. validate-run counts plan-body votes only from those
|
|
372
|
+
dispatches, so a round sent as `plan-body-r<N>` (2026-09-24, jobs dev-9065
|
|
373
|
+
implementation-planning 001) dispatched and its votes surfaced only at the end
|
|
374
|
+
as unbacked. For a v2 run, select the verifier's canonical
|
|
365
375
|
source `RoleExecution` row from the run manifest's static role state. Use its
|
|
366
376
|
`participantRef`, and set `sourceRoleExecutionRef` to the selected source
|
|
367
377
|
`RoleExecution` row's `roleExecutionRef`, not that row's
|
|
@@ -394,19 +404,19 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
|
|
|
394
404
|
**`--run-manifest` is what scopes the gate to the stage you are starting.** Seed uses it to overlay disk `done` / `active` onto `planBodyVerification.stageLedger`. The current plan's depends-on fills `ready` / `blocked` when no prior plan exists, so a first run does not treat every stage as in-scope. **Enforced:** `okstra_ctl.plan_items.planning_stage_ledger`.
|
|
395
405
|
2. For each analyser worker in the roster, use `okstra plan-items prompt` output verbatim as the instruction body in the materialization sequence above. Read §"Re-verification rounds (round 2+)" before preparing a later round; read §"Plan-body reverify prompt" only when diagnosing a prompt-contract failure.
|
|
396
406
|
3. Dispatch uses the same wrapper infrastructure as finding convergence, so the `--role-slug` is the same canonical `<role>-worker` that convergence uses — not a round-specific slug. Result file path: `runs/<task-type>/worker-results/<role>-worker-plan-verify-r<N>-implementation-planning-<seq>.md` (e.g. `codex-worker-plan-verify-r1-implementation-planning-003.md`). **`<seq>` is the report's sequence** — the one in this run's `final-report-<task-type>-<seq>` filename, NOT the `workerResults` sequence the initial analysis results carry. The two are equal in most runs and diverge in some (`reports: 004` alongside `workerResults: 005` is a real case). Provenance no longer globs by either seq: it resolves the expected filenames from this run's team-state `workerDispatches[]` rows — the paths the dispatches actually recorded — and falls back to the seq glob only when no team-state is readable, saying so in its finding. The `-worker-` token is load-bearing twice over: §"Plan-body reverify prompt" requires the same anchor headers as convergence, whose `**Audit sidecar path:**` is derived by `okstra_ctl.worker_artifact_paths.audit_sidecar_rel()` inserting `-audit-` after that token — a slug without it makes the header underivable and the helper raises. Record each `planItems[].verdicts[].worker` as the same `<role>-worker` string, because provenance compares it to this filename's prefix. **Enforced:** `tests/contract/test_reverify_dispatch_anchors.py` derives the sidecar from the documented name and re-extracts the prefix the provenance resolver uses.
|
|
397
|
-
**Verdict provenance.** Every verdict recorded in `planItems[].verdicts[]` MUST trace back to a dispatch that actually returned a result file. The whole gate — classification, self-fix eligibility, promotion, `gateBlockedBy` — is computed from these votes, so an unbacked vote lets the round be skipped while the gate still reads `passed`. **Enforced (advisory):** `
|
|
407
|
+
**Verdict provenance.** Every verdict recorded in `planItems[].verdicts[]` MUST trace back to a dispatch that actually returned a result file. The whole gate — classification, self-fix eligibility, promotion, `gateBlockedBy` — is computed from these votes, so an unbacked vote lets the round be skipped while the gate still reads `passed`. **Enforced (advisory):** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_plan_body_verdict_provenance` reports any `verdicts[].worker` with no recorded reverify dispatch whose result file exists; the finding is not in the blocking allowlist, so it surfaces as an advisory rather than failing the run — record it, never dismiss it. Recording a `verification-error` for a dispatch that produced no result is the correct way to represent a failed worker — inventing an `AGREE` is a contract violation, and renaming a result file to make provenance match destroys the link the check reads.
|
|
398
408
|
|
|
399
409
|
4. After all dispatches return, lead aggregates verdicts per `P-*` item across workers and classifies each:
|
|
400
410
|
|
|
401
|
-
**Every in-scope item carries at least one verdict row (BLOCKING).** Aggregation covers the dispatch queue, not deferred or observed stages. An in-scope item left with an empty `verdicts[]` is not a weak signal the gate can discount — it classifies `all-non-result`, states as `needs-reverify`, and folds into `passed-with-dissent` next to items two verifiers actually agreed on, so a plan item nobody judged reads as a passing one. This is the shape a self-fix round produces when the planner adds an in-scope item and the targeted round-N queue never picks it up. A worker that returned nothing is a `verification-error` row (step 3), not a missing row; if an in-scope item was never dispatched, dispatch it before scoring the round. **Enforced:** `
|
|
411
|
+
**Every in-scope item carries at least one verdict row (BLOCKING).** Aggregation covers the dispatch queue, not deferred or observed stages. An in-scope item left with an empty `verdicts[]` is not a weak signal the gate can discount — it classifies `all-non-result`, states as `needs-reverify`, and folds into `passed-with-dissent` next to items two verifiers actually agreed on, so a plan item nobody judged reads as a passing one. This is the shape a self-fix round produces when the planner adds an in-scope item and the targeted round-N queue never picks it up. A worker that returned nothing is a `verification-error` row (step 3), not a missing row; if an in-scope item was never dispatched, dispatch it before scoring the round. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_round_recorded_verdicts` fails any run whose `roundCount` ≥ 1 leaves an in-scope item with no verdict row.
|
|
402
412
|
|
|
403
413
|
- `full-consensus` — all participating analysers `AGREE` (SUPPLEMENT counts as agree on the item itself).
|
|
404
414
|
- `partial-consensus` — majority `AGREE` with two or more blocking `DISAGREE`s. On kinds `b` / `c` / `e` this is scored `majority-disagree` and **blocks approval** so the user decides; it is not folded into a passing gate.
|
|
405
415
|
- `dissent-isolated` — only one worker `DISAGREE`s, others `AGREE`. On a blocking kind (`b` / `c` / `e`, and kind `a` on `P-Var-*`) this is scored `majority-disagree` and **blocks approval**. Advisory-only `DISAGREE(d)` and `P-Rb-*` stay recorded dissent and do not block. (Distinct from finding-convergence `worker-unique`, which means the *opposite*: only one worker AGREEs.)
|
|
406
416
|
- `majority-disagree` — a *majority* of analysers `DISAGREE` (majority needs ≥2 participating non-error votes; rollback-ordering `DISAGREE(d)` votes are advisory and excluded from the tally), OR any blocking-kind dissent with ≥2 participating votes (a minority `DISAGREE` is not outvoted), OR an unresolved single-vote-blocking kind fires: one reproduced `DISAGREE(a)` on any item other than a `P-Var-*` one, or one reproduced `DISAGREE(f)` on a `P-Req-*` item (see §"Single-vote-blocking kinds"). This classification **blocks approval**. A valid critic correction is scored before these blocking rules, whether or not the analysers split evenly.
|
|
407
417
|
- `needs-reverify` — one of two shapes the round could not settle.
|
|
408
|
-
- **An even split on a blocking kind.** A panel splitting evenly (1-AGREE / 1-DISAGREE, 2-2, …) needs a critic decision. An unresolved single-vote-blocking kind remains `majority-disagree`; other unresolved splits are `needs-reverify`. Do **not** re-run the original two. Dispatch `critic-worker` immediately on those items only (`okstra plan-items prepare --tie-vote ...`, then `okstra plan-items prompt ...`). The prompt carries the analyser split and no other plan items. Read the answer with `okstra plan-items collect-verdicts --items <the --tie-vote plan-items artifact> --result critic-worker=<path> --output <envelope>` — `--items` takes that artifact, whose `dispatchQueue` is the tie items, so pointing the next step at the raw result is refused against this round's full queue. Record the critic vote as `verdicts[].worker = critic-worker` with `okstra plan-items apply-verdicts --state <plan-body-verification.json> --round <N> --append --items <the --tie-vote plan-items artifact> --result critic-worker=<path>` — `--items` persists the exact partial `dispatchQueue` used by verdict validation and `complete-round`. Earlier verdicts and completed-round history outside that queue remain unchanged. If a previous version saved the critic votes but left the full queue in state, the ordinary `okstra plan-items complete-round --state <state> --run-manifest <manifest> --round <N>` automatically reads this run's canonical prepared queue. It uses that queue when all votes recorded for this round are included, preserving a wider recorded batch when a later prepared queue excludes its votes. An explicit `--items <prepared artifact>` remains available for selecting an artifact. Enforced by `plan_items_cli._round_inputs` and `tests/run/test_plan_items.py` completion coverage. Do not fabricate new-round votes for already agreed items. Without it the result is checked against the whole persisted round queue and refused for every item the critic was never given (measured 2026-09-10: a 7-item tie round refused against 44 items), and the only way through was `--verdicts`, which the CLI's own help calls a historical envelope. Critic `AGREE` / `SUPPLEMENT` settles the split to `has-dissent`, including an earlier `DISAGREE(a)` or `DISAGREE(f)` on `P-Req-*`. This decision corrects the disputed judgement before single-vote blocking is evaluated; it does not delete the original dissent. Critic `DISAGREE` on a blocking kind is `majority-disagree`. **Enforced:** `
|
|
409
|
-
- **A lone dissent nobody cross-verified** — a single-vote-blocking kind fired but the item has **fewer than 2 participating non-error votes**, i.e. the lone dissent was never cross-verified because its peer returned `verification-error`. A single-vote-blocking kind means "one *confirmed* DISAGREE is enough"; an unconfirmed one is not, and on a `P-Var-*` item none fires at all — its kind `a` never blocks on one vote and takes a majority like `b` / `e`. This does **not** block approval — blocking on it would make a worker failure produce a stricter gate than a healthy roster, the same paradox the ≥2-vote majority rule already rules out. The item is re-dispatched in the next round (step 7); if it survives the round budget it is promoted per step 8 with a Statement that says verification never completed. **Enforced:** `
|
|
418
|
+
- **An even split on a blocking kind.** A panel splitting evenly (1-AGREE / 1-DISAGREE, 2-2, …) needs a critic decision. An unresolved single-vote-blocking kind remains `majority-disagree`; other unresolved splits are `needs-reverify`. Do **not** re-run the original two. Dispatch `critic-worker` immediately on those items only (`okstra plan-items prepare --tie-vote ...`, then `okstra plan-items prompt ...`). The prompt carries the analyser split and no other plan items. Read the answer with `okstra plan-items collect-verdicts --items <the --tie-vote plan-items artifact> --result critic-worker=<path> --output <envelope>` — `--items` takes that artifact, whose `dispatchQueue` is the tie items, so pointing the next step at the raw result is refused against this round's full queue. Record the critic vote as `verdicts[].worker = critic-worker` with `okstra plan-items apply-verdicts --state <plan-body-verification.json> --round <N> --append --items <the --tie-vote plan-items artifact> --result critic-worker=<path>` — `--items` persists the exact partial `dispatchQueue` used by verdict validation and `complete-round`. Earlier verdicts and completed-round history outside that queue remain unchanged. If a previous version saved the critic votes but left the full queue in state, the ordinary `okstra plan-items complete-round --state <state> --run-manifest <manifest> --round <N>` automatically reads this run's canonical prepared queue. It uses that queue when all votes recorded for this round are included, preserving a wider recorded batch when a later prepared queue excludes its votes. An explicit `--items <prepared artifact>` remains available for selecting an artifact. Enforced by `plan_items_cli._round_inputs` and `tests/run/test_plan_items.py` completion coverage. Do not fabricate new-round votes for already agreed items. Without it the result is checked against the whole persisted round queue and refused for every item the critic was never given (measured 2026-09-10: a 7-item tie round refused against 44 items), and the only way through was `--verdicts`, which the CLI's own help calls a historical envelope. Critic `AGREE` / `SUPPLEMENT` settles the split to `has-dissent`, including an earlier `DISAGREE(a)` or `DISAGREE(f)` on `P-Req-*`. This decision corrects the disputed judgement before single-vote blocking is evaluated; it does not delete the original dissent. Critic `DISAGREE` on a blocking kind is `majority-disagree`. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_unresolved_tie_was_reverified` fails an in-scope item that carries an even split on a blocking kind and has neither a `critic-worker` vote nor a `blocks: approval` clarification row, `_classify_plan_item_gate` scores the tie shape and fails a settled classification the votes do not support, and `okstra_ctl.plan_items.next_dispatch` returns kind `critic-tie` for exactly these items, so the tie round is the queue the CLI hands you rather than one you assemble. **With no critic on the roster the split is a user decision, not another round.** `okstra plan-items next-dispatch --state <plan-body-verification.json> --run-manifest <current-run-manifest.json>` answers `user-decision` (not `critic-tie`) when the run's `invocationAssignments` carries no `critic/*` entry, and its `itemIds` are the tie items. For each of them do what step 8 does for a surviving `majority-disagree`: `okstra approval-decision open` with `approvalContext.classification` set to `correctness-critical` when `_is_correctness_critical` is true, or `noncritical-dissent` otherwise, plus the matching `## 1. Clarification Items` row at `Blocks=approval`. Dispatch no further verification for those items. The `Blocks=approval` row is what withholds approval until the user disposes, exactly as for any other approval row; the gate retains unresolved single-vote blockers as `majority-disagree` and folds other `needs-reverify` items into `passed-with-dissent`, so the round closes on the gate it actually scored. A tie left with neither a critic vote nor a decision row surfaces as recorded dissent plus an `advisories[]` entry, not a round-blocking failure. **Enforced:** `okstra_ctl.plan_items.critic_is_rostered` reads the roster and `next_dispatch` returns the kind; `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_unresolved_tie_was_reverified` reads a `blocks: approval` row linked to the item as the settlement and emits its advisory only when neither settlement is recorded.
|
|
419
|
+
- **A lone dissent nobody cross-verified** — a single-vote-blocking kind fired but the item has **fewer than 2 participating non-error votes**, i.e. the lone dissent was never cross-verified because its peer returned `verification-error`. A single-vote-blocking kind means "one *confirmed* DISAGREE is enough"; an unconfirmed one is not, and on a `P-Var-*` item none fires at all — its kind `a` never blocks on one vote and takes a majority like `b` / `e`. This does **not** block approval — blocking on it would make a worker failure produce a stricter gate than a healthy roster, the same paradox the ≥2-vote majority rule already rules out. The item is re-dispatched in the next round (step 7); if it survives the round budget it is promoted per step 8 with a Statement that says verification never completed. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_classify_plan_item_gate` returns `needs-reverify` for this shape and `_recompute_plan_body_gate` folds it into `passed-with-dissent`.
|
|
410
420
|
- `contested` only meaningful when `maxRounds > 1`; at default `maxRounds=1`, fold any unresolved item into `partial-consensus`.
|
|
411
421
|
5. Gate result resolution:
|
|
412
422
|
- any `majority-disagree` item present AND `gating=true` → `blocked-by-disagreement`
|
|
@@ -426,10 +436,10 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
|
|
|
426
436
|
|
|
427
437
|
Reading the rules in this section and tallying the votes in an ad-hoc script is a contract violation, not a shortcut. The classification carries five special cases that a re-derivation drops one at a time — single-vote-blocking `a`/`f`, advisory-only `d`, the `P-Var-*` majority gate, the `P-Rb-*` exemption, and the `needs-reverify` shape — and a hand-written tally re-derives them from scratch every round, so the loop's later rounds score differently from its first. One implementation, called once per round, is what keeps round 5 scored the same way as round 1.
|
|
428
438
|
|
|
429
|
-
**Record the cause, not just the outcome.** The gate value names the outcome; `planBodyVerification.gateBlockedBy` (array) names every input that blocked it — `majority-disagree`, `coverage-gap`, `non-result`. Two independent inputs can block: a `majority-disagree` plan item, and a Requirement Coverage `gap` / `blocked C-NNN` row (`
|
|
439
|
+
**Record the cause, not just the outcome.** The gate value names the outcome; `planBodyVerification.gateBlockedBy` (array) names every input that blocked it — `majority-disagree`, `coverage-gap`, `non-result`. Two independent inputs can block: a `majority-disagree` plan item, and a Requirement Coverage `gap` / `blocked C-NNN` row (`scripts/okstra_ctl/phases/implementation_planning/profile.md` §"Requirement Coverage"). A coverage-only block still renders as `blocked-by-disagreement` because that is the only blocking non-abort value, so **without `gateBlockedBy` the report asserts a worker disagreement that never happened** and the reader hunts for a dissent that does not exist. Leave the array empty for a passing gate. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_gate_blocked_by` fails a passing gate that has a blocking coverage row — the coverage rule was prose-only before. The declared array itself is not compared against a recomputed one: `okstra plan-verify` returns `gate.blockedBy` from the same computation that produced the gate value, so recording what it returns is what makes the array right.
|
|
430
440
|
|
|
431
|
-
**A coverage row citing this run's own `C-NNN` is not an independent blocker.** When a coverage row's `blocked C-NNN` points at a clarification that step 8 below promoted from a `majority-disagree` item in *this same run*, that blocker is already counted once as the plan item. Counting it again as a coverage gap makes the run block on a clarification it just authored, and the row carries into the next run as a fresh blocker — the Requirement Coverage ↔ Clarification cycle. Such rows are excluded from `coverage-gap`. **Enforced:** `
|
|
432
|
-
6. `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>` derives `planBodyVerification.participatingAnalysers` from the current assigned roster and persisted votes, then atomically records the completed round. The gate arithmetic is unchanged, but a shrunken roster changes what the round can settle: with two participating analysers a 1-AGREE / 1-DISAGREE split is a tie, so it reaches neither consensus nor `majority-disagree` and the item has to go back for a round (see `needs-reverify` above). **Enforced:** `
|
|
441
|
+
**A coverage row citing this run's own `C-NNN` is not an independent blocker.** When a coverage row's `blocked C-NNN` points at a clarification that step 8 below promoted from a `majority-disagree` item in *this same run*, that blocker is already counted once as the plan item. Counting it again as a coverage gap makes the run block on a clarification it just authored, and the row carries into the next run as a fresh blocker — the Requirement Coverage ↔ Clarification cycle. Such rows are excluded from `coverage-gap`. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_independent_coverage_blockers`.
|
|
442
|
+
6. `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>` derives `planBodyVerification.participatingAnalysers` from the current assigned roster and persisted votes, then atomically records the completed round. The gate arithmetic is unchanged, but a shrunken roster changes what the round can settle: with two participating analysers a 1-AGREE / 1-DISAGREE split is a tie, so it reaches neither consensus nor `majority-disagree` and the item has to go back for a round (see `needs-reverify` above). **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_participating_analysers` recomputes `voting` from the recorded verdicts and fails a declared figure the table denies. `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_detect_uniform_verifier` remains advisory; do not copy its JSON output into state.
|
|
433
443
|
|
|
434
444
|
**Check each verifier's verdict distribution before the next round.** After `apply-verdicts` and before opening another worker batch, run `okstra plan-items next-dispatch --state <plan-body-verification.json> --run-manifest <current-run-manifest.json>`. Python owns that decision. Do not invent a full-roster round from a `needs-reverify` label, from every-item `UNVERIFIABLE`, or from `okstra plan-verify` warnings. `_detect_uniform_verifier` remains advisory; do not copy its JSON output into state.
|
|
435
445
|
|
|
@@ -451,23 +461,23 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
|
|
|
451
461
|
Then run `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>`. Python appends one immutable round history entry, records each verified item's votes, derives the current projection from the actual assigned roster, and stamps `completedAt` after the preceding verification command succeeds. The file accumulates across rounds; it is never truncated to the latest one. Report assembly later projects the completed nested `planBodyVerification` into the final record.
|
|
452
462
|
7. **Self-fix loop (one rewrite, targeting planner-fixable defects).** After the initial verification, lead may run one report-writer rewrite when a `majority-disagree` item has a majority of `DISAGREE` verdicts at `fixability == planner-fixable`. Re-verify changed items once, preserving verdicts on unchanged content. Then stop automatic self-fix regardless of outcome and follow step 8. The fixed order is initial verification → one planner self-fix → targeted re-verification → lead decision or immediate user confirmation. A second automatic self-fix is rejected by `plan_items_cli._record_self_fixes`; `_validate_self_fix_grouping` and session activity validation detect multiple recorded rewrites. No rewrite is needed when no item qualifies.
|
|
453
463
|
- **Group the targets by cause before instructing (BLOCKING).** Blocked items are usually several derivatives of one defect. Lead partitions this round's targets into cause groups and instructs each group as **"remove this cause"**, naming the derivatives it accounts for. The convergence command owns the persisted group and correction fields; the lead does not edit JSON state.
|
|
454
|
-
- lead instructs report-writer to rewrite the items in each cause group (NOT a full draft regeneration): one `rewrite` entry per item in a corrections ledger, whose `rule` states the cause group's required outcome; procedure in
|
|
464
|
+
- lead instructs report-writer to rewrite the items in each cause group (NOT a full draft regeneration): one `rewrite` entry per item in a corrections ledger, whose `rule` states the cause group's required outcome; procedure in `prompts/lead/report-writer.md` §"Corrective report-writer dispatch (ledger required)".
|
|
455
465
|
- missing or weak `P-Prep-*` contracts are repaired by adding kind-specific inline detail or an AI-prepared PREP item with a concrete proposal. Facts that require user or external authority remain `blocked` and keep their request material; never invent those facts during self-fix.
|
|
456
|
-
- **Drop plan items whose element the round deleted.** A self-fix rewrite may remove a plan element (a validation check, a rollback row). `P-*` ids are positional, so a deletion shifts every later row and silently re-points surviving verdicts at their neighbours — and a verdict recorded against a removed element keeps blocking a gate while being unfindable in the plan, so reading the plan never reveals the cause. After each round, re-extract plan items with `okstra plan-items extract` and re-verify any item whose `subject` no longer matches; never carry the old vote forward across a shift. **Enforced:** `
|
|
466
|
+
- **Drop plan items whose element the round deleted.** A self-fix rewrite may remove a plan element (a validation check, a rollback row). `P-*` ids are positional, so a deletion shifts every later row and silently re-points surviving verdicts at their neighbours — and a verdict recorded against a removed element keeps blocking a gate while being unfindable in the plan, so reading the plan never reveals the cause. After each round, re-extract plan items with `okstra plan-items extract` and re-verify any item whose `subject` no longer matches; never carry the old vote forward across a shift. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_verdicts_match_current_subjects` (re-pointing) and `_validate_plan_item_extraction_completeness` (dangling ids).
|
|
457
467
|
- **Classify each cause group before instructing it (BLOCKING).** A group is either an *authoring* defect — the plan says something wrong, incomplete, or self-contradictory, which self-fix owns — or a *citation* defect, where the plan points at an analysis artifact incorrectly. Only the first is self-fix work. For the second the finding already exists and already went through convergence, so the fix is to re-cite the converged artifact; instructing report-writer to re-derive the fact means the author reads the source material and produces a **finding that never went through convergence**, which the plan then carries as if it had. That is the role boundary the lead contract draws ("keep analysis, execution, verification, and report authoring responsibilities distinct; return defects to the role that owns them"), and report-writer is authoring-only by its own contract. `P-Req-*` items with breakage kind `f` are where this goes wrong most often: the question is usually whether a coverage row points correctly at something already measured, not whether the measurement is right. State the classification in the group's instruction so the author knows which of the two it is being asked to do.
|
|
458
|
-
- **A verdict older than the last self-fix is not a verdict unless the item's content is unchanged (BLOCKING).** A verdict cast in round 1 judged the text before the only automatic rewrite. Once that rewrite runs, a changed item's judgement is about a plan that no longer exists. `--round <N>` on `apply-verdicts` stamps each row and copies `contentHash` onto `verifiedContentHash`. `
|
|
468
|
+
- **A verdict older than the last self-fix is not a verdict unless the item's content is unchanged (BLOCKING).** A verdict cast in round 1 judged the text before the only automatic rewrite. Once that rewrite runs, a changed item's judgement is about a plan that no longer exists. `--round <N>` on `apply-verdicts` stamps each row and copies `contentHash` onto `verifiedContentHash`. `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_verdict_rounds_outlive_self_fix` fails an in-scope item whose verdict round is at or before `selfFixRoundsApplied` **and** whose `contentHash` does not match `verifiedContentHash`. Matching hashes keep the prior verdict — that is what avoids a sweep round over unchanged stages. Deferred and observed items are out of the gate and do not need a post-self-fix verdict. **Enforced:** `_validate_verdict_rounds_outlive_self_fix`.
|
|
459
469
|
- Lead re-runs plan-body verification, then records each worker Markdown result through `okstra plan-items apply-verdicts --state <plan-body-verification.json> --result <worker>=<result.md> --round <N>`. Score the result with `okstra plan-verify --narrative <report-writer-narrative.md> --state <plan-body-verification.json>`, then call `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>`. These commands fail on an assigned item the worker left unanswered, on a verdict for an item outside the queue, and on a duplicate worker result. `apply-verdicts` without `--append` replaces every recorded row of the queued items, so it also refuses — before writing — when a row belongs to a round that `complete-round` never closed, naming the round to close first; the votes of a closed round live in `planItems[].rounds`. Skipping `complete-round` between rounds and applying the next one lost 19 items' round-1 votes (2026-09-09). `--discard-open-rounds` is only for re-applying the lost rounds from their result files in order: it also restores `dispatchQueue` to the items those result files answer, because the persisted queue is the latest round's and a verdict for an earlier, wider queue is otherwise refused as outside it (round 1 = 26 items, critic tie = 8, round 3 = 19 on that same run — only the 19 survived). Re-apply each lost round with the flag, `complete-round` it, then the next. **Enforced:** `okstra_ctl.plan_items_cli._reject_uncompleted_round_loss` and `_restore_queue_from_results`.
|
|
460
470
|
- For a self-fix, record the correction through the typed convergence command rather than writing `selfFixNote` or `selfFixGroups` JSON. A resolved item does not create a clarification.
|
|
461
|
-
- **Each round is a worker batch.** Before dispatching round N ≥ 2, reclaim the previous round's completed verifiers exactly as at any other batch boundary (
|
|
462
|
-
- **Round completion.** A round is complete only after `okstra plan-verify` exits 0 and `okstra plan-items complete-round` succeeds. A round left with a non-zero exit carries its defect into the next round's inputs. Exit 0 with a non-empty `advisories[]` is a complete round: those findings are recorded, not round-blocking (step 5, §"`failures[]` carries only the blocking findings"). Report assembly and rendering occur only after the convergence state is terminal. **Enforced:** `
|
|
463
|
-
- **Loop termination.** The convergence command owns the round count and stop reason; the lead never edits either field in the state file. Both are recorded by `okstra plan-items complete-round`, and its two self-fix flags are not interchangeable. `--self-fix-group` is a claim that this round rewrote something, so it sets `selfFixGroups` and `selfFixRoundsApplied` and **requires** `--self-fix-stop-reason` — there is no default, because the value that used to fill in (`all-resolved`) is the one value step 8 reads as "nothing was left unresolved", which then forbids promoting the items the round did leave unresolved. `--self-fix-stop-reason` on its own is how a round that rewrote nothing records that the loop stops there; it writes `selfFixStopReason` and touches neither `selfFixGroups` nor `selfFixRoundsApplied`, because `
|
|
471
|
+
- **Each round is a worker batch.** Before dispatching round N ≥ 2, reclaim the previous round's completed verifiers exactly as at any other batch boundary (`prompts/lead/okstra-lead-contract.md` "Run-scoped worker-resource lifecycle") and emit `PROGRESS: phase-batch-cleanup panes=<n>`, then announce the round with `PROGRESS: phase-5.5.9-plan-verify round=<N> items=<count>`. Saying a round will "reuse" the previous verifiers and then dispatching under fresh names leaves every prior round holding its panes — five rounds of that is what exhausts the pane budget and blocks the next dispatch. **Enforced:** `validators/validate_session_conformance.py` `_check_plan_verify_cleanup_checkpoints` requires both lines once the state file records two or more rounds.
|
|
472
|
+
- **Round completion.** A round is complete only after `okstra plan-verify` exits 0 and `okstra plan-items complete-round` succeeds. A round left with a non-zero exit carries its defect into the next round's inputs. Exit 0 with a non-empty `advisories[]` is a complete round: those findings are recorded, not round-blocking (step 5, §"`failures[]` carries only the blocking findings"). Report assembly and rendering occur only after the convergence state is terminal. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_plan_body_state_rounds` requires one stored round per round number and a corresponding item vote.
|
|
473
|
+
- **Loop termination.** The convergence command owns the round count and stop reason; the lead never edits either field in the state file. Both are recorded by `okstra plan-items complete-round`, and its two self-fix flags are not interchangeable. `--self-fix-group` is a claim that this round rewrote something, so it sets `selfFixGroups` and `selfFixRoundsApplied` and **requires** `--self-fix-stop-reason` — there is no default, because the value that used to fill in (`all-resolved`) is the one value step 8 reads as "nothing was left unresolved", which then forbids promoting the items the round did leave unresolved. `--self-fix-stop-reason` on its own is how a round that rewrote nothing records that the loop stops there; it writes `selfFixStopReason` and touches neither `selfFixGroups` nor `selfFixRoundsApplied`, because `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_self_fix_grouping` requires the highest `selfFixGroups[].round` to equal `selfFixRoundsApplied` — a round number raised without a group has no value that passes. That stop-only call is refused while a planner-fixable majority `DISAGREE` remains and no self-fix rewrite is recorded, because validate-run requires at least one rewrite before such an item may be promoted; the fix is the rewrite, recorded with `--self-fix-group`. **Enforced:** `okstra_ctl.plan_items_cli._reject_unbacked_self_fix_stop` runs validate-run's `_validate_self_fix_before_clarification` before writing; `tests/run/test_plan_items.py::test_complete_round_refuses_a_stop_reason_with_no_rewrite_behind_it`. A user-directed correction does not consume the automatic self-fix limit, and a verification failure after that correction does not restart the automatic loop:
|
|
464
474
|
- `all-resolved` — no planner-fixable `majority-disagree` item remains. Exit.
|
|
465
|
-
- `no-progress` — the round resolved **zero** planner-fixable items relative to the previous round. Exit even with budget left: the same rewrite would repeat. Newly *introduced* defects count against progress, so a rewrite that trades one defect for another stops the loop rather than churning. A round that re-targets only what the previous round left unresolved is this same conclusion reached one dispatch earlier — exit on it under this reason rather than paying for the round that proves it. **Enforced (advisory):** `
|
|
475
|
+
- `no-progress` — the round resolved **zero** planner-fixable items relative to the previous round. Exit even with budget left: the same rewrite would repeat. Newly *introduced* defects count against progress, so a rewrite that trades one defect for another stops the loop rather than churning. A round that re-targets only what the previous round left unresolved is this same conclusion reached one dispatch earlier — exit on it under this reason rather than paying for the round that proves it. **Enforced (advisory):** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_detect_self_fix_recurrence` warns on that shape and names this stop reason.
|
|
466
476
|
- `max-rounds-reached` — the single automatic rewrite has been used. Exit to step 8. Count distinct `selfFixGroups[].round` values; `selfFixRoundsApplied` is the last verification round number, not the rewrite count. Extra verification batches before the rewrite do not increase the self-fix budget.
|
|
467
477
|
- `cause-group-recurrence` — legacy read-only value for reports produced before activity contract v1. A new activity-contract-v1 run cannot emit it because there is no second automatic self-fix round in which a cause group can recur. **Enforced at the only writer:** `okstra plan-items complete-round ... --self-fix-stop-reason` accepts `all-resolved` / `no-progress` / `max-rounds-reached` and nothing else (`scripts/okstra_ctl/plan_items_cli.py:261`), and it is what sets `selfFixStopReason`, so the value has no way into a new report. A legacy report that already carries it is still an *exhausted* loop, so step 8 may promote its surviving items: `_SELF_FIX_EXHAUSTED_REASONS` admits this value alongside `no-progress` / `max-rounds-reached`. Refusing it there left such a report with no exit at all — the loop may not run again, and the item may not be promoted either.
|
|
468
478
|
- `not-attempted` — the loop never ran because no item qualified.
|
|
469
479
|
The `no-progress` and `max-rounds-reached` exits are what make the loop terminate; `selfFixMaxRounds` alone is the backstop.
|
|
470
|
-
- a `majority-disagree` item with a majority of its deciding `DISAGREE` votes at `needs-user-input` is NOT a self-fix target — after correctness-critical precedence, it goes straight to the next step as `user-decision` rather than generic `noncritical-dissent`. **Enforced (promotion path):** `
|
|
480
|
+
- a `majority-disagree` item with a majority of its deciding `DISAGREE` votes at `needs-user-input` is NOT a self-fix target — after correctness-critical precedence, it goes straight to the next step as `user-decision` rather than generic `noncritical-dissent`. **Enforced (promotion path):** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_self_fix_before_clarification` demands an exhausted self-fix budget only of `planner-fixable` majorities, so a `needs-user-input` majority is promotable with no self-fix round, and `_validate_plan_body_clarification_matching` fails it when it reaches no `blocks: approval` row. The classification *value* is authoring guidance per step 8: `scripts/okstra_ctl/approval_decisions.py` checks it against the classification enum and its allowed dispositions only — no validator recomputes it from the votes' `fixability`.
|
|
471
481
|
8. **Resolve the remaining decisions without another automatic worker batch.** Run `okstra plan-items next-dispatch --state <state> --run-manifest <manifest>` after completing the self-fix verification. It returns `lead-decision` first for eligible items, then `user-decision` for the remainder. It does not schedule another critic or correction batch after the automatic rewrite. Enforcement: `plan_items.next_dispatch` and both state CLI callers.
|
|
472
482
|
|
|
473
483
|
For `lead-decision`, read the evidence and choose within the already agreed scope. Put the decision, why it is within lead authority, and source references in a Markdown file; run `okstra plan-items resolve-dissent --state <state> --item <P-id> --decision-file <file>`. This command records `leadDecision` and a visible `dissentLog` entry without changing votes. Gate status becomes `passed-with-dissent` when no other blocker remains. Enforcement: `plan_items_cli._resolve_dissent`, `validate-run._plan_item_decision_authority`, and `_lead_decision_applies` accept only current, verified, noncritical `b`/`c`/`e` judgements from at least two voting analysers after the one rewrite. A fact claim, `needs-user-input`, or non-result is ineligible. Changed content, scope or votes invalidate the decision's basis hash. The lead should cite the existing requirement or evidence granting authority; uncertainty about that authority goes to the user.
|
|
@@ -475,19 +485,19 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
|
|
|
475
485
|
For `user-decision`, ask interactively immediately with the concrete choices and their consequences. Requirements changes, scope expansion, risk acceptance, user preferences, unavailable facts, and failed verification stay user-owned. Do not infer approval, replace failure with success, or use self-fix exhaustion as risk acceptance. Record an existing applicable answer instead of asking again. If no answer is available, retain an explicit pending row; elapsed time is not consent. These interaction rules use the approval-blocker protocol in `okstra-lead-contract.md` and the existing approval ledger validation.
|
|
476
486
|
|
|
477
487
|
For every remaining **in-scope execution** blocker or unresolved tie outside lead authority (including `needs-user-input` from the start), add a row to `## 1. Clarification Items` with:
|
|
478
|
-
- do **not** promote an `observed` / `deferred` / `record` item. Those belong in `setAside`. A `Blocks=approval` C row for a frozen or unreached stage is how the clarification list grew while the next stage was already executable. **Enforced:** `
|
|
488
|
+
- do **not** promote an `observed` / `deferred` / `record` item. Those belong in `setAside`. A `Blocks=approval` C row for a frozen or unreached stage is how the clarification list grew while the next stage was already executable. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_plan_body_clarification_matching` uses `_plan_item_gate_class` (after stage scope), not the raw vote class.
|
|
479
489
|
- new `C-<N>` ID (numbering continues from any existing rows)
|
|
480
490
|
- `Statement` summarising the disagreement and the worker breakage `<kind>`
|
|
481
491
|
- `Kind` chosen per the standard policy (usually `decision` for option-level conflicts, `data-point` for path/symbol mismatches)
|
|
482
492
|
- `Blocks=approval`
|
|
483
493
|
- `origin=worker-finding` — the verifiers reached the defect on their own evidence (`_common-contract.md` §"Clarification request policy"). Do not invent a phase-named value: `origin` is a schema enum and `okstra approval-decision open` rejects anything outside it (`scripts/okstra_ctl/approval_decisions.py` `_reject_off_enum`); one report was published with `origin: "plan-body-verification"` and every later `okstra user-response` read of it failed schema validation. `lead-directed` is also wrong here — it marks the lead's own judgment and cannot be deferred without an interactive session.
|
|
484
494
|
- `userConfirmation` recording what actually happened before the row was written: `asked-and-answered`, `asked-awaiting`, or `deferred-no-interactive-session`.
|
|
485
|
-
- the report record's `planItems[].clarificationRefs[]` reaching that `C-<N>`. Under contract v3 the report record carries the **plural** field and the v3.0 schema forbids `clarificationId` on a plan item; report assembly derives the refs from the activity ledger's `clarificationRefs[]` + `planItemIds[]`, so record the decision through `okstra approval-decision` rather than writing the link by hand. The lead-owned state file keeps the singular `clarificationId`. `
|
|
495
|
+
- the report record's `planItems[].clarificationRefs[]` reaching that `C-<N>`. Under contract v3 the report record carries the **plural** field and the v3.0 schema forbids `clarificationId` on a plan item; report assembly derives the refs from the activity ledger's `clarificationRefs[]` + `planItemIds[]`, so record the decision through `okstra approval-decision` rather than writing the link by hand. The lead-owned state file keeps the singular `clarificationId`. `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_plan_body_clarification_matching` recomputes each item's class and fails when a majority-disagree item reaches no clarification, or reaches one that is missing or not `blocks: approval`.
|
|
486
496
|
- set `approvalContext.classification` to `user-decision` for a majority `needs-user-input` item, `correctness-critical` for `DISAGREE(a)`, `DISAGREE(f)` on `P-Req-*`, or an independent Requirement Coverage blocker, and `noncritical-dissent` for another surviving majority disagreement or an unsettled tie promoted under `user-decision`.
|
|
487
497
|
- record the decision through `okstra approval-decision open`. Each option carries `disposition`, exactly one `reach`, and optional `scopeEffects`. The activity ledger carries affected `planItemIds` and `clarificationRefs`; the approval row never copies those backtrace IDs. `select` is allowed only for `user-decision`. `accept-risk` is allowed for every classification, including `correctness-critical`: it ends the gate and keeps the DISAGREE votes as evidence. `request-revision` / `reject` withhold the next phase. **Enforced:** `scripts/okstra_ctl/approval_decisions.py` and report assembly.
|
|
488
498
|
- **Self-fix exhaustion is not risk acceptance.** A remaining item stays blocking until the user selects `accept-risk`, `select`, or `answer`. Record the user's non-empty original text. Do not require a second verification round to honour `accept-risk`.
|
|
489
499
|
- **Correctness-critical `accept-risk` does not rewrite the votes.** The linked items keep their DISAGREE (or remaining dissent) so a later stage can still see them. `request-revision` is the path that corrects the plan and re-verifies. **Enforced by keeping the two ledgers apart:** `okstra approval-decision resolve` (`scripts/okstra_ctl/approval_decisions.py` `resolve_decision`) appends the disposition to the approval ledger and touches no verdict; the votes live in the plan-body state file, which only `okstra plan-items apply-verdicts` writes. An `accept-risk` that appears to have cleared a DISAGREE means the state file was edited by hand, not that the disposition did it.
|
|
490
|
-
- When a correctness-critical `planner-fixable` item is promoted, its `Statement` MUST state "planner self-fix attempted but unresolved" and name the stop reason. `
|
|
500
|
+
- When a correctness-critical `planner-fixable` item is promoted, its `Statement` MUST state "planner self-fix attempted but unresolved" and name the stop reason. `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_self_fix_before_clarification` fails when a planner-fixable majority item is promoted while the budget is not exhausted — it requires `selfFixRoundsApplied >= 1` **and** `selfFixStopReason` in `{no-progress, max-rounds-reached}`, so neither `all-resolved` nor `not-attempted` can excuse a promotion.
|
|
491
501
|
- Approval state transitions are fixed:
|
|
492
502
|
- `open → answered` when the raw user response is recorded
|
|
493
503
|
- `answered → resolved` only after the selected disposition is applied and its checks pass
|
|
@@ -495,15 +505,15 @@ For contract 3.0, `prepare` checks the selected-direction draft with the same se
|
|
|
495
505
|
- `open → obsolete` only when a plan change removes the question
|
|
496
506
|
`open` blocks until the user judges. `answered` with a proceeding disposition (`accept-risk` / `select` / `answer`) does not block. `request-revision` / `reject` still withhold the next phase until this report's `supersessionLedger` records that the answer was incorporated (`superseded` or `no-dependent-statement`). A user-directed correction does not consume the automatic self-fix limit, and a failed check does not restart the automatic loop or reopen the row.
|
|
497
507
|
- A terminal row preserves its original dissent classification only from the convergence-owned state history. Every `user-decision-required` / `user-decision-evaluated` activity cites the row's `C-NNN` in `clarificationRefs` and affected plan items in `planItemIds`. A resolved decision names only existing `A-NNN` checks. Report assembly validates those links and derives the report backtraces; it does not accept copied IDs from the approval ledger. When an independent coverage-only blocker is corrected, keep the `C-NNN` in the non-blocking Requirement Coverage row's `decisionRefs`. `obsolete` is valid only after current evidence shows that the question or blocker disappeared.
|
|
498
|
-
9. Approval lives in the report record `frontmatter.approved` field — there is no in-body marker line. The user may set it to `true` (via `--approve` or the in-session wizard) when remaining `Blocks=approval` rows are user-proceeded (`accept-risk` / `select` / `answer`) even if the recorded `gateResult` is still `blocked-by-disagreement`. `aborted-non-result` still withholds approval. **Enforced:** run-prep (`scripts/okstra_ctl/run.py` `_validate_approved_plan` / `_blocking_gate_survives_user_decision`) and `
|
|
508
|
+
9. Approval lives in the report record `frontmatter.approved` field — there is no in-body marker line. The user may set it to `true` (via `--approve` or the in-session wizard) when remaining `Blocks=approval` rows are user-proceeded (`accept-risk` / `select` / `answer`) even if the recorded `gateResult` is still `blocked-by-disagreement`. `aborted-non-result` still withholds approval. **Enforced:** run-prep (`scripts/okstra_ctl/run.py` `_validate_approved_plan` / `_blocking_gate_survives_user_decision`) and `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_plan_body_gate_recompute`.
|
|
499
509
|
|
|
500
510
|
## Worker non-result handling in plan-body round (BLOCKING)
|
|
501
511
|
|
|
502
|
-
Mirrors finding convergence (
|
|
512
|
+
Mirrors finding convergence (`prompts/lead/convergence.md` §"Worker failure handling in reverify"). Concretely:
|
|
503
513
|
|
|
504
514
|
- A dispatch that returns terminal non-result MUST NOT be aggregated as `DISAGREE`.
|
|
505
515
|
- If at least one dispatch was issued AND **all** plan-body dispatches return non-result, the Gate result is `aborted-non-result`. Record one `contract-violation` event per non-result dispatch.
|
|
506
|
-
- When the gate is `aborted-non-result`, report-writer MUST keep the frontmatter `approved: false` (publishing `approved: true` under this gate result is a validator failure). A single row is added to `## 1. Clarification Items` with `Statement="plan-body verification could not run — all workers returned non-result"`, `Kind=decision`, `Blocks=approval`, allowing the user to either retry the phase or override by running `--approve` on the resume command (or confirming in the in-session wizard). The row MUST name which dispatches returned no result and what re-running them requires. **Enforced:** `
|
|
516
|
+
- When the gate is `aborted-non-result`, report-writer MUST keep the frontmatter `approved: false` (publishing `approved: true` under this gate result is a validator failure). A single row is added to `## 1. Clarification Items` with `Statement="plan-body verification could not run — all workers returned non-result"`, `Kind=decision`, `Blocks=approval`, allowing the user to either retry the phase or override by running `--approve` on the resume command (or confirming in the in-session wizard). The row MUST name which dispatches returned no result and what re-running them requires. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_aborted_gate_has_clarification` — `_validate_plan_body_clarification_matching` cannot cover this case because it walks `majority-disagree` items and an aborted round produces none, which is exactly how an aborted run used to reach the user with no stated blocker and stall.
|
|
507
517
|
|
|
508
518
|
**Take the path from the launch prompt, never from this filename (BLOCKING).**
|
|
509
519
|
The run's `## Run Paths` block renders `Plan-body verification state:` with the
|
|
@@ -546,9 +556,9 @@ queue validation and dispatch checks continue to enforce the generated input.
|
|
|
546
556
|
| the same file's `planBodyVerification` | final state after the self-fix loop | **overwrites** — each re-verification replaces current `planItems[].verdicts` |
|
|
547
557
|
| `data.json` `implementationPlanning.planBodyVerification` | published projection | report assembly copies the validated final state once |
|
|
548
558
|
|
|
549
|
-
**The gate is computed from the nested final projection.** The top-level audit history may differ because it preserves superseded rounds. Report assembly copies the final projection rather than asking the report writer to transcribe it. **Enforced:** `
|
|
559
|
+
**The gate is computed from the nested final projection.** The top-level audit history may differ because it preserves superseded rounds. Report assembly copies the final projection rather than asking the report writer to transcribe it. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_plan_body_state_file` requires the audit keys once a round has run, and `scripts/okstra_ctl/report_assembly.py` reads only the convergence-owned `planBodyVerification` projection.
|
|
550
560
|
|
|
551
|
-
The per-round structures mirror the finding-convergence state artifact (
|
|
561
|
+
The per-round structures mirror the finding-convergence state artifact (`prompts/lead/convergence.md` §"Convergence State Artifact"): `roundHistory[]` is the round-level ledger, and each item's `rounds[]` is its per-round vote history — the same split as that file's `roundHistory[]` / `findings[].rounds[]`.
|
|
552
562
|
|
|
553
563
|
```json
|
|
554
564
|
{
|
|
@@ -632,7 +642,7 @@ The per-round structures mirror the finding-convergence state artifact ([converg
|
|
|
632
642
|
|
|
633
643
|
`planItems[].rounds[].classification` enum: `full-consensus | partial-consensus | dissent-isolated | majority-disagree | needs-reverify | contested`. `needs-reverify` is the peer-error shape from §"Round protocol" step 4 (a single-vote-blocking kind with fewer than 2 participating non-error votes) — it survives into the state file when the round budget runs out before the re-dispatch resolves it, and `_recompute_plan_body_gate` folds it into `passed-with-dissent`. `contested` only appears when `maxRounds > 1`; at default `maxRounds=1` any otherwise-unresolved item folds into `partial-consensus` per the round protocol above.
|
|
634
644
|
|
|
635
|
-
`okstra plan-verify` scores the gate in its own vocabulary, which folds two of these labels together because only the `majority-disagree` boundary moves the gate. **Do not re-derive the mapping** — the scorer emits `gate.items[].stateClassification` with the state-file value already resolved, so record that. **Enforced:** `
|
|
645
|
+
`okstra plan-verify` scores the gate in its own vocabulary, which folds two of these labels together because only the `majority-disagree` boundary moves the gate. **Do not re-derive the mapping** — the scorer emits `gate.items[].stateClassification` with the state-file value already resolved, so record that. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_state_classification`.
|
|
636
646
|
|
|
637
647
|
| `gate.items[].classification` | `planItems[].rounds[].classification` | Condition |
|
|
638
648
|
|---|---|---|
|
|
@@ -653,14 +663,14 @@ The per-round structures mirror the finding-convergence state artifact ([converg
|
|
|
653
663
|
|
|
654
664
|
**Enforced:** plan-body reverify prompts go through the same dispatch gate as finding convergence — `okstra_ctl.worker_prompt_contract.validate_reverify_prompt`, called from `dispatch_state.py`, which checks the `**Model:**` header against the dispatch model, the phase-boundary block's position, and the required output-contract block.
|
|
655
665
|
|
|
656
|
-
Required prompt anchor headers are identical to finding convergence (see
|
|
666
|
+
Required prompt anchor headers are identical to finding convergence (see `prompts/lead/convergence.md` §"Required reverify-prompt anchor headers"). The prompt body changes from F-* listing to P-* listing:
|
|
657
667
|
|
|
658
|
-
The
|
|
668
|
+
The `prompts/lead/convergence.md` §"Required reverify output contract"
|
|
659
669
|
applies unchanged: append it verbatim after the response format below.
|
|
660
670
|
|
|
661
671
|
The prompt-body contract check runs on every `analysis`-audience prompt, not
|
|
662
672
|
just the critic's, so this round needs the same two lines the critic's
|
|
663
|
-
instructions need (
|
|
673
|
+
instructions need (`prompts/lead/convergence.md` §"What the critic
|
|
664
674
|
task-instructions file MUST contain"): a `**Prompt Delivery Mode:**` header and,
|
|
665
675
|
under `## Inputs`, exactly one `- Primary analysis packet:` line whose path ends
|
|
666
676
|
in `analysis-packet.md`. The literal label and the backticks are what the check
|
|
@@ -834,7 +844,7 @@ contract violation.
|
|
|
834
844
|
|
|
835
845
|
When `config.adversarial == true`, the lead prepends the adversarial framing from §"Adversarial plan-body posture" to the `## Instructions` block: the burden of proof is on the plan, the verifier opens and confirms every accessible cited path / command, and evidence that was opened but is insufficient yields the applicable `DISAGREE(<kind>)` rather than `AGREE`. Inability to inspect because of capability, credential, network, or service state yields `UNVERIFIABLE`, not DISAGREE. The verdict tokens, breakage kinds (a–f), classification, and the majority gate threshold are unchanged. This prepended framing supersedes the template's "Judge solely from plan internal consistency" instruction for the adversarial round.
|
|
836
846
|
|
|
837
|
-
The "Reverify prompt: required-reading suppression" rule in
|
|
847
|
+
The "Reverify prompt: required-reading suppression" rule in `prompts/lead/convergence.md` (lightweight mode does NOT inject a `[Required reading]` clause) applies here as well.
|
|
838
848
|
|
|
839
849
|
## Re-verification rounds (round 2+) — carry the dissent forward
|
|
840
850
|
|
|
@@ -856,4 +866,4 @@ An item with no recorded vote carrying a round number gets no block, and an enve
|
|
|
856
866
|
|
|
857
867
|
The two spellings are different anchors for different artifacts: `**Prior round dissent**` is the block `prompt` puts in the prompt, `**Prior dissent**` is the line the worker puts in its result. `scripts/okstra_ctl/verdict_blocks.py` parses the result line into the verdict block when it is present and leaves it empty when it is not, so an omitted answer line is still silent — the prompt is what is now guaranteed, not the response.
|
|
858
868
|
|
|
859
|
-
The analyser round limit is enforced by `plan_items_cli._validate_advisory_round` at verdict application and completion. It does not limit critic correction rounds or require an even split. `apply-verdicts --append` can update the critic’s current verdict in a later round while preserving analyser votes and completed round history. Normal invocation identity and result-provenance checks still apply. `
|
|
869
|
+
The analyser round limit is enforced by `plan_items_cli._validate_advisory_round` at verdict application and completion. It does not limit critic correction rounds or require an even split. `apply-verdicts --append` can update the critic’s current verdict in a later round while preserving analyser votes and completed round history. Normal invocation identity and result-provenance checks still apply. `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_critic_gate_class` applies valid critic corrections before analyser voting rules. `tests/run/test_plan_items.py` covers second and third critic rounds on tied and majority-dissent items; `tests/contract/test_plan_body_verification.py` covers critic correction of both agreement and disagreement.
|