okstra 0.205.1 → 0.206.1
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 +2 -2
- package/dist/commands/lifecycle/install.mjs +2 -1
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/docs/architecture.md +16 -16
- package/docs/cli.md +4 -4
- package/docs/contributor-change-matrix.md +3 -2
- package/docs/performance-improvement-plan-v2.md +1 -1
- package/docs/project-structure-overview.md +45 -21
- package/package.json +2 -3
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-spawn-followups.py +2 -2
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +3 -3
- package/runtime/prompts/lead/okstra-lead-contract.md +14 -55
- package/runtime/prompts/lead/phase-routing.md +82 -0
- package/runtime/prompts/lead/report-writer.md +2 -2
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/forbidden-actions.json +0 -94
- package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +1 -1
- 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/asset_roots.py +19 -0
- package/runtime/python/okstra_ctl/consumers.py +12 -0
- package/runtime/python/okstra_ctl/contract_graph.py +75 -10
- package/runtime/python/okstra_ctl/dispatch_state.py +22 -0
- package/runtime/python/okstra_ctl/doctor.py +15 -4
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +27 -1
- 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 +9 -485
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +8 -1
- package/runtime/python/okstra_ctl/next_phase.py +2 -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 +22 -6
- package/runtime/python/okstra_ctl/phases/__init__.py +4 -0
- package/runtime/python/okstra_ctl/phases/catalog.py +260 -0
- 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/{docs/task-process/error-analysis.md → runtime/python/okstra_ctl/phases/error_analysis/spec.md} +22 -7
- 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/__init__.py +4 -0
- package/runtime/python/okstra_ctl/phases/final_verification/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/final_verification/entry.py +166 -0
- package/runtime/{prompts/profiles/final-verification.md → python/okstra_ctl/phases/final_verification/profile.md} +5 -5
- package/runtime/python/okstra_ctl/{report_html/view_models/final_verification.py → phases/final_verification/report.py} +12 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/final_verification/report_assets}/final-verification-input.template.md +1 -1
- package/{docs/task-process/final-verification.md → runtime/python/okstra_ctl/phases/final_verification/spec.md} +42 -25
- package/runtime/python/okstra_ctl/phases/final_verification/target.py +296 -0
- package/runtime/python/okstra_ctl/phases/final_verification/validation.py +190 -0
- package/runtime/python/okstra_ctl/phases/final_verification/wizard.py +38 -0
- 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 +4 -4
- 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/{docs/task-process/implementation.md → runtime/python/okstra_ctl/phases/implementation/spec.md} +20 -8
- 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} +2 -2
- package/runtime/python/okstra_ctl/{report_html/view_models/implementation_option_selection.py → phases/implementation_option_selection/report.py} +2 -2
- package/{docs/task-process/implementation-option-selection.md → runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md} +20 -7
- 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/{docs/task-process/implementation-planning.md → runtime/python/okstra_ctl/phases/implementation_planning/spec.md} +30 -6
- 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/{docs/task-process/release-handoff.md → runtime/python/okstra_ctl/phases/release_handoff/spec.md} +22 -9
- 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/{docs/task-process/requirements-discovery.md → runtime/python/okstra_ctl/phases/requirements_discovery/spec.md} +25 -6
- 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} +1 -1
- 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/profile_show.py +9 -3
- package/runtime/python/okstra_ctl/render.py +9 -2
- package/runtime/python/okstra_ctl/render_final_report.py +3 -2
- package/runtime/python/okstra_ctl/report_assembly.py +14 -93
- package/runtime/python/okstra_ctl/report_html/context_links.py +1 -1
- package/runtime/python/okstra_ctl/report_html/render.py +3 -2
- package/runtime/python/okstra_ctl/report_html/router.py +9 -33
- 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_template_loader.py +35 -0
- package/runtime/python/okstra_ctl/report_validation_identity.py +38 -0
- package/runtime/python/okstra_ctl/report_views.py +18 -2
- package/runtime/python/okstra_ctl/run.py +113 -503
- package/runtime/python/okstra_ctl/stage_map.py +13 -0
- package/runtime/python/okstra_ctl/stage_targets.py +9 -286
- package/runtime/python/okstra_ctl/technical_verification_facts.py +52 -0
- package/runtime/python/okstra_ctl/user_response.py +199 -4
- package/runtime/python/okstra_ctl/verification_target.py +1 -1
- 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/state.py +6 -2
- package/runtime/python/okstra_ctl/wizard/steps_analysis.py +0 -97
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +27 -278
- 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_prompt_contract.py +36 -0
- package/runtime/python/okstra_ctl/workflow.py +26 -143
- package/runtime/skills/okstra-brief-gen/SKILL.md +3 -3
- package/runtime/skills/okstra-run/SKILL.md +1 -1
- package/runtime/skills/okstra-user-response/SKILL.md +23 -4
- 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 +287 -4091
- package/runtime/validators/validate_analysis_report.py +14 -126
- package/docs/task-process/README.md +0 -82
- package/docs/task-process/common-flow.md +0 -173
- 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/final-verification.json → python/okstra_ctl/phases/final_verification/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/final_verification/report_assets}/final-verification.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/final_verification/report_assets}/final-verification.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
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
- state the reporter's rejection criteria, missing routing inputs, and the evidence boundary behind each recommendation
|
|
25
25
|
- Primary focus areas:
|
|
26
26
|
- classify the work as bugfix, feature, improvement, refactor, or ops
|
|
27
|
-
- determine whether `error-analysis` or `implementation-option-selection` is the next safe step. Direct `implementation-planning` or `implementation` handoff is never a valid routing target — implementation requires direction selection followed by an approved `implementation-planning` report
|
|
28
27
|
- capture the reporter's **rejection criteria** — the delivered outcome that would make this work wrong or unacceptable — as a routing input. Consume it from the brief's `Desired Outcome` / `Out of Scope` / `Source Material` when present; when it is absent AND it would change the classification (e.g. bugfix vs feature) or the next-phase choice, raise it as one `decision` clarification row with `Evidence checked: none — reporter intent`. Never infer it — this is a reporter-intent signal, the mirror of improvement-discovery's `Anti-goals`
|
|
29
28
|
- record the rejection criteria in `requirementsDiscovery.rejectionCriteria` with the
|
|
30
29
|
`source` that produced it. When it was absent and would not have changed the
|
|
@@ -41,11 +40,11 @@
|
|
|
41
40
|
- A single-item / single-domain request is not fanned out (preserving the current single-routing behaviour).
|
|
42
41
|
- When a request spans two or more domains, or has two or more independently-startable work items,
|
|
43
42
|
publish each item as a `runs/requirements-discovery/fan-out/unit-<NNN>.md` packet.
|
|
44
|
-
The packet follows the `
|
|
43
|
+
The packet follows the `scripts/okstra_ctl/phases/requirements_discovery/report_assets/fan-out-unit.template.md` format, and its frontmatter
|
|
45
44
|
`domain`(work-category 5-enum: bugfix / feature / refactor / ops / improvement),
|
|
46
45
|
`depends-on`(an inline list of unit-ids within the same fan-out `[unit-001]`, or `[]` if none),
|
|
47
46
|
`recommended-next-phase`(error-analysis | implementation-option-selection) are filled in.
|
|
48
|
-
Each packet MUST carry a non-empty `## Requirement Provenance` section whose every bullet is `brief:EB-001` / `brief:PB-001` / `brief:EO-001` — an end-state id the brief declares — or `contract:<rule>`. When the brief pins ids, citing a heading is rejected: every brief carries the same generic headings, so a heading cannot say WHICH reporter line demanded this unit, and a fan-out unit becomes the brief for a whole downstream task. Only a brief authored before the end-state sections existed still takes the older `brief:<heading>` form, and there the heading must literally exist in it. `derived:` is not admissible in a packet — cross-packet derivation cannot be resolved from a single packet, so each unit anchors directly on the brief. A unit you cannot source that way is not a work item: raise it as a clarification row instead of publishing a packet for it. **Enforced:** `
|
|
47
|
+
Each packet MUST carry a non-empty `## Requirement Provenance` section whose every bullet is `brief:EB-001` / `brief:PB-001` / `brief:EO-001` — an end-state id the brief declares — or `contract:<rule>`. When the brief pins ids, citing a heading is rejected: every brief carries the same generic headings, so a heading cannot say WHICH reporter line demanded this unit, and a fan-out unit becomes the brief for a whole downstream task. Only a brief authored before the end-state sections existed still takes the older `brief:<heading>` form, and there the heading must literally exist in it. `derived:` is not admissible in a packet — cross-packet derivation cannot be resolved from a single packet, so each unit anchors directly on the brief. A unit you cannot source that way is not a work item: raise it as a clarification row instead of publishing a packet for it. **Enforced:** `scripts/okstra_ctl/phases/requirements_discovery/validation.py` `_check_provenance`.
|
|
49
48
|
- in `runs/requirements-discovery/fan-out/index.md`, list the packets in depends-on topological order
|
|
50
49
|
as a numbered list (`1. unit-001`) (a generated view; explicitly do not hand-edit). The depends-on graph
|
|
51
50
|
must be a DAG — `validate_fanout` rejects a cycle as a validation failure, so if a cycle appears,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Human-first requirements-discovery view model."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
4
|
+
from okstra_ctl.report_html.common import evidence_index
|
|
5
|
+
from okstra_ctl.report_html.models import HumanReportView, VisualEdge, VisualNode
|
|
6
|
+
from okstra_ctl.report_html.visualizations import decision_flow_figure
|
|
7
7
|
|
|
8
8
|
# Record fields this template anchors as `id-<row id>` (see
|
|
9
9
|
# `HumanReportView.anchored_fields`); ids elsewhere land in the ledger.
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# requirements-discovery process
|
|
2
2
|
|
|
3
|
+
## Guarantees
|
|
4
|
+
|
|
5
|
+
| ID | Guarantee | Enforcement |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| RD-1 | Optional fan-out absence is accepted | `scripts/okstra_ctl/phases/requirements_discovery/validation.py::validate_fanout` |
|
|
8
|
+
| RD-2 | Each packet cites an admissible brief or contract source | `scripts/okstra_ctl/phases/requirements_discovery/validation.py::_check_provenance` |
|
|
9
|
+
| RD-3 | Dependency cycles and unknown units are rejected | `scripts/okstra_ctl/phases/requirements_discovery/fanout.py::topological_order` |
|
|
10
|
+
| RD-4 | The index lists every packet in dependency order | `scripts/okstra_ctl/phases/requirements_discovery/validation.py::validate_fanout` |
|
|
11
|
+
| RD-5 | Packet destinations use the common routing contract | `scripts/okstra_ctl/report_routing.py::fanout_routing_errors` |
|
|
12
|
+
| RD-6 | Human intent and terminology are resolved from inspected evidence | unenforced — worker instructions and lead review; no generic validator proves intent semantics |
|
|
13
|
+
|
|
3
14
|
## Index
|
|
4
15
|
|
|
5
16
|
- [1. Purpose](#1-purpose)
|
|
@@ -105,9 +116,17 @@ Non-goals are source edit, plan authoring, build, and deployment.
|
|
|
105
116
|
|
|
106
117
|
## 6. Code reviewed
|
|
107
118
|
|
|
108
|
-
- [`skills/okstra-run/SKILL.md`](
|
|
109
|
-
- [`scripts/okstra_ctl/wizard/`](../../
|
|
110
|
-
- [`scripts/okstra_ctl/run.py`](../../
|
|
111
|
-
- [`scripts/okstra_ctl/workflow.py`](../../
|
|
112
|
-
- [`
|
|
113
|
-
- [`prompts/lead/okstra-lead-contract.md`](
|
|
119
|
+
- [`skills/okstra-run/SKILL.md`](../../../../skills/okstra-run/SKILL.md)
|
|
120
|
+
- [`scripts/okstra_ctl/wizard/`](../../wizard)
|
|
121
|
+
- [`scripts/okstra_ctl/run.py`](../../run.py)
|
|
122
|
+
- [`scripts/okstra_ctl/workflow.py`](../../workflow.py)
|
|
123
|
+
- [`scripts/okstra_ctl/phases/requirements_discovery/profile.md`](profile.md)
|
|
124
|
+
- [`prompts/lead/okstra-lead-contract.md`](../../../../prompts/lead/okstra-lead-contract.md)
|
|
125
|
+
|
|
126
|
+
## Phase ownership and validation
|
|
127
|
+
|
|
128
|
+
`profile.md` owns request discovery and decomposition instructions; `profile.json` owns roles. `validation.py` owns fan-out packet identity, work category, requirement provenance, dependency and index checks. `fanout.py` orders dependencies and rejects cycles and unknown units. `report.py` and `report_assets/` own the report body and fan-out packet template.
|
|
129
|
+
|
|
130
|
+
Common scope-provenance helpers continue to define brief end-state identifiers and citation grammar. Shared schemas remain in `schemas/final-report-v2.0.schema.json` and `schemas/final-report-v3.0.schema.json` under `requirementsDiscovery`. The common lead's `prompts/lead/phase-routing.md` owns destination selection; `report_routing.py` checks packet destinations.
|
|
131
|
+
|
|
132
|
+
`tests/test_fanout.py` checks real packet files, missing and invalid provenance, dependency cycles, missing dependencies and index order. The common validator calls `validate_requirements_discovery_fanout` only for this task type, preserving the optional fan-out behavior and the `requirements-discovery:` error prefix. Source inspection and human-intent decisions remain worker instructions rather than machine-enforced semantic claims.
|
|
@@ -6,25 +6,19 @@ and a `fan-out/` directory exists under the run dir.
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
8
|
import re
|
|
9
|
-
import sys
|
|
10
9
|
from dataclasses import dataclass, field
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
from okstra_ctl.work_categories import WORK_CATEGORIES # noqa: E402
|
|
19
|
-
from okstra_ctl.fanout import topological_order, CycleError # noqa: E402
|
|
20
|
-
from okstra_ctl.scope_provenance import ( # noqa: E402
|
|
12
|
+
from okstra_ctl.report_routing import fanout_routing_errors
|
|
13
|
+
from okstra_ctl.work_categories import WORK_CATEGORIES
|
|
14
|
+
from .fanout import topological_order, CycleError
|
|
15
|
+
from okstra_ctl.scope_provenance import (
|
|
21
16
|
brief_citation_problem,
|
|
22
17
|
brief_end_state_ids,
|
|
23
18
|
brief_headings,
|
|
24
19
|
parse_source,
|
|
25
20
|
)
|
|
26
21
|
|
|
27
|
-
_NEXT_PHASES = ("error-analysis", "implementation-planning")
|
|
28
22
|
_UNIT_RE = re.compile(r"unit-\d{3}")
|
|
29
23
|
# index.md 번호목록 항목에서만 unit-NNN 을 추출 — 내러티브 문장 중복 방지
|
|
30
24
|
_INDEX_ENTRY_RE = re.compile(r"^\s*\d+\.\s+(unit-\d{3})\b", re.MULTILINE)
|
|
@@ -138,8 +132,7 @@ def validate_fanout(run_dir: Path, brief_path: Path | None = None) -> Validation
|
|
|
138
132
|
errors.append(f"{pkt.name}: unit-id {uid!r} != filename stem {pkt.stem!r}")
|
|
139
133
|
if fm.get("domain") not in WORK_CATEGORIES:
|
|
140
134
|
errors.append(f"{pkt.name}: domain {fm.get('domain')!r} not in {WORK_CATEGORIES}")
|
|
141
|
-
|
|
142
|
-
errors.append(f"{pkt.name}: recommended-next-phase not in {_NEXT_PHASES}")
|
|
135
|
+
errors.extend(fanout_routing_errors(pkt.name, fm.get("recommended-next-phase")))
|
|
143
136
|
units[pkt.stem] = _parse_deps(fm.get("depends-on", ""))
|
|
144
137
|
|
|
145
138
|
if not units:
|
|
@@ -167,3 +160,9 @@ def validate_fanout(run_dir: Path, brief_path: Path | None = None) -> Validation
|
|
|
167
160
|
errors.append(f"index.md order {listed} is not the topological order {order}")
|
|
168
161
|
|
|
169
162
|
return ValidationResult(ok=not errors, errors=errors)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def validate_requirements_discovery_fanout(run_dir, failures, brief_path=None) -> None:
|
|
166
|
+
"""단계 산출물 오류를 공통 실행 검증의 오류 목록에 같은 접두로 전달한다."""
|
|
167
|
+
result = validate_fanout(Path(run_dir), brief_path)
|
|
168
|
+
failures.extend(f"requirements-discovery: {error}" for error in result.errors)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""기술 검증 단계의 입력과 관측 증거 정책."""
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"allowed": " - falsifiable experiment plans for the frozen unresolved facts\n - dependency installation, source experiments, tests and builds only in this run's experiment copies\n - command logs, observed signals and per-fact supported/refuted/inconclusive/not-run results\n - return to implementation-option-selection with evidence, without adoption or plan approval",
|
|
3
|
+
"forbidden": [
|
|
4
|
+
"source edits or installs in the project checkout, task worktree, or another worker's experiment copy",
|
|
5
|
+
"production credentials, remote writes, deployments, migrations, publishing, commits or merging experiments into product branches",
|
|
6
|
+
"marking candidates feasible, selecting a direction, approving a plan, or declaring task acceptance",
|
|
7
|
+
"starting another lifecycle phase inside this run; return the evidence to implementation-option-selection"
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""미확정 사실의 시험 입력과 관측 결과를 연결하며 도입 승인은 만들지 않는다."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TypedDict
|
|
9
|
+
|
|
10
|
+
from okstra_ctl.final_report_schema import load_schema_for_data, validate
|
|
11
|
+
from okstra_ctl.implementation_direction import validate_task_artifact_path
|
|
12
|
+
from okstra_ctl.json_boundary import load_owned_object_snapshot, serialize_owned_object
|
|
13
|
+
from okstra_ctl.technical_verification_facts import (
|
|
14
|
+
TechnicalVerificationError,
|
|
15
|
+
VerificationFact,
|
|
16
|
+
technical_verification_facts,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class VerificationInput(TypedDict):
|
|
21
|
+
sourceReport: str
|
|
22
|
+
sourceDataSha256: str
|
|
23
|
+
scope: str
|
|
24
|
+
facts: list[VerificationFact]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def resolve_technical_verification_input(
|
|
28
|
+
report: Path, project_root: Path, task_root: Path, task_key: str
|
|
29
|
+
) -> VerificationInput:
|
|
30
|
+
"""같은 작업의 후보 비교 기록을 시험 입력으로 고정한다."""
|
|
31
|
+
report = report if report.is_absolute() else project_root / report
|
|
32
|
+
validate_task_artifact_path(report, task_root, "technical verification source")
|
|
33
|
+
expected_parent = task_root / "runs/implementation-option-selection/reports"
|
|
34
|
+
if report.parent != expected_parent or not re.fullmatch(
|
|
35
|
+
r"final-report-implementation-option-selection-\d{3,}\.data\.json", report.name
|
|
36
|
+
):
|
|
37
|
+
raise TechnicalVerificationError(
|
|
38
|
+
"source must be this task's option-selection record"
|
|
39
|
+
)
|
|
40
|
+
snapshot = load_owned_object_snapshot(
|
|
41
|
+
report, artifact="technical verification source"
|
|
42
|
+
)
|
|
43
|
+
data = snapshot.value
|
|
44
|
+
errors = validate(data, load_schema_for_data(data))
|
|
45
|
+
if errors:
|
|
46
|
+
raise TechnicalVerificationError("invalid source report: " + "; ".join(errors))
|
|
47
|
+
if data["header"]["taskKey"] != task_key:
|
|
48
|
+
raise TechnicalVerificationError(
|
|
49
|
+
"source taskKey does not match verification task"
|
|
50
|
+
)
|
|
51
|
+
return {
|
|
52
|
+
"sourceReport": report.relative_to(project_root).as_posix(),
|
|
53
|
+
"sourceDataSha256": hashlib.sha256(snapshot.raw_bytes).hexdigest(),
|
|
54
|
+
"scope": "technical-evidence-only",
|
|
55
|
+
"facts": technical_verification_facts(data),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def write_technical_verification_input(
|
|
60
|
+
payload: VerificationInput, run_root: Path, seq: str
|
|
61
|
+
) -> Path:
|
|
62
|
+
"""실행 순번별 입력을 저장해 후속 실행이 이전 시험 범위를 덮지 않게 한다."""
|
|
63
|
+
path = run_root / "state" / f"technical-verification-input-{seq}.json"
|
|
64
|
+
serialized = serialize_owned_object(path, payload, artifact="technical verification input")
|
|
65
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
66
|
+
with path.open("x", encoding="utf-8") as output:
|
|
67
|
+
output.write(serialized)
|
|
68
|
+
return path
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def prepare_technical_verification_input(
|
|
72
|
+
source_path: str, project_root: Path, task_root: Path, task_key: str
|
|
73
|
+
) -> VerificationInput:
|
|
74
|
+
"""공통 준비가 부수효과를 시작하기 전에 필수 입력과 오류 형식을 고정한다."""
|
|
75
|
+
from okstra_ctl.prepare_error import PrepareError
|
|
76
|
+
|
|
77
|
+
if not source_path:
|
|
78
|
+
raise PrepareError("technical-verification requires --clarification-response pointing to this task's option-selection record")
|
|
79
|
+
try:
|
|
80
|
+
return resolve_technical_verification_input(
|
|
81
|
+
Path(source_path), project_root, task_root, task_key,
|
|
82
|
+
)
|
|
83
|
+
except ValueError as exc:
|
|
84
|
+
raise PrepareError(str(exc)) from exc
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def materialize_technical_input_packet(payload_json: str, run_root: Path, seq: str) -> str:
|
|
88
|
+
"""입력 저장과 패킷 안내를 함께 구성해 실행 순번별 증거 위치를 보존한다."""
|
|
89
|
+
import json
|
|
90
|
+
|
|
91
|
+
verification_path = write_technical_verification_input(
|
|
92
|
+
json.loads(payload_json), run_root, seq,
|
|
93
|
+
)
|
|
94
|
+
return (
|
|
95
|
+
"\n## Technical Verification Input\n\n"
|
|
96
|
+
f"- Frozen input: `{verification_path}`\n"
|
|
97
|
+
f"- Experiment copies only: `{run_root}/experiments/{seq}/<worker-id>/`\n"
|
|
98
|
+
"- Read the input before planning experiments; copy each fact identity verbatim into results.\n"
|
|
99
|
+
+ "\n```json\n" + payload_json + "\n```\n"
|
|
100
|
+
)
|
|
@@ -22,7 +22,7 @@ Copy boundaries and pre-execution plans are worker instructions reviewed during
|
|
|
22
22
|
|
|
23
23
|
Copy `sourceReport`, `sourceDataSha256` and `scope` from the frozen input into `technicalVerification`. Emit exactly one check per fact, preserving its `id`, `candidateId`, `factIndex` and `fact`. Each check records its hypothesis, procedure, signals, command evidence, observation, status and limitations. Use project-relative evidence paths. Mark contested results inconclusive.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Do not edit the source comparison, clear unrelated uncertainties, rank candidates, approve implementation, merge code or expand the user's pilot scope.
|
|
26
26
|
|
|
27
27
|
`validate_technical_verification_report` checks frozen-input identity and evidence before publication and during run validation. The report schema restricts the routing target and requires `frontmatter.approved=false`.
|
|
28
28
|
{{INCLUDE:_coverage-critic.md}}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
from
|
|
5
|
+
from okstra_ctl.report_html.common import evidence_index
|
|
6
|
+
from okstra_ctl.report_html.models import HumanReportView
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def build_technical_verification_view(data: dict) -> HumanReportView:
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Technical verification phase
|
|
2
|
+
|
|
3
|
+
## Purpose and boundaries
|
|
4
|
+
|
|
5
|
+
This phase collects experimental evidence for unresolved technical facts. It does not rank candidates, select a direction, approve implementation or modify the source comparison. The lead chooses the continuation using `prompts/lead/phase-routing.md`.
|
|
6
|
+
|
|
7
|
+
## Input and preparation
|
|
8
|
+
|
|
9
|
+
The existing `--clarification-response` argument identifies this task's option-selection data record. `entry.prepare_technical_verification_input` checks the required argument before run creation. `entry.resolve_technical_verification_input` checks ownership, the canonical record name, schema and task key, then freezes the source-byte SHA-256 and eligible fact identities. Shared `technical_verification_facts.py` retains candidate safety and unresolved user-decision rules for both producer and consumer phases.
|
|
10
|
+
|
|
11
|
+
`entry.materialize_technical_input_packet` writes `state/technical-verification-input-<seq>.json` without replacing an existing snapshot and appends the input and experiment directory to the existing analysis packet. Artifact names, CLI inputs and preparation order are unchanged.
|
|
12
|
+
|
|
13
|
+
## Execution and output
|
|
14
|
+
|
|
15
|
+
`profile.md` owns experiment preparation, isolated source copies, command evidence, convergence and fact classification. `profile.json` owns role requirements. Worker write boundaries remain in the common invocation write contract.
|
|
16
|
+
|
|
17
|
+
The report's `technicalVerification` block preserves `sourceReport`, `sourceDataSha256`, `scope` and each fact's identity. Checks record hypothesis, procedure, confirming and rejecting signals, observations, limitations and commands. Status values are `supported`, `refuted`, `inconclusive` and `not-run`.
|
|
18
|
+
|
|
19
|
+
Report schemas remain common: `schemas/final-report-v2.0.schema.json` and `schemas/final-report-v3.0.schema.json`, including the `technicalVerification` definition and approval/continuation constraints. `report.py` and `report_assets/` own the phase body; the common report engine owns publication and page layout.
|
|
20
|
+
|
|
21
|
+
## Validation and failure behavior
|
|
22
|
+
|
|
23
|
+
`validation.validate_technical_verification_report` checks frozen-input metadata, exactly one check per input fact, unchanged fact identity and execution evidence. Observed checks need commands; supported checks cannot cite a nonzero exit code. Log and working-directory evidence stays inside the matching run's experiment sequence and rejects symlink escapes. `not-run` may omit commands.
|
|
24
|
+
|
|
25
|
+
Both `report_assembly.py` and `validators/validate-run.py` call this validation and the common `report_routing.technical_verification_routing_errors` check. Publication rejects invalid evidence before replacing the report. These checks do not prove that a declared command produced a log or sandbox arbitrary shell commands. Copy isolation and pre-execution plan quality remain worker instructions reviewed during convergence.
|
|
26
|
+
|
|
27
|
+
## Tests
|
|
28
|
+
|
|
29
|
+
`tests/test_technical_verification.py` covers input ownership, unsafe/unclassified sources, source preservation, exclusive snapshot creation, fact completeness, failed or missing commands, logs, sequence and symlink boundaries, full preparation and schema-v2/v3 report rendering. `tests/report/test_report_assembly.py::test_technical_assembly_requires_evidence_before_replacing_record` checks publication preserves an existing record when evidence fails.
|
|
30
|
+
|
|
31
|
+
## Guarantees
|
|
32
|
+
|
|
33
|
+
| ID | Guarantee | Enforcement |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| TV-1 | Report checks preserve the frozen input metadata and cover every input fact exactly once. | `scripts/okstra_ctl/phases/technical_verification/validation.py::validate_technical_verification_report` |
|
|
36
|
+
| TV-2 | Observed results require execution evidence within the matching experiment sequence. | `scripts/okstra_ctl/phases/technical_verification/validation.py::_check_execution_evidence` |
|
|
37
|
+
| TV-3 | The continuation targets implementation option selection. | `scripts/okstra_ctl/report_routing.py::technical_verification_routing_errors` |
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""관측 판정과 실행별 시험 증거의 일치 검사."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from okstra_ctl.implementation_direction import validate_task_artifact_path
|
|
10
|
+
from okstra_ctl.json_boundary import load_owned_object_snapshot
|
|
11
|
+
from okstra_ctl.technical_verification_facts import TechnicalVerificationError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _check_execution_evidence(
|
|
15
|
+
check: Mapping[str, Any], project_root: Path, run_root: Path, seq: str
|
|
16
|
+
) -> list[str]:
|
|
17
|
+
"""관측 판정은 실행 기록과 실행별 시험 디렉터리를 요구한다."""
|
|
18
|
+
errors = []
|
|
19
|
+
commands = check.get("commands") or []
|
|
20
|
+
if check.get("status") != "not-run" and not commands:
|
|
21
|
+
errors.append(f"{check['id']}: an observed result requires command evidence")
|
|
22
|
+
for command in commands:
|
|
23
|
+
try:
|
|
24
|
+
log = project_root / command["logPath"]
|
|
25
|
+
experiment_root = run_root / "experiments" / seq
|
|
26
|
+
validate_task_artifact_path(log, experiment_root, "verification log")
|
|
27
|
+
if not log.read_text(encoding="utf-8").strip():
|
|
28
|
+
errors.append(f"{check['id']}: verification log is empty")
|
|
29
|
+
cwd = project_root / command["cwd"]
|
|
30
|
+
cwd.resolve(strict=True).relative_to(experiment_root.resolve(strict=True))
|
|
31
|
+
relative = cwd.relative_to(experiment_root)
|
|
32
|
+
current = run_root
|
|
33
|
+
for part in ("experiments", seq, *relative.parts):
|
|
34
|
+
current = current / part
|
|
35
|
+
if current.is_symlink():
|
|
36
|
+
raise TechnicalVerificationError(
|
|
37
|
+
"experiment cwd contains a symlink"
|
|
38
|
+
)
|
|
39
|
+
if not cwd.is_dir() or cwd.is_symlink():
|
|
40
|
+
errors.append(f"{check['id']}: experiment cwd must be a directory")
|
|
41
|
+
except (OSError, ValueError) as exc:
|
|
42
|
+
errors.append(f"{check['id']}: invalid execution evidence: {exc}")
|
|
43
|
+
if check.get("status") == "supported" and command.get("exitCode") != 0:
|
|
44
|
+
errors.append(f"{check['id']}: a supported result has a failed command")
|
|
45
|
+
return errors
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def validate_technical_verification_report(
|
|
49
|
+
data: Mapping[str, Any], report_path: Path, project_root: Path
|
|
50
|
+
) -> list[str]:
|
|
51
|
+
"""발행·최종 검증에서 같은 사실 집합과 실제 시험 산출물을 대조한다."""
|
|
52
|
+
if (data.get("header") or {}).get("taskType") != "technical-verification":
|
|
53
|
+
return []
|
|
54
|
+
match = re.fullmatch(
|
|
55
|
+
r"final-report-technical-verification-(\d{3,})\.data\.json", report_path.name
|
|
56
|
+
)
|
|
57
|
+
if match is None:
|
|
58
|
+
return ["technical verification report must use its canonical record path"]
|
|
59
|
+
run_root = report_path.parent.parent
|
|
60
|
+
path = run_root / "state" / f"technical-verification-input-{match[1]}.json"
|
|
61
|
+
try:
|
|
62
|
+
validate_task_artifact_path(path, run_root, "technical verification input")
|
|
63
|
+
source = load_owned_object_snapshot(
|
|
64
|
+
path, artifact="technical verification input"
|
|
65
|
+
).value
|
|
66
|
+
except (OSError, ValueError) as exc:
|
|
67
|
+
return [str(exc)]
|
|
68
|
+
block = data.get("technicalVerification") or {}
|
|
69
|
+
errors = []
|
|
70
|
+
for key in ("sourceReport", "sourceDataSha256", "scope"):
|
|
71
|
+
if block.get(key) != source.get(key):
|
|
72
|
+
errors.append(f"technicalVerification.{key} must match the run input")
|
|
73
|
+
expected = {fact["id"]: fact for fact in source["facts"]}
|
|
74
|
+
checks = block.get("checks") or []
|
|
75
|
+
ids = [check.get("id") for check in checks]
|
|
76
|
+
if len(ids) != len(set(ids)) or set(ids) != set(expected):
|
|
77
|
+
errors.append(
|
|
78
|
+
"technicalVerification.checks must cover each input fact exactly once"
|
|
79
|
+
)
|
|
80
|
+
for check in checks:
|
|
81
|
+
fact = expected.get(check.get("id"))
|
|
82
|
+
if fact is None:
|
|
83
|
+
continue
|
|
84
|
+
for key in ("candidateId", "factIndex", "fact"):
|
|
85
|
+
if check.get(key) != fact[key]:
|
|
86
|
+
errors.append(f"{check['id']}: {key} must preserve the input fact")
|
|
87
|
+
errors.extend(
|
|
88
|
+
_check_execution_evidence(check, project_root, run_root, match[1])
|
|
89
|
+
)
|
|
90
|
+
return errors
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""계획 승인 상태를 보고서와 사용자 응답에 동일하게 제공한다."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Mapping, Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class PlanApprovalState:
|
|
11
|
+
option_names: tuple[str, ...]
|
|
12
|
+
recommended_option: str
|
|
13
|
+
disabled_reason: str
|
|
14
|
+
blocker_ids: tuple[str, ...]
|
|
15
|
+
show_option_selector: bool = True
|
|
16
|
+
reentry_command: str = ""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def resolve_recommended_option(rec_name: str, names: tuple[str, ...]) -> str:
|
|
20
|
+
if rec_name in names:
|
|
21
|
+
return rec_name
|
|
22
|
+
matches = [
|
|
23
|
+
name
|
|
24
|
+
for name in names
|
|
25
|
+
if rec_name and (name.startswith(rec_name) or rec_name.startswith(name))
|
|
26
|
+
]
|
|
27
|
+
return matches[0] if len(matches) == 1 else names[0]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def plan_approval_state(data: dict) -> PlanApprovalState | None:
|
|
31
|
+
planning = data.get("implementationPlanning")
|
|
32
|
+
if not isinstance(planning, dict):
|
|
33
|
+
return None
|
|
34
|
+
blockers = tuple(
|
|
35
|
+
row["id"]
|
|
36
|
+
for row in data.get("clarificationItems", [])
|
|
37
|
+
if row.get("blocks") == "approval" and row.get("status") in {"open", "answered"}
|
|
38
|
+
)
|
|
39
|
+
reason = f"{len(blockers)} approval blocker(s) unresolved" if blockers else ""
|
|
40
|
+
if planning.get("planningContract") == "selected-direction":
|
|
41
|
+
if planning.get("outcome") == "direction-invalidated" and not reason:
|
|
42
|
+
reason = "selected direction invalidated"
|
|
43
|
+
task_key = (data.get("header") or {}).get("taskKey", "")
|
|
44
|
+
reentry_command = (
|
|
45
|
+
f"/okstra-run task-key={task_key} task-type=implementation-option-selection"
|
|
46
|
+
if planning.get("outcome") == "direction-invalidated" and task_key
|
|
47
|
+
else ""
|
|
48
|
+
)
|
|
49
|
+
return PlanApprovalState((), "", reason, blockers, False, reentry_command)
|
|
50
|
+
names = tuple(
|
|
51
|
+
row["name"]
|
|
52
|
+
for row in planning.get("optionCandidates", [])
|
|
53
|
+
if isinstance(row, dict) and row.get("name")
|
|
54
|
+
)
|
|
55
|
+
if not names:
|
|
56
|
+
return None
|
|
57
|
+
recommended = planning.get("recommendedOption", {}).get("name", "")
|
|
58
|
+
return PlanApprovalState(
|
|
59
|
+
names,
|
|
60
|
+
resolve_recommended_option(recommended, names),
|
|
61
|
+
reason,
|
|
62
|
+
blockers,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def plan_is_approved(report_data: Mapping[str, Any] | None) -> bool:
|
|
67
|
+
if not isinstance(report_data, Mapping):
|
|
68
|
+
return False
|
|
69
|
+
frontmatter = report_data.get("frontmatter")
|
|
70
|
+
return isinstance(frontmatter, Mapping) and frontmatter.get("approved") is True
|