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,298 @@
|
|
|
1
|
+
"""CS-2.1 SA — fleet-metric ledger emission + pure derivation.
|
|
2
|
+
|
|
3
|
+
This is the enforcement-side PUSH source for the three LEDGER-FAITHFUL named
|
|
4
|
+
fleet-trend metrics the CS-2.1 spine reserved (velocity / burn_down /
|
|
5
|
+
promise_broken_duration). It is additive observability on the harness's OWN
|
|
6
|
+
ledger:
|
|
7
|
+
|
|
8
|
+
* Events are appended to a SEPARATE sibling ledger,
|
|
9
|
+
``.phase-loop/fleet-metrics.jsonl`` — ``events.jsonl`` is never touched, so
|
|
10
|
+
its bytes, digests, and reconcile/fold semantics are provably unaffected.
|
|
11
|
+
* Every append is a single ``os.write`` on an ``O_APPEND`` fd (mirrors
|
|
12
|
+
``events._append_jsonl`` and ``train_ledger.append_record``).
|
|
13
|
+
* Derivation is pure (no I/O) so the velocity/burn_down/promise-broken-duration
|
|
14
|
+
series are unit-testable without a runner or a database.
|
|
15
|
+
|
|
16
|
+
The load-bearing boundary: this file lives on the ENFORCEMENT side of the
|
|
17
|
+
``.phase-loop`` wall. Portal never reads it. Only the sanitized aggregate
|
|
18
|
+
series (see ``fleet_metrics_export``) crosses to Portal, one-way.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
from datetime import datetime, timezone
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, Iterable
|
|
28
|
+
|
|
29
|
+
from .models import FleetMetricEvent
|
|
30
|
+
from .runtime_paths import (
|
|
31
|
+
ensure_phase_loop_excluded,
|
|
32
|
+
phase_loop_fleet_metrics_file,
|
|
33
|
+
phase_loop_fleet_metrics_read_files,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ── append + read (the sibling ledger) ─────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
def fleet_metrics_path(repo: Path) -> Path:
|
|
40
|
+
return phase_loop_fleet_metrics_file(repo)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def append_fleet_metric(repo: Path, event: FleetMetricEvent) -> None:
|
|
44
|
+
"""Atomically append one fleet-metric event to the sibling ledger."""
|
|
45
|
+
ensure_phase_loop_excluded(repo)
|
|
46
|
+
path = fleet_metrics_path(repo)
|
|
47
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
encoded = (json.dumps(event.to_json(), sort_keys=True) + "\n").encode("utf-8")
|
|
49
|
+
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o666)
|
|
50
|
+
try:
|
|
51
|
+
os.write(fd, encoded)
|
|
52
|
+
finally:
|
|
53
|
+
os.close(fd)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def read_fleet_metrics(repo: Path) -> list[dict[str, Any]]:
|
|
57
|
+
"""Tolerant reader: skips blank/malformed lines, keeps only fleet_metric records."""
|
|
58
|
+
records: list[dict[str, Any]] = []
|
|
59
|
+
for path in phase_loop_fleet_metrics_read_files(repo):
|
|
60
|
+
if not path.exists():
|
|
61
|
+
continue
|
|
62
|
+
with path.open(encoding="utf-8") as handle:
|
|
63
|
+
for line in handle:
|
|
64
|
+
line = line.strip()
|
|
65
|
+
if not line:
|
|
66
|
+
continue
|
|
67
|
+
try:
|
|
68
|
+
record = json.loads(line)
|
|
69
|
+
except json.JSONDecodeError:
|
|
70
|
+
continue
|
|
71
|
+
if isinstance(record, dict) and record.get("event_kind") == "fleet_metric":
|
|
72
|
+
records.append(record)
|
|
73
|
+
return records
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ── runner hook — emit at closeout ─────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
def record_phase_fleet_metrics(
|
|
79
|
+
repo: Path,
|
|
80
|
+
*,
|
|
81
|
+
phase: str,
|
|
82
|
+
completed: bool,
|
|
83
|
+
total_scope: int,
|
|
84
|
+
completed_count: int,
|
|
85
|
+
missing_gates: Iterable[str] = (),
|
|
86
|
+
produced_gates: Iterable[str] = (),
|
|
87
|
+
timestamp: str | None = None,
|
|
88
|
+
) -> list[FleetMetricEvent]:
|
|
89
|
+
"""Emit the fleet-metric events implied by one phase closeout.
|
|
90
|
+
|
|
91
|
+
Truth sources (all already known at the closeout site, runner.py):
|
|
92
|
+
* ``completed`` / ``completed_count`` / ``total_scope`` — velocity + burn_down.
|
|
93
|
+
* ``missing_gates`` (declared-but-not-produced, from ``validate_produced_gates``)
|
|
94
|
+
— a BROKEN promise per gate.
|
|
95
|
+
* ``produced_gates`` that had an earlier unmatched ``promise_broken`` — a
|
|
96
|
+
REPAIRED promise (break-anchored, so duration is broken→repaired, never
|
|
97
|
+
mere fulfillment lead-time).
|
|
98
|
+
|
|
99
|
+
Best-effort: callers wrap this so observability can never break the loop.
|
|
100
|
+
"""
|
|
101
|
+
ts = timestamp or _utc_now()
|
|
102
|
+
emitted: list[FleetMetricEvent] = []
|
|
103
|
+
|
|
104
|
+
if completed:
|
|
105
|
+
emitted.append(
|
|
106
|
+
FleetMetricEvent(
|
|
107
|
+
metric_kind="velocity",
|
|
108
|
+
timestamp=ts,
|
|
109
|
+
phase=phase,
|
|
110
|
+
payload={"completed_total": int(completed_count)},
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
remaining = max(0, int(total_scope) - int(completed_count))
|
|
114
|
+
emitted.append(
|
|
115
|
+
FleetMetricEvent(
|
|
116
|
+
metric_kind="burn_down",
|
|
117
|
+
timestamp=ts,
|
|
118
|
+
phase=phase,
|
|
119
|
+
payload={
|
|
120
|
+
"total_scope": int(total_scope),
|
|
121
|
+
"completed": int(completed_count),
|
|
122
|
+
"remaining": remaining,
|
|
123
|
+
},
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Which gates are currently open breaks (declared-but-not-produced without a
|
|
128
|
+
# later repair) — computed from the ledger so repairs are break-anchored.
|
|
129
|
+
open_breaks = _open_promise_breaks(read_fleet_metrics(repo))
|
|
130
|
+
missing = [str(g) for g in missing_gates if str(g).strip()]
|
|
131
|
+
produced = {str(g) for g in produced_gates if str(g).strip()}
|
|
132
|
+
|
|
133
|
+
for gate in missing:
|
|
134
|
+
if gate in open_breaks:
|
|
135
|
+
continue # already an open break — don't double-count
|
|
136
|
+
emitted.append(
|
|
137
|
+
FleetMetricEvent(
|
|
138
|
+
metric_kind="promise_broken",
|
|
139
|
+
timestamp=ts,
|
|
140
|
+
phase=phase,
|
|
141
|
+
payload={"gate": gate},
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
open_breaks[gate] = ts
|
|
145
|
+
|
|
146
|
+
for gate in sorted(produced):
|
|
147
|
+
if gate in open_breaks:
|
|
148
|
+
emitted.append(
|
|
149
|
+
FleetMetricEvent(
|
|
150
|
+
metric_kind="promise_repaired",
|
|
151
|
+
timestamp=ts,
|
|
152
|
+
phase=phase,
|
|
153
|
+
payload={"gate": gate},
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
open_breaks.pop(gate, None)
|
|
157
|
+
|
|
158
|
+
for event in emitted:
|
|
159
|
+
append_fleet_metric(repo, event)
|
|
160
|
+
return emitted
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _open_promise_breaks(records: Iterable[dict[str, Any]]) -> dict[str, str]:
|
|
164
|
+
"""Fold the ledger to gates with an open break (break with no later repair).
|
|
165
|
+
|
|
166
|
+
Returns ``{gate: broken_at_ts}``. Records are in append order.
|
|
167
|
+
"""
|
|
168
|
+
open_at: dict[str, str] = {}
|
|
169
|
+
for record in records:
|
|
170
|
+
kind = record.get("metric_kind")
|
|
171
|
+
gate = str((record.get("payload") or {}).get("gate") or "").strip()
|
|
172
|
+
if not gate:
|
|
173
|
+
continue
|
|
174
|
+
if kind == "promise_broken":
|
|
175
|
+
open_at.setdefault(gate, str(record.get("timestamp") or ""))
|
|
176
|
+
elif kind == "promise_repaired":
|
|
177
|
+
open_at.pop(gate, None)
|
|
178
|
+
return open_at
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ── pure derivation — the sanitizable aggregate series ─────────────────────
|
|
182
|
+
|
|
183
|
+
def derive_fleet_metric_series(
|
|
184
|
+
records: Iterable[dict[str, Any]],
|
|
185
|
+
*,
|
|
186
|
+
now: str | None = None,
|
|
187
|
+
) -> dict[str, Any]:
|
|
188
|
+
"""Fold fleet-metric records into ledger-faithful aggregate time-series.
|
|
189
|
+
|
|
190
|
+
Pure: no I/O. Output contains ONLY numeric aggregates + ISO timestamps +
|
|
191
|
+
fixed enum labels — no gate identifiers, paths, secrets, or payloads. This
|
|
192
|
+
is exactly what the export bridge is allowed to push to Portal.
|
|
193
|
+
"""
|
|
194
|
+
records = list(records)
|
|
195
|
+
now_ts = now or _utc_now()
|
|
196
|
+
|
|
197
|
+
velocity_points: list[dict[str, Any]] = []
|
|
198
|
+
burndown_points: list[dict[str, Any]] = []
|
|
199
|
+
# Break/repair pairing keyed by gate (gate id stays LOCAL — only durations leave).
|
|
200
|
+
break_open: dict[str, str] = {}
|
|
201
|
+
repaired_durations: list[float] = []
|
|
202
|
+
open_break_started: dict[str, str] = {}
|
|
203
|
+
|
|
204
|
+
for record in records:
|
|
205
|
+
kind = record.get("metric_kind")
|
|
206
|
+
ts = str(record.get("timestamp") or "")
|
|
207
|
+
payload = record.get("payload") or {}
|
|
208
|
+
if kind == "velocity":
|
|
209
|
+
velocity_points.append(
|
|
210
|
+
{"t": ts, "completed_total": int(payload.get("completed_total") or 0)}
|
|
211
|
+
)
|
|
212
|
+
elif kind == "burn_down":
|
|
213
|
+
burndown_points.append(
|
|
214
|
+
{
|
|
215
|
+
"t": ts,
|
|
216
|
+
"total": int(payload.get("total_scope") or 0),
|
|
217
|
+
"completed": int(payload.get("completed") or 0),
|
|
218
|
+
"remaining": int(payload.get("remaining") or 0),
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
elif kind == "promise_broken":
|
|
222
|
+
gate = str(payload.get("gate") or "")
|
|
223
|
+
if gate:
|
|
224
|
+
break_open.setdefault(gate, ts)
|
|
225
|
+
open_break_started.setdefault(gate, ts)
|
|
226
|
+
elif kind == "promise_repaired":
|
|
227
|
+
gate = str(payload.get("gate") or "")
|
|
228
|
+
started = break_open.pop(gate, None)
|
|
229
|
+
open_break_started.pop(gate, None)
|
|
230
|
+
if started:
|
|
231
|
+
repaired_durations.append(_seconds_between(started, ts))
|
|
232
|
+
|
|
233
|
+
# promise_broken_duration series: one point per broken promise carrying only
|
|
234
|
+
# its duration (seconds) + whether it was repaired — never the gate id.
|
|
235
|
+
broken_points: list[dict[str, Any]] = []
|
|
236
|
+
# Re-walk to preserve order + emit repaired ones with their measured duration.
|
|
237
|
+
replay_open: dict[str, str] = {}
|
|
238
|
+
for record in records:
|
|
239
|
+
kind = record.get("metric_kind")
|
|
240
|
+
ts = str(record.get("timestamp") or "")
|
|
241
|
+
gate = str((record.get("payload") or {}).get("gate") or "")
|
|
242
|
+
if kind == "promise_broken" and gate:
|
|
243
|
+
replay_open.setdefault(gate, ts)
|
|
244
|
+
elif kind == "promise_repaired" and gate and gate in replay_open:
|
|
245
|
+
started = replay_open.pop(gate)
|
|
246
|
+
broken_points.append(
|
|
247
|
+
{"t": ts, "broken_seconds": _seconds_between(started, ts), "repaired": True}
|
|
248
|
+
)
|
|
249
|
+
# Still-open breaks: duration measured to `now`.
|
|
250
|
+
for gate, started in sorted(open_break_started.items()):
|
|
251
|
+
broken_points.append(
|
|
252
|
+
{"t": now_ts, "broken_seconds": _seconds_between(started, now_ts), "repaired": False}
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
open_count = len(open_break_started)
|
|
256
|
+
open_seconds = [
|
|
257
|
+
_seconds_between(started, now_ts) for started in open_break_started.values()
|
|
258
|
+
]
|
|
259
|
+
mean_repaired = (
|
|
260
|
+
round(sum(repaired_durations) / len(repaired_durations), 3)
|
|
261
|
+
if repaired_durations
|
|
262
|
+
else None
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
"velocity": {"points": velocity_points},
|
|
267
|
+
"burn_down": {"points": burndown_points},
|
|
268
|
+
"promise_broken_duration": {
|
|
269
|
+
"points": broken_points,
|
|
270
|
+
"aggregate": {
|
|
271
|
+
"open_count": open_count,
|
|
272
|
+
"max_open_seconds": round(max(open_seconds), 3) if open_seconds else 0,
|
|
273
|
+
"mean_repaired_seconds": mean_repaired,
|
|
274
|
+
"repaired_count": len(repaired_durations),
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _seconds_between(start_ts: str, end_ts: str) -> float:
|
|
281
|
+
try:
|
|
282
|
+
start = _parse_ts(start_ts)
|
|
283
|
+
end = _parse_ts(end_ts)
|
|
284
|
+
except ValueError:
|
|
285
|
+
return 0.0
|
|
286
|
+
return max(0.0, round((end - start).total_seconds(), 3))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _parse_ts(value: str) -> datetime:
|
|
290
|
+
text = value.strip().replace("Z", "+00:00")
|
|
291
|
+
parsed = datetime.fromisoformat(text)
|
|
292
|
+
if parsed.tzinfo is None:
|
|
293
|
+
parsed = parsed.replace(tzinfo=timezone.utc)
|
|
294
|
+
return parsed
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _utc_now() -> str:
|
|
298
|
+
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""CS-2.1 SA — the one-way SANITIZED export bridge (enforcement → Portal).
|
|
2
|
+
|
|
3
|
+
This is the PUSH side of the ``.phase-loop`` wall. Enforcement computes the
|
|
4
|
+
ledger-faithful aggregate series (``fleet_metrics.derive_fleet_metric_series``)
|
|
5
|
+
and pushes ONLY sanitized aggregates + timestamps to Portal's control-plane
|
|
6
|
+
ingest. Portal never reaches back into ``.phase-loop`` — the direction is
|
|
7
|
+
strictly enforcement → Portal.
|
|
8
|
+
|
|
9
|
+
Sanitization is PROVABLE, not incidental: :func:`build_sanitized_export` runs
|
|
10
|
+
every payload through :func:`assert_sanitized`, which fails closed if any leaf
|
|
11
|
+
value looks like a filesystem path, a ``.phase-loop`` / ``events.jsonl``
|
|
12
|
+
reference, a secret, or a raw session payload. The named metrics only ever carry
|
|
13
|
+
numbers, ISO-8601 timestamps, and a fixed set of enum labels.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import re
|
|
20
|
+
import urllib.request
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from .fleet_metrics import derive_fleet_metric_series, read_fleet_metrics
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
EXPORT_SCHEMA = "fleet_metrics_export.v1"
|
|
28
|
+
|
|
29
|
+
# The complete set of NAMED ledger-faithful series this bridge is allowed to
|
|
30
|
+
# push. Matches the series_kinds the CS-2.1 spine reserved for `ledger-faithful`.
|
|
31
|
+
NAMED_SERIES = ("velocity", "burn_down", "promise_broken_duration")
|
|
32
|
+
|
|
33
|
+
# A repo id is a control-plane LABEL, never a path — enforce a slug shape.
|
|
34
|
+
_REPO_SLUG_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$")
|
|
35
|
+
|
|
36
|
+
# ISO-8601 UTC timestamps are the only strings allowed as leaf *values* (besides
|
|
37
|
+
# the fixed enum labels), so a stray path can never masquerade as a timestamp.
|
|
38
|
+
_ISO_TS_RE = re.compile(
|
|
39
|
+
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Substrings that must NEVER appear anywhere in the payload — the wall markers
|
|
43
|
+
# plus common secret/path tells. A hit is a hard sanitization failure.
|
|
44
|
+
_FORBIDDEN_SUBSTRINGS = (
|
|
45
|
+
"/",
|
|
46
|
+
"\\",
|
|
47
|
+
".phase-loop",
|
|
48
|
+
"events.jsonl",
|
|
49
|
+
"state.json",
|
|
50
|
+
"secret",
|
|
51
|
+
"token",
|
|
52
|
+
"api_key",
|
|
53
|
+
"authorization",
|
|
54
|
+
"password",
|
|
55
|
+
"session",
|
|
56
|
+
"prompt",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Keys allowed to appear in the payload tree. Anything else (e.g. a raw "path",
|
|
60
|
+
# "gate", "payload", "command" field) is a sanitization failure — this is what
|
|
61
|
+
# stops raw ledger internals from leaking.
|
|
62
|
+
_ALLOWED_KEYS = frozenset(
|
|
63
|
+
{
|
|
64
|
+
"schema",
|
|
65
|
+
"repo",
|
|
66
|
+
"captured_at",
|
|
67
|
+
"series",
|
|
68
|
+
"series_kind",
|
|
69
|
+
"provenance",
|
|
70
|
+
"points",
|
|
71
|
+
"aggregate",
|
|
72
|
+
# numeric/label leaves inside points + aggregate:
|
|
73
|
+
"t",
|
|
74
|
+
"completed_total",
|
|
75
|
+
"total",
|
|
76
|
+
"completed",
|
|
77
|
+
"remaining",
|
|
78
|
+
"broken_seconds",
|
|
79
|
+
"repaired",
|
|
80
|
+
"open_count",
|
|
81
|
+
"max_open_seconds",
|
|
82
|
+
"mean_repaired_seconds",
|
|
83
|
+
"repaired_count",
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class SanitizationError(ValueError):
|
|
89
|
+
"""Raised when an export payload carries a forbidden path/secret/payload field."""
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def build_export_payload(repo_id: str, series: dict[str, Any], captured_at: str) -> dict[str, Any]:
|
|
93
|
+
"""Shape the derived series into the ``fleet_metrics_export.v1`` wire payload.
|
|
94
|
+
|
|
95
|
+
Every pushed series is ``provenance: ledger-faithful`` — the DB CHECK on the
|
|
96
|
+
Portal side binds these three series_kinds to exactly that provenance.
|
|
97
|
+
"""
|
|
98
|
+
if not _REPO_SLUG_RE.match(repo_id):
|
|
99
|
+
raise SanitizationError(f"repo id is not a control-plane slug: {repo_id!r}")
|
|
100
|
+
return {
|
|
101
|
+
"schema": EXPORT_SCHEMA,
|
|
102
|
+
"repo": repo_id,
|
|
103
|
+
"captured_at": captured_at,
|
|
104
|
+
"series": [
|
|
105
|
+
{
|
|
106
|
+
"series_kind": name,
|
|
107
|
+
"provenance": "ledger-faithful",
|
|
108
|
+
**series.get(name, {}),
|
|
109
|
+
}
|
|
110
|
+
for name in NAMED_SERIES
|
|
111
|
+
],
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def build_sanitized_export(repo: Path, *, repo_id: str, captured_at: str) -> dict[str, Any]:
|
|
116
|
+
"""Read the enforcement ledger, derive the series, and return a proven-sanitized payload."""
|
|
117
|
+
series = derive_fleet_metric_series(read_fleet_metrics(repo), now=captured_at)
|
|
118
|
+
payload = build_export_payload(repo_id, series, captured_at)
|
|
119
|
+
assert_sanitized(payload)
|
|
120
|
+
return payload
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def assert_sanitized(payload: Any, *, _path: str = "$") -> None:
|
|
124
|
+
"""Fail closed unless every node is a numeric aggregate, ISO timestamp, or fixed label.
|
|
125
|
+
|
|
126
|
+
Walks the whole tree:
|
|
127
|
+
* dict keys must be in ``_ALLOWED_KEYS``;
|
|
128
|
+
* every string value must be an allowed enum label or a strict ISO-8601
|
|
129
|
+
timestamp — and must contain no forbidden substring;
|
|
130
|
+
* numbers/bools/None pass; nested lists/dicts recurse.
|
|
131
|
+
"""
|
|
132
|
+
if isinstance(payload, dict):
|
|
133
|
+
for key, value in payload.items():
|
|
134
|
+
if key not in _ALLOWED_KEYS:
|
|
135
|
+
raise SanitizationError(f"forbidden key at {_path}.{key}")
|
|
136
|
+
assert_sanitized(value, _path=f"{_path}.{key}")
|
|
137
|
+
return
|
|
138
|
+
if isinstance(payload, (list, tuple)):
|
|
139
|
+
for i, item in enumerate(payload):
|
|
140
|
+
assert_sanitized(item, _path=f"{_path}[{i}]")
|
|
141
|
+
return
|
|
142
|
+
if isinstance(payload, bool) or payload is None:
|
|
143
|
+
return
|
|
144
|
+
if isinstance(payload, (int, float)):
|
|
145
|
+
return
|
|
146
|
+
if isinstance(payload, str):
|
|
147
|
+
lowered = payload.lower()
|
|
148
|
+
for bad in _FORBIDDEN_SUBSTRINGS:
|
|
149
|
+
if bad in lowered:
|
|
150
|
+
raise SanitizationError(f"forbidden substring {bad!r} at {_path}: {payload!r}")
|
|
151
|
+
if payload in _ALLOWED_LABELS or _ISO_TS_RE.match(payload):
|
|
152
|
+
return
|
|
153
|
+
# The one free-form string is the control-plane repo LABEL, allowed only
|
|
154
|
+
# under the `$.repo` key and only if it is a slug (no path/secret shape).
|
|
155
|
+
if _path == "$.repo" and _REPO_SLUG_RE.match(payload):
|
|
156
|
+
return
|
|
157
|
+
raise SanitizationError(f"non-timestamp, non-label string at {_path}: {payload!r}")
|
|
158
|
+
raise SanitizationError(f"unsupported leaf type {type(payload).__name__} at {_path}")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# Fixed enum labels permitted as string leaf VALUES (schema/provenance/kind/repo).
|
|
162
|
+
# Note: repo ids are validated separately by shape; the wall markers can never be
|
|
163
|
+
# a repo id because a slug forbids "/".
|
|
164
|
+
_ALLOWED_LABELS = frozenset({EXPORT_SCHEMA, "ledger-faithful", *NAMED_SERIES})
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def push_export(
|
|
168
|
+
url: str,
|
|
169
|
+
payload: dict[str, Any],
|
|
170
|
+
*,
|
|
171
|
+
ingest_key: str,
|
|
172
|
+
timeout: float = 15.0,
|
|
173
|
+
) -> int:
|
|
174
|
+
"""One-way HTTP POST of a proven-sanitized payload to Portal's ingest route.
|
|
175
|
+
|
|
176
|
+
Fails closed on missing url/key. Re-asserts sanitization immediately before
|
|
177
|
+
the wire write so nothing unsanitized can ever leave, even if a caller built
|
|
178
|
+
the payload by hand. Returns the HTTP status code.
|
|
179
|
+
"""
|
|
180
|
+
if not url:
|
|
181
|
+
raise ValueError("fleet-metrics export url is not configured")
|
|
182
|
+
if not ingest_key:
|
|
183
|
+
raise ValueError("fleet-metrics export ingest key is not configured")
|
|
184
|
+
assert_sanitized(payload)
|
|
185
|
+
body = json.dumps(payload, sort_keys=True).encode("utf-8")
|
|
186
|
+
request = urllib.request.Request(
|
|
187
|
+
url,
|
|
188
|
+
data=body,
|
|
189
|
+
method="POST",
|
|
190
|
+
headers={
|
|
191
|
+
"content-type": "application/json",
|
|
192
|
+
"x-ledger-export-key": ingest_key,
|
|
193
|
+
},
|
|
194
|
+
)
|
|
195
|
+
with urllib.request.urlopen(request, timeout=timeout) as response: # noqa: S310 (trusted control-plane url)
|
|
196
|
+
return int(response.status)
|