phase-loop-runtime 0.2.0__py3-none-any.whl
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.
- phase_loop_runtime/__init__.py +20 -0
- phase_loop_runtime/_contract_docs/phase-loop/extraction-readiness.md +90 -0
- phase_loop_runtime/_contract_docs/phase-loop/granular-execution-policy.md +137 -0
- phase_loop_runtime/_contract_docs/phase-loop/harness-capability-matrix.md +510 -0
- phase_loop_runtime/_contract_docs/phase-loop/harness-skill-matrix.md +125 -0
- phase_loop_runtime/_contract_docs/phase-loop/harness-substrate-manifest.md +232 -0
- phase_loop_runtime/_contract_docs/phase-loop/pi-loop-control.md +247 -0
- phase_loop_runtime/_contract_docs/phase-loop/protocol.md +2759 -0
- phase_loop_runtime/_contract_docs/phase-loop/runtime-boundary.md +289 -0
- phase_loop_runtime/_contract_docs/phase-loop/spec-discovery-roots.md +47 -0
- phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-governed-pipeline.md +42 -0
- phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-portal-projection.md +34 -0
- phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-regenesis.md +29 -0
- phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-report.md +32 -0
- phase_loop_runtime/_contract_docs/runtime/verification-evidence-contract.md +52 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/commands.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/mixed.json +5 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/timeout-note.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/redacted-summary.json +6 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/sample.json +7 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/edge.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-a.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-b.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-c.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/claimed-results.json +5 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/metrics.json +9 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/findings.md +10 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/scores.json +4 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/summary.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/bundle-check.json +8 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/operator-note.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/events-sample.jsonl +3 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/receipt.txt +1 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/evidence.json +13 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/summary.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/matrix.json +9 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/notes.md +4 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/console-summary.txt +2 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/manifest.json +16 -0
- phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/screenshot-standin.ppm +5 -0
- phase_loop_runtime/adoption_bundle.py +273 -0
- phase_loop_runtime/advisor_board/CONTRACTS.md +257 -0
- phase_loop_runtime/advisor_board/__init__.py +288 -0
- phase_loop_runtime/advisor_board/backing.py +137 -0
- phase_loop_runtime/advisor_board/backing_omnigent.py +498 -0
- phase_loop_runtime/advisor_board/config.py +225 -0
- phase_loop_runtime/advisor_board/events.py +115 -0
- phase_loop_runtime/advisor_board/fixtures/advisor-boards.example.toml +59 -0
- phase_loop_runtime/advisor_board/fixtures.py +91 -0
- phase_loop_runtime/advisor_board/harness_mapping.py +125 -0
- phase_loop_runtime/advisor_board/matrix.py +146 -0
- phase_loop_runtime/advisor_board/observability.py +481 -0
- phase_loop_runtime/advisor_board/presets.py +188 -0
- phase_loop_runtime/advisor_board/registries.py +309 -0
- phase_loop_runtime/advisor_board/resolver.py +253 -0
- phase_loop_runtime/advisor_board/schema.py +269 -0
- phase_loop_runtime/advisor_board/standin.py +166 -0
- phase_loop_runtime/advisor_board/validation.py +159 -0
- phase_loop_runtime/agent_runtime_provider.py +676 -0
- phase_loop_runtime/baml_modular.py +656 -0
- phase_loop_runtime/baml_src/dotfiles_adoption_manifest.baml +37 -0
- phase_loop_runtime/baml_src/dotfiles_c4_document.baml +14 -0
- phase_loop_runtime/baml_src/dotfiles_plan_manifest.baml +50 -0
- phase_loop_runtime/baml_src/dotfiles_runtime_projection.baml +16 -0
- phase_loop_runtime/baml_src/dotfiles_task_catalog.baml +20 -0
- phase_loop_runtime/baml_src/emit_phase_closeout.baml +86 -0
- phase_loop_runtime/baml_src/evaluate_suspected_fake_evidence.baml +36 -0
- phase_loop_runtime/baml_src/verification_evidence.baml +40 -0
- phase_loop_runtime/broker.py +181 -0
- phase_loop_runtime/build_bundle.py +294 -0
- phase_loop_runtime/capability_registry.py +835 -0
- phase_loop_runtime/classifier.py +87 -0
- phase_loop_runtime/claude_agent_view.py +625 -0
- phase_loop_runtime/claude_channel_sidecar.py +1086 -0
- phase_loop_runtime/cli.py +2599 -0
- phase_loop_runtime/closeout.py +865 -0
- phase_loop_runtime/closeout_classifier.py +120 -0
- phase_loop_runtime/closeout_evidence_audit.py +112 -0
- phase_loop_runtime/closeout_validation.py +144 -0
- phase_loop_runtime/closeout_validators.py +203 -0
- phase_loop_runtime/consiliency_gates.py +374 -0
- phase_loop_runtime/consiliency_ingest.py +399 -0
- phase_loop_runtime/consiliency_layout.py +176 -0
- phase_loop_runtime/consiliency_scaffold.py +305 -0
- phase_loop_runtime/cross_repo_channel.py +331 -0
- phase_loop_runtime/discovery.py +2368 -0
- phase_loop_runtime/dispatch_lock.py +167 -0
- phase_loop_runtime/doc_delta_validator.py +98 -0
- phase_loop_runtime/docs_audit.py +285 -0
- phase_loop_runtime/docs_freshness.py +468 -0
- phase_loop_runtime/docs_surfaces.py +150 -0
- phase_loop_runtime/dotfiles_profile_plugin.py +96 -0
- phase_loop_runtime/events.py +154 -0
- phase_loop_runtime/events_migration.py +96 -0
- phase_loop_runtime/evidence_audit.py +1082 -0
- phase_loop_runtime/evidence_audit_config.py +172 -0
- phase_loop_runtime/fleet_map.py +426 -0
- phase_loop_runtime/fleet_metrics.py +298 -0
- phase_loop_runtime/fleet_metrics_export.py +196 -0
- phase_loop_runtime/git_discipline.py +479 -0
- phase_loop_runtime/git_ops.py +122 -0
- phase_loop_runtime/git_topology.py +252 -0
- phase_loop_runtime/governed_bundle.py +137 -0
- phase_loop_runtime/governed_premerge.py +219 -0
- phase_loop_runtime/governed_review.py +264 -0
- phase_loop_runtime/handoff.py +932 -0
- phase_loop_runtime/handoff_path.py +14 -0
- phase_loop_runtime/injection.py +740 -0
- phase_loop_runtime/install_status.py +88 -0
- phase_loop_runtime/lane_scheduler.py +317 -0
- phase_loop_runtime/launcher.py +2760 -0
- phase_loop_runtime/lease_store.py +553 -0
- phase_loop_runtime/maintenance.py +401 -0
- phase_loop_runtime/migrate_handoffs.py +201 -0
- phase_loop_runtime/models.py +2144 -0
- phase_loop_runtime/observability.py +1290 -0
- phase_loop_runtime/panel_invoker.py +1638 -0
- phase_loop_runtime/phase_loop_drift_audit.py +388 -0
- phase_loop_runtime/phase_worktree_executor.py +433 -0
- phase_loop_runtime/pipeline_adapter/__init__.py +12 -0
- phase_loop_runtime/pipeline_adapter/branch_ops.py +274 -0
- phase_loop_runtime/pipeline_adapter/flag.py +55 -0
- phase_loop_runtime/pipeline_adapter/markers.py +13 -0
- phase_loop_runtime/pipeline_adapter/merge_policy.py +72 -0
- phase_loop_runtime/pipeline_adapter/ratification.py +100 -0
- phase_loop_runtime/pipeline_adapter/sibling_matcher.py +123 -0
- phase_loop_runtime/plan_ir.py +523 -0
- phase_loop_runtime/plan_manifest.py +415 -0
- phase_loop_runtime/planner_validation.py +176 -0
- phase_loop_runtime/profiles.py +462 -0
- phase_loop_runtime/prompts.py +365 -0
- phase_loop_runtime/provenance.py +165 -0
- phase_loop_runtime/publishing.py +339 -0
- phase_loop_runtime/py.typed +1 -0
- phase_loop_runtime/reconcile.py +1550 -0
- phase_loop_runtime/redaction.py +184 -0
- phase_loop_runtime/reflection_sync.py +293 -0
- phase_loop_runtime/release_guard.py +186 -0
- phase_loop_runtime/render.py +432 -0
- phase_loop_runtime/repo_validation.py +427 -0
- phase_loop_runtime/review_summary.py +150 -0
- phase_loop_runtime/roadmap_lint.py +425 -0
- phase_loop_runtime/route_log.py +59 -0
- phase_loop_runtime/route_policy/__init__.py +11 -0
- phase_loop_runtime/route_policy/fixtures/route_selection.golden.json +263 -0
- phase_loop_runtime/runner.py +8400 -0
- phase_loop_runtime/runtime_paths.py +225 -0
- phase_loop_runtime/runtime_projection.py +113 -0
- phase_loop_runtime/runtime_resources.py +36 -0
- phase_loop_runtime/schema_export.py +292 -0
- phase_loop_runtime/schemas/phase_loop_closeout.schema.json +366 -0
- phase_loop_runtime/skill_install.py +176 -0
- phase_loop_runtime/skill_inventory.py +662 -0
- phase_loop_runtime/skill_paths.py +42 -0
- phase_loop_runtime/skill_sources_plugin.py +37 -0
- phase_loop_runtime/skills_bundle/claude-advisor-board/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-advisor-board/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/claude-advisor-panel/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-advisor-panel/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/claude-execute-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-execute-detailed/SKILL.md +152 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/SKILL.md +745 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/allocate_worktree_name.sh +33 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/audit_lane_file_touches.py +197 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/state.py +274 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/team_teardown.sh +32 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/validate_plan_doc.py +642 -0
- phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/verify_harness.sh +96 -0
- phase_loop_runtime/skills_bundle/claude-phase-loop/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-phase-loop/SKILL.md +115 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/SKILL.md +439 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/assets/review_prompt.md +19 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/roadmap-template.md +193 -0
- phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
- phase_loop_runtime/skills_bundle/claude-plan-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-plan-detailed/SKILL.md +334 -0
- phase_loop_runtime/skills_bundle/claude-plan-detailed/assets/review_prompt.md +25 -0
- phase_loop_runtime/skills_bundle/claude-plan-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-plan-phase/SKILL.md +776 -0
- phase_loop_runtime/skills_bundle/claude-plan-phase/assets/review_prompt.md +17 -0
- phase_loop_runtime/skills_bundle/claude-plan-phase/scripts/validate_plan_doc.py +1092 -0
- phase_loop_runtime/skills_bundle/claude-run-train/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-run-train/SKILL.md +75 -0
- phase_loop_runtime/skills_bundle/claude-skill-editor/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-skill-editor/SKILL.md +243 -0
- phase_loop_runtime/skills_bundle/claude-skill-editor/assets/editor_prompt.md +72 -0
- phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/SKILL.md +249 -0
- phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
- phase_loop_runtime/skills_bundle/claude-task-contextualizer/README.md +1 -0
- phase_loop_runtime/skills_bundle/claude-task-contextualizer/SKILL.md +74 -0
- phase_loop_runtime/skills_bundle/claude-task-contextualizer/references/task-templates.md +103 -0
- phase_loop_runtime/skills_bundle/codex-advisor-board/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-advisor-board/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/codex-advisor-panel/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-advisor-panel/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/codex-execute-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-execute-detailed/SKILL.md +154 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/SKILL.md +231 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/allocate_worktree_name.sh +33 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/audit_lane_file_touches.py +197 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/state.py +274 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/team_teardown.sh +32 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/validate_plan_doc.py +642 -0
- phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/verify_harness.sh +96 -0
- phase_loop_runtime/skills_bundle/codex-phase-loop/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-phase-loop/SKILL.md +248 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/SKILL.md +150 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/assets/review_prompt.md +19 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/roadmap-template.md +193 -0
- phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
- phase_loop_runtime/skills_bundle/codex-plan-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-plan-detailed/SKILL.md +119 -0
- phase_loop_runtime/skills_bundle/codex-plan-detailed/assets/review_prompt.md +25 -0
- phase_loop_runtime/skills_bundle/codex-plan-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-plan-phase/SKILL.md +307 -0
- phase_loop_runtime/skills_bundle/codex-plan-phase/assets/review_prompt.md +17 -0
- phase_loop_runtime/skills_bundle/codex-plan-phase/scripts/validate_plan_doc.py +1092 -0
- phase_loop_runtime/skills_bundle/codex-run-train/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-run-train/SKILL.md +75 -0
- phase_loop_runtime/skills_bundle/codex-skill-editor/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-skill-editor/SKILL.md +94 -0
- phase_loop_runtime/skills_bundle/codex-skill-editor/assets/editor_prompt.md +72 -0
- phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/SKILL.md +114 -0
- phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
- phase_loop_runtime/skills_bundle/codex-task-contextualizer/README.md +1 -0
- phase_loop_runtime/skills_bundle/codex-task-contextualizer/SKILL.md +102 -0
- phase_loop_runtime/skills_bundle/codex-task-contextualizer/references/task-templates.md +103 -0
- phase_loop_runtime/skills_bundle/gemini-advisor-board/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-advisor-board/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/gemini-advisor-panel/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-advisor-panel/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/gemini-execute-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-execute-detailed/SKILL.md +144 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/SKILL.md +198 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/allocate_worktree_name.sh +33 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/audit_lane_file_touches.py +197 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/state.py +274 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/team_teardown.sh +32 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/validate_plan_doc.py +642 -0
- phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/verify_harness.sh +96 -0
- phase_loop_runtime/skills_bundle/gemini-phase-loop/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-phase-loop/SKILL.md +111 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/SKILL.md +147 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/assets/review_prompt.md +19 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/roadmap-template.md +193 -0
- phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
- phase_loop_runtime/skills_bundle/gemini-plan-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-plan-detailed/SKILL.md +119 -0
- phase_loop_runtime/skills_bundle/gemini-plan-detailed/assets/review_prompt.md +25 -0
- phase_loop_runtime/skills_bundle/gemini-plan-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-plan-phase/SKILL.md +217 -0
- phase_loop_runtime/skills_bundle/gemini-plan-phase/assets/review_prompt.md +17 -0
- phase_loop_runtime/skills_bundle/gemini-plan-phase/scripts/validate_plan_doc.py +1092 -0
- phase_loop_runtime/skills_bundle/gemini-run-train/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-run-train/SKILL.md +75 -0
- phase_loop_runtime/skills_bundle/gemini-skill-editor/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-skill-editor/SKILL.md +87 -0
- phase_loop_runtime/skills_bundle/gemini-skill-editor/assets/editor_prompt.md +72 -0
- phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/SKILL.md +109 -0
- phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
- phase_loop_runtime/skills_bundle/gemini-task-contextualizer/README.md +1 -0
- phase_loop_runtime/skills_bundle/gemini-task-contextualizer/SKILL.md +98 -0
- phase_loop_runtime/skills_bundle/gemini-task-contextualizer/references/task-templates.md +103 -0
- phase_loop_runtime/skills_bundle/opencode-advisor-board/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-advisor-board/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/opencode-advisor-panel/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-advisor-panel/SKILL.md +38 -0
- phase_loop_runtime/skills_bundle/opencode-execute-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-execute-detailed/SKILL.md +144 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/SKILL.md +196 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/allocate_worktree_name.sh +33 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/audit_lane_file_touches.py +197 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/state.py +274 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/team_teardown.sh +32 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/validate_plan_doc.py +642 -0
- phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/verify_harness.sh +96 -0
- phase_loop_runtime/skills_bundle/opencode-phase-loop/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-phase-loop/SKILL.md +113 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/SKILL.md +147 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/assets/review_prompt.md +19 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/roadmap-template.md +193 -0
- phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
- phase_loop_runtime/skills_bundle/opencode-plan-detailed/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-plan-detailed/SKILL.md +119 -0
- phase_loop_runtime/skills_bundle/opencode-plan-detailed/assets/review_prompt.md +25 -0
- phase_loop_runtime/skills_bundle/opencode-plan-phase/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-plan-phase/SKILL.md +211 -0
- phase_loop_runtime/skills_bundle/opencode-plan-phase/assets/review_prompt.md +17 -0
- phase_loop_runtime/skills_bundle/opencode-plan-phase/scripts/validate_plan_doc.py +1092 -0
- phase_loop_runtime/skills_bundle/opencode-run-train/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-run-train/SKILL.md +75 -0
- phase_loop_runtime/skills_bundle/opencode-skill-editor/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-skill-editor/SKILL.md +87 -0
- phase_loop_runtime/skills_bundle/opencode-skill-editor/assets/editor_prompt.md +72 -0
- phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/SKILL.md +109 -0
- phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
- phase_loop_runtime/skills_bundle/opencode-task-contextualizer/README.md +1 -0
- phase_loop_runtime/skills_bundle/opencode-task-contextualizer/SKILL.md +98 -0
- phase_loop_runtime/skills_bundle/opencode-task-contextualizer/references/task-templates.md +103 -0
- phase_loop_runtime/state.py +156 -0
- phase_loop_runtime/state_degradation.py +136 -0
- phase_loop_runtime/state_ops.py +676 -0
- phase_loop_runtime/train_ledger.py +215 -0
- phase_loop_runtime/train_roadmap.py +339 -0
- phase_loop_runtime/train_runner.py +1197 -0
- phase_loop_runtime/verification_evidence.py +650 -0
- phase_loop_runtime/verification_evidence_validator.py +75 -0
- phase_loop_runtime/visual_evidence_validator.py +36 -0
- phase_loop_runtime/worker_pool.py +123 -0
- phase_loop_runtime/worktree_index.py +270 -0
- phase_loop_runtime-0.2.0.data/data/share/phase-loop-runtime/protocol/protocol.md +2475 -0
- phase_loop_runtime-0.2.0.dist-info/METADATA +106 -0
- phase_loop_runtime-0.2.0.dist-info/RECORD +370 -0
- phase_loop_runtime-0.2.0.dist-info/WHEEL +5 -0
- phase_loop_runtime-0.2.0.dist-info/entry_points.txt +9 -0
- phase_loop_runtime-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,2759 @@
|
|
|
1
|
+
# Phase-Loop Protocol
|
|
2
|
+
|
|
3
|
+
This document is the canonical shared output contract for harness-neutral
|
|
4
|
+
phase-loop artifacts. It freezes the artifact shapes, failure vocabulary, and
|
|
5
|
+
promotion semantics that every executor, adapter, and manual/operator flow must
|
|
6
|
+
produce or consume during the current phase-loop roadmap family.
|
|
7
|
+
Harness-specific launch mechanics, runtime paths, and private skill install
|
|
8
|
+
details stay in harness-local runtime docs.
|
|
9
|
+
Harness-specific path ownership for the dotfiles-hosted substrate is recorded
|
|
10
|
+
in `docs/phase-loop/harness-substrate-manifest.md`; this protocol remains the
|
|
11
|
+
schema and artifact contract.
|
|
12
|
+
IF-0-SUBSTRATE-1 limits downstream substrate citations to runtime code, CLI
|
|
13
|
+
wrappers, bridge skills, shared runner skills, protocol docs, fixtures, tests,
|
|
14
|
+
scripts, and canonical `.phase-loop/**` state; broader dotfiles checkout
|
|
15
|
+
contents are not client dependencies.
|
|
16
|
+
Harness workflow skill naming is recorded in
|
|
17
|
+
`docs/phase-loop/harness-skill-matrix.md`; that matrix freezes the
|
|
18
|
+
`<harness>-<workflow>` contract, Pi Agent role-style exceptions, direct route
|
|
19
|
+
compatibility, and the governed-pipeline `.pipeline/skills/**` namespace guard.
|
|
20
|
+
|
|
21
|
+
## Plan Frontmatter
|
|
22
|
+
|
|
23
|
+
Execution-ready phase plans remain compatible with the existing
|
|
24
|
+
`phase_loop_plan_version: 1` contract:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
---
|
|
28
|
+
phase_loop_plan_version: 1
|
|
29
|
+
phase: <PHASE_ALIAS>
|
|
30
|
+
roadmap: <repo-relative roadmap path>
|
|
31
|
+
roadmap_sha256: <sha256 of roadmap file bytes at planning time>
|
|
32
|
+
phase_loop_mutation: <optional release_dispatch>
|
|
33
|
+
release_base_ref: <optional base ref for release dispatch, default origin/main>
|
|
34
|
+
source_bundle: <optional repo-relative Pipeline source bundle path>
|
|
35
|
+
source_bundle_sha256: <optional sha256 of Pipeline source bundle bytes>
|
|
36
|
+
pipeline_phase_id: <optional Pipeline phase id>
|
|
37
|
+
pipeline_mode: <optional standalone|pipeline_optional|pipeline_required>
|
|
38
|
+
---
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Required fields:
|
|
42
|
+
|
|
43
|
+
- `phase_loop_plan_version`
|
|
44
|
+
- `phase`
|
|
45
|
+
- `roadmap`
|
|
46
|
+
- `roadmap_sha256`
|
|
47
|
+
|
|
48
|
+
Optional fields:
|
|
49
|
+
|
|
50
|
+
- `phase_loop_mutation`
|
|
51
|
+
- `release_base_ref`
|
|
52
|
+
- `source_bundle`
|
|
53
|
+
- `source_bundle_sha256`
|
|
54
|
+
- `pipeline_phase_id`
|
|
55
|
+
- `pipeline_mode`
|
|
56
|
+
|
|
57
|
+
`PIPELINE_PLAN_FRONTMATTER_CONTRACT` is additive. Pipeline-aware metadata is additive
|
|
58
|
+
metadata, not a replacement for `phase`, `roadmap`, or `roadmap_sha256`.
|
|
59
|
+
Existing standalone plans with only `phase_loop_plan_version: 1`,
|
|
60
|
+
`phase`, `roadmap`, and `roadmap_sha256` remain valid when no Pipeline mode
|
|
61
|
+
requires bundle metadata.
|
|
62
|
+
|
|
63
|
+
The supported `pipeline_mode` literals are:
|
|
64
|
+
|
|
65
|
+
- `standalone`
|
|
66
|
+
- `pipeline_optional`
|
|
67
|
+
- `pipeline_required`
|
|
68
|
+
|
|
69
|
+
Pipeline metadata is required only when `pipeline_mode` is `pipeline_required`.
|
|
70
|
+
A pipeline-required plan missing `source_bundle` or `source_bundle_sha256`, or
|
|
71
|
+
whose `source_bundle_sha256` no longer matches the source bundle bytes, is a
|
|
72
|
+
typed repairable non-human diagnostic rather than partial execution readiness.
|
|
73
|
+
Unknown `pipeline_mode` values fail closed as typed metadata diagnostics.
|
|
74
|
+
|
|
75
|
+
Plans without this metadata, or with metadata that no longer matches the
|
|
76
|
+
selected roadmap, are stale for autonomous execution and must route back
|
|
77
|
+
through planning instead of execution.
|
|
78
|
+
|
|
79
|
+
## Spec Delta Closeout
|
|
80
|
+
|
|
81
|
+
`spec_delta_closeout.v1` is the metadata-only decision record used when a phase
|
|
82
|
+
may change reusable harness specs, runtime contracts, roadmap/plan templates, or
|
|
83
|
+
workflow skills. Roadmap builders name the expected policy for each phase,
|
|
84
|
+
phase plans copy that policy into a machine-readable `Spec Closeout Plan`,
|
|
85
|
+
execute-phase closeouts choose exactly one decision, and phase-loop handoffs
|
|
86
|
+
preserve the same decision for downstream routing.
|
|
87
|
+
|
|
88
|
+
Required fields:
|
|
89
|
+
|
|
90
|
+
- `schema`: `spec_delta_closeout.v1`
|
|
91
|
+
- `decision`: one of `no_spec_delta`, `roadmap_amendment`,
|
|
92
|
+
`canonical_spec_update`, `governed_pipeline_refresh`,
|
|
93
|
+
`mirror_cutover_required`, `dotfiles_skill_source_update`, or
|
|
94
|
+
`human_source_judgment_required`
|
|
95
|
+
- `target_surfaces`: repo-relative paths or globs describing the spec surfaces
|
|
96
|
+
affected or intentionally deferred
|
|
97
|
+
- `evidence_paths`: repo-relative metadata evidence, such as a phase plan,
|
|
98
|
+
lane closeout, verification log, or runner artifact
|
|
99
|
+
- `redaction_posture`: `metadata_only`
|
|
100
|
+
- `blocker_class`: optional frozen blocker class when the decision cannot be
|
|
101
|
+
completed automatically
|
|
102
|
+
|
|
103
|
+
The frozen target-surface vocabulary for dotfiles-hosted harness spec work is
|
|
104
|
+
`shared/phase-loop/protocol.md`, `vendor/phase-loop-runtime/protocol/protocol.md`,
|
|
105
|
+
`vendor/phase-loop-runtime/src/phase_loop_runtime/baml_src/emit_phase_closeout.baml`,
|
|
106
|
+
`codex-config/skills/**`, `claude-config/claude-skills/**`,
|
|
107
|
+
`gemini-config/skills/**`, `opencode-config/skills/**`,
|
|
108
|
+
`vendor/phase-loop-skills/**`, and `vendor/phase-loop-runtime/tests/**`.
|
|
109
|
+
|
|
110
|
+
`metadata_only` allows repo-relative paths, hashes, phase aliases, IF gates,
|
|
111
|
+
decision literals, artifact names, and redacted diagnostics. It forbids raw
|
|
112
|
+
specs, raw patch bodies, credentials, provider-supplied payloads, local environment values, and
|
|
113
|
+
inferred reads from ignored/private/raw evidence sources. Standalone dotfiles
|
|
114
|
+
phase-loop runs may emit `no_spec_delta` or `dotfiles_skill_source_update`
|
|
115
|
+
without Governed Pipeline, `.pipeline/**`, Portal contracts, ReGenesis files,
|
|
116
|
+
credentials, PMCP, owner shell setup, or sibling repo writes. Decisions that
|
|
117
|
+
require Governed Pipeline ingest, mirror cutover, or human source judgment are
|
|
118
|
+
metadata-only downstream routing signals, not dotfiles write authorization.
|
|
119
|
+
|
|
120
|
+
Missing or malformed spec-delta closeout evidence is a repairable automation
|
|
121
|
+
blocker with `blocker_class=contract_bug` unless the phase explicitly chooses
|
|
122
|
+
`human_source_judgment_required`, in which case the closeout must name the
|
|
123
|
+
non-secret human input required.
|
|
124
|
+
|
|
125
|
+
## Plan-Doc-Current Heuristic
|
|
126
|
+
|
|
127
|
+
When a phase has been reopened to `planned`, the runner checks the current plan
|
|
128
|
+
document before dispatching another planning turn. The helper
|
|
129
|
+
`is_plan_doc_current(repo, phase, plan, roadmap, recent_commit_window=50)` is
|
|
130
|
+
true only after `find_plan_artifact(repo, phase, roadmap=roadmap)` returns the
|
|
131
|
+
same roadmap-matching plan path and the plan either appears in
|
|
132
|
+
`git log --name-only -n 50 -- <plan>` or its frontmatter includes a non-empty
|
|
133
|
+
`last_generated` field.
|
|
134
|
+
|
|
135
|
+
For `phase-loop run`, `phase-loop resume`, and `phase-loop dry-run`, a reopened
|
|
136
|
+
`planned` phase with a current plan document dispatches execution directly by
|
|
137
|
+
default. Operators can pass `--force-replan` to bypass this heuristic and launch
|
|
138
|
+
the planning turn even when the plan document is current.
|
|
139
|
+
|
|
140
|
+
The skip is auditable but non-terminal. Before launching execution, the runner
|
|
141
|
+
appends an event with `status: plan_skipped` and
|
|
142
|
+
`metadata.plan_doc_skip` containing `reason: plan_doc_current`, the
|
|
143
|
+
repo-relative `plan_artifact`, and `forced_replan: false`.
|
|
144
|
+
`plan_skipped` is valid only through `EVENT_STATUSES`; it must never be added
|
|
145
|
+
to `PHASE_STATUSES`, `automation.status`, closeout `terminal_status`, or
|
|
146
|
+
reducer phase-state outputs.
|
|
147
|
+
|
|
148
|
+
## Run Loop Mode
|
|
149
|
+
|
|
150
|
+
`phase-loop run` and `phase-loop resume` count `--max-phases N` as dispatched actions by default.
|
|
151
|
+
This preserves the legacy behavior: `--max-phases 1`
|
|
152
|
+
launches exactly one child action, so an unplanned phase launches planning and
|
|
153
|
+
stops before the later execute action.
|
|
154
|
+
|
|
155
|
+
`--full-phase` changes the counting unit from dispatched actions to complete phase cycles.
|
|
156
|
+
In full-phase mode, `--max-phases 1 --full-phase` keeps dispatching
|
|
157
|
+
within the selected phase until one plan-plus-execute cycle reaches `complete`,
|
|
158
|
+
`blocked`, `awaiting_phase_closeout`, or another no-launch terminal state. A
|
|
159
|
+
phase that already has a current plan can skip planning through the
|
|
160
|
+
`plan_skipped` path and count the following execute launch as one complete phase
|
|
161
|
+
cycle. `--max-phases 2 --full-phase` completes two such phase cycles when the
|
|
162
|
+
roadmap has two ready phases.
|
|
163
|
+
|
|
164
|
+
Existing operators who rely on action-count behavior should keep using
|
|
165
|
+
`--max-phases` without `--full-phase`. Operators who intend complete phase
|
|
166
|
+
cycles should add `--full-phase`. When `--max-phases` is explicitly supplied
|
|
167
|
+
without `--full-phase`, the runner may emit a non-blocking legacy-unit hint once
|
|
168
|
+
per run session. `--no-deprecation-hints` suppresses that hint for `run` and
|
|
169
|
+
`resume`.
|
|
170
|
+
|
|
171
|
+
`PIPELINE_PROTECTED_SOURCE_CATEGORIES` freezes the protected source vocabulary
|
|
172
|
+
for Pipeline bridge work:
|
|
173
|
+
|
|
174
|
+
- `specs` - Pipeline specs.
|
|
175
|
+
- `diagrams` - Pipeline diagrams.
|
|
176
|
+
- `adapter_config` - Pipeline adapter config.
|
|
177
|
+
- `definition_files` - Pipeline definition files, including
|
|
178
|
+
`pipeline.definition.json`.
|
|
179
|
+
- `portal_contracts` - Portal contracts.
|
|
180
|
+
- `phase_artifacts` - Pipeline phase artifacts.
|
|
181
|
+
|
|
182
|
+
Protected-source categories remain high-level. Canonical spec adoption semantics
|
|
183
|
+
are expressed through optional protected-source role metadata, not by adding
|
|
184
|
+
new categories or making dotfiles the adoption authority.
|
|
185
|
+
`PIPELINE_PROTECTED_SOURCE_ROLES` freezes the adoption-sensitive role
|
|
186
|
+
vocabulary that source bundles may attach to protected-source entries:
|
|
187
|
+
|
|
188
|
+
- `seed_spec`
|
|
189
|
+
- `predecessor_spec`
|
|
190
|
+
- `active_canonical_spec`
|
|
191
|
+
- `archived_spec`
|
|
192
|
+
- `managed_mirror_file`
|
|
193
|
+
- `unmanaged_spec_input`
|
|
194
|
+
- `legacy_specs_bundle`
|
|
195
|
+
- `root_specs_intake`
|
|
196
|
+
- `pipeline_specs_canonical`
|
|
197
|
+
- `adapter_configured_intake_root`
|
|
198
|
+
- `mirror_manifest`
|
|
199
|
+
- `archive_manifest`
|
|
200
|
+
|
|
201
|
+
The upstream canonical adoption config keys are metadata-only bridge terms:
|
|
202
|
+
`canonical_sources.adoption_mode`,
|
|
203
|
+
`canonical_sources.active_canonical_root`, `canonical_sources.mirror_root`,
|
|
204
|
+
`canonical_sources.archive_manifest_path`,
|
|
205
|
+
`canonical_sources.spec_intake_roots`, and
|
|
206
|
+
`canonical_sources.legacy_seed_roots`. The adoption mode literals are
|
|
207
|
+
`track_existing`, `greenfield_single_spec`, `greenfield_spec_bundle`, and
|
|
208
|
+
`brownfield_existing_specs`.
|
|
209
|
+
|
|
210
|
+
Standalone `phase_loop_plan_version: 1` plans remain valid without Pipeline or
|
|
211
|
+
canonical adoption metadata. A pipeline-required source bundle that declares
|
|
212
|
+
role metadata as required must fail closed with a repairable non-human
|
|
213
|
+
`contract_bug` diagnostic when an adoption-sensitive role is missing, stale,
|
|
214
|
+
malformed, unknown, or cannot be represented in closeout metadata.
|
|
215
|
+
|
|
216
|
+
Phase-loop planners and executors may read protected sources when the selected
|
|
217
|
+
source bundle authorizes that context, but they must not mutate Pipeline-owned
|
|
218
|
+
protected sources by inference. Any Pipeline-owned write must be explicit in the
|
|
219
|
+
bundle policy and in the phase-plan owned-file contract.
|
|
220
|
+
|
|
221
|
+
## Handoff Storage
|
|
222
|
+
|
|
223
|
+
Workflow skill handoffs are stored in repo-local generated state under
|
|
224
|
+
`.dev-skills/handoffs/<skill-name>/`. The canonical latest pointer for a skill
|
|
225
|
+
is resolved by `shared/phase-loop/handoff_path.py` through
|
|
226
|
+
`resolve_handoff_path(repo, skill_name)` and points to
|
|
227
|
+
`.dev-skills/handoffs/<skill-name>/latest.md`. The resolver returns a `Path`
|
|
228
|
+
only; callers create directories and write content.
|
|
229
|
+
|
|
230
|
+
`phase-loop migrate-handoffs [--dry-run | --apply] [--json]` is the hard-cut
|
|
231
|
+
migration command. It scans legacy Claude, Codex, Gemini, and OpenCode skill
|
|
232
|
+
handoff roots, selects only handoffs whose frontmatter matches the current
|
|
233
|
+
repo, and emits metadata-only records with `skill_name`, `source`, `target`,
|
|
234
|
+
`action`, and `status`. Dry-run mode reports planned moves without mutation.
|
|
235
|
+
Apply mode moves matching `latest.md` files and timestamped siblings into the
|
|
236
|
+
repo-local directory and is idempotent when the target already has matching
|
|
237
|
+
content.
|
|
238
|
+
|
|
239
|
+
Apply mode is guarded by `_quiesced(repo)`. The guard succeeds only when
|
|
240
|
+
`.phase-loop/state.json` has no in-flight phase states, `.phase-loop/events.jsonl`
|
|
241
|
+
has no recent action except `closeout` or `manual_repair`, and no
|
|
242
|
+
`.phase-loop/*.lock` file exists. A failed quiescence check reports the first
|
|
243
|
+
blocker condition and performs no moves.
|
|
244
|
+
|
|
245
|
+
Legacy harness handoff roots are read only for migration after this cutover.
|
|
246
|
+
Reflections remain in harness-specific `reflections/` trees and are not moved
|
|
247
|
+
by HANDOFFS. The root `.gitignore` excludes `/.dev-skills/` so migrated or newly
|
|
248
|
+
written handoffs do not become source artifacts.
|
|
249
|
+
|
|
250
|
+
## Ledger Debug
|
|
251
|
+
|
|
252
|
+
`phase-loop status --ledger-debug` is a read-only operator diagnostic surface
|
|
253
|
+
for reducer-rejected ledger events. The flag belongs only to the `status`
|
|
254
|
+
subcommand. Default text status output remains unchanged without the flag:
|
|
255
|
+
operators still see only the existing `Ledger warnings: N` summary when warning
|
|
256
|
+
records exist.
|
|
257
|
+
|
|
258
|
+
With `--ledger-debug`, text status appends a `Rejected events` section after
|
|
259
|
+
the ledger warning count. Each row reports the rejected event phase, timestamp,
|
|
260
|
+
action, status, canonical reason, and a redacted `raw_event_summary`.
|
|
261
|
+
|
|
262
|
+
The same debug mode then appends `Duplicates skipped`. This section reports
|
|
263
|
+
events that matched an earlier event in the same reconcile call and were not
|
|
264
|
+
processed again. The zero-duplicate case is explicit as `none`.
|
|
265
|
+
|
|
266
|
+
`phase-loop status --json --ledger-debug` adds a top-level
|
|
267
|
+
`rejected_events` array:
|
|
268
|
+
|
|
269
|
+
```json
|
|
270
|
+
[
|
|
271
|
+
{
|
|
272
|
+
"phase": "RUNNER",
|
|
273
|
+
"timestamp": "2026-05-23T00:00:00Z",
|
|
274
|
+
"action": "execute",
|
|
275
|
+
"status": "complete",
|
|
276
|
+
"reason": "provenance_mismatch",
|
|
277
|
+
"raw_event_summary": {
|
|
278
|
+
"schema_version": 2,
|
|
279
|
+
"source": "fixture",
|
|
280
|
+
"phase": "RUNNER",
|
|
281
|
+
"action": "execute",
|
|
282
|
+
"status": "complete",
|
|
283
|
+
"timestamp": "2026-05-23T00:00:00Z",
|
|
284
|
+
"roadmap_sha256_present": true,
|
|
285
|
+
"phase_sha256_present": true
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The JSON field is present as `[]` in debug mode when there are no rejected
|
|
292
|
+
events, and absent from default JSON status output.
|
|
293
|
+
|
|
294
|
+
Debug JSON also adds `duplicates_skipped` as `[]` or as records with phase,
|
|
295
|
+
timestamp, action, status, automation_status, blocker_class, duplicate_key, and
|
|
296
|
+
redacted raw_event_summary. The field is absent from default JSON output.
|
|
297
|
+
|
|
298
|
+
The frozen ledger-debug rejection reasons are:
|
|
299
|
+
|
|
300
|
+
- `provenance_mismatch`
|
|
301
|
+
- `phase_missing`
|
|
302
|
+
- `not_in_allowed_status_set`
|
|
303
|
+
- `legacy_pre_schema_v2`
|
|
304
|
+
- `planned_without_plan_artifact`
|
|
305
|
+
- `blocker_supersession`
|
|
306
|
+
|
|
307
|
+
The `raw_event_summary` is an event identity summary only. It may include
|
|
308
|
+
schema version, source, phase, action, status, timestamp, and whether roadmap
|
|
309
|
+
or phase hashes were present. It must not serialize raw event payloads,
|
|
310
|
+
provider output, prompts, local environment contents, credentials, or arbitrary
|
|
311
|
+
nested metadata.
|
|
312
|
+
|
|
313
|
+
Ledger Debug is diagnostic-only. It does not repair, rewrite, or reclassify
|
|
314
|
+
historical events. After reading the debug output, operators continue to use
|
|
315
|
+
the existing reconcile, reopen, manual repair, or roadmap amendment workflows.
|
|
316
|
+
Persistent rejection logs and automatic repair are out of scope for this
|
|
317
|
+
surface.
|
|
318
|
+
|
|
319
|
+
## Ledger Dedup
|
|
320
|
+
|
|
321
|
+
Each `reconcile(repo, roadmap)` call performs an in-memory duplicate pass over
|
|
322
|
+
normalized automation events after roadmap filtering and before validation or
|
|
323
|
+
phase-state mutation. The duplicate key is:
|
|
324
|
+
|
|
325
|
+
```text
|
|
326
|
+
(timestamp, phase, action, status, automation_status, blocker_class)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`phase` is uppercased before comparison. `automation_status` and
|
|
330
|
+
`blocker_class` are read from top-level event fields when present, otherwise
|
|
331
|
+
from nested automation, child_automation, terminal summary, or blocker metadata.
|
|
332
|
+
|
|
333
|
+
Dedup is first-event-wins. The first event for a key is processed normally.
|
|
334
|
+
Subsequent events with the same key do not append ledger warnings, do not update
|
|
335
|
+
phase state, do not replace closeout summaries, and are recorded on
|
|
336
|
+
`StateSnapshot.ledger_duplicates_skipped` in encounter order. The ledger itself
|
|
337
|
+
is never rewritten or truncated.
|
|
338
|
+
|
|
339
|
+
This de-noises repeated identical events only. Rejected non-duplicate events
|
|
340
|
+
remain visible under `Rejected events`, and genuine provenance, status, schema,
|
|
341
|
+
planned-artifact, or blocker-supersession warnings still contribute to
|
|
342
|
+
`Ledger warnings: N`. Ledger Dedup extends the v25 LEDGERDEBUG operator surface;
|
|
343
|
+
it does not repair historical event content.
|
|
344
|
+
|
|
345
|
+
## Skills Bundle
|
|
346
|
+
|
|
347
|
+
The harness-neutral workflow skill source bundle lives at
|
|
348
|
+
`vendor/phase-loop-skills/`. Base source directories are unprefixed:
|
|
349
|
+
`execute-phase`, `plan-phase`, `plan-detailed`, `phase-roadmap-builder`,
|
|
350
|
+
`phase-loop`, `skill-editor`, `skill-improvement-planner`, and
|
|
351
|
+
`task-contextualizer`. Installed names retain the harness prefix, for example
|
|
352
|
+
`codex-execute-phase` and `claude-plan-phase`.
|
|
353
|
+
|
|
354
|
+
Harness-specific differences are escape hatches, not a second source tree.
|
|
355
|
+
Per-harness overlays live under
|
|
356
|
+
`vendor/phase-loop-skills/<skill-name>/_overrides/<harness>/` and are layered
|
|
357
|
+
over the unprefixed source during installation. Operators author those overlay
|
|
358
|
+
files explicitly; the installer does not auto-generate operator-authored
|
|
359
|
+
exceptions.
|
|
360
|
+
|
|
361
|
+
`phase_loop_runtime.skill_paths` exposes the resolver API used by workflow
|
|
362
|
+
skills and installers:
|
|
363
|
+
|
|
364
|
+
- `current_harness(harness: str | None = None) -> str`
|
|
365
|
+
- `resolve_skill_bundle_root(harness: str | None = None) -> Path`
|
|
366
|
+
- `resolve_skill_helper_root(harness: str | None = None) -> Path`
|
|
367
|
+
- `resolve_handoff_root(repo: Path) -> Path`
|
|
368
|
+
- `resolve_reflection_root(skill_name: str, harness: str | None = None) -> Path`
|
|
369
|
+
|
|
370
|
+
Resolver precedence is explicit function argument, `PHASE_LOOP_HARNESS`,
|
|
371
|
+
`PHASE_LOOP_SKILL_BUNDLE`, then the documented harness defaults:
|
|
372
|
+
`~/.claude/skills`, `~/.codex/skills`, `~/.gemini/skills`, and
|
|
373
|
+
`~/.config/opencode/skills`. Handoffs resolve to repo-local
|
|
374
|
+
`.dev-skills/handoffs/`; reflections remain under the selected harness skill
|
|
375
|
+
root.
|
|
376
|
+
|
|
377
|
+
`phase-loop install --harness {claude,codex,gemini,opencode}
|
|
378
|
+
[--source <bundle-path>] [--destination <install-root>] [--symlink|--copy]
|
|
379
|
+
[--dry-run|--apply]` is the metadata-only install surface. Dry-run mode reports
|
|
380
|
+
planned source, destination, harness, skill name, install mode, and action
|
|
381
|
+
without mutating destinations. Apply mode is idempotent and installs
|
|
382
|
+
harness-prefixed workflow skills from `vendor/phase-loop-skills/`.
|
|
383
|
+
|
|
384
|
+
`phase-loop install --status --json` is a read-only runtime visibility surface.
|
|
385
|
+
It never installs, replaces, copies, symlinks, deletes, or rewrites skill files.
|
|
386
|
+
The JSON reports per-harness skill parity, console-script availability, BAML
|
|
387
|
+
closeout schema availability, and `.dev-skills/` ignore readiness using
|
|
388
|
+
symbolic metadata only.
|
|
389
|
+
|
|
390
|
+
## Operating Modes
|
|
391
|
+
|
|
392
|
+
The phase-loop runner supports three operating modes:
|
|
393
|
+
|
|
394
|
+
- `standalone`: local dotfiles execution. A `phase_loop_plan_version: 1` plan
|
|
395
|
+
with only the required `phase`, `roadmap`, and `roadmap_sha256` fields
|
|
396
|
+
remains valid without Pipeline metadata. Standalone runs do not require
|
|
397
|
+
governed-pipeline, `.pipeline/**`, Portal, Greenfield, or a source bundle.
|
|
398
|
+
- `pipeline_optional`: dotfiles may consume supplied Pipeline metadata and
|
|
399
|
+
source-bundle context when present, but missing Pipeline metadata does not
|
|
400
|
+
invalidate the standalone plan contract.
|
|
401
|
+
- `pipeline_required`: execution must fail closed before child launch when
|
|
402
|
+
validated Pipeline context is missing, stale, malformed, or mismatched.
|
|
403
|
+
|
|
404
|
+
Pipeline-required execution requires a validated `source_bundle`,
|
|
405
|
+
`source_bundle_sha256`, `pipeline_phase_id`, bundle freshness, protected-source
|
|
406
|
+
entries, and protected-source hash checks before execution. When
|
|
407
|
+
`freshness.source_bundle_hash` is a SHA-256 digest, it must match the source
|
|
408
|
+
bundle bytes. Protected-source entries must cite one of
|
|
409
|
+
`PIPELINE_PROTECTED_SOURCE_CATEGORIES`, and protected-source files must exist
|
|
410
|
+
with matching SHA-256 hashes.
|
|
411
|
+
|
|
412
|
+
Governed Pipeline owns adoption, source-bundle emission, canonical refresh,
|
|
413
|
+
replan, closeout ingest, Greenfield reduction, and Portal projection.
|
|
414
|
+
Governed-pipeline owns canonical source-truth refresh, source-bundle emission,
|
|
415
|
+
protected-source freshness, scheduling, closeout ingest, Greenfield reduction,
|
|
416
|
+
and Portal projection. Dotfiles consumes those inputs and emits redacted
|
|
417
|
+
metadata; it does not infer authority over governed-pipeline, Portal,
|
|
418
|
+
Greenfield, `.pipeline/**`, private evidence, raw data, credentials, or
|
|
419
|
+
provider-supplied payloads. Host bootstrap, Shell config, SSH setup, MCP gateway setup,
|
|
420
|
+
generic 1Password setup, raw-evidence, credential-bearing payloads, and local
|
|
421
|
+
environment values are outside the protocol substrate.
|
|
422
|
+
Governed-pipeline also owns canonical spec adoption, archive creation, managed
|
|
423
|
+
mirror refresh, source-truth reconciliation, canonical refresh, replan, and
|
|
424
|
+
preflight block decisions. Dotfiles may echo validated adoption role metadata
|
|
425
|
+
and advisory hints in closeout, but it must not make archive, mirror,
|
|
426
|
+
canonical refresh, replan, or block decisions.
|
|
427
|
+
|
|
428
|
+
## Dotfiles Source And Visibility Contracts
|
|
429
|
+
|
|
430
|
+
Dotfiles source authority for governed-pipeline adoption is frozen in
|
|
431
|
+
`docs/dotfiles-source-authority-contract.md`. That document classifies every
|
|
432
|
+
top-level path with `path_glob`, `classification`, `owner`,
|
|
433
|
+
`ingestion_policy`, and rationale. Governed-pipeline may pull paths classified
|
|
434
|
+
as `authority`; it must reject `derived`, `runtime_state`, `private`, and
|
|
435
|
+
`out_of_scope` paths as adoption source material.
|
|
436
|
+
|
|
437
|
+
Dotfiles cross-repo visibility is frozen in
|
|
438
|
+
`docs/dotfiles-visibility-contract.md`. That contract exposes only adoption
|
|
439
|
+
inputs, redacted runtime metadata, and the operating-mode declaration. The
|
|
440
|
+
surface is pull-only: dotfiles does not push into governed-pipeline or Portal,
|
|
441
|
+
governed-pipeline must not write into dotfiles, consumers must not depend on
|
|
442
|
+
runtime state paths, and BAML schema contracts must be imported rather than
|
|
443
|
+
paraphrased.
|
|
444
|
+
|
|
445
|
+
## Dotfiles Schema Pack
|
|
446
|
+
|
|
447
|
+
The dotfiles BAML schema pack lives under
|
|
448
|
+
`vendor/phase-loop-runtime/src/phase_loop_runtime/baml_src/` and freezes the direct import contracts
|
|
449
|
+
for downstream consumers. The authority class names are
|
|
450
|
+
`DotfilesAdoptionManifest`, `DotfilesRuntimeProjection`,
|
|
451
|
+
`DotfilesC4Document`, and `DotfilesTaskCatalog`.
|
|
452
|
+
|
|
453
|
+
Consumers import the `.baml` contracts directly or use
|
|
454
|
+
`phase_loop_runtime.baml_modular.export_function_schema(<class name>)` for a
|
|
455
|
+
dialect-clean JSON Schema projection. Protocol prose may describe the pack and
|
|
456
|
+
its consumers, but it must not paraphrase full field definitions as an
|
|
457
|
+
alternate schema source. `DotfilesAdoptionManifest` is the governed-pipeline
|
|
458
|
+
pull manifest shape, `DotfilesRuntimeProjection` is the redacted runtime
|
|
459
|
+
visibility shape, `DotfilesC4Document` carries Markdown/Mermaid source for
|
|
460
|
+
deterministic rendering, and `DotfilesTaskCatalog` carries audience-tagged task
|
|
461
|
+
entries.
|
|
462
|
+
|
|
463
|
+
After DOTADOPT, `docs/adoption/dotfiles-adoption-bundle.json` is the canonical
|
|
464
|
+
governed-pipeline pull-only ingestion target for dotfiles. The fixture is
|
|
465
|
+
generated by `generate_adoption_bundle`, validates as
|
|
466
|
+
`DotfilesAdoptionManifest`, and carries `sha256` digests for imported `.baml`
|
|
467
|
+
schema sources. This handoff does not produce canonical HTML, rendered Mermaid,
|
|
468
|
+
Portal views, governed-pipeline files, archives, mirrors, or any alternate
|
|
469
|
+
schema authority; the `.baml` files remain the schema source of truth.
|
|
470
|
+
|
|
471
|
+
### Adoption Bundle Lifecycle
|
|
472
|
+
|
|
473
|
+
The runtime exposes `phase-loop adoption-bundle status --repo <path>` to compare
|
|
474
|
+
current `vendor/phase-loop-runtime/src/phase_loop_runtime/baml_src/*.baml` digests with
|
|
475
|
+
`docs/adoption/dotfiles-adoption-bundle.json`. It exits `0` when fresh, `1`
|
|
476
|
+
when stale, and `2` when the bundle or schema contract cannot be loaded.
|
|
477
|
+
`phase-loop adoption-bundle refresh --repo <path>` regenerates that JSON bundle
|
|
478
|
+
through `generate_adoption_bundle`, preserving the committed fixture metadata
|
|
479
|
+
for `generated_at` and `operating_mode`; it exits `0` after a successful
|
|
480
|
+
refresh or idempotent no-op and `1` when regeneration fails.
|
|
481
|
+
|
|
482
|
+
`.githooks/pre-commit-adoption-bundle` is the optional local automation path.
|
|
483
|
+
When staged BAML files are present it runs the status command, runs refresh only
|
|
484
|
+
for stale bundles, and stages `docs/adoption/dotfiles-adoption-bundle.json`
|
|
485
|
+
after a successful refresh. The hook is never installed by default; operators
|
|
486
|
+
opt in with `phase-loop init --install-hooks`.
|
|
487
|
+
|
|
488
|
+
In standalone mode, root `specs/**` is the default human-visible future-spec
|
|
489
|
+
discovery root when no phase plan, source bundle, or repo-local config
|
|
490
|
+
overrides it. Legacy or project-specific seed roots such as `Specs/**` are
|
|
491
|
+
explicit input roots only; accepted future specs should normalize toward root
|
|
492
|
+
`specs/**` for later governed-pipeline intake.
|
|
493
|
+
|
|
494
|
+
## Dispatch Hints
|
|
495
|
+
|
|
496
|
+
Roadmaps and phase plans may include an optional markdown section named
|
|
497
|
+
`Dispatch Hints` without changing the required frontmatter contract. The parser
|
|
498
|
+
must treat this section as optional and fail closed when it is absent.
|
|
499
|
+
|
|
500
|
+
Supported keys:
|
|
501
|
+
|
|
502
|
+
- `preferred executors`
|
|
503
|
+
- `allowed executors`
|
|
504
|
+
- `fallback executors`
|
|
505
|
+
- `disabled executors`
|
|
506
|
+
- `required capabilities`
|
|
507
|
+
|
|
508
|
+
Supported action selectors:
|
|
509
|
+
|
|
510
|
+
- `roadmap`
|
|
511
|
+
- `plan`
|
|
512
|
+
- `execute`
|
|
513
|
+
- `repair`
|
|
514
|
+
- `review`
|
|
515
|
+
- `maintain-skills`
|
|
516
|
+
|
|
517
|
+
Accepted forms:
|
|
518
|
+
|
|
519
|
+
```markdown
|
|
520
|
+
## Dispatch Hints
|
|
521
|
+
- preferred executors: `codex`
|
|
522
|
+
- allowed executors: `codex`, `claude`
|
|
523
|
+
- fallback executors: `codex`
|
|
524
|
+
- disabled executors: `manual`
|
|
525
|
+
- required capabilities: `live_launch`, `structured_output`
|
|
526
|
+
- execute preferred executors: `codex`
|
|
527
|
+
- review allowed executors: `codex`, `claude`
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
or action-grouped subsections such as:
|
|
531
|
+
|
|
532
|
+
```markdown
|
|
533
|
+
## Dispatch Hints
|
|
534
|
+
### Default
|
|
535
|
+
- preferred executors: `codex`
|
|
536
|
+
|
|
537
|
+
### Review
|
|
538
|
+
- allowed executors: `codex`, `claude`
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
Dispatch hint precedence is:
|
|
542
|
+
|
|
543
|
+
1. operator / CLI override
|
|
544
|
+
2. phase-plan hints
|
|
545
|
+
3. roadmap hints
|
|
546
|
+
4. registry defaults
|
|
547
|
+
|
|
548
|
+
Disabled executors and required capabilities remain conservative filters: they
|
|
549
|
+
must not be silently ignored when a preferred executor conflicts with them.
|
|
550
|
+
|
|
551
|
+
## Planner Validation
|
|
552
|
+
|
|
553
|
+
Planner skills validate emitted plan-document literals before writing a plan
|
|
554
|
+
artifact. The shared entrypoint is
|
|
555
|
+
`phase_loop_runtime.planner_validation.validate_plan_dispatch_hints(plan_text,
|
|
556
|
+
*, dispatch_capabilities=None, executors=None, product_loop_actions=None)`.
|
|
557
|
+
It returns metadata-only `ValidationFinding` records with `field_path`,
|
|
558
|
+
`literal`, `allowed_values`, and `suggested_fix`; it never writes files, mutates
|
|
559
|
+
runner state, prints output, or raises on invalid plan text.
|
|
560
|
+
|
|
561
|
+
The validator defaults to `DISPATCH_CAPABILITIES`, `EXECUTORS`, and
|
|
562
|
+
`PRODUCT_LOOP_ACTIONS` from `phase_loop_runtime.models`. It checks
|
|
563
|
+
`Dispatch Hints` executor and required-capability literals, `Execution Policy`
|
|
564
|
+
selectors and executor assignments, and closeout example literals for
|
|
565
|
+
`terminal_status`, `verification_status`, and `blocker_class`. Markdown body
|
|
566
|
+
content outside those planner-emitted protocol surfaces is not validated.
|
|
567
|
+
|
|
568
|
+
Planner skills must call the validator after the complete draft exists and
|
|
569
|
+
before the project-path plan document is written. A finding blocks the write and
|
|
570
|
+
surfaces a validation_failed closeout with `terminal_status=blocked`,
|
|
571
|
+
`verification_status=blocked`, `blocker_class=contract_bug`,
|
|
572
|
+
`human_required=false`, and a non-secret summary of the findings. This is a
|
|
573
|
+
source-side guard: v24 CLOSEOUTHARDEN remains the runtime soft-fail layer for
|
|
574
|
+
child executor closeout drift, and neither layer coerces unknown literals into
|
|
575
|
+
nearby valid statuses.
|
|
576
|
+
|
|
577
|
+
## Execution Policy
|
|
578
|
+
|
|
579
|
+
Roadmaps and phase plans may include an optional `## Execution Policy` section
|
|
580
|
+
when executor selection also needs model, effort, work-unit defaults, fallback,
|
|
581
|
+
or policy source provenance. Existing artifacts do not need this section.
|
|
582
|
+
`Dispatch Hints` remain valid and are the executor-only fallback surface when no
|
|
583
|
+
execution policy is present.
|
|
584
|
+
|
|
585
|
+
The accepted syntax is line-oriented markdown:
|
|
586
|
+
|
|
587
|
+
```markdown
|
|
588
|
+
## Execution Policy
|
|
589
|
+
- work-unit defaults: work-unit=`lane_execute`, effort=`medium`, unsupported=`inherit_default`, inherit-default=`true`
|
|
590
|
+
- execute: executor=`pi`, model=`auto`, effort=`medium`, work-unit=`lane_execute`, reason=`simple bounded lane default`
|
|
591
|
+
- repair: executor=`claude`, model=`claude-opus-4-8`, effort=`high`, work-unit=`repair`
|
|
592
|
+
- SL-2: executor=`gemini`, model=`phase-loop-execute-medium`, effort=`medium`, work-unit=`lane_execute`, unsupported=`fallback`, fallback=`phase-loop-execute-medium`
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Supported selectors are `work-unit defaults`, `roadmap`, `plan`, `execute`,
|
|
596
|
+
`repair`, `review`, `maintain-skills`, and lane-specific selectors such as
|
|
597
|
+
`SL-2`. Lane-specific policy only resolves metadata for that lane; POLICYDSL
|
|
598
|
+
does not schedule lane work units.
|
|
599
|
+
Reducer and verification lanes use lane selectors with
|
|
600
|
+
`work-unit=phase_reducer` or `work-unit=phase_verify`; invented action
|
|
601
|
+
selectors such as `reduce` and `verify` are invalid.
|
|
602
|
+
|
|
603
|
+
Execution policy precedence is frozen as:
|
|
604
|
+
|
|
605
|
+
1. CLI/operator override
|
|
606
|
+
2. phase-plan policy
|
|
607
|
+
3. roadmap policy
|
|
608
|
+
4. `Dispatch Hints`
|
|
609
|
+
5. registry defaults
|
|
610
|
+
|
|
611
|
+
The resolver must record policy source and override reason for model, effort,
|
|
612
|
+
executor, and fallback decisions. Invalid model, effort, work-unit, fallback, or
|
|
613
|
+
silent downgrade cases fail closed unless explicit fallback or default
|
|
614
|
+
inheritance is recorded.
|
|
615
|
+
|
|
616
|
+
A plan or roadmap whose `## Execution Policy` block fails to parse is recorded
|
|
617
|
+
as a `contract_bug` blocker carrying `path:line_number` and the offending raw
|
|
618
|
+
line, surfaced through `phase-loop status` and `phase-loop handoff`. The runner
|
|
619
|
+
stays alive and exits with code `5` (blocked), distinct from `1` (child launch
|
|
620
|
+
failure) and `0` (success), so operator rotation wrappers can branch on the
|
|
621
|
+
exit code instead of grepping stdout for `Terminal status: blocked`.
|
|
622
|
+
|
|
623
|
+
`phase-loop status --json` exposes `pipeline_mode` at the top level and the
|
|
624
|
+
resolved per-phase execution policy under an `execution_policy` block keyed by
|
|
625
|
+
phase alias, with sub-keys for `plan`, `execute`, `repair`, and `review`. Each
|
|
626
|
+
sub-key reports the resolved `executor`, `model`, `effort`, and `source`
|
|
627
|
+
(`phase-plan policy` or `roadmap policy`). Rotation wrappers consult this block
|
|
628
|
+
to pre-resolve executor pins before dispatching, avoiding the per-phase
|
|
629
|
+
`sandbox_command_restriction` failures that arise when an external rotation
|
|
630
|
+
pushes an executor that the plan policy pins to a different harness.
|
|
631
|
+
|
|
632
|
+
`phase-loop status --runtime-projection --json` emits the
|
|
633
|
+
`DotfilesRuntimeProjection` shape. The projection maps the selected
|
|
634
|
+
`pipeline_mode` to `operating_mode`, validates the emitted JSON through the BAML
|
|
635
|
+
schema parser, and must not include absolute host paths, secret references,
|
|
636
|
+
environment values, provider tokens, or local account identifiers. The surface
|
|
637
|
+
is standalone-first: it works without governed-pipeline, a source bundle,
|
|
638
|
+
Portal, Greenfield, or any acknowledged Pipeline contract.
|
|
639
|
+
|
|
640
|
+
## Start Gate
|
|
641
|
+
|
|
642
|
+
Before `run`, `resume`, or `dry-run` dispatches planning, execution, lane
|
|
643
|
+
scheduling, work-unit mode, repair, closeout, or `launch_with_spec`, the runner
|
|
644
|
+
performs a cross-phase dirty start gate. The gate scans at most the last 50
|
|
645
|
+
ledger events, ignores malformed events and same-phase evidence, keeps the
|
|
646
|
+
latest dirty evidence per prior phase, and intersects `phase_owned_dirty_paths`
|
|
647
|
+
plus `previous_phase_owned_paths` with current git dirty paths. A prior phase
|
|
648
|
+
holds its dirty-path lien only while it remains in the active plan: an
|
|
649
|
+
`unplanned` phase (dropped by a roadmap edit) or one absent from the roadmap
|
|
650
|
+
entirely is skipped, so its stale ownership cannot perpetually block dispatch.
|
|
651
|
+
|
|
652
|
+
When an overlap remains dirty, the runner refuses dispatch and appends
|
|
653
|
+
`start_gate_refused` with `blocker_class=dirty_worktree_conflict` and
|
|
654
|
+
`metadata.start_gate.status=refused`. The metadata names the current phase, the
|
|
655
|
+
offending phase, its `offending_status`, `last_event_timestamp`,
|
|
656
|
+
`overlapping_dirty_paths`, `scanned_events`, and operator `next_actions`,
|
|
657
|
+
ordered most-reliable first: rerun with `phase-loop run
|
|
658
|
+
--allow-cross-phase-dirty "<reason>"` to bypass the gate; commit the overlapping
|
|
659
|
+
path(s) or set them aside with `git stash -u` (untracked output counts as
|
|
660
|
+
dirty); and, only when the offending phase is `blocked`, run `phase-loop
|
|
661
|
+
reconcile --phase <ALIAS> --to-status planned --allow-dirty --reason <text>` to
|
|
662
|
+
record `manual_recovery` for that dirty-state blocker. The terminal summary is
|
|
663
|
+
blocked and the refusal does not launch a child process.
|
|
664
|
+
|
|
665
|
+
Operators may pass `--allow-cross-phase-dirty <reason>` to `phase-loop run`,
|
|
666
|
+
`phase-loop resume`, or `phase-loop dry-run`. The reason is required and the
|
|
667
|
+
runner appends audited `metadata.start_gate.status=bypassed` before continuing.
|
|
668
|
+
The bypass is limited to this STARTGATE check: it does not mark verification
|
|
669
|
+
passed, perform recovery, auto-commit output, or bypass dirty-worktree,
|
|
670
|
+
Pipeline, access, repair, executor-policy, or launch blockers.
|
|
671
|
+
|
|
672
|
+
## Model Routing & Run Mode
|
|
673
|
+
|
|
674
|
+
Model selection has two orthogonal axes (model-routing-v1).
|
|
675
|
+
|
|
676
|
+
`model_policy` (*what model*): a `model_class` role — `planner`, `implementer`,
|
|
677
|
+
or `worker` — resolves to a concrete model per executor. An execution-policy
|
|
678
|
+
rule may carry `model_class`; resolution composes registry defaults → shipped
|
|
679
|
+
`model_policy` → plan `## Execution Policy` → CLI override (lowest to highest
|
|
680
|
+
precedence). A checkout with no `model_policy` and no `model_class` resolves
|
|
681
|
+
model and effort exactly as the registry defaults (the empty-policy back-compat
|
|
682
|
+
contract). Requesting an effort a provider does not support raises unless the
|
|
683
|
+
rule opts into the provider `effort_map` fallback; the shipped policy does so for
|
|
684
|
+
its `max`-effort planning actions. `worker` never authors a final patch
|
|
685
|
+
(`execute`/`repair`), and an executor whose ceiling is below `max` (e.g. gemini)
|
|
686
|
+
is never selected as the max-effort planner of record.
|
|
687
|
+
|
|
688
|
+
`run_mode` (*how governed*): `autonomous` (default) or `governed` (opt-in, via
|
|
689
|
+
`--governed` / `PHASE_LOOP_RUN_MODE=governed`). Autonomous invokes no advisor
|
|
690
|
+
panel and adds no `human_required` — the panel is never spawned (the run-level
|
|
691
|
+
guard short-circuits before any leg). Governed adds a plan-stage and pre-merge
|
|
692
|
+
panel gate that reuses the `review_finding` `block`/`nit` severity vocabulary on
|
|
693
|
+
a seam separate from the closeout validator registry; the reviewer pool is
|
|
694
|
+
vendor-disjoint from the author, the review loop is bounded, and every escalation
|
|
695
|
+
terminal is a non-human `review_gate_block` surfaced in the run-end summary —
|
|
696
|
+
never a synchronous human wait.
|
|
697
|
+
|
|
698
|
+
Governed mode is **live on the serial dispatch path** (model-routing-v2). The
|
|
699
|
+
plan-stage gate runs on first-attempt plans (not repair re-plans). The pre-merge
|
|
700
|
+
gate runs **inside the closeout, after `git add` stages the owned paths and
|
|
701
|
+
before the commit is finalized**, and reviews the **exact staged index**
|
|
702
|
+
(`git diff --cached`) over the paths being committed — so the artifact the panel
|
|
703
|
+
reviews is, by construction, exactly what gets committed (no separately-derived
|
|
704
|
+
path set, no untracked-file omission). Reviewer≠author is the **union** of the
|
|
705
|
+
phase's dispatch `selected_executor` vendors (rotation/repair may have several);
|
|
706
|
+
all are excluded. The gate is **fail-closed** (governed is the opt-in enforcement
|
|
707
|
+
mode): a `block`, a non-conforming/unparseable verdict, or no usable reviewer
|
|
708
|
+
disjoint from the author HOLDS the merge as a non-human `review_gate_block` — it
|
|
709
|
+
never silently advisory-passes. Verdicts use a strict terminal-line contract (the
|
|
710
|
+
last line begins with `AGREE` / `PARTIALLY AGREE` / `DISAGREE`). The panel spawns
|
|
711
|
+
the codex + gemini subscription CLI legs (the claude leg is `unavailable` pending
|
|
712
|
+
a native-Agent path). The autonomous default is byte-identical — the gate is a
|
|
713
|
+
no-op off the governed path. Governed mode is **EXPERIMENTAL and fail-safe** — it
|
|
714
|
+
may over-block, but never silently passes unreviewed or self-reviewed code. With
|
|
715
|
+
only the codex + gemini legs live, a **multi-vendor** phase (authored by one vendor
|
|
716
|
+
and repaired by the other) has no disjoint reviewer and is HELD with an explicit
|
|
717
|
+
reason until the deferred claude leg lands. Out of scope / remaining threads:
|
|
718
|
+
concurrent-wave dispatch is **not** governed yet; a held phase is not auto-repaired
|
|
719
|
+
(the findings-driven executor re-dispatch is the remaining thread); and the
|
|
720
|
+
`model_class` escalation decision is recorded on the dispatch metadata but not
|
|
721
|
+
yet re-routed into live model selection.
|
|
722
|
+
|
|
723
|
+
## Reconcile Command
|
|
724
|
+
|
|
725
|
+
`phase-loop reconcile` has two distinct modes.
|
|
726
|
+
|
|
727
|
+
Completion reconcile:
|
|
728
|
+
|
|
729
|
+
`phase-loop reconcile --phase <ALIAS> [--closeout-commit <SHA>]
|
|
730
|
+
[--repair-summary <text>] [--verification-status passed]
|
|
731
|
+
[--verification-log <path>] [--recovery-mode]`
|
|
732
|
+
|
|
733
|
+
This synthesizes a v28-shape `manual_repair` event for the named phase,
|
|
734
|
+
recording the current `HEAD` (or the supplied SHA) as the closeout commit and
|
|
735
|
+
marking `clears_blocker=true`. It then re-reconciles so `phase-loop status`
|
|
736
|
+
reflects the cleared blocker.
|
|
737
|
+
|
|
738
|
+
The command refuses if the working tree is dirty (override with
|
|
739
|
+
`--allow-dirty`) so the synthesized event always references a clean closeout
|
|
740
|
+
commit. This compresses the post-`dirty_worktree_conflict` recovery ritual —
|
|
741
|
+
manually authoring the v28 P3/P4 event shape and appending to events.jsonl —
|
|
742
|
+
to one CLI call. Use only as a recovery tool when the executor's work is
|
|
743
|
+
correct but ownership classification or lane-evidence gaps left the runner
|
|
744
|
+
blocked; do not use to bypass legitimate verification failures.
|
|
745
|
+
|
|
746
|
+
When completion reconcile uses `--verification-status passed`, it must also
|
|
747
|
+
provide `--verification-log <path>` pointing at the runner-owned
|
|
748
|
+
`verification.json` artifact for the run. The runner validates the artifact
|
|
749
|
+
schema, the sibling `verification.log` SHA-256 against `log_sha256`, and every
|
|
750
|
+
command, env-refresh, and suite exit code before appending a completion repair
|
|
751
|
+
event. Remediation output and event metadata include only the artifact path,
|
|
752
|
+
validation code, and exit summary; raw verification log output is never copied
|
|
753
|
+
into reconcile metadata.
|
|
754
|
+
|
|
755
|
+
Blocked-state recovery:
|
|
756
|
+
|
|
757
|
+
`phase-loop reconcile --phase <ALIAS> --to-status planned --reason <text>`
|
|
758
|
+
|
|
759
|
+
This is an explicit blocked-state recovery transition. It is for stale
|
|
760
|
+
non-human dirty-state blockers only, and it records an auditable
|
|
761
|
+
`manual_recovery` event with `from=blocked`, `to=planned` or `to=unplanned`,
|
|
762
|
+
`trigger=cli`, `clears_blocker=true`, and `verification_status=not_run`.
|
|
763
|
+
It does not mark verification passed and is mutually exclusive with
|
|
764
|
+
`--verification-status passed`.
|
|
765
|
+
|
|
766
|
+
Recovery is allowed only when the selected phase currently reconciles as
|
|
767
|
+
`blocked` and the blocker is dirty-state-derived, such as
|
|
768
|
+
`dirty_worktree_conflict`, phase-owned dirty output, previous-phase-owned dirty
|
|
769
|
+
output, or stale dirty terminal metadata that no longer blocks current git
|
|
770
|
+
state. If a current plan artifact exists, replay reports the phase as
|
|
771
|
+
`planned`; if no current plan artifact exists, replay reports it as
|
|
772
|
+
`unplanned` so the operator can run the planning workflow again.
|
|
773
|
+
|
|
774
|
+
Sticky or human-required blockers are refused. This includes
|
|
775
|
+
`missing_secret`, `account_or_billing_setup`, `admin_approval`,
|
|
776
|
+
`product_decision_missing`, and `destructive_operation`. Access, product,
|
|
777
|
+
destructive, contract, repeated-verification, and unknown blockers require
|
|
778
|
+
explicit dirty-state evidence before recovery can clear stale blocker fields.
|
|
779
|
+
|
|
780
|
+
### Recovery Mode
|
|
781
|
+
|
|
782
|
+
`phase-loop reconcile --recovery-mode` is for recovery states where the operator
|
|
783
|
+
intentionally needs to reconcile while the worktree is dirty. It implies the
|
|
784
|
+
dirty-tree override, records `manual_repair.recovery_mode=true`, and requires
|
|
785
|
+
explicit `--closeout-commit`, `--repair-summary`, and `--verification-status`
|
|
786
|
+
arguments so the audit trail does not silently inherit defaults.
|
|
787
|
+
|
|
788
|
+
Use recovery mode for parallel-race recovery when a plan doc or other recovery
|
|
789
|
+
artifact is dirty because another runner already produced the phase-owned
|
|
790
|
+
output; the repair summary should state which runner or closeout created the
|
|
791
|
+
dirty path. Use it for stale-event recovery when a clean closeout event exists
|
|
792
|
+
but the active status still reflects an older blocked event. Use it for a
|
|
793
|
+
manual-repair flow only after the operator has verified the repair and can name
|
|
794
|
+
the commit and verification result. Recovery mode does not replace legitimate
|
|
795
|
+
verification repair; failed verification still requires repair or a blocked
|
|
796
|
+
closeout, not a reconciled completion.
|
|
797
|
+
|
|
798
|
+
## Reopen Command
|
|
799
|
+
|
|
800
|
+
`phase-loop reopen --phase <ALIAS> --reason <text> [--allow-dirty]` is the
|
|
801
|
+
symmetric counterpart to `reconcile`: where `reconcile` advances a `blocked`
|
|
802
|
+
phase to `complete`, `reopen` reverts a spurious `complete` phase back to
|
|
803
|
+
`planned`. Use when an executor reported a phase as complete +
|
|
804
|
+
`verification_status=passed` but the underlying IF gates were not actually
|
|
805
|
+
satisfied (e.g., a repair iteration that reported done with zero diff and no
|
|
806
|
+
real work).
|
|
807
|
+
|
|
808
|
+
The command appends a typed `phase_reopen` event with `status: planned` and
|
|
809
|
+
metadata `{reason, prior_status, prior_closeout_commit, reopen_commit}`. The
|
|
810
|
+
reducer recognizes `action: phase_reopen` and flips the phase back to
|
|
811
|
+
`planned` regardless of plan-artifact existence. Subsequent
|
|
812
|
+
`phase-loop run` invocations re-execute the phase.
|
|
813
|
+
|
|
814
|
+
Refuses if the phase is not currently `complete` (cannot reopen what isn't
|
|
815
|
+
closed). Refuses if the working tree is dirty (override with `--allow-dirty`)
|
|
816
|
+
so `reopen_commit` references a clean state. The `--reason` field is
|
|
817
|
+
required and recorded on the event for audit.
|
|
818
|
+
|
|
819
|
+
This command exists because the runner trusts the executor's reported
|
|
820
|
+
terminal status by default; if an executor hallucinates completion, the
|
|
821
|
+
runner has no way to independently verify the IF gates were satisfied. A
|
|
822
|
+
future "closeout-payload IF-gate cross-check" would prevent the bug at
|
|
823
|
+
emission time; `reopen` is the recovery path until that ships.
|
|
824
|
+
|
|
825
|
+
## Evidence Audit Command
|
|
826
|
+
|
|
827
|
+
`phase-loop evidence-audit --repo . [--dirty-only|--full-tree|--full-tree-loose]
|
|
828
|
+
[--tier-2]` is an operator-callable spot-check for fake-evidence patterns in
|
|
829
|
+
dirty-tree artifacts. Run it before `phase-loop reconcile` on phases producing
|
|
830
|
+
comparison/verdict evidence (visual-fidelity diffs, audit reports, schema
|
|
831
|
+
validation traces).
|
|
832
|
+
|
|
833
|
+
Surfaced after the regen 2026-05-22 VISUALMATCH incident, where the
|
|
834
|
+
executor committed 21 artifact files that satisfied the v20 closeout
|
|
835
|
+
schema (terminal_status: complete, produced_if_gates populated, real
|
|
836
|
+
dirty_paths) but contained faked evidence: 19 "distinct" prototype
|
|
837
|
+
PNGs all shared one md5 hash; 19/19 similarity scores at exactly
|
|
838
|
+
0.999999; boilerplate verdict markdown. v20's IF-gate Tier 1
|
|
839
|
+
validator (`closeout_validation.validate_produced_gates`) only checks
|
|
840
|
+
that produced gate names match plan `## Produces` names — it cannot
|
|
841
|
+
see evidence content. Operators must spot-check; `phase-loop
|
|
842
|
+
evidence-audit` codifies the spot-check.
|
|
843
|
+
|
|
844
|
+
Tier 1 detectors run by default:
|
|
845
|
+
|
|
846
|
+
- **`duplicate-content`** — N or more files share the same sha256.
|
|
847
|
+
Default threshold N=3 (`--min-duplicates`). Catches the placeholder-
|
|
848
|
+
duplicated pattern (e.g., the 19 identical PNGs).
|
|
849
|
+
- **`uniform-numeric`** — numeric arrays >= 4 elements (or object
|
|
850
|
+
arrays where every object has an identical numeric field) where all
|
|
851
|
+
values are within `--uniform-epsilon` (default 1e-6). Catches the
|
|
852
|
+
template-fill pattern (e.g., 19/19 entries at similarity=0.999999).
|
|
853
|
+
- **`missing-references`** — JSON artifacts cite path-shaped string
|
|
854
|
+
values that don't exist on disk. Default `--dirty-only` and `--full-tree`
|
|
855
|
+
scans use strict missing-reference mode: the string must have a known
|
|
856
|
+
data/artifact extension and appear under object key/value context that
|
|
857
|
+
names evidence, source, screenshot, fixture, path, reference, or artifact
|
|
858
|
+
material. `--full-tree-loose` is the loose forensic compatibility path; it
|
|
859
|
+
scans the full tree with the older liberal path-shaped string behavior.
|
|
860
|
+
The strict missing-reference default is calibrated to avoid code-string and
|
|
861
|
+
command false positives while still catching cited-but-never-created
|
|
862
|
+
evidence paths.
|
|
863
|
+
|
|
864
|
+
Tier 2 detectors are operator-invoked with `--tier-2`; default invocation
|
|
865
|
+
remains Tier 1 only. Runner closeout integration may also run Tier 2 as the
|
|
866
|
+
first fuzzy stage before a default-OFF Tier 3 check when closeout-time Tier 3
|
|
867
|
+
is explicitly enabled:
|
|
868
|
+
|
|
869
|
+
- **`loose-uniform`** — numeric arrays >= 4 elements (or object arrays
|
|
870
|
+
where every object has a common numeric field) whose coefficient of
|
|
871
|
+
variation is below `--loose-uniform-stdev-threshold` (default 1e-3).
|
|
872
|
+
Exact uniformity is left to the Tier 1 `uniform-numeric` detector and
|
|
873
|
+
is not double-reported as loose-uniform.
|
|
874
|
+
- **`boilerplate-text`** — text files with high non-path token overlap.
|
|
875
|
+
The detector normalizes whitespace and punctuation, lower-cases tokens,
|
|
876
|
+
strips path-shaped tokens, skips binary files, and reports groups with
|
|
877
|
+
overlap above `--boilerplate-token-overlap-threshold` (default 0.80)
|
|
878
|
+
and size at least `--boilerplate-min-group-size` (default 3). The default
|
|
879
|
+
threshold is calibrated against `tests/fixtures/evidence-audit-calibration/`:
|
|
880
|
+
known-fake boilerplate and templated prose fixtures must flag, known-real
|
|
881
|
+
fixtures must not flag, and borderline fixtures remain low-confidence Tier 2
|
|
882
|
+
uncertainty rather than Tier 1-style hard failures.
|
|
883
|
+
- **`size-distribution`** — sibling-directory file groups whose byte-size
|
|
884
|
+
coefficient of variation is below
|
|
885
|
+
`--size-distribution-variance-threshold` (default 0.05), with at least
|
|
886
|
+
`--size-distribution-min-group-size` files (default 3).
|
|
887
|
+
|
|
888
|
+
Text output renders Tier 2 findings with `tier2:` prefixes. JSON output
|
|
889
|
+
includes the `tier2_findings` object only when `--tier-2` is enabled.
|
|
890
|
+
|
|
891
|
+
Tier 3 is default OFF. Operator invocations use `phase-loop evidence-audit
|
|
892
|
+
--enable-tier-3`; runner closeout invocations use `phase-loop run
|
|
893
|
+
--enable-tier-3` or a per-phase config opt-in. The runner also accepts
|
|
894
|
+
`--tier-3-budget` and records `tier3_budget` and `tier3_calls_made` per
|
|
895
|
+
closeout invocation. The default budget is 3. When budget is exhausted,
|
|
896
|
+
remaining Tier 2 uncertain findings are recorded as
|
|
897
|
+
`UNCERTAIN-OPERATOR-REVIEW` without another Tier 3 call.
|
|
898
|
+
|
|
899
|
+
Tier 3 enables the `EvaluateSuspectedFakeEvidence` BAML call only for Tier 2
|
|
900
|
+
uncertain loose-uniform findings, after Tier 1 duplicate-content,
|
|
901
|
+
uniform-numeric, and missing-reference detectors do not already produce
|
|
902
|
+
suspect findings. Clean audits, Tier 1 suspect audits, and Tier 2
|
|
903
|
+
certain-suspect findings bypass Tier 3. For runner closeouts, a `fake`
|
|
904
|
+
verdict or a non-uncertain judgment below the effective confidence threshold
|
|
905
|
+
produces a non-human `contract_bug` blocker with `metadata.tier3_judgment`.
|
|
906
|
+
`real` at or above threshold proceeds. `uncertain`, timeout, parse failure,
|
|
907
|
+
or wrapper error emits warning metadata but does not automatically block.
|
|
908
|
+
|
|
909
|
+
The Tier 3 input contract is bounded: the wrapper sends a Tier 2 signal
|
|
910
|
+
summary, the sample artifact content truncated to 8192 bytes by default,
|
|
911
|
+
and expected artifact characteristics. The structured output is
|
|
912
|
+
`EvidenceJudgment` with `verdict`, `confidence`, `reasoning`, and
|
|
913
|
+
`specific_concerns`. On timeout or parse failure, the wrapper returns an
|
|
914
|
+
uncertain judgment with `confidence: 0.0` and redacted `tier3_call_error`
|
|
915
|
+
reasoning.
|
|
916
|
+
|
|
917
|
+
Every runner-triggered Tier 3 call appends an `evidence_audit_tier3` event to
|
|
918
|
+
`.phase-loop/events.jsonl`. Event metadata includes `prompt_sha256`,
|
|
919
|
+
`response_sha256`, `verdict`, `confidence`, `token_counts`, `latency_ms`, and
|
|
920
|
+
`estimated_cost_usd`, plus the active budget counters. Token counts and
|
|
921
|
+
estimated cost may be null when the current provider wrapper does not expose
|
|
922
|
+
usage details. The ledger shape is prepared for the downstream
|
|
923
|
+
`phase-loop status --tier-3-history` query surface.
|
|
924
|
+
|
|
925
|
+
Per-phase config is optional at `.phase-loop/evidence-audit.yaml`:
|
|
926
|
+
|
|
927
|
+
```yaml
|
|
928
|
+
tier2_enabled: true
|
|
929
|
+
tier3_enabled: false
|
|
930
|
+
tier3_confidence_threshold: 0.85
|
|
931
|
+
phase_aliases_exclude_tier3:
|
|
932
|
+
- T2DETECTORS
|
|
933
|
+
- T3SCHEMA
|
|
934
|
+
- T3RUNNER
|
|
935
|
+
- T3VALIDATE
|
|
936
|
+
phases:
|
|
937
|
+
FUTUREPHASE:
|
|
938
|
+
tier2_enabled: true
|
|
939
|
+
tier3_enabled: true
|
|
940
|
+
tier3_confidence_threshold: 0.85
|
|
941
|
+
disable_detectors: []
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
The shipped default excludes `T2DETECTORS`, `T3SCHEMA`, `T3RUNNER`, and
|
|
945
|
+
`T3VALIDATE` so v23 cannot trigger Tier 3 against itself even if an operator
|
|
946
|
+
sets the global flag. Malformed config is a repairable non-human
|
|
947
|
+
`contract_bug`.
|
|
948
|
+
|
|
949
|
+
Operator invocation example:
|
|
950
|
+
|
|
951
|
+
```bash
|
|
952
|
+
phase-loop evidence-audit --repo . --enable-tier-3 --json
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
Runner invocation example:
|
|
956
|
+
|
|
957
|
+
```bash
|
|
958
|
+
phase-loop run --repo . --enable-tier-3 --tier-3-budget 3
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
### Enabling Tier 3
|
|
962
|
+
|
|
963
|
+
Operators must treat Tier 3 as an opt-in rollout path, not a default runner
|
|
964
|
+
mode. Before enabling it for a phase, run the non-secret calibration corpus:
|
|
965
|
+
|
|
966
|
+
```bash
|
|
967
|
+
python3 tests/calibrate_tier3.py --dry-run
|
|
968
|
+
python3 tests/calibrate_tier3.py --confidence-threshold 0.85 --fail-on-accuracy-threshold 0.80
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
The dry run validates fixture shape and expected outcomes without live LLM
|
|
972
|
+
calls. A live calibration run reports verdict, confidence, estimated token and
|
|
973
|
+
cost metadata, accuracy, borderline confidence distribution, recommended
|
|
974
|
+
confidence threshold, and total estimated cost. Use the same provider, model,
|
|
975
|
+
and temperature for reproducibility; confidence should normally remain within
|
|
976
|
+
+/-0.05 on the same fixtures.
|
|
977
|
+
|
|
978
|
+
Roll out one phase at a time through `.phase-loop/evidence-audit.yaml`, monitor
|
|
979
|
+
recent invocation summaries with `phase-loop status --tier-3-history`, and
|
|
980
|
+
rollback by disabling the phase entry if false positives, latency, or cost
|
|
981
|
+
exceed operator tolerance. The status-history surface only reports timestamp,
|
|
982
|
+
phase, verdict, confidence, cost, and latency; it must not expose raw prompts,
|
|
983
|
+
raw responses, artifact contents, or provider-supplied payloads.
|
|
984
|
+
|
|
985
|
+
The v23 `phase_aliases_exclude_tier3` entries for `T2DETECTORS`, `T3SCHEMA`,
|
|
986
|
+
`T3RUNNER`, and `T3VALIDATE` remain in place until v23 completes. Removing
|
|
987
|
+
those exclusions is a separate explicit operator opt-in.
|
|
988
|
+
|
|
989
|
+
Exit codes: 0 if clean (no findings), 5 if suspect findings present.
|
|
990
|
+
Use the exit code as a pre-reconcile gate:
|
|
991
|
+
|
|
992
|
+
```bash
|
|
993
|
+
phase-loop evidence-audit --repo . && \
|
|
994
|
+
phase-loop reconcile --repo . --roadmap specs/phase-plans-vN.md \
|
|
995
|
+
--phase <ALIAS> ...
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
## Drift Audit Subcommand
|
|
999
|
+
|
|
1000
|
+
`phase-loop closeout-drift-audit --repo . [--repo ../other] [--days 7]
|
|
1001
|
+
[--scope closeout|all-events] [--json]` is an operator-callable pre-flight
|
|
1002
|
+
for closeout literal drift. It scans `.phase-loop/runs/**/terminal-summary.json`
|
|
1003
|
+
and closeout-class entries in `.phase-loop/events.jsonl`, compares
|
|
1004
|
+
`terminal_status`, `verification_status`, `blocker_class`, executor literals,
|
|
1005
|
+
and dispatch capabilities against the live allowlists imported from
|
|
1006
|
+
`phase_loop_runtime.models`, and reports any literal not currently allowed.
|
|
1007
|
+
|
|
1008
|
+
Default scope is `closeout`. This is the normal closeout reconciliation gate
|
|
1009
|
+
and restricts event-ledger scanning to closeout-class surfaces such as
|
|
1010
|
+
terminal summaries, closeout metadata, automation metadata, manual repairs,
|
|
1011
|
+
and reopen/reconcile events. `--scope all-events` is an explicit forensic mode
|
|
1012
|
+
for wider event-ledger scans; it is not a replacement for the closeout-class
|
|
1013
|
+
pre-flight gate.
|
|
1014
|
+
|
|
1015
|
+
Text output is stable for operator review: it includes the scope, days window,
|
|
1016
|
+
cutoff timestamp, per-repo files/events scanned, malformed event counts, setup
|
|
1017
|
+
diagnostics, and per-field literal drift sections. JSON output includes
|
|
1018
|
+
`allowlists`, `repos`, `counts`, `drift`, and `setup_diagnostics`, so
|
|
1019
|
+
`phase-loop closeout-drift-audit --repo . --json | python3 -m json.tool` is a
|
|
1020
|
+
valid machine-readable smoke check.
|
|
1021
|
+
|
|
1022
|
+
Exit codes are:
|
|
1023
|
+
|
|
1024
|
+
- `0` when no drift or setup errors are found.
|
|
1025
|
+
- `1` when drift is found, for example the recurring reproduction payload
|
|
1026
|
+
`terminal_status: "dry_run"` without adding `dry_run` to `PHASE_STATUSES`.
|
|
1027
|
+
- `2` when setup or input errors prevent a trustworthy scan, such as a missing
|
|
1028
|
+
repo path, missing `.phase-loop` directory, invalid `--days`, or invalid
|
|
1029
|
+
scope.
|
|
1030
|
+
|
|
1031
|
+
Example closeout gate:
|
|
1032
|
+
|
|
1033
|
+
```bash
|
|
1034
|
+
phase-loop closeout-drift-audit --repo . --days 7 --scope closeout && \
|
|
1035
|
+
phase-loop reconcile --repo . --roadmap specs/phase-plans-vN.md \
|
|
1036
|
+
--phase <ALIAS> ...
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
Example cross-repo forensic scan:
|
|
1040
|
+
|
|
1041
|
+
```bash
|
|
1042
|
+
phase-loop closeout-drift-audit --repo . --repo ../governed-pipeline \
|
|
1043
|
+
--days 14 --scope all-events --json
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
### Verified Dirty Closeout Auto-Recovery
|
|
1047
|
+
|
|
1048
|
+
When the runner has already performed a verified dirty closeout recovery, a
|
|
1049
|
+
later `phase-loop status` reconciliation may supersede the stale non-human
|
|
1050
|
+
blocker without requiring an operator-authored `manual_repair` event. The
|
|
1051
|
+
current phase's latest trusted event must carry
|
|
1052
|
+
`metadata.completion_dirty_worktree.reason:
|
|
1053
|
+
verified_dirty_closeout_recovery`, a `metadata.closeout.closeout_commit`,
|
|
1054
|
+
`metadata.terminal_summary.verification_status: passed`, and no
|
|
1055
|
+
`metadata.completion_dirty_worktree.unowned_dirty_paths`.
|
|
1056
|
+
|
|
1057
|
+
The repository worktree must be clean before this reducer fires. A successful
|
|
1058
|
+
reduction marks the phase complete, clears blocker and dirty-path fields,
|
|
1059
|
+
preserves closeout summary metadata, and records the ledger warning reason
|
|
1060
|
+
`clean_verified_dirty_closeout_recovery_superseded_nonhuman_blocker`. Human
|
|
1061
|
+
blockers and unrelated blocker classes remain authoritative; this reducer only
|
|
1062
|
+
repairs stale non-human dirty-closeout blockers.
|
|
1063
|
+
|
|
1064
|
+
### Executor Degradation Cache
|
|
1065
|
+
|
|
1066
|
+
Session-scoped executor degradation is stored at the canonical
|
|
1067
|
+
`.phase-loop/executor-degradation.json` path. There is no legacy
|
|
1068
|
+
`.codex/phase-loop/**` fallback for this sidecar. The JSON object is keyed by
|
|
1069
|
+
executor name, and each record has these fields: `since`, `ttl_seconds`,
|
|
1070
|
+
`demoted_to`, `reason`, `source_phase`, and `blocker_summary`. `demoted_to`
|
|
1071
|
+
is limited to `proof_gated` or `manual_only`.
|
|
1072
|
+
|
|
1073
|
+
`state_degradation.load_degradation(repo)` returns valid records and tolerates
|
|
1074
|
+
missing or corrupted files by returning `{}`. `record_degradation(repo,
|
|
1075
|
+
executor, reason, source_phase, blocker_summary, ttl_seconds,
|
|
1076
|
+
demoted_to="proof_gated")` validates `demoted_to` and writes with a temporary
|
|
1077
|
+
file plus `os.replace`. `active_degraded_executors(repo, *, now=None)` returns
|
|
1078
|
+
the TTL-filtered active executor set, and `clear(repo)` removes the sidecar
|
|
1079
|
+
idempotently. `phase-loop archive-state` does not move this cache, so session
|
|
1080
|
+
demotion can survive runtime ledger archival.
|
|
1081
|
+
|
|
1082
|
+
FOUND publishes this cache contract only. DISPATCH wires launcher emissions,
|
|
1083
|
+
dispatch filtering, and any future `--reset-capability` control.
|
|
1084
|
+
|
|
1085
|
+
### Blocker Classification Heuristics
|
|
1086
|
+
|
|
1087
|
+
Metadata-only launcher preflight must report only redacted probe metadata:
|
|
1088
|
+
command availability, return code, byte counts, and boolean surface presence.
|
|
1089
|
+
It must not persist stdout or stderr excerpts. Missing login, token, or
|
|
1090
|
+
subscription signals classify as `account_or_billing_setup` with
|
|
1091
|
+
`suggested_ttl_seconds: 300` and `demoted_to: proof_gated`.
|
|
1092
|
+
|
|
1093
|
+
Capacity-like provider signals classify as `unretryable_external_outage` with
|
|
1094
|
+
`suggested_ttl_seconds: 1800` and `demoted_to: manual_only`. The frozen
|
|
1095
|
+
capacity patterns are `capacity`, `exhausted`, `rate.limit`, `503`, and
|
|
1096
|
+
`temporarily.unavailable`; `claude auth status` quota-like JSON is reduced
|
|
1097
|
+
through the same capacity path without storing credential or provider-supplied payload
|
|
1098
|
+
values.
|
|
1099
|
+
|
|
1100
|
+
### Session Capability Degradation During Dispatch
|
|
1101
|
+
|
|
1102
|
+
`resolve_dispatch_decision(..., repo=...)` consults
|
|
1103
|
+
`active_degraded_executors(repo)` after live availability has been confirmed
|
|
1104
|
+
for a candidate and before selecting it. A session-degraded executor is skipped
|
|
1105
|
+
silently so a live fallback can run. If every otherwise viable live candidate
|
|
1106
|
+
is session-degraded, dispatch returns `blocked_reason:
|
|
1107
|
+
all_candidates_session_degraded` with a summary naming the action and no
|
|
1108
|
+
credential or provider-supplied payload values.
|
|
1109
|
+
|
|
1110
|
+
`phase-loop run --reset-capability`, `phase-loop resume --reset-capability`,
|
|
1111
|
+
and `phase-loop dry-run --reset-capability` clear only
|
|
1112
|
+
`.phase-loop/executor-degradation.json` before dispatch setup. They do not
|
|
1113
|
+
archive, rewrite, or reconcile `.phase-loop/state.json`,
|
|
1114
|
+
`.phase-loop/events.jsonl`, or legacy `.codex/phase-loop/**`.
|
|
1115
|
+
|
|
1116
|
+
### Rotation
|
|
1117
|
+
|
|
1118
|
+
`phase-loop run`, `phase-loop resume`, and `phase-loop dry-run` accept
|
|
1119
|
+
`--rotate-executors <csv>`, `--rotation-mode <phase|work_unit>`, and
|
|
1120
|
+
`--rotation-on-policy-pin <skip|fallback-next>`. The rotation list uses executor
|
|
1121
|
+
names from the frozen executor vocabulary, trims whitespace, deduplicates in
|
|
1122
|
+
order, and fails closed with a non-human `contract_bug` blocker when the list is
|
|
1123
|
+
empty or contains an invalid executor.
|
|
1124
|
+
|
|
1125
|
+
Rotation injects the current cursor executor as an operator-layer preferred executor
|
|
1126
|
+
before `resolve_dispatch_decision(..., repo=...)`. It rotates only
|
|
1127
|
+
executors; model and effort still come from the profile and Execution Policy
|
|
1128
|
+
chain. Plan and roadmap Execution Policy pins remain higher precedence than
|
|
1129
|
+
rotation. A policy pin launches with its pinned executor regardless of cursor
|
|
1130
|
+
position.
|
|
1131
|
+
|
|
1132
|
+
`--rotation-mode phase` consumes rotation at phase launch boundaries.
|
|
1133
|
+
`--rotation-mode work_unit` consumes rotation at work-unit launch starts. A
|
|
1134
|
+
running phase or work unit keeps the executor selected at launch; rotation never
|
|
1135
|
+
switches an in-flight unit. `--rotation-on-policy-pin=skip` is the default and
|
|
1136
|
+
counts a policy pin as a consumed rotation turn. `fallback-next` preserves the
|
|
1137
|
+
cursor until the next non-pinned phase or work unit consumes it.
|
|
1138
|
+
|
|
1139
|
+
The accepted mode literals are `phase` and `work_unit`. The accepted
|
|
1140
|
+
policy-pin literals are `skip` and `fallback-next`.
|
|
1141
|
+
|
|
1142
|
+
Executor degradation remains the DISPATCH authority: candidates listed in
|
|
1143
|
+
`.phase-loop/executor-degradation.json` are excluded by the existing
|
|
1144
|
+
`active_degraded_executors(repo)` filter during final dispatch. Rotation does
|
|
1145
|
+
not read provider-supplied payloads or reimplement degradation. New launch events with a
|
|
1146
|
+
resolved dispatch decision stamp top-level `selected_executor`; old events
|
|
1147
|
+
without that field reduce identically.
|
|
1148
|
+
|
|
1149
|
+
Default harness policy is explicit. Simple bounded scheduler-assigned lane
|
|
1150
|
+
execution defaults to `executor=pi`; Claude or Anthropic model lanes default to
|
|
1151
|
+
Claude Code CLI unless a policy explicitly selects a Pi-wrapped Claude route
|
|
1152
|
+
and records the override reason. Codex and Gemini fallback routes are
|
|
1153
|
+
CLI-based, reason-coded, and must not silently switch to API-key command
|
|
1154
|
+
adapters.
|
|
1155
|
+
|
|
1156
|
+
## Skill Namespace Contract
|
|
1157
|
+
|
|
1158
|
+
Harness-local workflow skills follow the `<harness>-<workflow>` pattern frozen
|
|
1159
|
+
in `docs/phase-loop/harness-skill-matrix.md`. The active harness families are
|
|
1160
|
+
Codex, Claude Code, Gemini CLI, and OpenCode. Direct Codex, direct Gemini, and
|
|
1161
|
+
direct OpenCode launcher routes remain compatibility-supported during this
|
|
1162
|
+
roadmap, while Claude Code continues to use the `claude -p` path.
|
|
1163
|
+
|
|
1164
|
+
Pi Agent role-style skills are explicit exceptions: `phase-loop-supervisor`,
|
|
1165
|
+
`phase-loop-repair`, and `phase-loop-closeout`. They are adapter roles, not a
|
|
1166
|
+
fifth unnormalized harness workflow family.
|
|
1167
|
+
|
|
1168
|
+
Governed-pipeline `.pipeline/skills/**` is a downstream product/runtime
|
|
1169
|
+
namespace outside dotfiles skill normalization. Dotfiles artifacts may mention
|
|
1170
|
+
canonical workflow skill names as bridge vocabulary, but dotfiles must not
|
|
1171
|
+
rewrite, rename, install, or validate governed-pipeline `.pipeline/skills/**`
|
|
1172
|
+
as `<harness>-<workflow>` skills.
|
|
1173
|
+
|
|
1174
|
+
## Work-Unit Policy
|
|
1175
|
+
|
|
1176
|
+
WORKPOLICY freezes provider-neutral policy metadata for future model and effort
|
|
1177
|
+
selection. It is a contract surface only; v8 does not schedule individual lanes
|
|
1178
|
+
as runner-owned work units.
|
|
1179
|
+
|
|
1180
|
+
The work-unit kind vocabulary is:
|
|
1181
|
+
|
|
1182
|
+
- `roadmap_build`
|
|
1183
|
+
- `phase_plan`
|
|
1184
|
+
- `lane_execute`
|
|
1185
|
+
- `lane_review`
|
|
1186
|
+
- `phase_reducer`
|
|
1187
|
+
- `phase_verify`
|
|
1188
|
+
- `repair`
|
|
1189
|
+
- `closeout`
|
|
1190
|
+
|
|
1191
|
+
The normalized effort vocabulary is:
|
|
1192
|
+
|
|
1193
|
+
- `minimal`
|
|
1194
|
+
- `low`
|
|
1195
|
+
- `medium`
|
|
1196
|
+
- `high`
|
|
1197
|
+
- `xhigh`
|
|
1198
|
+
- `max`
|
|
1199
|
+
|
|
1200
|
+
Unsupported provider policy must resolve to exactly one explicit behavior:
|
|
1201
|
+
|
|
1202
|
+
- `block`: fail closed when the selected provider cannot honor the requested
|
|
1203
|
+
work-unit, model, effort, or thinking policy.
|
|
1204
|
+
- `fallback`: use a named fallback policy, executor, model alias, or effort
|
|
1205
|
+
mapping and record that fallback in launch metadata.
|
|
1206
|
+
- `inherit_default`: use the provider or profile default only when that default
|
|
1207
|
+
inheritance is explicitly recorded.
|
|
1208
|
+
|
|
1209
|
+
Silent downgrade is forbidden. A requested effort such as `xhigh` must not
|
|
1210
|
+
become `high` unless `fallback` or `inherit_default` was selected and recorded.
|
|
1211
|
+
|
|
1212
|
+
Provider capability normalization covers:
|
|
1213
|
+
|
|
1214
|
+
- Codex/OpenAI: accepts the normalized work-unit and effort metadata directly
|
|
1215
|
+
for `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`.
|
|
1216
|
+
- Claude Code: maps normalized policy onto Claude effort controls; unsupported
|
|
1217
|
+
high-end distinctions such as `xhigh` require explicit fallback to `max`.
|
|
1218
|
+
- Gemini CLI: defaults to built-in routing aliases, using `pro` for
|
|
1219
|
+
planning/review and `auto` for execution/repair so Gemini CLI can apply its
|
|
1220
|
+
own fallback behavior. Explicit phase-loop proof runs may still use
|
|
1221
|
+
run-local user-scope `modelConfigs.customAliases` for
|
|
1222
|
+
`phase-loop-plan-high`, `phase-loop-execute-medium`, and
|
|
1223
|
+
`phase-loop-review-high`; those aliases carry
|
|
1224
|
+
`thinkingConfig.thinkingLevel` and non-secret auth selector metadata. A
|
|
1225
|
+
project-local `.gemini/settings.json` beside the prompt workspace is not
|
|
1226
|
+
sufficient evidence for custom policy injection.
|
|
1227
|
+
- Gemini API/OpenAI-compatible: remains metadata-only unless a future adapter
|
|
1228
|
+
phase explicitly owns and verifies an API path.
|
|
1229
|
+
- OpenCode: records normalized work-unit and effort metadata for adapter
|
|
1230
|
+
selection without changing current dispatch behavior.
|
|
1231
|
+
- Pi Agent: consumes repo-local `phase-loop-pi/**` prompts, skills,
|
|
1232
|
+
extensions, and `pi-config/**` installation metadata through a context-file
|
|
1233
|
+
launch. `executor=pi` is bounded to simple scheduler-assigned lane work and
|
|
1234
|
+
never owns global scheduling, runtime ledger mutation, worktree allocation,
|
|
1235
|
+
or merge reduction.
|
|
1236
|
+
- Manual handoff: non-default and selected only by operator, roadmap, or
|
|
1237
|
+
phase-plan policy.
|
|
1238
|
+
- Generic command adapters: require explicit adapter inputs and fail closed
|
|
1239
|
+
when policy cannot be mapped.
|
|
1240
|
+
|
|
1241
|
+
## Automation Handoffs
|
|
1242
|
+
|
|
1243
|
+
This protocol is harness-neutral, not a blanket support claim. Operator-facing
|
|
1244
|
+
docs must pair it with the current maturity matrix in
|
|
1245
|
+
`docs/phase-loop/harness-capability-matrix.md` so shared artifact shapes are
|
|
1246
|
+
not mistaken for proof that every executor is live-supported.
|
|
1247
|
+
|
|
1248
|
+
## Phase-Loop Closeout Schema (v1)
|
|
1249
|
+
|
|
1250
|
+
The canonical closeout schema (`phase_loop_closeout.v1`) is a nested object that describes the terminal state of a phase execution. It is emitted as a JSON object with these top-level fields:
|
|
1251
|
+
|
|
1252
|
+
- `schema`: Always `phase_loop_closeout.v1`.
|
|
1253
|
+
- `phase`: The phase alias (e.g., `RUNNER`).
|
|
1254
|
+
- `terminal_status`: The high-level phase outcome (`complete`, `blocked`, `failed_verification`, `human_required`, `stale_input`).
|
|
1255
|
+
- `automation`: An object describing the next machine steps.
|
|
1256
|
+
- `artifacts`: An object listing produced artifacts and plan metadata.
|
|
1257
|
+
- `verification`: An object describing verification status and evidence.
|
|
1258
|
+
- `blocker`: An object describing why a phase is blocked, if applicable.
|
|
1259
|
+
- `source_bundle`: An object describing the pipeline source context. Standalone
|
|
1260
|
+
closeout keeps this object present with `pipeline_mode: standalone` and may
|
|
1261
|
+
omit Pipeline-only identity fields.
|
|
1262
|
+
- `source_truth_impact`: An advisory object describing metadata-only
|
|
1263
|
+
source-truth impact hints for changed paths.
|
|
1264
|
+
- `lane`: Optional scheduler-owned lane closeout metadata for work-unit
|
|
1265
|
+
launches, containing lane identity, wave identity, worktree identity,
|
|
1266
|
+
verification status, changed paths, and redacted evidence refs.
|
|
1267
|
+
|
|
1268
|
+
Native v1 JSON closeout must not include deprecated root-level v5 automation aliases
|
|
1269
|
+
such as `status`, `next_skill`, `next_command`,
|
|
1270
|
+
`verification_status`, `artifact`, or `artifact_state`. Those values belong
|
|
1271
|
+
inside the nested `automation` object.
|
|
1272
|
+
Terminal-summary extraction remains a legacy compatibility path for rendered
|
|
1273
|
+
`automation:` blocks; it is not the native JSON fixture contract.
|
|
1274
|
+
|
|
1275
|
+
## Native Output Schema Enforcement
|
|
1276
|
+
|
|
1277
|
+
NATIVE added `CLOSEOUT_SCHEMA` in `phase_loop_runtime.models` as the temporary
|
|
1278
|
+
structured-output contract before BAML became the single source of truth. The
|
|
1279
|
+
schema requires
|
|
1280
|
+
`terminal_status`, `verification_status`, `dirty_paths`, and
|
|
1281
|
+
`produced_if_gates`. A closeout that claims `terminal_status=complete` must
|
|
1282
|
+
report at least one produced IF gate at the schema layer.
|
|
1283
|
+
|
|
1284
|
+
Codex live launches that require a closeout write `CLOSEOUT_SCHEMA` to a
|
|
1285
|
+
temporary JSON file, append `--output-schema <path>`, record the path on
|
|
1286
|
+
`LaunchSpec.cleanup_paths`, and remove it after subprocess completion. Claude
|
|
1287
|
+
live launches that require a closeout append `--json-schema <compact-json>`
|
|
1288
|
+
with the same schema. Gemini, OpenCode, PI, command adapters, and manual paths
|
|
1289
|
+
do not receive native CLI schema flags during NATIVE.
|
|
1290
|
+
|
|
1291
|
+
The NATIVE runner still accepts legacy rendered `automation:` blocks during the
|
|
1292
|
+
compatibility window. Native JSON closeouts are normalized back into the shared
|
|
1293
|
+
automation fields before reducer logic runs.
|
|
1294
|
+
|
|
1295
|
+
## BAML Closeout Schema
|
|
1296
|
+
|
|
1297
|
+
BAMLBASE moves the closeout-emission boundary to the declarative
|
|
1298
|
+
`EmitPhaseCloseout` function in
|
|
1299
|
+
`vendor/phase-loop-runtime/src/phase_loop_runtime/baml_src/emit_phase_closeout.baml`. The BAML source
|
|
1300
|
+
defines `PhaseLoopCloseoutV1` with the same root fields as the NATIVE closeout
|
|
1301
|
+
schema: `terminal_status`, `verification_status`, `dirty_paths`,
|
|
1302
|
+
`produced_if_gates`, `next_action`, blocker metadata, and
|
|
1303
|
+
`required_human_inputs`.
|
|
1304
|
+
|
|
1305
|
+
`phase_loop_runtime.baml_modular.build_baml_request("EmitPhaseCloseout",
|
|
1306
|
+
payload)` loads the vendored BAML source and returns the model-facing prompt
|
|
1307
|
+
plus request metadata. Harness prompt injection consumes that rendered prompt
|
|
1308
|
+
instead of duplicating the closeout field ceremony in each skill.
|
|
1309
|
+
|
|
1310
|
+
`phase_loop_runtime.baml_modular.parse_baml_response("EmitPhaseCloseout",
|
|
1311
|
+
raw_text)` parses child closeout output through the BAML runtime and then
|
|
1312
|
+
normalizes the typed value back into the runner's shared automation fields.
|
|
1313
|
+
BAML validation errors are reported as repairable non-human `contract_bug`
|
|
1314
|
+
blockers with non-secret summaries.
|
|
1315
|
+
|
|
1316
|
+
### BAML Prompt Template
|
|
1317
|
+
|
|
1318
|
+
`phase_loop_runtime.baml_modular.render_baml_prompt(prompt_template,
|
|
1319
|
+
context_constants)` performs the repo-local prompt-template pass before BAML
|
|
1320
|
+
loads source text. It supports `{{ name }}` and `{{ name | join(', ') }}` for
|
|
1321
|
+
explicitly provided constants, without adding Jinja2 or changing BAML runtime
|
|
1322
|
+
syntax.
|
|
1323
|
+
|
|
1324
|
+
Closeout prompts currently expose `allowed_terminal_statuses`,
|
|
1325
|
+
`allowed_verification_statuses`, and `allowed_blocker_classes`. These values
|
|
1326
|
+
render from `phase_loop_runtime.models.PHASE_STATUSES`,
|
|
1327
|
+
`phase_loop_runtime.models.BLOCKER_CLASSES`, and the verification-status
|
|
1328
|
+
allowlist so model-facing taxonomy text stays aligned with `models.py`. The
|
|
1329
|
+
blocker-class rendering includes the compatibility `none` marker; closeout
|
|
1330
|
+
payloads should still use `null` when no blocker is present.
|
|
1331
|
+
|
|
1332
|
+
BAML runtime placeholders such as `{{ phase_alias }}`, `{{ ctx.output_format
|
|
1333
|
+
}}`, and control blocks remain BAML-owned and must pass through unchanged.
|
|
1334
|
+
Unknown taxonomy constants are repairable `BamlValidationError` failures, not
|
|
1335
|
+
silent downgrades to stale hard-coded prompt text.
|
|
1336
|
+
|
|
1337
|
+
### Closeout Evidence Audit
|
|
1338
|
+
|
|
1339
|
+
Roadmaps may opt in to the post-commit closeout evidence audit with
|
|
1340
|
+
`closeout_evidence_audit: true` in roadmap frontmatter or in the first H2
|
|
1341
|
+
metadata block. When enabled, commit closeout runs the audit after the closeout
|
|
1342
|
+
commit lands and never amends that commit.
|
|
1343
|
+
|
|
1344
|
+
The audit compares parseable closeout claims such as `Added`, `Created`,
|
|
1345
|
+
`Wrote`, and `Updated` backticked symbols to the closeout commit diff. Filename
|
|
1346
|
+
basename matches, path-suffix matches, or identifier matches in the diff content
|
|
1347
|
+
count as evidence. If evidence is missing, the closeout event is downgraded to
|
|
1348
|
+
`blocked` with `blocker_class: closeout_evidence_drift` and the redacted
|
|
1349
|
+
summary shape `<N> of <M> closeout claims have no matching files in the
|
|
1350
|
+
closeout diff`.
|
|
1351
|
+
|
|
1352
|
+
The frozen blocker literal for this downgrade is `closeout_evidence_drift`.
|
|
1353
|
+
Audit metadata may record only status and counts. Blocker summaries, metadata,
|
|
1354
|
+
logs, and terminal summaries must not include raw commit bodies, raw diff bodies,
|
|
1355
|
+
secret-bearing payloads, or unmatched symbol text.
|
|
1356
|
+
|
|
1357
|
+
### Closeout Hardening
|
|
1358
|
+
|
|
1359
|
+
`EmitPhaseCloseout` uses field-anchored enum lists for `terminal_status` and
|
|
1360
|
+
`verification_status`, an explicit `dry_run` negative example, a
|
|
1361
|
+
terminal-status decision tree, and field-pair invariants for
|
|
1362
|
+
`terminal_status` plus `verification_status`. `dry_run` remains an event-level
|
|
1363
|
+
execution mode and must not appear in a phase closeout payload.
|
|
1364
|
+
|
|
1365
|
+
Native closeout parsing first runs
|
|
1366
|
+
`phase_loop_runtime.discovery.parse_closeout_payload_doc(text, kind)` over the
|
|
1367
|
+
extracted JSON payload. Unknown `terminal_status`, `verification_status`, or
|
|
1368
|
+
`blocker_class` literals soft-fail into `CloseoutParseError` diagnostics before
|
|
1369
|
+
BAML schema parsing. The runner converts those diagnostics into non-human
|
|
1370
|
+
`contract_bug` blockers whose summary names the invalid literal and field.
|
|
1371
|
+
|
|
1372
|
+
Operator remediation is deliberate: patch the executor prompt when the literal
|
|
1373
|
+
is prompt drift, or amend the runner allowlist and reinstall the runtime when a
|
|
1374
|
+
new literal is intentionally added. The runtime must not coerce unknown
|
|
1375
|
+
closeout literals into nearby statuses.
|
|
1376
|
+
|
|
1377
|
+
## Schema-Flow Architecture
|
|
1378
|
+
|
|
1379
|
+
`vendor/phase-loop-runtime/src/phase_loop_runtime/baml_src/emit_phase_closeout.baml` is the canonical
|
|
1380
|
+
closeout contract. `phase_loop_runtime.baml_modular.export_function_schema(
|
|
1381
|
+
"EmitPhaseCloseout")` reads that BAML function, exports the `PhaseLoopCloseoutV1`
|
|
1382
|
+
object shape, applies documented Codex/Claude JSON Schema dialect
|
|
1383
|
+
normalization, and fails with `BamlValidationError` when the BAML function or
|
|
1384
|
+
return class cannot be exported. Missing BAML schema export is a repairable
|
|
1385
|
+
non-human `contract_bug`; the runtime must not silently downgrade to a duplicate
|
|
1386
|
+
hand-written schema.
|
|
1387
|
+
The canonical helper call is `export_function_schema("EmitPhaseCloseout")`.
|
|
1388
|
+
|
|
1389
|
+
`phase_loop_runtime.models.CLOSEOUT_SCHEMA` is a compatibility import path for
|
|
1390
|
+
existing callers and is computed from
|
|
1391
|
+
`export_function_schema("EmitPhaseCloseout")` at module import time. Codex
|
|
1392
|
+
launches that require closeout write that schema to `--output-schema <path>`;
|
|
1393
|
+
Claude launches pass the same canonical schema through
|
|
1394
|
+
`--json-schema <compact-json>`. Gemini, OpenCode, and PI do not expose matching
|
|
1395
|
+
native flags, so their closeout prompts embed deterministic schema-description
|
|
1396
|
+
text from `inject_schema_description(prompt, schema)` with the canonical schema
|
|
1397
|
+
hash and ordered fields.
|
|
1398
|
+
|
|
1399
|
+
All executor closeouts still pass through `parse_closeout_payload_doc(...)`,
|
|
1400
|
+
`parse_baml_response("EmitPhaseCloseout", raw_text)`, and then through IF-Gate
|
|
1401
|
+
Tier 1 validation.
|
|
1402
|
+
The runner compares `produced_if_gates` with the active plan's declared
|
|
1403
|
+
`Produces` / `Interfaces provided` gates, so native flags, prompt embedding,
|
|
1404
|
+
BAML parse, and IF-gate cross-check all consume the same BAML-authored schema
|
|
1405
|
+
flow.
|
|
1406
|
+
|
|
1407
|
+
### Strict Mode Transition
|
|
1408
|
+
|
|
1409
|
+
BAMLBASE ends the NATIVE compatibility window for native JSON closeouts.
|
|
1410
|
+
Completed closeouts that omit `produced_if_gates`, report an empty gate list,
|
|
1411
|
+
or otherwise violate `PhaseLoopCloseoutV1` are rejected before runner state can
|
|
1412
|
+
advance. Legacy rendered `automation:` blocks remain a compatibility path only
|
|
1413
|
+
for actions that do not emit native JSON closeouts. IF-Gate Tier 1 validation
|
|
1414
|
+
continues to compare the typed `produced_if_gates` list with the active phase
|
|
1415
|
+
plan's declared gates.
|
|
1416
|
+
|
|
1417
|
+
## IF-Gate Tier 1 Validation
|
|
1418
|
+
|
|
1419
|
+
NATIVE adds `validate_produced_gates(plan_path, closeout_payload)` in
|
|
1420
|
+
`phase_loop_runtime.closeout_validation`. It extracts the active phase plan's
|
|
1421
|
+
declared IF gates from `Produces` and lane `Interfaces provided` declarations,
|
|
1422
|
+
then compares them with closeout `produced_if_gates`.
|
|
1423
|
+
|
|
1424
|
+
During the NATIVE compatibility window, a completed legacy closeout with no
|
|
1425
|
+
`produced_if_gates` field records a warning and remains compatible. A completed
|
|
1426
|
+
closeout with `produced_if_gates: []`, missing expected gates, or unexpected
|
|
1427
|
+
gates is blocked as repairable non-human `contract_bug`. This is the Tier 1
|
|
1428
|
+
scope check only; filesystem evidence verification remains out of scope.
|
|
1429
|
+
|
|
1430
|
+
#### Automation Object
|
|
1431
|
+
|
|
1432
|
+
Phase-loop aware skills and manual TUI runs must emit a machine-readable `automation` object with this exact field set:
|
|
1433
|
+
|
|
1434
|
+
```yaml
|
|
1435
|
+
automation:
|
|
1436
|
+
status: <phase status literal>
|
|
1437
|
+
next_skill: <skill name or none>
|
|
1438
|
+
next_command: <command string or none>
|
|
1439
|
+
next_model_hint: <model profile key or none>
|
|
1440
|
+
next_effort_hint: <reasoning effort hint or none>
|
|
1441
|
+
human_required: <true|false>
|
|
1442
|
+
blocker_class: <frozen blocker class or none>
|
|
1443
|
+
blocker_summary: <short actionable summary or none>
|
|
1444
|
+
required_human_inputs: []
|
|
1445
|
+
verification_status: <not_run|passed|failed|blocked>
|
|
1446
|
+
artifact: <absolute artifact path or none>
|
|
1447
|
+
artifact_state: <staged|tracked|modified|unstaged|untracked|blocked|none>
|
|
1448
|
+
```
|
|
1449
|
+
|
|
1450
|
+
#### Artifacts Object
|
|
1451
|
+
|
|
1452
|
+
- `plan_path`: Path to the phase plan file.
|
|
1453
|
+
- `plan_sha256`: SHA-256 of the phase plan file.
|
|
1454
|
+
- `artifact_paths`: A map of logical names to absolute paths for produced artifacts.
|
|
1455
|
+
- `changed_paths`: A list of repository paths modified during the phase.
|
|
1456
|
+
- `evidence_refs`: Metadata-only evidence references. Entries may include paths,
|
|
1457
|
+
labels, and hashes, but not raw transcripts, provider-supplied payloads, local
|
|
1458
|
+
environment values, credentials, or private evidence bytes.
|
|
1459
|
+
|
|
1460
|
+
#### Source Truth Impact Object
|
|
1461
|
+
|
|
1462
|
+
`source_truth_impact` is advisory metadata only. Impact hints are advisory:
|
|
1463
|
+
governed-pipeline owns canonical refresh, replan, and block decisions.
|
|
1464
|
+
Dotfiles must not update governed-pipeline canonical docs/specs, `.pipeline/**`,
|
|
1465
|
+
Portal contracts, Greenfield authority files, raw-evidence, or legacy
|
|
1466
|
+
`.codex/phase-loop/` state in response to these hints.
|
|
1467
|
+
|
|
1468
|
+
- `changed_path_boundaries`: A list of objects containing `path` and `category`.
|
|
1469
|
+
Category is one of `code`, `tests`, `docs`, `specs`,
|
|
1470
|
+
`active_canonical_spec`, `managed_root_mirror_spec`, `mirror_manifest`,
|
|
1471
|
+
`archive_manifest`, `archived_spec`, `unmanaged_spec`,
|
|
1472
|
+
`pipeline_sources`, `portal_contract_refs`, `greenfield_authority_refs`,
|
|
1473
|
+
or `unknown`. The broad `specs` category remains a compatibility literal,
|
|
1474
|
+
but standalone root `specs/**` paths classify as `unmanaged_spec` unless a
|
|
1475
|
+
validated source bundle marks them as managed mirror or archived material.
|
|
1476
|
+
- `canonical_refresh_recommended`: Boolean advisory signal for source-truth
|
|
1477
|
+
sensitive changes.
|
|
1478
|
+
- `canonical_refresh_reason_codes`: A list using `docs_source_truth_touched`,
|
|
1479
|
+
`specs_source_truth_touched`, `active_specs_touched`,
|
|
1480
|
+
`managed_mirror_specs_touched`, `mirror_manifests_touched`,
|
|
1481
|
+
`archive_manifests_touched`, `archived_specs_touched`,
|
|
1482
|
+
`unmanaged_specs_touched`, `adoption_contracts_touched`,
|
|
1483
|
+
`contract_refs_touched`, `pipeline_sources_touched`,
|
|
1484
|
+
`portal_contract_refs_touched`, or `greenfield_authority_refs_touched`.
|
|
1485
|
+
- `redaction_posture`: One of `metadata_only` or
|
|
1486
|
+
`rejected_forbidden_metadata`.
|
|
1487
|
+
|
|
1488
|
+
Impact and evidence metadata exclude raw patch bodies, raw transcripts, secret-like values,
|
|
1489
|
+
absolute private paths, provider-supplied payloads, credential-bearing payloads, local environment contents,
|
|
1490
|
+
and private evidence bytes. Redaction violations make the closeout
|
|
1491
|
+
malformed instead of preserving the forbidden content.
|
|
1492
|
+
|
|
1493
|
+
#### Verification Object
|
|
1494
|
+
|
|
1495
|
+
- `status`: One of the local verification status literals such as `passed`,
|
|
1496
|
+
`failed`, `blocked`, `not_run`, or `unknown`.
|
|
1497
|
+
- `commands`: Optional command strings used as metadata-only proof of what was
|
|
1498
|
+
checked.
|
|
1499
|
+
- `agent_reported_verification_status`: Additive metadata preserving the
|
|
1500
|
+
executor's self-reported verification value before runner evidence
|
|
1501
|
+
validation.
|
|
1502
|
+
- `results`: Optional metadata-only validation records. Closeout acceptance of
|
|
1503
|
+
`passed` is authoritative only when backed by a valid IF-0-VC-1
|
|
1504
|
+
`verification.json` artifact whose sibling `verification.log` hash matches
|
|
1505
|
+
`log_sha256` and whose command, env-refresh, and suite exit codes are all
|
|
1506
|
+
zero.
|
|
1507
|
+
|
|
1508
|
+
By default, `PHASE_LOOP_VERIFY_ENFORCE=hard` behavior is active: missing,
|
|
1509
|
+
malformed, nonzero, or tampered verification evidence blocks a passed closeout
|
|
1510
|
+
with blocker class `verification_evidence_missing`. Operators may set
|
|
1511
|
+
`PHASE_LOOP_VERIFY_ENFORCE=warn` to preserve the reported closeout while
|
|
1512
|
+
emitting structured warning metadata during staged adoption. Operational
|
|
1513
|
+
evidence amendments must use
|
|
1514
|
+
`verification_evidence.append_evidence_entry(doc_path, entry)`, which appends a
|
|
1515
|
+
fresh entry with a runner timestamp and preserves prior document bytes. A
|
|
1516
|
+
blocked operational gate may be re-verdicted only from a fresh entry of the
|
|
1517
|
+
originally specified evidence kind.
|
|
1518
|
+
|
|
1519
|
+
#### DFPARSOAK Receipt Boundary
|
|
1520
|
+
|
|
1521
|
+
DFPARSOAK is the dotfiles substrate soak for the parallel bridge. A valid
|
|
1522
|
+
DFPARSOAK receipt cites Greenfield `GFPARSOAK` and governed-pipeline
|
|
1523
|
+
`GPPARSOAK` as metadata-only upstream receipts with phase alias, repo-relative
|
|
1524
|
+
path, sha256 digest, produced interfaces, verification status, and redacted
|
|
1525
|
+
evidence refs.
|
|
1526
|
+
|
|
1527
|
+
The local wave proof uses scheduler-owned git-worktree assignment metadata and
|
|
1528
|
+
records Pi Agent default coverage, Claude Code CLI exception coverage, and
|
|
1529
|
+
Codex/Gemini fallback coverage. Fallback or default inheritance must be
|
|
1530
|
+
explicitly recorded; silent downgrade is invalid.
|
|
1531
|
+
|
|
1532
|
+
DFPARSOAK closeout evidence must remain redacted evidence handles or hashes. It
|
|
1533
|
+
must preserve no sibling-repo mutation and must not contain raw logs, raw
|
|
1534
|
+
transcripts, raw prompts, provider-supplied payloads, credentials, local environment contents, raw
|
|
1535
|
+
diffs, ignored private paths, or host-only evidence paths.
|
|
1536
|
+
|
|
1537
|
+
#### Source Bundle Object
|
|
1538
|
+
|
|
1539
|
+
- `pipeline_mode`: One of `standalone`, `pipeline_optional`, or
|
|
1540
|
+
`pipeline_required`.
|
|
1541
|
+
- `path`: Path to the source bundle JSON. Required for `pipeline_required`;
|
|
1542
|
+
optional or absent for standalone closeout.
|
|
1543
|
+
- `sha256`: SHA-256 of the source bundle. Required for `pipeline_required`;
|
|
1544
|
+
optional or absent for standalone closeout.
|
|
1545
|
+
- `phase_id`: The canonical pipeline phase ID. Required for
|
|
1546
|
+
`pipeline_required`; optional or absent for standalone closeout.
|
|
1547
|
+
- `protected_sources`: Optional metadata-only protected-source echo. Entries
|
|
1548
|
+
may include `path`, `category`, `sha256`, and adoption-sensitive `role`, but
|
|
1549
|
+
must not include raw specification bodies, raw patch bodies, provider-supplied payloads, credentials,
|
|
1550
|
+
local environment contents, private evidence, or absolute private paths.
|
|
1551
|
+
|
|
1552
|
+
Pipeline-required execution must fail closed before child launch when the plan
|
|
1553
|
+
or deterministic bridge output cannot supply matching `source_bundle.path`,
|
|
1554
|
+
`source_bundle.sha256`, `source_bundle.phase_id`, `source_bundle.pipeline_mode`,
|
|
1555
|
+
`phase`, `phase_alias`, `plan_path`, and `plan_sha256`. The resulting direct
|
|
1556
|
+
closeout is still a typed `phase_loop_closeout.v1` blocker with metadata-only
|
|
1557
|
+
diagnostics. Dotfiles reports the consumed identity; governed-pipeline remains
|
|
1558
|
+
the canonical source authority.
|
|
1559
|
+
- `pipeline_mode`: One of `standalone`, `pipeline_optional`, `pipeline_required`.
|
|
1560
|
+
|
|
1561
|
+
#### Lane Object
|
|
1562
|
+
|
|
1563
|
+
The optional `lane` object is present only when a closeout summarizes a
|
|
1564
|
+
runner-assigned work unit. It records scheduler-owned metadata without making
|
|
1565
|
+
dotfiles the scheduler, runtime ledger, worktree allocator, merge reducer, or
|
|
1566
|
+
authority-digest owner:
|
|
1567
|
+
|
|
1568
|
+
- `lane_id`: Lane identity selected by the phase plan.
|
|
1569
|
+
- `wave_id`: Wave identity assigned by the scheduler.
|
|
1570
|
+
- `worktree_path`: Worktree identity assigned by the scheduler.
|
|
1571
|
+
- `verification_status`: Work-unit verification result.
|
|
1572
|
+
- `changed_paths`: Repository paths changed by the work unit.
|
|
1573
|
+
- `evidence_refs`: Redacted evidence references such as artifact paths and
|
|
1574
|
+
digests; raw transcripts, credentials, private file contents, and provider
|
|
1575
|
+
tokens are not closeout fields.
|
|
1576
|
+
|
|
1577
|
+
When human-readable top-level handoff fields such as `artifact`,
|
|
1578
|
+
`artifact_state`, `next_skill`, or `next_command` are also present, they must
|
|
1579
|
+
agree with `automation.*` or the handoff is malformed.
|
|
1580
|
+
|
|
1581
|
+
The phase status vocabulary used by `automation.status` and reconciled state is
|
|
1582
|
+
frozen to `PHASE_STATUSES`:
|
|
1583
|
+
|
|
1584
|
+
- `unplanned`
|
|
1585
|
+
- `planned`
|
|
1586
|
+
- `executing`
|
|
1587
|
+
- `executed`
|
|
1588
|
+
- `awaiting_phase_closeout`
|
|
1589
|
+
- `complete`
|
|
1590
|
+
- `blocked`
|
|
1591
|
+
- `unknown`
|
|
1592
|
+
|
|
1593
|
+
`EVENT_STATUSES` is the event-ledger validation vocabulary. It includes all
|
|
1594
|
+
`PHASE_STATUSES` plus the event-only status:
|
|
1595
|
+
|
|
1596
|
+
- `plan_skipped`
|
|
1597
|
+
|
|
1598
|
+
## Roadmap Validation
|
|
1599
|
+
|
|
1600
|
+
Roadmap-aware command-start paths run warning-only phase heading validation
|
|
1601
|
+
after roadmap selection and before normal execution. The validator only
|
|
1602
|
+
examines `### Phase ...` heading lines; it does not validate roadmap body
|
|
1603
|
+
content, enforce a numbering convention, rewrite roadmap files, or change event
|
|
1604
|
+
provenance semantics.
|
|
1605
|
+
|
|
1606
|
+
Each validation finding carries:
|
|
1607
|
+
|
|
1608
|
+
- `line_number`
|
|
1609
|
+
- `raw_text`
|
|
1610
|
+
- `reason`
|
|
1611
|
+
- `suggested_fix`
|
|
1612
|
+
|
|
1613
|
+
Warnings are emitted to stderr using this operator-facing shape:
|
|
1614
|
+
|
|
1615
|
+
```text
|
|
1616
|
+
phase-loop roadmap warning: line <line>: <reason>; raw heading: '<heading>'; suggested fix: <fix>
|
|
1617
|
+
```
|
|
1618
|
+
|
|
1619
|
+
The validator reports loose phase-heading candidates that strict
|
|
1620
|
+
`PHASE_HEADING_RE` would not include in phase hash lookup, duplicate phase
|
|
1621
|
+
aliases, and aliases outside `[A-Z][A-Z0-9._-]*`. Commands must continue with
|
|
1622
|
+
their existing return-code policy and stdout payloads. Common fixes are to use
|
|
1623
|
+
`### Phase <number> - Title (ALIAS)`, keep aliases uppercase, and give each
|
|
1624
|
+
phase a unique final parenthesized alias.
|
|
1625
|
+
|
|
1626
|
+
## Event Ledger Records
|
|
1627
|
+
|
|
1628
|
+
Durable loop events are append-only records stored in the active runtime ledger.
|
|
1629
|
+
Shared event semantics include:
|
|
1630
|
+
|
|
1631
|
+
- `schema_version: 2`
|
|
1632
|
+
- `roadmap_sha256`
|
|
1633
|
+
- `phase_sha256`
|
|
1634
|
+
- event status literals from `EVENT_STATUSES`
|
|
1635
|
+
- blocker metadata when a blocker exists
|
|
1636
|
+
- optional top-level `selected_executor` when dispatch or work-unit launch
|
|
1637
|
+
selected an executor
|
|
1638
|
+
- model provenance and executor metadata when available
|
|
1639
|
+
- dispatch metadata when selection occurs: `selected_executor`, `source`,
|
|
1640
|
+
`selected_via`, `considered_executors`, `fallback_applied`,
|
|
1641
|
+
`blocked_reason`, and `blocked_summary`
|
|
1642
|
+
- optional delegation metadata when a run proposes or launches nested work:
|
|
1643
|
+
request contract, approval or denial decision, budget metadata, and
|
|
1644
|
+
parent-child lineage pointing to child artifacts without copying prompt bodies
|
|
1645
|
+
or collapsing native Claude team activity into runner-visible child work
|
|
1646
|
+
|
|
1647
|
+
Manual/operator events must use the same contract surface as autonomous runs.
|
|
1648
|
+
Legacy hashless records may remain visible for audit but must not drive future
|
|
1649
|
+
autonomous execution.
|
|
1650
|
+
|
|
1651
|
+
## Hotfix Work Unit
|
|
1652
|
+
|
|
1653
|
+
`phase-loop hotfix --init-stub <path>` writes a minimal stub with `objective`
|
|
1654
|
+
and `verification_command` fields and exits without creating a run directory.
|
|
1655
|
+
|
|
1656
|
+
`phase-loop hotfix --reason <text> --plan <stub-path>` is the sanctioned
|
|
1657
|
+
emergency path for a single bounded change with no interface freeze. Anything
|
|
1658
|
+
that changes interfaces, roadmap scope, or downstream work uses a roadmap
|
|
1659
|
+
phase instead.
|
|
1660
|
+
|
|
1661
|
+
A hotfix execution creates `.phase-loop/runs/<ts>-hotfix-<slug>/`, records
|
|
1662
|
+
launch metadata, runs dependency-manifest env refresh plus the stub command and
|
|
1663
|
+
the effective suite command through IF-0-VC-1 verification evidence, validates
|
|
1664
|
+
the artifact with the same RG gate semantics used for execute closeout, and
|
|
1665
|
+
appends a closeout event containing `work_unit: hotfix`, the redacted reason,
|
|
1666
|
+
the plan stub, `verification_artifact_path`, `verification_log_path`, and the
|
|
1667
|
+
artifact validation summary. A hotfix closeout cannot report
|
|
1668
|
+
`verification_status: passed` unless the validation accepts the evidence
|
|
1669
|
+
artifact and sibling log.
|
|
1670
|
+
|
|
1671
|
+
## Closeout Event Emission
|
|
1672
|
+
|
|
1673
|
+
After a live child executor emits a valid native closeout, the runner appends an
|
|
1674
|
+
executor-terminal `LoopEvent` before applying later runner-owned dirty-path or
|
|
1675
|
+
closeout classification. The event is append-only evidence with:
|
|
1676
|
+
|
|
1677
|
+
- `action: executor.closeout`
|
|
1678
|
+
- `status` equal to the executor closeout `terminal_status`
|
|
1679
|
+
- current `roadmap_sha256` and `phase_sha256` from
|
|
1680
|
+
`event_provenance(roadmap, phase)`
|
|
1681
|
+
- `metadata.executor_closeout_event.source_status`
|
|
1682
|
+
- `metadata.executor_closeout_event.verification_status`
|
|
1683
|
+
- `metadata.executor_closeout_event.produced_if_gates`
|
|
1684
|
+
- `metadata.executor_closeout_event.dirty_paths`
|
|
1685
|
+
- normalized child automation metadata under `metadata.child_automation`
|
|
1686
|
+
|
|
1687
|
+
Writers emit only the `executor.closeout` action for this executor-terminal
|
|
1688
|
+
record. Legacy ledgers that contain `action: run` with a dict-valued
|
|
1689
|
+
`metadata.executor_closeout_event` remain valid audit evidence; reducers
|
|
1690
|
+
normalize that legacy shape to `executor.closeout` at read time for identity and
|
|
1691
|
+
status processing without rewriting the on-disk event.
|
|
1692
|
+
|
|
1693
|
+
The executor-terminal event does not replace the later runner-classified event.
|
|
1694
|
+
Reducer authority remains ledger-order based: the most recent terminal event
|
|
1695
|
+
with valid provenance wins. A later runner-classified `blocked` event therefore
|
|
1696
|
+
supersedes an earlier executor-terminal `complete`, while the earlier executor
|
|
1697
|
+
event remains preserved for audit and recovery.
|
|
1698
|
+
|
|
1699
|
+
## Live Adapter Contract
|
|
1700
|
+
|
|
1701
|
+
The live-adapter readiness contract is frozen around these shared code surfaces:
|
|
1702
|
+
|
|
1703
|
+
- `LaunchRequest` carries adapter input selection: repo, roadmap, phase, plan,
|
|
1704
|
+
model, permission policy, injection metadata, dispatch policy, and optional
|
|
1705
|
+
delegation lineage. Mixed-run delegation keeps the resolved
|
|
1706
|
+
`DispatchDecision`, typed `DelegationRequest`, and `ParentChildRunMetadata`
|
|
1707
|
+
on the shared request object instead of reconstructing them later. TEAMGOV
|
|
1708
|
+
extends this request contract with `claude_execution_mode` literals
|
|
1709
|
+
`solo`, `subagent`, and `agent_team`, plus optional typed
|
|
1710
|
+
`claude_team_policy` and `phase_team_eligibility` metadata for governed
|
|
1711
|
+
Claude-native collaboration.
|
|
1712
|
+
- `LaunchSpec` carries the reduced launch contract written to `launch.json`:
|
|
1713
|
+
the redacted command, availability, proof gate, promotion status, auth
|
|
1714
|
+
preflight mode, timeout posture, output-capture format, delivery literals
|
|
1715
|
+
`prompt_only`, `inline`, `stdin`, `context_file`, and `manual`, plus the
|
|
1716
|
+
`terminal-summary.json` artifact path contract. When an adapter needs
|
|
1717
|
+
executor-specific launch state such as permission posture, selected agent,
|
|
1718
|
+
provider-qualified model, or reasoning variant, that metadata must also stay
|
|
1719
|
+
in `LaunchSpec` and `launch.json` instead of being inferred later from raw
|
|
1720
|
+
output. The generic `command` adapter is frozen as an explicit
|
|
1721
|
+
`command_template` contract: it records `command_adapter_name`,
|
|
1722
|
+
`command_template`, `wrapped_cwd`, delivery mode, `context_path`, and
|
|
1723
|
+
`context_sha256`; it must fail closed when required placeholders or adapter
|
|
1724
|
+
inputs are missing. Governed Claude launch specs must also preserve the
|
|
1725
|
+
resolved `claude_execution_mode`, the typed `claude_team_policy`, and the
|
|
1726
|
+
evaluated `phase_team_eligibility` so unsafe native-team requests are
|
|
1727
|
+
rejected before launch and safe governed launches record their policy even
|
|
1728
|
+
when public task-list CLI flags remain unavailable.
|
|
1729
|
+
- `LaunchResult` carries the observed child result and the same reduced
|
|
1730
|
+
availability metadata back into events and monitors.
|
|
1731
|
+
- `ExecutorCapabilityRecord` is the frozen registry surface for per-executor
|
|
1732
|
+
live readiness, proof-gate status, promotion requirements, permission
|
|
1733
|
+
posture, auth-preflight probe mode, timeout policy, output capture, and
|
|
1734
|
+
terminal-summary reduction expectations. The registry vocabulary is frozen to
|
|
1735
|
+
`promotion_status` literals `live`, `proof_gated`, and `manual_only`; shared
|
|
1736
|
+
failure literals `adapter_failure` and `phase_failure`; and blocker posture
|
|
1737
|
+
literals `human_required` and `repairable_non_human`.
|
|
1738
|
+
|
|
1739
|
+
Later adapter phases may fill in executor-specific command vectors and
|
|
1740
|
+
disposable proof artifacts, but they must not move these shared fields into ad
|
|
1741
|
+
hoc dicts or harness-specific markdown.
|
|
1742
|
+
|
|
1743
|
+
Shared failure reduction rules are also frozen here:
|
|
1744
|
+
|
|
1745
|
+
- `adapter_failure` covers launch, output-capture, automation-closeout,
|
|
1746
|
+
terminal-summary, stale-handoff, and other executor-path failures where the
|
|
1747
|
+
selected harness could not satisfy the shared contract for the requested
|
|
1748
|
+
phase.
|
|
1749
|
+
- `phase_failure` covers requested work that ran inside the shared contract but
|
|
1750
|
+
still failed because the product change, test result, or verification outcome
|
|
1751
|
+
was genuinely unsuccessful.
|
|
1752
|
+
- `human_required` blockers require a true operator action such as account,
|
|
1753
|
+
billing, secret, admin, destructive-operation, or product-decision input.
|
|
1754
|
+
- `repairable_non_human` blockers are runner or adapter failures that should
|
|
1755
|
+
route back through repair or planning instead of being described as success.
|
|
1756
|
+
|
|
1757
|
+
## Launch Artifacts
|
|
1758
|
+
|
|
1759
|
+
Observed launches reduce into these shared artifacts:
|
|
1760
|
+
|
|
1761
|
+
- `launch.json` records the launch request/spec metadata, redacted command,
|
|
1762
|
+
availability, proof gate, promotion requirements, auth-preflight posture,
|
|
1763
|
+
output capture, delivery metadata, `context_path`, `context_sha256`,
|
|
1764
|
+
`expected_skill_pack`, `skill_bundle_sha256`, `fallback_mode`, and artifact
|
|
1765
|
+
paths without persisting raw prompt or skill-body content. Mixed-run launch
|
|
1766
|
+
records must also preserve `dispatch_decision`, `delegation_request`,
|
|
1767
|
+
`delegation_decision`, and `parent_child` lineage metadata when present.
|
|
1768
|
+
Governed Claude launch metadata must additionally preserve
|
|
1769
|
+
`claude_execution_mode`, `claude_team_policy`, and
|
|
1770
|
+
`phase_team_eligibility`.
|
|
1771
|
+
- `phase-loop` is the neutral operator alias for the same runner implementation
|
|
1772
|
+
as `codex-phase-loop`; documentation and bootstrap may expose both names, but
|
|
1773
|
+
they must point to the same runtime contract.
|
|
1774
|
+
- Live Claude launches must also reduce the non-interactive `claude -p`
|
|
1775
|
+
response back into the shared `automation:` contract. Missing or malformed
|
|
1776
|
+
closeout is a repairable non-human blocker, not silent success. TEAMGOV keeps
|
|
1777
|
+
team creation, task creation, task list, and direct teammate messaging
|
|
1778
|
+
denied by default outside governed native-team mode, and native-team policy
|
|
1779
|
+
metadata must stay distinct from runner-brokered delegation lineage.
|
|
1780
|
+
- Live Gemini launches may use a minimal `--prompt` that points at the
|
|
1781
|
+
run-local `context.md` artifact, must set `--skip-trust` for disposable
|
|
1782
|
+
headless repos, and must reduce `text`, `json`, or `stream-json` output back
|
|
1783
|
+
into the shared `automation:` contract. Installed-skill conflicts stay
|
|
1784
|
+
warning-only when repo-sourced injected context succeeded.
|
|
1785
|
+
- Live OpenCode launches must use `opencode run` with explicit `--dir`,
|
|
1786
|
+
`--agent`, `--model`, `--format json`, and the shared `context.md` artifact.
|
|
1787
|
+
If the selected OpenCode agent posture would otherwise remain permissive, the
|
|
1788
|
+
runner must record that posture explicitly and refuse the launch unless an
|
|
1789
|
+
operator or runner policy intentionally opts in. OpenCode `json` output and
|
|
1790
|
+
closeout-bearing default output must both reduce back into the shared
|
|
1791
|
+
`automation:` contract, and installed-skill conflicts remain warning-only
|
|
1792
|
+
when repo-sourced injected context succeeded.
|
|
1793
|
+
- `.phase-loop/runs/<run-id>/context.md` is the frozen run-local context
|
|
1794
|
+
artifact path when the selected delivery mode requires a file. It must live
|
|
1795
|
+
beside `launch.json` and contain the same repo-sourced workflow bundle the
|
|
1796
|
+
adapter receives: workflow command, action-specific instructions, injected
|
|
1797
|
+
skill bodies, delegation guidance when present, and closeout requirements.
|
|
1798
|
+
- `terminal-summary.json` records the shared child-exit reduction used by
|
|
1799
|
+
closeout, repair, and monitor flows.
|
|
1800
|
+
|
|
1801
|
+
Adapters may add transport-specific metadata under additional keys, but the
|
|
1802
|
+
artifact filenames and their shared contract roles stay frozen.
|
|
1803
|
+
|
|
1804
|
+
## Operator Maturity Boundaries
|
|
1805
|
+
|
|
1806
|
+
The shared protocol freezes artifact shapes, reentry semantics, delegation
|
|
1807
|
+
metadata, and closeout reduction across supported harnesses. It does not by
|
|
1808
|
+
itself promote an executor to live-supported status. Live support, experimental
|
|
1809
|
+
status, or manual-only posture must come from the current roadmap closeout and
|
|
1810
|
+
capability matrix, while this document stays limited to the common artifact and
|
|
1811
|
+
monitoring contract.
|
|
1812
|
+
|
|
1813
|
+
Operator-facing maturity labels remain distinct from registry promotion
|
|
1814
|
+
statuses:
|
|
1815
|
+
|
|
1816
|
+
- `live-supported` means the current roadmap closeout and disposable proof
|
|
1817
|
+
support autonomous use of that executor.
|
|
1818
|
+
- `proof-blocked` means the executor can still expose launch metadata or manual
|
|
1819
|
+
reentry surfaces, but the latest shared disposable proof did not satisfy the
|
|
1820
|
+
autonomous contract.
|
|
1821
|
+
- `experimental` means the executor contract is intentionally narrower than the
|
|
1822
|
+
first-class live adapters.
|
|
1823
|
+
- `manual-only` means manual import or operator reentry is supported without a
|
|
1824
|
+
current autonomous live-support claim.
|
|
1825
|
+
|
|
1826
|
+
## Terminal Summary
|
|
1827
|
+
|
|
1828
|
+
`build_terminal_summary(...)` must emit exactly these fields:
|
|
1829
|
+
|
|
1830
|
+
- `terminal_status`
|
|
1831
|
+
- `terminal_blocker`
|
|
1832
|
+
- `verification_status`
|
|
1833
|
+
- `next_action`
|
|
1834
|
+
- `dirty_paths`
|
|
1835
|
+
- `phase_owned_dirty`
|
|
1836
|
+
- `phase_owned_dirty_paths`
|
|
1837
|
+
- `previous_phase_owned_paths`
|
|
1838
|
+
- `unowned_dirty_paths`
|
|
1839
|
+
- `pre_existing_dirty_paths`
|
|
1840
|
+
- `artifact_paths`
|
|
1841
|
+
|
|
1842
|
+
These fields are the shared child-exit reduction contract. Adapters may add
|
|
1843
|
+
transport-specific metadata elsewhere, but they must not rename or omit these
|
|
1844
|
+
summary fields.
|
|
1845
|
+
|
|
1846
|
+
## Monitor Payloads
|
|
1847
|
+
|
|
1848
|
+
`build_notification_payload(...)` must emit exactly these top-level fields:
|
|
1849
|
+
|
|
1850
|
+
- `timestamp`
|
|
1851
|
+
- `repo`
|
|
1852
|
+
- `roadmap`
|
|
1853
|
+
- `event_kind`
|
|
1854
|
+
- `monitor_status`
|
|
1855
|
+
- `current_phase`
|
|
1856
|
+
- `current_status`
|
|
1857
|
+
- `human_required`
|
|
1858
|
+
- `blocker_class`
|
|
1859
|
+
- `blocker_summary`
|
|
1860
|
+
- `required_human_inputs`
|
|
1861
|
+
- `latest_heartbeat`
|
|
1862
|
+
- `terminal_summary`
|
|
1863
|
+
- `state_path`
|
|
1864
|
+
- `event_path`
|
|
1865
|
+
- `tui_handoff_path`
|
|
1866
|
+
- `run_log_path`
|
|
1867
|
+
- `recommended_action`
|
|
1868
|
+
- `not_run_ratio`
|
|
1869
|
+
- `not_run_count`
|
|
1870
|
+
- `sample_size`
|
|
1871
|
+
- `threshold`
|
|
1872
|
+
|
|
1873
|
+
This payload is the normalized machine callback surface for monitors,
|
|
1874
|
+
notification commands, and external supervisors.
|
|
1875
|
+
|
|
1876
|
+
## Human-Required Blockers
|
|
1877
|
+
|
|
1878
|
+
The blocker taxonomy is frozen to these literals:
|
|
1879
|
+
|
|
1880
|
+
- `missing_secret`
|
|
1881
|
+
- `account_or_billing_setup`
|
|
1882
|
+
- `admin_approval`
|
|
1883
|
+
- `destructive_operation`
|
|
1884
|
+
- `ambiguous_roadmap_selection`
|
|
1885
|
+
- `product_decision_missing`
|
|
1886
|
+
- `dirty_worktree_conflict`
|
|
1887
|
+
- `branch_sync_conflict`
|
|
1888
|
+
- `sandbox_command_restriction`
|
|
1889
|
+
- `upstream_phase_unmet`
|
|
1890
|
+
- `contract_bug`
|
|
1891
|
+
- `gold_record_amendment`
|
|
1892
|
+
- `stalled_child_observation`
|
|
1893
|
+
- `repeated_verification_failure`
|
|
1894
|
+
- `unretryable_external_outage`
|
|
1895
|
+
- `stuck_loop`
|
|
1896
|
+
- `merge_conflict`
|
|
1897
|
+
- `operator_override_missing_reason`
|
|
1898
|
+
- `concurrent_dispatch`
|
|
1899
|
+
- `review_gate_block`
|
|
1900
|
+
- `docs_freshness_stale`
|
|
1901
|
+
- `consiliency_gate_blocked`
|
|
1902
|
+
|
|
1903
|
+
`consiliency_gate_blocked` is the non-human blocker raised by the CS-0.6
|
|
1904
|
+
`.consiliency/` L0 gates (presence, local-integrity, layout-validity,
|
|
1905
|
+
version-skew) when the opt-in `PHASE_LOOP_CONSILIENCY_GATES=hard` control is
|
|
1906
|
+
set and at least one gate has a finding. The default is `warn`
|
|
1907
|
+
(`PHASE_LOOP_CONSILIENCY_GATES` unset or `warn`): findings are recorded in the
|
|
1908
|
+
closeout's `consiliency_gates`/`consiliency_gates_detail` fields but never
|
|
1909
|
+
block. A repo with no `.consiliency/manifest` is a pure no-op (`skipped`) --
|
|
1910
|
+
the gates only ever act on repos that have opted in. The version-skew gate
|
|
1911
|
+
never escalates past `warn` regardless of this control (the version-skew
|
|
1912
|
+
protocol pins Phase 0 severity to `warn`). It never sets `human_required` —
|
|
1913
|
+
the fix is to resolve the named finding (author the missing doc, repair the
|
|
1914
|
+
manifest, etc.) or drop back to `warn`/`off`.
|
|
1915
|
+
|
|
1916
|
+
`docs_freshness_stale` is the non-human blocker raised by the docs-freshness
|
|
1917
|
+
closeout gate (issue #18) when a **release/package phase** closes `complete`
|
|
1918
|
+
while a public-doc surface (top-level/package `README.md`, `CHANGELOG`,
|
|
1919
|
+
release-notes) still carries an unambiguous stale placeholder
|
|
1920
|
+
(`recovery commit pending`, `TBD`, …). Unlike the `PHASE_LOOP_REVIEW` review
|
|
1921
|
+
gates, it is a hard gate governed by its own `PHASE_LOOP_DOCS_FRESHNESS` control
|
|
1922
|
+
(`hard` default | `warn` | `off`) and so blocks independent of
|
|
1923
|
+
`PHASE_LOOP_REVIEW`; it is **inert for non-release phases** (status `skipped`).
|
|
1924
|
+
The closeout payload always carries `docs_freshness: passed|skipped|blocked`
|
|
1925
|
+
plus a `docs_freshness_detail` evidence record, so a clean worktree alone cannot
|
|
1926
|
+
imply public docs are current. It never sets `human_required` — the fix is to
|
|
1927
|
+
refresh the named doc surface (or add a `<!-- freshness-ok -->` marker for a
|
|
1928
|
+
legitimate use of an otherwise-suspicious token).
|
|
1929
|
+
|
|
1930
|
+
`review_gate_block` is the non-human blocker raised by a pluggable closeout
|
|
1931
|
+
review validator (doc-delta, verification-evidence, visual-evidence, …) when
|
|
1932
|
+
the `PHASE_LOOP_REVIEW` control is dialed to `block`. Review gates default to
|
|
1933
|
+
`PHASE_LOOP_REVIEW=warn` (record a finding to the closeout and continue — the
|
|
1934
|
+
autonomous loop is never stalled), so this blocker appears only under explicit
|
|
1935
|
+
opt-in. It never sets `human_required`; the finding's `code`/`reason` name an
|
|
1936
|
+
agent-recoverable fix (update docs, attach a verification log or screenshot, or
|
|
1937
|
+
record a justified opt-out).
|
|
1938
|
+
|
|
1939
|
+
`stuck_loop` fires when a phase has been ping-ponging in
|
|
1940
|
+
`(action=run, status=executing)` past the runner's iteration cap
|
|
1941
|
+
(default 5, via `--stuck-loop-iterations`) or time ceiling (default
|
|
1942
|
+
30 minutes, via `--stuck-loop-minutes`) without converging to
|
|
1943
|
+
`complete` or `blocked`. The blocker's `metadata.stuck_loop` payload
|
|
1944
|
+
includes the `trigger` (iteration_cap or time_ceiling), iteration
|
|
1945
|
+
count, elapsed minutes, and first/latest executing-event timestamps.
|
|
1946
|
+
Resolve by either `phase-loop reopen --phase <ALIAS> --reason "..."`
|
|
1947
|
+
(if work isn't actually done and a re-plan is needed) or
|
|
1948
|
+
`phase-loop reconcile --phase <ALIAS> ...` (if the work is complete
|
|
1949
|
+
but the executor failed to report it).
|
|
1950
|
+
|
|
1951
|
+
Before setting `human_required=true`, the runner or skill must record safe,
|
|
1952
|
+
redacted access or environment probes when access is part of the blocker.
|
|
1953
|
+
|
|
1954
|
+
When live launch remains blocked behind metadata-only auth or subscription
|
|
1955
|
+
checks, the default blocker reduction is `account_or_billing_setup` unless a
|
|
1956
|
+
different frozen blocker literal is explicitly justified by the observed probe
|
|
1957
|
+
result.
|
|
1958
|
+
|
|
1959
|
+
## Lane IR Plan Parser Contract
|
|
1960
|
+
|
|
1961
|
+
Execution-ready phase plans may be reduced into `PhasePlanIR` without launching
|
|
1962
|
+
lane work. The IR is parser-only and records:
|
|
1963
|
+
|
|
1964
|
+
- `PhasePlanIR` metadata, attached `ExecutionPolicyDocument`, dispatch hints,
|
|
1965
|
+
lane records, dependency edges, and typed diagnostics
|
|
1966
|
+
- `PhasePlanLane` identity, display name, owned files, read-only status,
|
|
1967
|
+
`depends_on`, `blocks`, provided interfaces, consumed interfaces, task
|
|
1968
|
+
buckets, verification commands, `parallel_safe`, reducer kind, and
|
|
1969
|
+
lane-specific execution policy
|
|
1970
|
+
- `LaneDependency` edges from producer or prerequisite lanes to dependent lanes
|
|
1971
|
+
- `LaneTaskSet` buckets for `test`, `impl`, `verify`, and `other`
|
|
1972
|
+
- `LaneIRDiagnostic` with `kind`, `message`, optional `lane_id`,
|
|
1973
|
+
`human_required=false`, and `blocker_class=contract_bug`
|
|
1974
|
+
|
|
1975
|
+
The frozen diagnostic kinds are `cycle`, `overlapping_write_ownership`,
|
|
1976
|
+
`unsafe_concurrent_lane`, `missing_producer_dependency`, `missing_owned_files`,
|
|
1977
|
+
`malformed_owned_files`, `malformed_dependencies`,
|
|
1978
|
+
`unsupported_lane_policy`, and `missing_lane_sections`.
|
|
1979
|
+
|
|
1980
|
+
The frozen reducer kinds are `none`, `acceptance_reducer`,
|
|
1981
|
+
`compatibility_reducer`, `verification_reducer`, and `summary_reducer`.
|
|
1982
|
+
|
|
1983
|
+
Malformed lane IR is a repairable non-human blocker. The parser fails closed
|
|
1984
|
+
for dependency cycles, overlapping write ownership, missing producer
|
|
1985
|
+
dependencies, malformed or missing owned-file lines, malformed dependencies,
|
|
1986
|
+
and unsupported lane policy literals. Existing `phase_loop_plan_version: 1`
|
|
1987
|
+
frontmatter remains unchanged. The LANEIR contract does not add scheduler mode,
|
|
1988
|
+
worktree fanout, runner-owned lane launches, reducer launches, or any change to
|
|
1989
|
+
coarse phase execution defaults.
|
|
1990
|
+
|
|
1991
|
+
## Work-Unit Lifecycle
|
|
1992
|
+
|
|
1993
|
+
UNITRUN adds runner-owned work-unit records behind explicit work-unit mode while
|
|
1994
|
+
leaving coarse phase execution intact. A work-unit identity is frozen as
|
|
1995
|
+
`<phase>.<kind>.<lane-or-reducer-id>.<attempt>` and is represented by typed
|
|
1996
|
+
fields on `WorkUnitIdentity`: `phase`, `kind`, `lane_id`, `attempt`, and
|
|
1997
|
+
`work_unit_id`.
|
|
1998
|
+
|
|
1999
|
+
The frozen work-unit statuses are `pending`, `running`, `complete`, `blocked`,
|
|
2000
|
+
`skipped`, `superseded`, and `awaiting-closeout`. These are intentionally
|
|
2001
|
+
separate from phase statuses such as `planned`, `executing`, and `complete`.
|
|
2002
|
+
|
|
2003
|
+
The state snapshot may include `work_units` and `latest_work_unit`. Each
|
|
2004
|
+
`WorkUnitState` records identity, status, parent phase event, policy, artifact
|
|
2005
|
+
paths, `heartbeat_path`, `terminal_summary_path`, retry lineage, blocker
|
|
2006
|
+
metadata, and timestamps. Malformed work-unit state records are ignored by the
|
|
2007
|
+
work-unit loader rather than breaking legacy phase-level state reads.
|
|
2008
|
+
|
|
2009
|
+
The event ledger may include `event_kind: work_unit` entries. A
|
|
2010
|
+
`WorkUnitEventMetadata` record stores launch metadata, heartbeat path, terminal
|
|
2011
|
+
summary path, closeout summary, retry lineage, blocker metadata, and the current
|
|
2012
|
+
work-unit status. These records coexist with phase-level `LoopEvent` entries and
|
|
2013
|
+
must not cause older phase-level reconciliation to treat work-unit statuses as
|
|
2014
|
+
phase statuses.
|
|
2015
|
+
|
|
2016
|
+
The shared `automation:` closeout contract remains valid at phase level.
|
|
2017
|
+
Work-unit closeout adds optional work-unit fields through `WorkUnitCloseout` and
|
|
2018
|
+
optional `terminal-summary.work_unit` data without changing the required frozen
|
|
2019
|
+
terminal summary fields. A lane work-unit closeout may record `lane_id`,
|
|
2020
|
+
`wave_id`, `worktree_path`, `verification_status`, `changed_paths`, and
|
|
2021
|
+
redacted `evidence_refs`; it must not store raw transcripts, credential
|
|
2022
|
+
payloads, private source bytes, or provider tokens. Work-unit metrics may
|
|
2023
|
+
include `work_unit_metric.v1.work_unit_id`, `work_unit_metric.v1.lane_id`, and
|
|
2024
|
+
`work_unit_metric.v1.wave_id`.
|
|
2025
|
+
|
|
2026
|
+
Resume semantics are work-unit granular: completed units are skipped,
|
|
2027
|
+
non-human blocked or stale running units may be retried with a new attempt,
|
|
2028
|
+
stale attempts are marked `superseded`, and human-required blocked units remain
|
|
2029
|
+
blocked until operator action resolves them. UNITRUN does not schedule
|
|
2030
|
+
dependency waves or isolated worktrees; WAVESCHED owns that later behavior.
|
|
2031
|
+
|
|
2032
|
+
## Lane Scheduler
|
|
2033
|
+
|
|
2034
|
+
WAVESCHED adds runner-owned lane scheduling behind an explicit scheduler gate.
|
|
2035
|
+
Lane scheduler modes are `off`, `serialized`, and `concurrent`. `off` is the
|
|
2036
|
+
default and preserves coarse phase execution. `serialized` may select one ready
|
|
2037
|
+
lane on the main worktree. `concurrent` may select multiple ready lanes only
|
|
2038
|
+
when writer ownership is disjoint and each writer has a `git_worktree`
|
|
2039
|
+
assignment.
|
|
2040
|
+
|
|
2041
|
+
`LaneWave` records `wave_id`, ordered `lane_ids`, scheduler `mode`, and
|
|
2042
|
+
optional `LaneWorktreeAssignment` records. `LaneWaveDecision` records status
|
|
2043
|
+
`ready`, `blocked`, or `empty`, plus pending, completed, blocked lane IDs, and
|
|
2044
|
+
typed diagnostics. `LaneWorktreeAssignment` records `lane_id`,
|
|
2045
|
+
`worktree_path`, `isolation_mode`, optional branch, and optional `base_sha`.
|
|
2046
|
+
Worktree isolation modes are `main_worktree` and `git_worktree`.
|
|
2047
|
+
|
|
2048
|
+
The scheduler-owned assignment field list accepted by dotfiles is: lane id
|
|
2049
|
+
(`lane_id`), wave id (`wave_id`), worktree path (`worktree_path`), base SHA
|
|
2050
|
+
(`base_sha`), isolation mode (`isolation_mode`), owned files (`owned_files`),
|
|
2051
|
+
read-only refs (`read_only_refs`), harness route (`harness_route`), model
|
|
2052
|
+
(`model`), effort (`effort`), and fallback reason (`fallback_reason`).
|
|
2053
|
+
Governed Pipeline owns scheduling, runtime ledger, worktree assignment, and
|
|
2054
|
+
merge reduction. Greenfield owns deterministic authority contracts, authority
|
|
2055
|
+
refs, and authority digests. Dotfiles consumes governed-pipeline assignments
|
|
2056
|
+
and Greenfield authority refs as inputs and must not promote those inputs into
|
|
2057
|
+
dotfiles-owned runtime authority.
|
|
2058
|
+
|
|
2059
|
+
Worktree placement uses `<WORKTREE-PATH-REDACTED>
|
|
2060
|
+
when `/mnt/workspace` exists; otherwise it uses default sibling placement next
|
|
2061
|
+
to the repo. The runner must halt between lane launches when a stop file is
|
|
2062
|
+
present and must not rewrite in-flight heartbeat, terminal-summary, metric, or
|
|
2063
|
+
closeout artifacts.
|
|
2064
|
+
|
|
2065
|
+
Concurrent scheduling is available only for scheduler-approved
|
|
2066
|
+
`parallel_safe` non-reducer writer waves with disjoint writable ownership and
|
|
2067
|
+
isolated `git_worktree` assignments. Serialized scheduling and coarse phase
|
|
2068
|
+
execution remain compatibility paths and do not require scheduler-owned
|
|
2069
|
+
worktree assignments. Unsafe concurrent selection must fail closed with typed
|
|
2070
|
+
diagnostics for overlapping owned files, missing worktree assignment, stale
|
|
2071
|
+
base SHA, active work unit, human-required blocked work unit,
|
|
2072
|
+
reducer-in-wave, stop-file interruption, and malformed closeout.
|
|
2073
|
+
|
|
2074
|
+
Dirty-path reduction uses `DirtyPathClassification` with classifications
|
|
2075
|
+
`pre_existing`, `lane_owned`, `peer_owned`, `reducer_owned`, and `unowned`.
|
|
2076
|
+
|
|
2077
|
+
Ignored, private, raw-data, credential, and evidence-source files are
|
|
2078
|
+
read-protected by default. Child executors may read those paths only when the
|
|
2079
|
+
run-local context, phase plan, or Pipeline source bundle explicitly allowlists
|
|
2080
|
+
the exact path or glob as a read input. Owned output paths do not imply
|
|
2081
|
+
permission to read ignored or private source inputs, and old memory or prior
|
|
2082
|
+
phase behavior is not an allowlist.
|
|
2083
|
+
|
|
2084
|
+
Before any phase or work-unit closeout claims success, the child executor must
|
|
2085
|
+
audit `git status --short` against the active owned-file contract. Dirty paths
|
|
2086
|
+
must be classified as phase/lane-owned, planning/control, pre-existing
|
|
2087
|
+
unrelated, or unowned. Ignored phase-owned outputs may be preserved only when
|
|
2088
|
+
the plan/source bundle includes an explicit allowlist or staging policy;
|
|
2089
|
+
otherwise the executor must report a repairable `dirty_worktree_conflict`.
|
|
2090
|
+
|
|
2091
|
+
File-rename pairs in the dirty tree — both the `R src -> dst` form that git
|
|
2092
|
+
emits when both sides are staged, and the unpaired `D src` + `?? dst` form
|
|
2093
|
+
that arises when an executor performs a filesystem-only `mv` — are detected by
|
|
2094
|
+
exact blob-hash equality between the deleted HEAD blob and the untracked
|
|
2095
|
+
working-tree file. When the destination is phase-owned, the source path is
|
|
2096
|
+
promoted to `phase_owned_dirty` automatically so a "sensible refactor
|
|
2097
|
+
instinct" move does not fire as `unowned_dirty_paths`. Detection is exact,
|
|
2098
|
+
not similarity-based: a move that also rewrites content is not paired, and
|
|
2099
|
+
must be declared in the plan's owned-file contract explicitly.
|
|
2100
|
+
|
|
2101
|
+
## Parallel Plan Safety
|
|
2102
|
+
|
|
2103
|
+
Planning turns that run concurrently for the same roadmap may leave sibling
|
|
2104
|
+
phase plan documents in the worktree. A dirty path is expected sibling planning
|
|
2105
|
+
dirt only when it is a repo-relative `plans/phase-plan-<roadmap-version>-<alias>.md`
|
|
2106
|
+
artifact, `<alias>` exists in the selected roadmap, `<alias>` is not the current
|
|
2107
|
+
phase, and `<roadmap-version>` matches the selected roadmap filename.
|
|
2108
|
+
|
|
2109
|
+
Runner dirty-path classification must report those paths as
|
|
2110
|
+
`expected_sibling_dirty_paths` with `expected_sibling_dirty: true`, and must not
|
|
2111
|
+
also include them in `unowned_dirty_paths` or let them cause a
|
|
2112
|
+
`dirty_worktree_conflict`. Current-phase plan docs, foreign roadmap versions,
|
|
2113
|
+
alternate directories, absolute paths, path traversal, and non-plan dirt remain
|
|
2114
|
+
governed by the ordinary owned-file and control-path rules.
|
|
2115
|
+
|
|
2116
|
+
## Harness Lane Workflows
|
|
2117
|
+
|
|
2118
|
+
HARNESSLANE adds a runner-owned lane assignment payload for harness-specific
|
|
2119
|
+
work units while preserving the coarse phase execution path. The frozen payload
|
|
2120
|
+
is `HarnessLaneAssignment` with `schema=harness_lane_assignment.v1` semantics:
|
|
2121
|
+
|
|
2122
|
+
- `phase`
|
|
2123
|
+
- `lane_id`
|
|
2124
|
+
- `work_unit_kind`
|
|
2125
|
+
- `prompt_kind`
|
|
2126
|
+
- `wave_id`
|
|
2127
|
+
- `owned_files`
|
|
2128
|
+
- `read_only_refs`
|
|
2129
|
+
- `consumed_interfaces`
|
|
2130
|
+
- `depends_on`
|
|
2131
|
+
- `execution_policy`
|
|
2132
|
+
- `worktree_assignment`
|
|
2133
|
+
- `harness_route`
|
|
2134
|
+
- `model`
|
|
2135
|
+
- `effort`
|
|
2136
|
+
- `fallback_reason`
|
|
2137
|
+
- `closeout_schema_required`
|
|
2138
|
+
- `reducer_kind`
|
|
2139
|
+
- `metadata`
|
|
2140
|
+
|
|
2141
|
+
The frozen `HarnessWorkUnitPromptKind` literals are `implementation`, `review`,
|
|
2142
|
+
`reducer`, `verify`, and `closeout`. Implementation prompts can edit only the
|
|
2143
|
+
assigned `owned_files`. Review prompts must not make production edits. Reducer
|
|
2144
|
+
prompts may summarize producer results and closeout state, but must not claim
|
|
2145
|
+
producer write ownership.
|
|
2146
|
+
|
|
2147
|
+
`build_lane_prompt_bundle`, `render_harness_lane_context`,
|
|
2148
|
+
`render_harness_review_context`, and `render_harness_reducer_context` render a
|
|
2149
|
+
single selected work unit for Codex, Claude, Gemini, OpenCode, or command
|
|
2150
|
+
adapters. Each bundle carries the selected lane id, owned files, consumed
|
|
2151
|
+
interfaces, work-unit kind, execution-policy summary, worktree assignment
|
|
2152
|
+
metadata, and the required shared `automation:` closeout fields. These bundles
|
|
2153
|
+
do not grant whole-phase implementation authority.
|
|
2154
|
+
|
|
2155
|
+
`LaunchSpec.harness_lane_assignment` and `launch.json.harness_lane_assignment`
|
|
2156
|
+
freeze lane metadata for live launches. `launch.json.lane_id` and
|
|
2157
|
+
`launch.json.work_unit_kind` are convenience copies for metrics and terminal
|
|
2158
|
+
summaries. Claude `subagent` and `agent_team` launches remain gated by
|
|
2159
|
+
team-safe lane policy, runner work-unit state, delegation broker depth/fanout
|
|
2160
|
+
limits, and owned-file boundaries. Gemini lane launches use built-in routing
|
|
2161
|
+
aliases such as `pro` and `auto` by default; explicit run-local aliases such as
|
|
2162
|
+
`phase-loop-execute-medium` and `phase-loop-review-high` remain opt-in proof
|
|
2163
|
+
surfaces and fail closed through the unsupported-policy diagnostic path when
|
|
2164
|
+
misspelled. OpenCode lane launches record explicit `opencode run`,
|
|
2165
|
+
`--dir`, `--agent`, provider-qualified `--model`, optional `--variant`,
|
|
2166
|
+
`--format json`, and permission posture metadata. Command adapter lane launches
|
|
2167
|
+
require an explicit template that includes `{context_file}`.
|
|
2168
|
+
|
|
2169
|
+
## Closeout Modes
|
|
2170
|
+
|
|
2171
|
+
Closeout policy is frozen to:
|
|
2172
|
+
|
|
2173
|
+
- `manual`
|
|
2174
|
+
- `commit`
|
|
2175
|
+
- `push`
|
|
2176
|
+
|
|
2177
|
+
`manual` preserves verified phase-owned dirty output without mutating git
|
|
2178
|
+
state. `commit` may preserve only trusted phase-owned dirty paths in a local
|
|
2179
|
+
commit. `push` may build on that local commit only when branch/base topology is
|
|
2180
|
+
safe and explicit.
|
|
2181
|
+
|
|
2182
|
+
## Roadmap Amendments
|
|
2183
|
+
|
|
2184
|
+
Execution discoveries that change downstream scope must amend the nearest
|
|
2185
|
+
downstream roadmap phase that is not already executing. After the amendment:
|
|
2186
|
+
|
|
2187
|
+
- reconcile against the amended roadmap before selecting the next phase
|
|
2188
|
+
- treat older downstream phase plans or handoffs as stale when their roadmap
|
|
2189
|
+
metadata no longer matches
|
|
2190
|
+
- regenerate any affected downstream phase plan through the planner before
|
|
2191
|
+
execution resumes
|
|
2192
|
+
- preserve prior ledger records for audit, but let later trusted events on the
|
|
2193
|
+
amended roadmap suppress stale mismatch noise
|
|
2194
|
+
|
|
2195
|
+
## Delegation Broker
|
|
2196
|
+
|
|
2197
|
+
Cross-harness delegation remains runner-owned. Child runs may recommend nested
|
|
2198
|
+
work only through a typed delegation request contract. The frozen request shape
|
|
2199
|
+
includes:
|
|
2200
|
+
|
|
2201
|
+
- `request_id`
|
|
2202
|
+
- `product_action`
|
|
2203
|
+
- `target_executor`
|
|
2204
|
+
- `reason`
|
|
2205
|
+
- `owned_files`
|
|
2206
|
+
- `expected_output`
|
|
2207
|
+
- `priority`
|
|
2208
|
+
- optional `review_context`
|
|
2209
|
+
- optional `repair_context`
|
|
2210
|
+
- optional metadata-only `budget`
|
|
2211
|
+
|
|
2212
|
+
Delegation approval is validated in this order:
|
|
2213
|
+
|
|
2214
|
+
1. active-loop mode
|
|
2215
|
+
2. ownership boundaries
|
|
2216
|
+
3. depth limit
|
|
2217
|
+
4. fanout limit
|
|
2218
|
+
5. budget metadata
|
|
2219
|
+
6. dispatch policy
|
|
2220
|
+
|
|
2221
|
+
Denied requests must produce a typed non-human blocked outcome with a clear
|
|
2222
|
+
reason code and summary. Approved requests must reuse the normal runner launch
|
|
2223
|
+
path so launch artifacts record request metadata, resolved executor, observed
|
|
2224
|
+
launch path, and parent-child linkage without changing the frozen top-level
|
|
2225
|
+
monitor payload fields.
|
|
2226
|
+
|
|
2227
|
+
Claude-native subagents and agent teams do not implicitly create runner child
|
|
2228
|
+
runs. They become runner-visible child work only after a typed delegation
|
|
2229
|
+
request is emitted and approved through this broker contract.
|
|
2230
|
+
|
|
2231
|
+
Delegated cross-harness child work is frozen to `execute`, `repair`, and
|
|
2232
|
+
`review`, and approved child executors are limited to `codex` and `claude`.
|
|
2233
|
+
Native Claude team tasks stay internal unless they externalize through the same
|
|
2234
|
+
typed `DelegationRequest` broker path.
|
|
2235
|
+
Top-level executor switching remains an operator or runner decision, not a
|
|
2236
|
+
native-team side effect: choose the top-level harness through normal dispatch
|
|
2237
|
+
selection or `--executor`, then require typed broker approval before any child
|
|
2238
|
+
work crosses harness boundaries.
|
|
2239
|
+
|
|
2240
|
+
The shared mixed-run lineage surface includes:
|
|
2241
|
+
|
|
2242
|
+
- `DispatchDecision.selected_via`
|
|
2243
|
+
- `DispatchDecision.considered_executors`
|
|
2244
|
+
- typed delegation denial `reason_code`
|
|
2245
|
+
- `ParentChildRunMetadata.parent_executor`
|
|
2246
|
+
- `ParentChildRunMetadata.child_executor`
|
|
2247
|
+
- `ParentChildRunMetadata.child_artifact_root`
|
|
2248
|
+
- `ParentChildRunMetadata.child_worktree_root`
|
|
2249
|
+
- `ParentChildRunMetadata.child_closeout_result`
|
|
2250
|
+
|
|
2251
|
+
## Manual Advancement and Import
|
|
2252
|
+
|
|
2253
|
+
Manual TUI runs remain valid when they emit the same shared artifact contract
|
|
2254
|
+
as automated runs. Shared manual rules:
|
|
2255
|
+
|
|
2256
|
+
- append a `manual` source event when phase-loop state already exists
|
|
2257
|
+
- keep `automation.*` aligned with the human-readable handoff
|
|
2258
|
+
- use `manual_repair` with `clears_blocker=true` only when a repair actually
|
|
2259
|
+
clears the recorded blocker
|
|
2260
|
+
- autonomous child executors should not edit `.phase-loop/` or claim durable
|
|
2261
|
+
ledger mutation unless the injected launch explicitly permits and confirms
|
|
2262
|
+
it; the parent runner owns blocker clearing and closeout reconciliation from
|
|
2263
|
+
valid shared automation closeouts
|
|
2264
|
+
- when a manual run amended the roadmap downstream, compute
|
|
2265
|
+
`roadmap_sha256` and `phase_sha256` from the amended roadmap for the current
|
|
2266
|
+
phase being closed instead of reusing stale plan hashes
|
|
2267
|
+
- installed bridge skills are recommended for manual takeover and TUI reentry,
|
|
2268
|
+
but repo-injected workflow bundles remain the autonomous source of truth for
|
|
2269
|
+
child launches
|
|
2270
|
+
|
|
2271
|
+
## Runtime Path
|
|
2272
|
+
|
|
2273
|
+
For this roadmap family, `.phase-loop/` is the canonical durable runtime path
|
|
2274
|
+
for state, ledger, handoff, and observed-run artifacts. Existing
|
|
2275
|
+
`.codex/phase-loop/` artifacts remain a legacy read fallback so active repos are
|
|
2276
|
+
not stranded during migration. New writes use `.phase-loop/`.
|
|
2277
|
+
Legacy `.codex/phase-loop/` is never a new write target for closeout,
|
|
2278
|
+
delegation, source-bundle, or runner evidence artifacts.
|
|
2279
|
+
|
|
2280
|
+
`phase-loop init [--repo <path>] [--dry-run]` initializes the repo-local
|
|
2281
|
+
handoff storage expected by workflow skills. It idempotently adds
|
|
2282
|
+
`/.dev-skills/` to the target repo `.gitignore` without duplication and creates
|
|
2283
|
+
`.dev-skills/handoffs/` unless `--dry-run` is set.
|
|
2284
|
+
|
|
2285
|
+
## Runtime Boundary
|
|
2286
|
+
|
|
2287
|
+
The phase-loop runtime is isolated as a coherent internal package within the
|
|
2288
|
+
dotfiles repository. It provides a stable CLI and Python API boundary for
|
|
2289
|
+
external tools and repositories.
|
|
2290
|
+
The authoritative boundary document is `docs/phase-loop/runtime-boundary.md`.
|
|
2291
|
+
The dotfiles-specific substrate path inventory is
|
|
2292
|
+
`docs/phase-loop/harness-substrate-manifest.md`; it is intentionally separate
|
|
2293
|
+
from this schema contract.
|
|
2294
|
+
|
|
2295
|
+
Key boundary properties:
|
|
2296
|
+
- The primary CLI entrypoint is `phase-loop` (aliased as `codex-phase-loop`).
|
|
2297
|
+
- The CLI supports a `version` command and `--version` flag for contract reporting.
|
|
2298
|
+
- Public Python modules are exposed under the `phase_loop_runtime.*` namespace.
|
|
2299
|
+
- Internal modules not explicitly listed in `__all__` or the boundary document
|
|
2300
|
+
are considered private and subject to change.
|
|
2301
|
+
- Durable state and events are stored under `.phase-loop/` (legacy fallback
|
|
2302
|
+
`.codex/phase-loop/`).
|
|
2303
|
+
- New writes use `.phase-loop/`.
|
|
2304
|
+
|
|
2305
|
+
## Extraction Readiness
|
|
2306
|
+
|
|
2307
|
+
The protocol and runtime boundary are considered "Extraction Ready" when:
|
|
2308
|
+
1. All public CLI commands are documented and stable.
|
|
2309
|
+
2. All machine-readable artifacts follow frozen schemas.
|
|
2310
|
+
3. Cross-repo compatibility fixtures exist for downstream validation.
|
|
2311
|
+
4. The migration path does not break existing `dotfiles` installation.
|
|
2312
|
+
|
|
2313
|
+
This roadmap family confirms that phase-loop has met these criteria.
|
|
2314
|
+
|
|
2315
|
+
## Cross-Repo Compatibility Fixtures
|
|
2316
|
+
|
|
2317
|
+
To ensure stable integration with downstream repositories such as `governed-pipeline`, this repository provides a set of canonical fixtures that demonstrate various phase-loop outcomes.
|
|
2318
|
+
|
|
2319
|
+
### Fixture Location
|
|
2320
|
+
|
|
2321
|
+
Stable fixtures are located under:
|
|
2322
|
+
`vendor/phase-loop-runtime/tests/fixtures/phase_loop_pipeline_bridge/`
|
|
2323
|
+
|
|
2324
|
+
These fixtures are dotfiles-owned substrate. The governed-pipeline mirror
|
|
2325
|
+
location is `packages/pipeline-runtime/test/fixtures/phase-loop-bridge/`, and
|
|
2326
|
+
mirror updates, closeout ingest, canonical refresh, replan, and preflight block
|
|
2327
|
+
decisions are governed-pipeline-owned work. Dotfiles must not edit the
|
|
2328
|
+
governed-pipeline mirror path from this repository.
|
|
2329
|
+
Fixture and closeout examples remain metadata-only: they do not authorize raw
|
|
2330
|
+
evidence reads, credential-bearing payloads, provider-supplied payloads, local environment
|
|
2331
|
+
values, `.pipeline/**` writes, Portal writes, Greenfield writes, or broad
|
|
2332
|
+
dotfiles root dependencies.
|
|
2333
|
+
|
|
2334
|
+
### Available Scenarios
|
|
2335
|
+
|
|
2336
|
+
- `complete.json`: A standard successful phase execution.
|
|
2337
|
+
- `blocked.json`: A phase blocked by a non-human restriction (e.g., sandbox).
|
|
2338
|
+
- `stale_input.json`: A phase with mismatched plan or roadmap hashes.
|
|
2339
|
+
- `failed_verification.json`: A phase where verification failed.
|
|
2340
|
+
- `human_required.json`: A phase requiring operator intervention (e.g., billing).
|
|
2341
|
+
- `dfbundlecloseout_standalone.json`: A standalone closeout without
|
|
2342
|
+
Pipeline-only source bundle path or hash identity.
|
|
2343
|
+
- `dfdriftsignal_canonical_refresh_recommended.json`: Advisory changed-path
|
|
2344
|
+
metadata recommending governed-pipeline canonical refresh.
|
|
2345
|
+
- `dfbundlecloseout_malformed_*.json`: Negative fixtures for malformed
|
|
2346
|
+
closeout, missing bundle hash, deprecated root aliases, invalid nested
|
|
2347
|
+
objects, invalid terminal status, and forbidden metadata.
|
|
2348
|
+
- `dfadoptbridge_adoption_complete.json`: Adoption bridge fixture with active
|
|
2349
|
+
canonical spec, managed root mirror, mirror manifest, archive manifest,
|
|
2350
|
+
source-bundle identity, protected-source roles, and SHA-256 evidence refs.
|
|
2351
|
+
- `dfadoptbridge_stale_mirror_manifest.json`: Adoption bridge fixture for
|
|
2352
|
+
stale mirror manifest metadata and governed-pipeline-owned refresh handling.
|
|
2353
|
+
- `dfadoptbridge_unmanaged_spec_input.json`: Adoption bridge fixture for
|
|
2354
|
+
unmanaged root `specs/**` intake as metadata-only evidence.
|
|
2355
|
+
|
|
2356
|
+
Downstream consumers should use these fixtures to verify their ingestion logic against the `phase_loop_closeout.v1` schema.
|
|
2357
|
+
|
|
2358
|
+
DFADOPTBRIDGE fixtures extend the adoption bridge matrix for governed-pipeline
|
|
2359
|
+
v11. They cover adoption complete, blocked adoption metadata, stale source
|
|
2360
|
+
bundle, stale mirror manifest, unmanaged spec input, archive manifest touched,
|
|
2361
|
+
standalone non-adoption, deprecated root aliases, and redaction rejection.
|
|
2362
|
+
Pipeline-required fixtures use `source_bundle.path`, `source_bundle.sha256`,
|
|
2363
|
+
`source_bundle.phase_id`, protected-source `path`, protected-source `sha256`,
|
|
2364
|
+
protected-source `role`, plan `sha256`, changed-path categories, advisory
|
|
2365
|
+
reason codes, and evidence-ref `sha256` metadata. They do not grant dotfiles
|
|
2366
|
+
permission to write governed-pipeline mirror paths, `.pipeline/**`, Portal
|
|
2367
|
+
contracts, Greenfield authority files, raw-evidence, raw data, credentials, or
|
|
2368
|
+
legacy `.codex/phase-loop/**` state.
|
|
2369
|
+
|
|
2370
|
+
DFADOPTSOAK is the dotfiles adoption integration release gate. It proves root
|
|
2371
|
+
`specs/**` standalone advisory hints, explicit non-default spec-root intake
|
|
2372
|
+
metadata, unconfigured non-default root non-advisory behavior,
|
|
2373
|
+
pipeline-required source-bundle echo, stale-input blockers, v14 bridge fixture
|
|
2374
|
+
retention, DFADOPTBRIDGE fixture parity, and unittest-first verification. It
|
|
2375
|
+
consumes governed-pipeline `CADOPTSOAK` only as read-only metadata evidence;
|
|
2376
|
+
governed-pipeline retains ownership of mirror copies, adoption decisions,
|
|
2377
|
+
archive creation, managed mirror refresh, source-truth reconciliation,
|
|
2378
|
+
source-bundle emission, canonical refresh, replan, and preflight block
|
|
2379
|
+
decisions.
|
|
2380
|
+
|
|
2381
|
+
DFFAKESMOKE adds a metadata-only local substrate receipt at
|
|
2382
|
+
`docs/phase-loop/dffakesmoke-substrate-receipt.md` and a fake-smoke matrix at
|
|
2383
|
+
`vendor/phase-loop-runtime/tests/fixtures/phase_loop_fake_smoke/matrix.json`. The receipt fields are
|
|
2384
|
+
`phase`, `roadmap_sha256`, `plan_path`, `fake_fixture_matrix`,
|
|
2385
|
+
`smoke_commands`, `work_unit_evidence_refs`, `verification_status`,
|
|
2386
|
+
`changed_path_boundaries`, and `redaction_posture`. These fields are
|
|
2387
|
+
downstream evidence refs only; governed-pipeline remains responsible for
|
|
2388
|
+
scheduling, runtime ledger policy, worktree assignment, closeout ingest, and
|
|
2389
|
+
merge reduction.
|
|
2390
|
+
|
|
2391
|
+
DFPROMPTSYNC adds a prompt-safe contract map at
|
|
2392
|
+
`docs/phase-loop/dfpromptsync-contract-map.md`, a readiness receipt at
|
|
2393
|
+
`docs/phase-loop/dfpromptsync-readiness.md`, and a fixture matrix at
|
|
2394
|
+
`vendor/phase-loop-runtime/tests/fixtures/phase_loop_prompt_sync/matrix.json`. Prompt bundles and harness
|
|
2395
|
+
lane workflows may cite schema names, field names, fixture paths, artifact
|
|
2396
|
+
refs, and digests from those receipts, but must not copy raw secrets, raw
|
|
2397
|
+
transcripts, raw patch bodies, raw provider-supplied payloads, credential file contents, local
|
|
2398
|
+
env values, or prompt-only containment claims.
|
|
2399
|
+
|
|
2400
|
+
## Direct Invocation Policy
|
|
2401
|
+
|
|
2402
|
+
INVOKEOPT adds support for optional direct invocation by external systems (such as Governed Pipeline) while preserving the shared artifact contract as the primary interoperability surface.
|
|
2403
|
+
|
|
2404
|
+
### Supported CLI Shape
|
|
2405
|
+
|
|
2406
|
+
When direct invocation is enabled, the `phase-loop` CLI exposes a deterministic shape:
|
|
2407
|
+
|
|
2408
|
+
```bash
|
|
2409
|
+
phase-loop execute <phase> --bundle <path> --output <path> --mode <execute|repair|review>
|
|
2410
|
+
```
|
|
2411
|
+
|
|
2412
|
+
- `<phase>`: The phase alias to execute.
|
|
2413
|
+
- `--bundle <path>`: Path to a `phase-source-bundle.v1` artifact.
|
|
2414
|
+
- `--output <path>`: Path where exactly one closeout JSON file must be written.
|
|
2415
|
+
- `--mode`: One of `execute`, `repair`, or `review`.
|
|
2416
|
+
Invalid mode literals fail at the direct CLI boundary before child launch.
|
|
2417
|
+
|
|
2418
|
+
### Rejection Policy
|
|
2419
|
+
|
|
2420
|
+
Unsupported direct invocation must fail closed with a typed diagnostic. The following scenarios result in a `sandbox_command_restriction` or `contract_bug` blocker:
|
|
2421
|
+
|
|
2422
|
+
- Missing or invalid `<phase>`.
|
|
2423
|
+
- Missing `--bundle` when `pipeline_required` mode or the phase plan requires it.
|
|
2424
|
+
- Missing `--output` when direct invocation is attempted.
|
|
2425
|
+
- Invalid or unsupported `--mode`.
|
|
2426
|
+
- Stale `freshness.source_bundle_hash` when it is a SHA-256 digest.
|
|
2427
|
+
- Unknown phase IDs or aliases in the source bundle.
|
|
2428
|
+
- Missing or stale protected source entries.
|
|
2429
|
+
- Malformed `phase_loop_closeout.v1` payloads.
|
|
2430
|
+
- Missing or incompatible runner for the requested phase.
|
|
2431
|
+
|
|
2432
|
+
### Output Semantics
|
|
2433
|
+
|
|
2434
|
+
Output semantics are deterministic: exactly one closeout JSON file is written
|
|
2435
|
+
to the path specified by `--output` for supported `execute`, `repair`, and
|
|
2436
|
+
`review` invocations, including typed bridge blockers detected before child
|
|
2437
|
+
launch. Standard output may contain human-readable logs, but machine
|
|
2438
|
+
integration must depend only on the output file.
|
|
2439
|
+
|
|
2440
|
+
## DFPARSOAK Integrated Soak
|
|
2441
|
+
|
|
2442
|
+
DFPARSOAK adds the integrated parallel substrate soak source map at
|
|
2443
|
+
`docs/phase-loop/dfparsoak-source-map.md`, the governed-pipeline-consumable
|
|
2444
|
+
receipt at `docs/phase-loop/dfparsoak-receipt.md`, the operator runbook at
|
|
2445
|
+
`docs/phase-loop/dfparsoak-runbook.md`, and the fixture matrix at
|
|
2446
|
+
`vendor/phase-loop-runtime/tests/fixtures/phase_loop_dfparsoak/matrix.json`.
|
|
2447
|
+
|
|
2448
|
+
The soak closeout records `lane_id`, `wave_id`, `worktree_path`,
|
|
2449
|
+
`isolation_mode`, `base_sha`, `harness_route`, `work_unit_kind`, `model`,
|
|
2450
|
+
`effort`, `policy_source`, `fallback_reason`, verification status, changed
|
|
2451
|
+
paths, and redacted evidence refs. Governed Pipeline still owns scheduling and
|
|
2452
|
+
closeout ingest; Greenfield still owns authority schemas and contract-pack
|
|
2453
|
+
expectations.
|
|
2454
|
+
|
|
2455
|
+
## Closeout Exceptions (Graduated Ownership Gate)
|
|
2456
|
+
|
|
2457
|
+
Roadmap v40 introduces a **graduated** closeout gate for the case where a phase
|
|
2458
|
+
verifies green (`verification_status == "passed"`) but the executor modified
|
|
2459
|
+
files outside the plan's declared owned-files globs. Instead of an unconditional
|
|
2460
|
+
hard block, beyond-ownership paths are classified and either auto-committed as a
|
|
2461
|
+
recorded soft exception or held for an explicit operator break-glass. This
|
|
2462
|
+
section freezes the shared vocabulary (PROTO); GATE and BREAKGLASS implement the
|
|
2463
|
+
behavior. The frozen constants live in
|
|
2464
|
+
`vendor/phase-loop-runtime/src/phase_loop_runtime/models.py` and are the single
|
|
2465
|
+
source of truth — this prose and the four harness `runtime-state.md` mirrors
|
|
2466
|
+
restate them and must agree.
|
|
2467
|
+
|
|
2468
|
+
### Sensitivity taxonomy (`SENSITIVITY_CLASSES`)
|
|
2469
|
+
|
|
2470
|
+
Every unowned dirty path is mapped to one sensitivity class:
|
|
2471
|
+
|
|
2472
|
+
- **SAFE** (`SAFE_SENSITIVITY_CLASSES`): `docs`, `plans`, `handoffs`,
|
|
2473
|
+
`config_nonsource`. Low blast radius; may auto-pass as a soft exception when
|
|
2474
|
+
verification passed.
|
|
2475
|
+
- **UNSAFE** (`UNSAFE_SENSITIVITY_CLASSES`): `source`, `ci`, `secrets`,
|
|
2476
|
+
`lockfile`. Always blocks unless the operator supplies an explicit break-glass
|
|
2477
|
+
reason.
|
|
2478
|
+
|
|
2479
|
+
**Deny-by-default rule:** any path that matches **no** SAFE class is treated as
|
|
2480
|
+
UNSAFE. An unmatched/unknown path is never auto-passed. This is the safety
|
|
2481
|
+
substitute for separation of duties in a single-operator fleet: the classifier,
|
|
2482
|
+
not a human second-signature, decides what may auto-commit.
|
|
2483
|
+
|
|
2484
|
+
### Closeout-exception record (`CloseoutException`)
|
|
2485
|
+
|
|
2486
|
+
A frozen dataclass with exactly these fields:
|
|
2487
|
+
|
|
2488
|
+
| Field | Meaning |
|
|
2489
|
+
|---|---|
|
|
2490
|
+
| `paths` | the beyond-ownership paths this exception covers |
|
|
2491
|
+
| `exception_kind` | a `CLOSEOUT_EXCEPTION_KINDS` member: `soft` or `break_glass` |
|
|
2492
|
+
| `sensitivity_class` | the `SENSITIVITY_CLASSES` member that classified the paths |
|
|
2493
|
+
| `reason` | operator-supplied break-glass reason (`None` for `soft`) |
|
|
2494
|
+
| `verification_status` | always `passed` — unverified work is never excepted |
|
|
2495
|
+
|
|
2496
|
+
- `soft` — all unowned paths were SAFE; the gate auto-commits and records the
|
|
2497
|
+
exception. No blocker.
|
|
2498
|
+
- `break_glass` — unowned paths included an UNSAFE class; commit only under an
|
|
2499
|
+
explicit operator reason. An empty/missing reason is the
|
|
2500
|
+
`operator_override_missing_reason` blocker. A verified-but-unowned UNSAFE path
|
|
2501
|
+
with no override is the `closeout_scope_violation` blocker.
|
|
2502
|
+
|
|
2503
|
+
### Visibility
|
|
2504
|
+
|
|
2505
|
+
Every exception is recorded in closeout metadata under the
|
|
2506
|
+
`closeout.closeout_exceptions` key (`CLOSEOUT_EXCEPTIONS_METADATA_KEY`) and in
|
|
2507
|
+
the event ledger, with its own tally — never silently folded into a clean-pass
|
|
2508
|
+
count. `soft` and `break_glass` exceptions share the tally, distinguished by
|
|
2509
|
+
`exception_kind`.
|
|
2510
|
+
|
|
2511
|
+
### Graduated gate (GATE)
|
|
2512
|
+
|
|
2513
|
+
`_perform_phase_closeout` classifies the beyond-ownership dirty remainder via
|
|
2514
|
+
`closeout_classifier.classify_unowned_path(repo_relpath) -> SensitivityVerdict`
|
|
2515
|
+
(`{sensitivity_class, safe}`) and splits it:
|
|
2516
|
+
|
|
2517
|
+
- **all-SAFE remainder** → the SAFE paths join the commit and are recorded as
|
|
2518
|
+
`soft` `CloseoutException`s (one per sensitivity class). No blocker;
|
|
2519
|
+
`verification_status` stays `passed`.
|
|
2520
|
+
- **any-UNSAFE remainder** → the UNSAFE subset blocks with `closeout_scope_violation`
|
|
2521
|
+
(human-required); SAFE paths in a mixed remainder still soft-commit.
|
|
2522
|
+
|
|
2523
|
+
Classifier precedence is load-bearing: UNSAFE-specific patterns (`secrets`,
|
|
2524
|
+
`lockfile`, `ci`) are matched before any broad SAFE rule, then `tests` → `source`
|
|
2525
|
+
(UNSAFE), then the narrow SAFE rules, then deny-by-default → `source`.
|
|
2526
|
+
`config_nonsource` is a tight filename allowlist (not a `.toml`/`.yaml`/`.json`
|
|
2527
|
+
suffix rule), and a bare `.txt` is docs only under `docs/`.
|
|
2528
|
+
|
|
2529
|
+
**`tests` are UNSAFE.** The taxonomy has no `tests` class; test paths only earn
|
|
2530
|
+
owned status via structural sibling matching upstream (`_runtime_relaxation_evidence`,
|
|
2531
|
+
`expanded_dirty_ownership_matches`). A test path reaching the classifier failed that
|
|
2532
|
+
matching, so it maps to `source` (UNSAFE) and is never auto-committed — operator
|
|
2533
|
+
break-glass (BREAKGLASS), not auto-commit, is the escape.
|
|
2534
|
+
|
|
2535
|
+
### Operator break-glass (BREAKGLASS)
|
|
2536
|
+
|
|
2537
|
+
The escape for the `closeout_scope_violation` block is the
|
|
2538
|
+
`--closeout-allow-unowned <reason>` CLI flag (valid on `run`/`resume`/`dry-run`
|
|
2539
|
+
only; requires a non-empty reason, rejected pre-`run_loop`; requires `--phase`,
|
|
2540
|
+
which bounds the blast radius to a single phase). It threads
|
|
2541
|
+
`allow_unowned_reason` through `run_loop` into `_perform_phase_closeout`, where
|
|
2542
|
+
the UNSAFE remainder is folded into the same closeout commit as `break_glass`
|
|
2543
|
+
`CloseoutException`s carrying the reason — the remainder is emptied, so the
|
|
2544
|
+
post-commit scope block does not fire. No amend, no second commit.
|
|
2545
|
+
|
|
2546
|
+
- **`secrets` are NEVER break-glassable.** A path whose `classify_unowned_path`
|
|
2547
|
+
class is `secrets` (`.env*`, `*.pem`, `secrets/**`) is held back regardless of
|
|
2548
|
+
the reason and still blocks with `closeout_scope_violation` — a one-line reason
|
|
2549
|
+
must not commit a secret into history (irreversible). Break-glass folds only
|
|
2550
|
+
`source`/`ci`/`lockfile`.
|
|
2551
|
+
- **Empty reason.** The CLI rejects a blank reason before `run_loop`. For
|
|
2552
|
+
programmatic callers, an explicitly-empty reason reaching closeout emits the
|
|
2553
|
+
`operator_override_missing_reason` blocker and does NOT commit the unsafe paths.
|
|
2554
|
+
- **Audit / bounded blast radius.** Every break-glass commit records a
|
|
2555
|
+
`break_glass` `CloseoutException` (exact paths + reason, in the shared tally)
|
|
2556
|
+
AND a phase-scoped `closeout_allow_unowned` attestation event in the ledger.
|
|
2557
|
+
The audit always names what was punched through and why.
|
|
2558
|
+
|
|
2559
|
+
### Reconcile parity (BREAKGLASS SL-2)
|
|
2560
|
+
|
|
2561
|
+
`reconcile` reads the `closeout_allow_unowned` attestation event to lift the
|
|
2562
|
+
`unowned_dirty_paths` bail in `_clean_verified_dirty_closeout_recovery_supersedes_blocker`
|
|
2563
|
+
(the **non-human** verified-dirty-closeout-recovery path) — so a phase whose
|
|
2564
|
+
verified dirty closeout already committed but recorded an unowned remainder
|
|
2565
|
+
recovers under recorded operator attestation. This is *parity*, not the primary
|
|
2566
|
+
escape: a live human-required `closeout_scope_violation` is broken through by the
|
|
2567
|
+
`--closeout-allow-unowned` rerun (which commits), not by reconcile. Attestation
|
|
2568
|
+
matching mirrors `_lane_ir_override`: `roadmap_sha256` + `phase_sha256` (content
|
|
2569
|
+
freshness) + phase + non-empty reason. A **stale** attestation (content drifted
|
|
2570
|
+
since it was written) no longer matches and does not authorize a later closeout;
|
|
2571
|
+
a recorded `secrets`-class remainder never recovers (the clean-worktree guard is
|
|
2572
|
+
the real backstop — SL-1 never commits a secret, so the worktree stays dirty).
|
|
2573
|
+
|
|
2574
|
+
## Cross-Repo Release Train (#29)
|
|
2575
|
+
|
|
2576
|
+
The cross-repo release-train coordinator (`train_runner.run_train`) orchestrates
|
|
2577
|
+
multi-repo changes in a single atomic train: draft PRs open across all nodes in
|
|
2578
|
+
topo order, a train-level governed review gates the full diff, then nodes merge
|
|
2579
|
+
sequentially with downstream re-verification against the upstream MERGED SHA
|
|
2580
|
+
before each downstream merge.
|
|
2581
|
+
|
|
2582
|
+
This section documents the coordinator's ledger shape, the merge-SHA-pinned
|
|
2583
|
+
cross-repo gate, and the merge/re-verify invariants that are enforced
|
|
2584
|
+
structurally (not just by convention).
|
|
2585
|
+
|
|
2586
|
+
### Train Ledger Shape
|
|
2587
|
+
|
|
2588
|
+
The train coordinator writes a side-car JSONL ledger at a caller-supplied path
|
|
2589
|
+
(never inside any repo's `.phase-loop/`). Each line is a `LedgerRecord`:
|
|
2590
|
+
|
|
2591
|
+
```json
|
|
2592
|
+
{
|
|
2593
|
+
"node_id": "<repo>/<plan-path>",
|
|
2594
|
+
"status": "pending|running|pr_open|approved|merged|blocked",
|
|
2595
|
+
"branch": "<feature-branch-name>",
|
|
2596
|
+
"pr_url": "<draft-PR-url>",
|
|
2597
|
+
"head_sha": "<draft-PR-head-commit-SHA>",
|
|
2598
|
+
"upstream_merge_sha": "<merge-commit-SHA-from-upstream-merge>",
|
|
2599
|
+
"merge_order": 0
|
|
2600
|
+
}
|
|
2601
|
+
```
|
|
2602
|
+
|
|
2603
|
+
Fields:
|
|
2604
|
+
|
|
2605
|
+
- `node_id`: `"<repo>/<plan-path>"` — identifies the train node.
|
|
2606
|
+
- `status`: one of `pending`, `running`, `pr_open`, `approved`, `merged`,
|
|
2607
|
+
`blocked`.
|
|
2608
|
+
- `branch`: the feature branch opened by P3 for this node.
|
|
2609
|
+
- `pr_url`: the GitHub PR URL opened by P3.
|
|
2610
|
+
- `head_sha`: the commit SHA at the HEAD of the draft PR when it was opened
|
|
2611
|
+
(the *draft pin*).
|
|
2612
|
+
- `upstream_merge_sha`: the merge-commit SHA of this node's PR (populated
|
|
2613
|
+
after the P4 merge step). For root nodes (no upstream dependency) this is
|
|
2614
|
+
the node's own merge SHA. For downstream nodes this field is also written
|
|
2615
|
+
when a dependent upstream's merged SHA is required.
|
|
2616
|
+
- `merge_order`: zero-based integer indicating the sequential merge position
|
|
2617
|
+
in the topo-sorted order.
|
|
2618
|
+
|
|
2619
|
+
A synthetic `_train_review_` record (`node_id="_train_review_"`) is appended
|
|
2620
|
+
when the train-level review panel approves. Its `status` is `"approved"`.
|
|
2621
|
+
|
|
2622
|
+
Idempotent resume: re-running `run_train` reads the ledger to skip nodes that
|
|
2623
|
+
are already `pr_open` (confirmed via a live `_pr_is_open` check) or already
|
|
2624
|
+
`merged`. A `blocked` node in the ledger is retried on resume.
|
|
2625
|
+
|
|
2626
|
+
### Merge-SHA-Pinned Cross-Repo Gate (False-Green Killer)
|
|
2627
|
+
|
|
2628
|
+
Before merging each downstream node, the coordinator:
|
|
2629
|
+
|
|
2630
|
+
1. Calls `set_upstream_ref(workspace, channel, upstream_merged_sha)` to
|
|
2631
|
+
re-resolve the downstream workspace's channel to the upstream **MERGED SHA**
|
|
2632
|
+
(not the draft SHA used during P3).
|
|
2633
|
+
2. Calls `reverify_fn(workspace, roadmap_path, run_mode)` to re-run the
|
|
2634
|
+
downstream's per-repo `run_loop` in verification mode.
|
|
2635
|
+
3. **Only** if reverify passes does `merge_pr_fn(workspace, branch)` execute
|
|
2636
|
+
for the downstream.
|
|
2637
|
+
|
|
2638
|
+
Step 1 **must precede** step 2. This ordering is structural (not advisory):
|
|
2639
|
+
the `set_upstream_ref` call is in the call log before the `reverify` call, as
|
|
2640
|
+
asserted by `tests/test_train_invariants.py::TestInvariant2FalseGreenKiller`.
|
|
2641
|
+
|
|
2642
|
+
A downstream that was green only against the draft ref (step 1 skipped) would
|
|
2643
|
+
produce a false-green verdict — the entire reason for the merged-SHA-pinned
|
|
2644
|
+
gate.
|
|
2645
|
+
|
|
2646
|
+
### Merge and Re-Verify Invariants
|
|
2647
|
+
|
|
2648
|
+
The following invariants are enforced by the coordinator and asserted in the
|
|
2649
|
+
test suite (`tests/test_train_invariants.py`):
|
|
2650
|
+
|
|
2651
|
+
**INV-1 — No merge before approval.** `merge_pr_fn` is never called until the
|
|
2652
|
+
train-level review panel approves. A rejection returns
|
|
2653
|
+
`status="review_halted"` with `terminal_blocker.human_required=False`; no node
|
|
2654
|
+
is merged.
|
|
2655
|
+
|
|
2656
|
+
**INV-2 — Merged SHA injected before re-verify (false-green killer).** For
|
|
2657
|
+
every downstream node, `set_upstream_ref` is called with the upstream MERGED
|
|
2658
|
+
SHA before `reverify_fn`. The ref passed to `set_upstream_ref` must equal the
|
|
2659
|
+
upstream merge-commit SHA, not the draft SHA. The ordering is asserted via a
|
|
2660
|
+
shared call log.
|
|
2661
|
+
|
|
2662
|
+
**INV-3 — Preflight failure opens zero PRs.** `run_train` runs preflight on
|
|
2663
|
+
all nodes before entering the per-node loop. Any preflight error causes an
|
|
2664
|
+
immediate return with `status="preflight_failed"` and zero `publish_from_worktree`
|
|
2665
|
+
calls.
|
|
2666
|
+
|
|
2667
|
+
**INV-4 — Train state off `.phase-loop/`.** The ledger path must not be inside
|
|
2668
|
+
any repo's `.phase-loop/`. `append_record` raises `ValueError` on violation.
|
|
2669
|
+
|
|
2670
|
+
**INV-5 — Autonomous mode stops at `drafts_open`.** Cross-repo merges are
|
|
2671
|
+
never auto-merged. With `run_mode="autonomous"` and `_merge_phase_enabled=True`
|
|
2672
|
+
the coordinator returns `status="drafts_open"` without calling `merge_pr_fn` or
|
|
2673
|
+
the review panel.
|
|
2674
|
+
|
|
2675
|
+
**INV-6 — `_live_reverify` reads real `StateSnapshot` failure signals.** The
|
|
2676
|
+
live-default `_live_reverify` returns False on `closeout_terminal_status` in
|
|
2677
|
+
`{"blocked","stale_input","failed_verification","human_required"}`, on
|
|
2678
|
+
`human_required=True`, and on `blocker_class is not None`.
|
|
2679
|
+
`closeout_terminal_status=None` is NOT a failure (verify mode may leave this
|
|
2680
|
+
field absent on a clean run).
|
|
2681
|
+
|
|
2682
|
+
**INV-6a — `run_loop` failure-signal contract (load-bearing for the false-green
|
|
2683
|
+
killer).** Because INV-6 treats `closeout_terminal_status=None` +
|
|
2684
|
+
`human_required=False` + `blocker_class=None` as a *pass*, the false-green killer
|
|
2685
|
+
is only sound if `run_loop` **always** surfaces a real verification failure
|
|
2686
|
+
through at least one of those three signals — i.e. it must NEVER return a
|
|
2687
|
+
"silent-failure" snapshot (all three clear) for a genuine failure. The runtime
|
|
2688
|
+
upholds this today: failure paths set `blocker_class` (defaulting to
|
|
2689
|
+
`contract_bug`), a non-`complete` `closeout_terminal_status`
|
|
2690
|
+
(`failed_verification`/`blocked`/`stale_input`), or `human_required=True`. Any
|
|
2691
|
+
future change to `run_loop` that can return a clean-looking snapshot on a real
|
|
2692
|
+
failure would silently defeat the downstream re-verify gate and MUST instead
|
|
2693
|
+
raise or set a failure signal. (INV-6's consumer-side tests pin `_live_reverify`;
|
|
2694
|
+
this producer-side contract is the assumption they rest on.)
|
|
2695
|
+
|
|
2696
|
+
**Forward-only guard.** A downstream re-verify failure halts the merge train
|
|
2697
|
+
at that node (`status="merge_halted"`); already-merged upstream nodes stay
|
|
2698
|
+
merged and are never reverted. The remaining nodes are blocked. Resuming with
|
|
2699
|
+
`--governed` after the integration issue is fixed picks up from the first
|
|
2700
|
+
unmerged node.
|
|
2701
|
+
|
|
2702
|
+
### Documented Limitation: Draft-Time Pin in Downstream PR
|
|
2703
|
+
|
|
2704
|
+
The downstream draft PR (opened during P3) retains the P3-time upstream pin,
|
|
2705
|
+
not the P4 merged-SHA pin. The re-resolution in step 1 of the gate changes
|
|
2706
|
+
only the **local workspace file** used for the re-verify pass; it does not
|
|
2707
|
+
amend the draft PR's content or rebase the feature branch.
|
|
2708
|
+
|
|
2709
|
+
As a result, the merged downstream PR carries the **draft-time pin** (the
|
|
2710
|
+
version of the upstream artifact that was current when the draft PR was opened),
|
|
2711
|
+
not the merge-commit SHA.
|
|
2712
|
+
|
|
2713
|
+
This is safe under **expand/contract** upstream contracts: the upstream artifact
|
|
2714
|
+
must be backward-compatible at the draft-time version, so the downstream
|
|
2715
|
+
integrates correctly even if the upstream has since moved to the merge SHA.
|
|
2716
|
+
Teams should author upstream changes as expand/contract (additive first,
|
|
2717
|
+
backward-compatible) so sequential merges are safe regardless of this pin
|
|
2718
|
+
limitation.
|
|
2719
|
+
|
|
2720
|
+
A future release may address this by amending the downstream draft PR after the
|
|
2721
|
+
upstream merges (requiring an update-existing-PR primitive); that work is
|
|
2722
|
+
deferred.
|
|
2723
|
+
|
|
2724
|
+
### Cross-Repo Train Roadmap Format
|
|
2725
|
+
|
|
2726
|
+
A train roadmap is a Markdown file consumed by `parse_train_roadmap()`:
|
|
2727
|
+
|
|
2728
|
+
```markdown
|
|
2729
|
+
# Release Train: <name>
|
|
2730
|
+
|
|
2731
|
+
## Nodes
|
|
2732
|
+
|
|
2733
|
+
### Node: <repo> / <plan-path>
|
|
2734
|
+
|
|
2735
|
+
**Depends on:** (none)
|
|
2736
|
+
**Channel:** (none)
|
|
2737
|
+
|
|
2738
|
+
### Node: <downstream-repo> / <downstream-plan>
|
|
2739
|
+
|
|
2740
|
+
**Depends on:** <repo> / <plan-path>
|
|
2741
|
+
**Channel:** submodule path=<submodule-path>
|
|
2742
|
+
```
|
|
2743
|
+
|
|
2744
|
+
**Channel descriptors:**
|
|
2745
|
+
|
|
2746
|
+
- `submodule path=<path>`: the downstream uses a git submodule at `<path>`
|
|
2747
|
+
pointing to the upstream repo. The coordinator calls `set_upstream_ref` with
|
|
2748
|
+
the upstream merged SHA to update the submodule pointer before re-verify.
|
|
2749
|
+
- `pin file=<file> key=<yaml-key>`: the downstream reads the upstream SHA from
|
|
2750
|
+
a pinfile at `<file>` under the YAML key `<key>`.
|
|
2751
|
+
|
|
2752
|
+
**Authoring recommendations:**
|
|
2753
|
+
|
|
2754
|
+
- Declare `**Depends on:** (none)` and `**Channel:** (none)` explicitly for
|
|
2755
|
+
root nodes.
|
|
2756
|
+
- Use expand/contract upstream changes so sequential merges are safe even if
|
|
2757
|
+
the downstream PR carries the draft-time pin.
|
|
2758
|
+
- Keep node `<repo>/<plan-path>` identifiers unique within a train.
|
|
2759
|
+
- Topo order is computed automatically; declare dependencies, not order.
|