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
|
@@ -15,6 +15,16 @@ for _ssot_dir in (_VALIDATORS_DIR.parent / "scripts", _VALIDATORS_DIR.parent / "
|
|
|
15
15
|
sys.path.insert(0, str(_ssot_dir))
|
|
16
16
|
|
|
17
17
|
from okstra_ctl.analysis_inputs import ANALYSIS_TASK_TYPES
|
|
18
|
+
from okstra_ctl.phases.change_impact_analysis.validation import (
|
|
19
|
+
EVIDENCE_COLLECTIONS as CHANGE_IMPACT_EVIDENCE_COLLECTIONS,
|
|
20
|
+
validate_change_impact_semantics,
|
|
21
|
+
)
|
|
22
|
+
from okstra_ctl.phases.feature_analysis.validation import (
|
|
23
|
+
EVIDENCE_COLLECTIONS as FEATURE_EVIDENCE_COLLECTIONS,
|
|
24
|
+
_validate_feature_semantics,
|
|
25
|
+
)
|
|
26
|
+
from okstra_ctl.analysis_scope import _normalized_project_path, _path_is_included
|
|
27
|
+
from okstra_ctl.phases.project_analysis.validation import validate_project_semantics
|
|
18
28
|
from okstra_ctl.final_report_paths import final_report_data_path
|
|
19
29
|
from okstra_ctl.paths import RunRef
|
|
20
30
|
from okstra_ctl.report_views import analysis_review_context
|
|
@@ -35,19 +45,8 @@ _EVIDENCE_COLLECTIONS = {
|
|
|
35
45
|
"externalSystems",
|
|
36
46
|
"featureIndex",
|
|
37
47
|
),
|
|
38
|
-
"feature-analysis":
|
|
39
|
-
|
|
40
|
-
"domainRules",
|
|
41
|
-
"stateChanges",
|
|
42
|
-
"externalInteractions",
|
|
43
|
-
),
|
|
44
|
-
"change-impact-analysis": (
|
|
45
|
-
"preservedBehaviors",
|
|
46
|
-
"impactItems",
|
|
47
|
-
"dependencyBlastRadius",
|
|
48
|
-
"testImpact",
|
|
49
|
-
"operationalImpact",
|
|
50
|
-
),
|
|
48
|
+
"feature-analysis": FEATURE_EVIDENCE_COLLECTIONS,
|
|
49
|
+
"change-impact-analysis": CHANGE_IMPACT_EVIDENCE_COLLECTIONS,
|
|
51
50
|
}
|
|
52
51
|
_ANALYSIS_PARENT_KEYS = {
|
|
53
52
|
"project-analysis": "projectAnalysis",
|
|
@@ -111,33 +110,6 @@ def validate_analysis_snapshot(
|
|
|
111
110
|
)
|
|
112
111
|
|
|
113
112
|
|
|
114
|
-
def _normalized_project_path(path: object) -> PurePosixPath | None:
|
|
115
|
-
if not isinstance(path, str):
|
|
116
|
-
return None
|
|
117
|
-
raw = path.strip().rstrip("/")
|
|
118
|
-
if not raw or raw == "." or raw.startswith("/"):
|
|
119
|
-
return None
|
|
120
|
-
if any(part in {"", ".", ".."} for part in raw.split("/")):
|
|
121
|
-
return None
|
|
122
|
-
normalized = PurePosixPath(raw)
|
|
123
|
-
if normalized.is_absolute():
|
|
124
|
-
return None
|
|
125
|
-
return normalized
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def _path_is_included(path: object, included_paths: list[object]) -> bool:
|
|
129
|
-
candidate = _normalized_project_path(path)
|
|
130
|
-
if candidate is None:
|
|
131
|
-
return False
|
|
132
|
-
for raw in included_paths:
|
|
133
|
-
included = _normalized_project_path(raw)
|
|
134
|
-
if included is not None and (
|
|
135
|
-
candidate == included or included in candidate.parents
|
|
136
|
-
):
|
|
137
|
-
return True
|
|
138
|
-
return False
|
|
139
|
-
|
|
140
|
-
|
|
141
113
|
def _validate_included_paths(included_paths: list[object], errors: list[str]) -> None:
|
|
142
114
|
for index, path in enumerate(included_paths):
|
|
143
115
|
if _normalized_project_path(path) is None:
|
|
@@ -201,90 +173,6 @@ def _validate_all_current_code_evidence(
|
|
|
201
173
|
)
|
|
202
174
|
|
|
203
175
|
|
|
204
|
-
def _validate_project_semantics(data: dict, errors: list[str]) -> None:
|
|
205
|
-
common = data.get("analysisCommon") or {}
|
|
206
|
-
included = ((common.get("scope") or {}).get("includedPaths") or [])
|
|
207
|
-
analysis = data.get("projectAnalysis") or {}
|
|
208
|
-
for row in analysis.get("featureIndex") or []:
|
|
209
|
-
if not isinstance(row, dict):
|
|
210
|
-
continue
|
|
211
|
-
entry = row.get("representativeEntryPoint") or {}
|
|
212
|
-
if not _path_is_included(entry.get("path"), included):
|
|
213
|
-
errors.append(
|
|
214
|
-
f"projectAnalysis.featureIndex `{row.get('id') or '?'}` representative "
|
|
215
|
-
"entry point must be inside analysisCommon.scope.includedPaths"
|
|
216
|
-
)
|
|
217
|
-
_validate_component_references(analysis, errors)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
def _validate_component_references(analysis: dict, errors: list[str]) -> None:
|
|
221
|
-
"""Every componentId a row names has to be a component this report declares.
|
|
222
|
-
|
|
223
|
-
The reader clicks these — they render as links to the component's row — so
|
|
224
|
-
a reference to a component that was never listed is a dead anchor, and one
|
|
225
|
-
to a component that exists under a different id is worse: it reads as a
|
|
226
|
-
relationship the analysis never found.
|
|
227
|
-
"""
|
|
228
|
-
declared = {
|
|
229
|
-
row.get("id")
|
|
230
|
-
for row in analysis.get("components") or []
|
|
231
|
-
if isinstance(row, dict) and row.get("id")
|
|
232
|
-
}
|
|
233
|
-
if not declared:
|
|
234
|
-
return
|
|
235
|
-
|
|
236
|
-
def check(component_id: object, where: str) -> None:
|
|
237
|
-
if isinstance(component_id, str) and component_id and component_id not in declared:
|
|
238
|
-
errors.append(f"{where} names component `{component_id}`, which is not declared")
|
|
239
|
-
|
|
240
|
-
for row in analysis.get("internalInterfaces") or []:
|
|
241
|
-
if not isinstance(row, dict):
|
|
242
|
-
continue
|
|
243
|
-
where = f"projectAnalysis.internalInterfaces `{row.get('id') or '?'}`"
|
|
244
|
-
check(row.get("ownerComponentId"), where)
|
|
245
|
-
for consumer in row.get("consumers") or []:
|
|
246
|
-
check(consumer, where)
|
|
247
|
-
for row in analysis.get("workflows") or []:
|
|
248
|
-
if not isinstance(row, dict):
|
|
249
|
-
continue
|
|
250
|
-
for step in row.get("steps") or []:
|
|
251
|
-
if isinstance(step, dict):
|
|
252
|
-
check(step.get("componentId"), f"projectAnalysis.workflows `{row.get('id') or '?'}`")
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
def _validate_feature_semantics(data: dict, errors: list[str]) -> None:
|
|
256
|
-
analysis = data.get("featureAnalysis") or {}
|
|
257
|
-
common_target = (((data.get("analysisCommon") or {}).get("scope") or {}).get(
|
|
258
|
-
"resolvedTarget"
|
|
259
|
-
) or {})
|
|
260
|
-
feature_target = analysis.get("target") or {}
|
|
261
|
-
for field in ("inputMode", "requestedValue"):
|
|
262
|
-
if feature_target.get(field) != common_target.get(field):
|
|
263
|
-
errors.append(
|
|
264
|
-
f"featureAnalysis.target.{field} must match "
|
|
265
|
-
f"analysisCommon.scope.resolvedTarget.{field}"
|
|
266
|
-
)
|
|
267
|
-
resolved_feature = common_target.get("feature") or {}
|
|
268
|
-
if common_target.get("inputMode") == "feature-index" and (
|
|
269
|
-
feature_target.get("featureId") != resolved_feature.get("id")
|
|
270
|
-
):
|
|
271
|
-
errors.append(
|
|
272
|
-
"featureAnalysis.target.featureId must match the resolved feature id"
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def _validate_change_impact_semantics(data: dict, errors: list[str]) -> None:
|
|
277
|
-
analysis = data.get("changeImpactAnalysis") or {}
|
|
278
|
-
allowed = {"constraint", "unknown"}
|
|
279
|
-
for index, row in enumerate(analysis.get("planningInputs") or []):
|
|
280
|
-
if not isinstance(row, dict):
|
|
281
|
-
continue
|
|
282
|
-
for field in sorted(set(row) - allowed):
|
|
283
|
-
errors.append(
|
|
284
|
-
f"changeImpactAnalysis.planningInputs[{index}] forbids `{field}`; "
|
|
285
|
-
"planning inputs may contain constraints and unknowns only"
|
|
286
|
-
)
|
|
287
|
-
|
|
288
176
|
|
|
289
177
|
def _required_worker_roles(
|
|
290
178
|
run_manifest: dict, errors: list[str]
|
|
@@ -475,11 +363,11 @@ def validate_analysis_semantics(
|
|
|
475
363
|
)
|
|
476
364
|
_validate_all_current_code_evidence(task_type, data, errors)
|
|
477
365
|
if task_type == "project-analysis":
|
|
478
|
-
|
|
366
|
+
validate_project_semantics(data, errors)
|
|
479
367
|
elif task_type == "feature-analysis":
|
|
480
368
|
_validate_feature_semantics(data, errors)
|
|
481
369
|
elif task_type == "change-impact-analysis":
|
|
482
|
-
|
|
370
|
+
validate_change_impact_semantics(data, errors)
|
|
483
371
|
_validate_scope_before_dispatch(
|
|
484
372
|
data, analysis_worker_roles, reporter_confirmation, errors
|
|
485
373
|
)
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
"""Human-first implementation-planning view and approval state."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import re
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
|
|
7
|
-
from ..common import evidence_index
|
|
8
|
-
from ..models import HumanReportView, VisualEdge, VisualNode
|
|
9
|
-
from ..visualizations import stage_map_figure
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@dataclass(frozen=True)
|
|
13
|
-
class PlanApprovalState:
|
|
14
|
-
option_names: tuple[str, ...]
|
|
15
|
-
recommended_option: str
|
|
16
|
-
disabled_reason: str
|
|
17
|
-
blocker_ids: tuple[str, ...]
|
|
18
|
-
show_option_selector: bool = True
|
|
19
|
-
reentry_command: str = ""
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def resolve_recommended_option(rec_name: str, names: tuple[str, ...]) -> str:
|
|
23
|
-
if rec_name in names:
|
|
24
|
-
return rec_name
|
|
25
|
-
matches = [
|
|
26
|
-
name
|
|
27
|
-
for name in names
|
|
28
|
-
if rec_name and (name.startswith(rec_name) or rec_name.startswith(name))
|
|
29
|
-
]
|
|
30
|
-
return matches[0] if len(matches) == 1 else names[0]
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def plan_approval_state(data: dict) -> PlanApprovalState | None:
|
|
34
|
-
planning = data.get("implementationPlanning")
|
|
35
|
-
if not isinstance(planning, dict):
|
|
36
|
-
return None
|
|
37
|
-
blockers = tuple(
|
|
38
|
-
row["id"]
|
|
39
|
-
for row in data.get("clarificationItems", [])
|
|
40
|
-
if row.get("blocks") == "approval"
|
|
41
|
-
and row.get("status") in {"open", "answered"}
|
|
42
|
-
)
|
|
43
|
-
reason = f"{len(blockers)} approval blocker(s) unresolved" if blockers else ""
|
|
44
|
-
if planning.get("planningContract") == "selected-direction":
|
|
45
|
-
if planning.get("outcome") == "direction-invalidated" and not reason:
|
|
46
|
-
reason = "selected direction invalidated"
|
|
47
|
-
task_key = (data.get("header") or {}).get("taskKey", "")
|
|
48
|
-
reentry_command = (
|
|
49
|
-
f"/okstra-run task-key={task_key} "
|
|
50
|
-
"task-type=implementation-option-selection"
|
|
51
|
-
if planning.get("outcome") == "direction-invalidated" and task_key
|
|
52
|
-
else ""
|
|
53
|
-
)
|
|
54
|
-
return PlanApprovalState(
|
|
55
|
-
(), "", reason, blockers, False, reentry_command
|
|
56
|
-
)
|
|
57
|
-
names = tuple(
|
|
58
|
-
row["name"]
|
|
59
|
-
for row in planning.get("optionCandidates", [])
|
|
60
|
-
if isinstance(row, dict) and row.get("name")
|
|
61
|
-
)
|
|
62
|
-
if not names:
|
|
63
|
-
return None
|
|
64
|
-
recommended = planning.get("recommendedOption", {}).get("name", "")
|
|
65
|
-
return PlanApprovalState(
|
|
66
|
-
names,
|
|
67
|
-
resolve_recommended_option(recommended, names),
|
|
68
|
-
reason,
|
|
69
|
-
blockers,
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def _stage_figure(planning: dict):
|
|
74
|
-
nodes = tuple(
|
|
75
|
-
VisualNode(
|
|
76
|
-
f"stage-{row['stage']}",
|
|
77
|
-
f"Stage {row['stage']} · {row['title']}",
|
|
78
|
-
"stage",
|
|
79
|
-
"planned",
|
|
80
|
-
row["exitContractSummary"],
|
|
81
|
-
)
|
|
82
|
-
for row in planning["stageMap"]
|
|
83
|
-
)
|
|
84
|
-
edges = tuple(
|
|
85
|
-
VisualEdge(f"stage-{dependency}", f"stage-{row['stage']}", "precedes", "dependency")
|
|
86
|
-
for row in planning["stageMap"]
|
|
87
|
-
for dependency in re.findall(r"\d+", row["dependsOn"])
|
|
88
|
-
)
|
|
89
|
-
return stage_map_figure(nodes=nodes, edges=edges, title="Implementation stage dependencies")
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
# Record fields this template anchors as `id-<row id>` (see
|
|
93
|
-
# `HumanReportView.anchored_fields`). The implementer-facing tables the
|
|
94
|
-
# approver does not read — validation checklist, cross-project dependencies,
|
|
95
|
-
# migration risk, rollback — are not drawn, so their rows land in the ledger.
|
|
96
|
-
ANCHORED_FIELDS = (
|
|
97
|
-
"implementationPlanning.directionRealization.fileStructure",
|
|
98
|
-
"implementationPlanning.directionRealization.planningInvariants",
|
|
99
|
-
"implementationPlanning.optionCandidates.fileStructure",
|
|
100
|
-
"implementationPlanning.designPreparation.items",
|
|
101
|
-
"implementationPlanning.requirementCoverage",
|
|
102
|
-
"implementationPlanning.planBodyVerification.planItems",
|
|
103
|
-
"implementationPlanning.supersessionLedger",
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def build_implementation_planning_view(data: dict) -> HumanReportView:
|
|
108
|
-
planning = data["implementationPlanning"]
|
|
109
|
-
figure = _stage_figure(planning) if planning.get("stageMap") else None
|
|
110
|
-
approval = plan_approval_state(data)
|
|
111
|
-
activities = tuple(
|
|
112
|
-
row for row in data.get("agentActivity", []) if isinstance(row, dict)
|
|
113
|
-
)
|
|
114
|
-
decision_cards = tuple(
|
|
115
|
-
row
|
|
116
|
-
for row in data.get("clarificationItems", [])
|
|
117
|
-
if isinstance(row, dict)
|
|
118
|
-
and row.get("blocks") == "approval"
|
|
119
|
-
and isinstance(row.get("approvalContext"), dict)
|
|
120
|
-
)
|
|
121
|
-
context = {
|
|
122
|
-
"humanSummary": data["humanSummary"],
|
|
123
|
-
"planning": planning,
|
|
124
|
-
"narrative": planning.get("userNarrative", {}),
|
|
125
|
-
"stageFigure": figure,
|
|
126
|
-
"approval": approval,
|
|
127
|
-
"openDecisions": [
|
|
128
|
-
row
|
|
129
|
-
for row in data.get("clarificationItems", [])
|
|
130
|
-
if isinstance(row, dict)
|
|
131
|
-
and row.get("blocks") == "approval"
|
|
132
|
-
and row.get("status") in {"open", "answered"}
|
|
133
|
-
],
|
|
134
|
-
"agentActivities": activities,
|
|
135
|
-
"activityById": {
|
|
136
|
-
row["activityId"]: row for row in activities if row.get("activityId")
|
|
137
|
-
},
|
|
138
|
-
"decisionCards": decision_cards,
|
|
139
|
-
"evidenceIndex": evidence_index(data, ANCHORED_FIELDS),
|
|
140
|
-
}
|
|
141
|
-
return HumanReportView(
|
|
142
|
-
"implementation-planning",
|
|
143
|
-
"html/tasks/implementation-planning.template.html",
|
|
144
|
-
context,
|
|
145
|
-
(figure,) if figure is not None else (),
|
|
146
|
-
anchored_fields=ANCHORED_FIELDS,
|
|
147
|
-
)
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
"""미확정 사실의 시험 입력과 관측 결과를 연결하며 도입 승인은 만들지 않는다."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import hashlib
|
|
6
|
-
import re
|
|
7
|
-
from collections.abc import Mapping
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import Any, TypedDict
|
|
10
|
-
|
|
11
|
-
from .clarification_items import USER_INPUT_BLOCKS, progress_blocking_ids
|
|
12
|
-
from .final_report_schema import load_schema_for_data, validate
|
|
13
|
-
from .implementation_direction import validate_task_artifact_path
|
|
14
|
-
from .json_boundary import load_owned_object_snapshot, serialize_owned_object
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class VerificationFact(TypedDict):
|
|
18
|
-
id: str
|
|
19
|
-
candidateId: str
|
|
20
|
-
factIndex: int
|
|
21
|
-
fact: str
|
|
22
|
-
whyItMatters: str
|
|
23
|
-
evidence: list[str]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class VerificationInput(TypedDict):
|
|
27
|
-
sourceReport: str
|
|
28
|
-
sourceDataSha256: str
|
|
29
|
-
scope: str
|
|
30
|
-
facts: list[VerificationFact]
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class TechnicalVerificationError(ValueError):
|
|
34
|
-
"""시험 입력 또는 관측 결과가 원본 사실과 일치하지 않는다."""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def technical_verification_facts(data: Mapping[str, Any]) -> list[VerificationFact]:
|
|
38
|
-
"""안전 차단이 없는 후보의 명시적인 기술 검증 사실만 추출한다."""
|
|
39
|
-
blockers = progress_blocking_ids(
|
|
40
|
-
data.get("clarificationItems"), USER_INPUT_BLOCKS, report_data=data
|
|
41
|
-
)
|
|
42
|
-
if blockers:
|
|
43
|
-
raise TechnicalVerificationError(
|
|
44
|
-
"unresolved user decisions: " + ", ".join(blockers)
|
|
45
|
-
)
|
|
46
|
-
selection = data.get("implementationOptionSelection") or {}
|
|
47
|
-
facts: list[VerificationFact] = []
|
|
48
|
-
for candidate in selection.get("candidateAudit") or []:
|
|
49
|
-
if candidate.get("safetyBlockers"):
|
|
50
|
-
continue
|
|
51
|
-
for index, fact in enumerate(candidate.get("unresolvedFeasibilityFacts") or []):
|
|
52
|
-
if fact.get("resolutionKind") != "technical-verification":
|
|
53
|
-
continue
|
|
54
|
-
facts.append(
|
|
55
|
-
{
|
|
56
|
-
"id": f"TV-{len(facts) + 1:03d}",
|
|
57
|
-
"candidateId": candidate["id"],
|
|
58
|
-
"factIndex": index,
|
|
59
|
-
"fact": fact["fact"],
|
|
60
|
-
"whyItMatters": fact["whyItMatters"],
|
|
61
|
-
"evidence": fact["evidence"],
|
|
62
|
-
}
|
|
63
|
-
)
|
|
64
|
-
if not facts:
|
|
65
|
-
raise TechnicalVerificationError("no eligible technical-verification facts")
|
|
66
|
-
return facts
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def resolve_technical_verification_input(
|
|
70
|
-
report: Path, project_root: Path, task_root: Path, task_key: str
|
|
71
|
-
) -> VerificationInput:
|
|
72
|
-
"""같은 작업의 후보 비교 기록을 시험 입력으로 고정한다."""
|
|
73
|
-
report = report if report.is_absolute() else project_root / report
|
|
74
|
-
validate_task_artifact_path(report, task_root, "technical verification source")
|
|
75
|
-
expected_parent = task_root / "runs/implementation-option-selection/reports"
|
|
76
|
-
if report.parent != expected_parent or not re.fullmatch(
|
|
77
|
-
r"final-report-implementation-option-selection-\d{3,}\.data\.json", report.name
|
|
78
|
-
):
|
|
79
|
-
raise TechnicalVerificationError(
|
|
80
|
-
"source must be this task's option-selection record"
|
|
81
|
-
)
|
|
82
|
-
snapshot = load_owned_object_snapshot(
|
|
83
|
-
report, artifact="technical verification source"
|
|
84
|
-
)
|
|
85
|
-
data = snapshot.value
|
|
86
|
-
errors = validate(data, load_schema_for_data(data))
|
|
87
|
-
if errors:
|
|
88
|
-
raise TechnicalVerificationError("invalid source report: " + "; ".join(errors))
|
|
89
|
-
if data["header"]["taskKey"] != task_key:
|
|
90
|
-
raise TechnicalVerificationError(
|
|
91
|
-
"source taskKey does not match verification task"
|
|
92
|
-
)
|
|
93
|
-
return {
|
|
94
|
-
"sourceReport": report.relative_to(project_root).as_posix(),
|
|
95
|
-
"sourceDataSha256": hashlib.sha256(snapshot.raw_bytes).hexdigest(),
|
|
96
|
-
"scope": "technical-evidence-only",
|
|
97
|
-
"facts": technical_verification_facts(data),
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def write_technical_verification_input(
|
|
102
|
-
payload: VerificationInput, run_root: Path, seq: str
|
|
103
|
-
) -> Path:
|
|
104
|
-
"""실행 순번별 입력을 저장해 후속 실행이 이전 시험 범위를 덮지 않게 한다."""
|
|
105
|
-
path = run_root / "state" / f"technical-verification-input-{seq}.json"
|
|
106
|
-
serialized = serialize_owned_object(path, payload, artifact="technical verification input")
|
|
107
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
108
|
-
with path.open("x", encoding="utf-8") as output:
|
|
109
|
-
output.write(serialized)
|
|
110
|
-
return path
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def _check_execution_evidence(
|
|
114
|
-
check: Mapping[str, Any], project_root: Path, run_root: Path, seq: str
|
|
115
|
-
) -> list[str]:
|
|
116
|
-
"""관측 판정은 실행 기록과 실행별 시험 디렉터리를 요구한다."""
|
|
117
|
-
errors = []
|
|
118
|
-
commands = check.get("commands") or []
|
|
119
|
-
if check.get("status") != "not-run" and not commands:
|
|
120
|
-
errors.append(f"{check['id']}: an observed result requires command evidence")
|
|
121
|
-
for command in commands:
|
|
122
|
-
try:
|
|
123
|
-
log = project_root / command["logPath"]
|
|
124
|
-
experiment_root = run_root / "experiments" / seq
|
|
125
|
-
validate_task_artifact_path(log, experiment_root, "verification log")
|
|
126
|
-
if not log.read_text(encoding="utf-8").strip():
|
|
127
|
-
errors.append(f"{check['id']}: verification log is empty")
|
|
128
|
-
cwd = project_root / command["cwd"]
|
|
129
|
-
cwd.resolve(strict=True).relative_to(experiment_root.resolve(strict=True))
|
|
130
|
-
relative = cwd.relative_to(experiment_root)
|
|
131
|
-
current = run_root
|
|
132
|
-
for part in ("experiments", seq, *relative.parts):
|
|
133
|
-
current = current / part
|
|
134
|
-
if current.is_symlink():
|
|
135
|
-
raise TechnicalVerificationError(
|
|
136
|
-
"experiment cwd contains a symlink"
|
|
137
|
-
)
|
|
138
|
-
if not cwd.is_dir() or cwd.is_symlink():
|
|
139
|
-
errors.append(f"{check['id']}: experiment cwd must be a directory")
|
|
140
|
-
except (OSError, ValueError) as exc:
|
|
141
|
-
errors.append(f"{check['id']}: invalid execution evidence: {exc}")
|
|
142
|
-
if check.get("status") == "supported" and command.get("exitCode") != 0:
|
|
143
|
-
errors.append(f"{check['id']}: a supported result has a failed command")
|
|
144
|
-
return errors
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def validate_technical_verification_report(
|
|
148
|
-
data: Mapping[str, Any], report_path: Path, project_root: Path
|
|
149
|
-
) -> list[str]:
|
|
150
|
-
"""발행·최종 검증에서 같은 사실 집합과 실제 시험 산출물을 대조한다."""
|
|
151
|
-
if (data.get("header") or {}).get("taskType") != "technical-verification":
|
|
152
|
-
return []
|
|
153
|
-
match = re.fullmatch(
|
|
154
|
-
r"final-report-technical-verification-(\d{3,})\.data\.json", report_path.name
|
|
155
|
-
)
|
|
156
|
-
if match is None:
|
|
157
|
-
return ["technical verification report must use its canonical record path"]
|
|
158
|
-
run_root = report_path.parent.parent
|
|
159
|
-
path = run_root / "state" / f"technical-verification-input-{match[1]}.json"
|
|
160
|
-
try:
|
|
161
|
-
validate_task_artifact_path(path, run_root, "technical verification input")
|
|
162
|
-
source = load_owned_object_snapshot(
|
|
163
|
-
path, artifact="technical verification input"
|
|
164
|
-
).value
|
|
165
|
-
except (OSError, ValueError) as exc:
|
|
166
|
-
return [str(exc)]
|
|
167
|
-
block = data.get("technicalVerification") or {}
|
|
168
|
-
errors = []
|
|
169
|
-
for key in ("sourceReport", "sourceDataSha256", "scope"):
|
|
170
|
-
if block.get(key) != source.get(key):
|
|
171
|
-
errors.append(f"technicalVerification.{key} must match the run input")
|
|
172
|
-
expected = {fact["id"]: fact for fact in source["facts"]}
|
|
173
|
-
checks = block.get("checks") or []
|
|
174
|
-
ids = [check.get("id") for check in checks]
|
|
175
|
-
if len(ids) != len(set(ids)) or set(ids) != set(expected):
|
|
176
|
-
errors.append(
|
|
177
|
-
"technicalVerification.checks must cover each input fact exactly once"
|
|
178
|
-
)
|
|
179
|
-
for check in checks:
|
|
180
|
-
fact = expected.get(check.get("id"))
|
|
181
|
-
if fact is None:
|
|
182
|
-
continue
|
|
183
|
-
for key in ("candidateId", "factIndex", "fact"):
|
|
184
|
-
if check.get(key) != fact[key]:
|
|
185
|
-
errors.append(f"{check['id']}: {key} must preserve the input fact")
|
|
186
|
-
errors.extend(
|
|
187
|
-
_check_execution_evidence(check, project_root, run_root, match[1])
|
|
188
|
-
)
|
|
189
|
-
if (block.get("routing") or {}).get(
|
|
190
|
-
"nextTaskType"
|
|
191
|
-
) != "implementation-option-selection":
|
|
192
|
-
errors.append(
|
|
193
|
-
"technical verification returns only to implementation-option-selection"
|
|
194
|
-
)
|
|
195
|
-
return errors
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|