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.
Files changed (370) hide show
  1. phase_loop_runtime/__init__.py +20 -0
  2. phase_loop_runtime/_contract_docs/phase-loop/extraction-readiness.md +90 -0
  3. phase_loop_runtime/_contract_docs/phase-loop/granular-execution-policy.md +137 -0
  4. phase_loop_runtime/_contract_docs/phase-loop/harness-capability-matrix.md +510 -0
  5. phase_loop_runtime/_contract_docs/phase-loop/harness-skill-matrix.md +125 -0
  6. phase_loop_runtime/_contract_docs/phase-loop/harness-substrate-manifest.md +232 -0
  7. phase_loop_runtime/_contract_docs/phase-loop/pi-loop-control.md +247 -0
  8. phase_loop_runtime/_contract_docs/phase-loop/protocol.md +2759 -0
  9. phase_loop_runtime/_contract_docs/phase-loop/runtime-boundary.md +289 -0
  10. phase_loop_runtime/_contract_docs/phase-loop/spec-discovery-roots.md +47 -0
  11. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-governed-pipeline.md +42 -0
  12. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-portal-projection.md +34 -0
  13. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-regenesis.md +29 -0
  14. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-report.md +32 -0
  15. phase_loop_runtime/_contract_docs/runtime/verification-evidence-contract.md +52 -0
  16. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/commands.txt +2 -0
  17. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/manifest.json +16 -0
  18. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/mixed.json +5 -0
  19. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/manifest.json +16 -0
  20. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/timeout-note.txt +2 -0
  21. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/manifest.json +16 -0
  22. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/redacted-summary.json +6 -0
  23. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/manifest.json +16 -0
  24. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/sample.json +7 -0
  25. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/edge.txt +2 -0
  26. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/manifest.json +16 -0
  27. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/manifest.json +16 -0
  28. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-a.txt +1 -0
  29. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-b.txt +1 -0
  30. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-c.txt +1 -0
  31. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/claimed-results.json +5 -0
  32. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/manifest.json +16 -0
  33. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/manifest.json +16 -0
  34. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/metrics.json +9 -0
  35. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/findings.md +10 -0
  36. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/manifest.json +16 -0
  37. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/manifest.json +16 -0
  38. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/scores.json +4 -0
  39. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/summary.txt +1 -0
  40. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/bundle-check.json +8 -0
  41. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/manifest.json +16 -0
  42. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/operator-note.txt +2 -0
  43. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/events-sample.jsonl +3 -0
  44. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/manifest.json +16 -0
  45. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/receipt.txt +1 -0
  46. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/evidence.json +13 -0
  47. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/manifest.json +16 -0
  48. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/summary.txt +2 -0
  49. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/manifest.json +16 -0
  50. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/matrix.json +9 -0
  51. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/notes.md +4 -0
  52. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/console-summary.txt +2 -0
  53. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/manifest.json +16 -0
  54. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/screenshot-standin.ppm +5 -0
  55. phase_loop_runtime/adoption_bundle.py +273 -0
  56. phase_loop_runtime/advisor_board/CONTRACTS.md +257 -0
  57. phase_loop_runtime/advisor_board/__init__.py +288 -0
  58. phase_loop_runtime/advisor_board/backing.py +137 -0
  59. phase_loop_runtime/advisor_board/backing_omnigent.py +498 -0
  60. phase_loop_runtime/advisor_board/config.py +225 -0
  61. phase_loop_runtime/advisor_board/events.py +115 -0
  62. phase_loop_runtime/advisor_board/fixtures/advisor-boards.example.toml +59 -0
  63. phase_loop_runtime/advisor_board/fixtures.py +91 -0
  64. phase_loop_runtime/advisor_board/harness_mapping.py +125 -0
  65. phase_loop_runtime/advisor_board/matrix.py +146 -0
  66. phase_loop_runtime/advisor_board/observability.py +481 -0
  67. phase_loop_runtime/advisor_board/presets.py +188 -0
  68. phase_loop_runtime/advisor_board/registries.py +309 -0
  69. phase_loop_runtime/advisor_board/resolver.py +253 -0
  70. phase_loop_runtime/advisor_board/schema.py +269 -0
  71. phase_loop_runtime/advisor_board/standin.py +166 -0
  72. phase_loop_runtime/advisor_board/validation.py +159 -0
  73. phase_loop_runtime/agent_runtime_provider.py +676 -0
  74. phase_loop_runtime/baml_modular.py +656 -0
  75. phase_loop_runtime/baml_src/dotfiles_adoption_manifest.baml +37 -0
  76. phase_loop_runtime/baml_src/dotfiles_c4_document.baml +14 -0
  77. phase_loop_runtime/baml_src/dotfiles_plan_manifest.baml +50 -0
  78. phase_loop_runtime/baml_src/dotfiles_runtime_projection.baml +16 -0
  79. phase_loop_runtime/baml_src/dotfiles_task_catalog.baml +20 -0
  80. phase_loop_runtime/baml_src/emit_phase_closeout.baml +86 -0
  81. phase_loop_runtime/baml_src/evaluate_suspected_fake_evidence.baml +36 -0
  82. phase_loop_runtime/baml_src/verification_evidence.baml +40 -0
  83. phase_loop_runtime/broker.py +181 -0
  84. phase_loop_runtime/build_bundle.py +294 -0
  85. phase_loop_runtime/capability_registry.py +835 -0
  86. phase_loop_runtime/classifier.py +87 -0
  87. phase_loop_runtime/claude_agent_view.py +625 -0
  88. phase_loop_runtime/claude_channel_sidecar.py +1086 -0
  89. phase_loop_runtime/cli.py +2599 -0
  90. phase_loop_runtime/closeout.py +865 -0
  91. phase_loop_runtime/closeout_classifier.py +120 -0
  92. phase_loop_runtime/closeout_evidence_audit.py +112 -0
  93. phase_loop_runtime/closeout_validation.py +144 -0
  94. phase_loop_runtime/closeout_validators.py +203 -0
  95. phase_loop_runtime/consiliency_gates.py +374 -0
  96. phase_loop_runtime/consiliency_ingest.py +399 -0
  97. phase_loop_runtime/consiliency_layout.py +176 -0
  98. phase_loop_runtime/consiliency_scaffold.py +305 -0
  99. phase_loop_runtime/cross_repo_channel.py +331 -0
  100. phase_loop_runtime/discovery.py +2368 -0
  101. phase_loop_runtime/dispatch_lock.py +167 -0
  102. phase_loop_runtime/doc_delta_validator.py +98 -0
  103. phase_loop_runtime/docs_audit.py +285 -0
  104. phase_loop_runtime/docs_freshness.py +468 -0
  105. phase_loop_runtime/docs_surfaces.py +150 -0
  106. phase_loop_runtime/dotfiles_profile_plugin.py +96 -0
  107. phase_loop_runtime/events.py +154 -0
  108. phase_loop_runtime/events_migration.py +96 -0
  109. phase_loop_runtime/evidence_audit.py +1082 -0
  110. phase_loop_runtime/evidence_audit_config.py +172 -0
  111. phase_loop_runtime/fleet_map.py +426 -0
  112. phase_loop_runtime/fleet_metrics.py +298 -0
  113. phase_loop_runtime/fleet_metrics_export.py +196 -0
  114. phase_loop_runtime/git_discipline.py +479 -0
  115. phase_loop_runtime/git_ops.py +122 -0
  116. phase_loop_runtime/git_topology.py +252 -0
  117. phase_loop_runtime/governed_bundle.py +137 -0
  118. phase_loop_runtime/governed_premerge.py +219 -0
  119. phase_loop_runtime/governed_review.py +264 -0
  120. phase_loop_runtime/handoff.py +932 -0
  121. phase_loop_runtime/handoff_path.py +14 -0
  122. phase_loop_runtime/injection.py +740 -0
  123. phase_loop_runtime/install_status.py +88 -0
  124. phase_loop_runtime/lane_scheduler.py +317 -0
  125. phase_loop_runtime/launcher.py +2760 -0
  126. phase_loop_runtime/lease_store.py +553 -0
  127. phase_loop_runtime/maintenance.py +401 -0
  128. phase_loop_runtime/migrate_handoffs.py +201 -0
  129. phase_loop_runtime/models.py +2144 -0
  130. phase_loop_runtime/observability.py +1290 -0
  131. phase_loop_runtime/panel_invoker.py +1638 -0
  132. phase_loop_runtime/phase_loop_drift_audit.py +388 -0
  133. phase_loop_runtime/phase_worktree_executor.py +433 -0
  134. phase_loop_runtime/pipeline_adapter/__init__.py +12 -0
  135. phase_loop_runtime/pipeline_adapter/branch_ops.py +274 -0
  136. phase_loop_runtime/pipeline_adapter/flag.py +55 -0
  137. phase_loop_runtime/pipeline_adapter/markers.py +13 -0
  138. phase_loop_runtime/pipeline_adapter/merge_policy.py +72 -0
  139. phase_loop_runtime/pipeline_adapter/ratification.py +100 -0
  140. phase_loop_runtime/pipeline_adapter/sibling_matcher.py +123 -0
  141. phase_loop_runtime/plan_ir.py +523 -0
  142. phase_loop_runtime/plan_manifest.py +415 -0
  143. phase_loop_runtime/planner_validation.py +176 -0
  144. phase_loop_runtime/profiles.py +462 -0
  145. phase_loop_runtime/prompts.py +365 -0
  146. phase_loop_runtime/provenance.py +165 -0
  147. phase_loop_runtime/publishing.py +339 -0
  148. phase_loop_runtime/py.typed +1 -0
  149. phase_loop_runtime/reconcile.py +1550 -0
  150. phase_loop_runtime/redaction.py +184 -0
  151. phase_loop_runtime/reflection_sync.py +293 -0
  152. phase_loop_runtime/release_guard.py +186 -0
  153. phase_loop_runtime/render.py +432 -0
  154. phase_loop_runtime/repo_validation.py +427 -0
  155. phase_loop_runtime/review_summary.py +150 -0
  156. phase_loop_runtime/roadmap_lint.py +425 -0
  157. phase_loop_runtime/route_log.py +59 -0
  158. phase_loop_runtime/route_policy/__init__.py +11 -0
  159. phase_loop_runtime/route_policy/fixtures/route_selection.golden.json +263 -0
  160. phase_loop_runtime/runner.py +8400 -0
  161. phase_loop_runtime/runtime_paths.py +225 -0
  162. phase_loop_runtime/runtime_projection.py +113 -0
  163. phase_loop_runtime/runtime_resources.py +36 -0
  164. phase_loop_runtime/schema_export.py +292 -0
  165. phase_loop_runtime/schemas/phase_loop_closeout.schema.json +366 -0
  166. phase_loop_runtime/skill_install.py +176 -0
  167. phase_loop_runtime/skill_inventory.py +662 -0
  168. phase_loop_runtime/skill_paths.py +42 -0
  169. phase_loop_runtime/skill_sources_plugin.py +37 -0
  170. phase_loop_runtime/skills_bundle/claude-advisor-board/README.md +1 -0
  171. phase_loop_runtime/skills_bundle/claude-advisor-board/SKILL.md +38 -0
  172. phase_loop_runtime/skills_bundle/claude-advisor-panel/README.md +1 -0
  173. phase_loop_runtime/skills_bundle/claude-advisor-panel/SKILL.md +38 -0
  174. phase_loop_runtime/skills_bundle/claude-execute-detailed/README.md +1 -0
  175. phase_loop_runtime/skills_bundle/claude-execute-detailed/SKILL.md +152 -0
  176. phase_loop_runtime/skills_bundle/claude-execute-phase/README.md +1 -0
  177. phase_loop_runtime/skills_bundle/claude-execute-phase/SKILL.md +745 -0
  178. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  179. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  180. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  181. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  182. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  183. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  184. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  185. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/state.py +274 -0
  186. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  187. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/team_teardown.sh +32 -0
  188. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/validate_plan_doc.py +642 -0
  189. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/verify_harness.sh +96 -0
  190. phase_loop_runtime/skills_bundle/claude-phase-loop/README.md +1 -0
  191. phase_loop_runtime/skills_bundle/claude-phase-loop/SKILL.md +115 -0
  192. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/README.md +1 -0
  193. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/SKILL.md +439 -0
  194. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/assets/review_prompt.md +19 -0
  195. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  196. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/roadmap-template.md +193 -0
  197. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  198. phase_loop_runtime/skills_bundle/claude-plan-detailed/README.md +1 -0
  199. phase_loop_runtime/skills_bundle/claude-plan-detailed/SKILL.md +334 -0
  200. phase_loop_runtime/skills_bundle/claude-plan-detailed/assets/review_prompt.md +25 -0
  201. phase_loop_runtime/skills_bundle/claude-plan-phase/README.md +1 -0
  202. phase_loop_runtime/skills_bundle/claude-plan-phase/SKILL.md +776 -0
  203. phase_loop_runtime/skills_bundle/claude-plan-phase/assets/review_prompt.md +17 -0
  204. phase_loop_runtime/skills_bundle/claude-plan-phase/scripts/validate_plan_doc.py +1092 -0
  205. phase_loop_runtime/skills_bundle/claude-run-train/README.md +1 -0
  206. phase_loop_runtime/skills_bundle/claude-run-train/SKILL.md +75 -0
  207. phase_loop_runtime/skills_bundle/claude-skill-editor/README.md +1 -0
  208. phase_loop_runtime/skills_bundle/claude-skill-editor/SKILL.md +243 -0
  209. phase_loop_runtime/skills_bundle/claude-skill-editor/assets/editor_prompt.md +72 -0
  210. phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/README.md +1 -0
  211. phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/SKILL.md +249 -0
  212. phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  213. phase_loop_runtime/skills_bundle/claude-task-contextualizer/README.md +1 -0
  214. phase_loop_runtime/skills_bundle/claude-task-contextualizer/SKILL.md +74 -0
  215. phase_loop_runtime/skills_bundle/claude-task-contextualizer/references/task-templates.md +103 -0
  216. phase_loop_runtime/skills_bundle/codex-advisor-board/README.md +1 -0
  217. phase_loop_runtime/skills_bundle/codex-advisor-board/SKILL.md +38 -0
  218. phase_loop_runtime/skills_bundle/codex-advisor-panel/README.md +1 -0
  219. phase_loop_runtime/skills_bundle/codex-advisor-panel/SKILL.md +38 -0
  220. phase_loop_runtime/skills_bundle/codex-execute-detailed/README.md +1 -0
  221. phase_loop_runtime/skills_bundle/codex-execute-detailed/SKILL.md +154 -0
  222. phase_loop_runtime/skills_bundle/codex-execute-phase/README.md +1 -0
  223. phase_loop_runtime/skills_bundle/codex-execute-phase/SKILL.md +231 -0
  224. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  225. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  226. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  227. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  228. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  229. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  230. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  231. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/state.py +274 -0
  232. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  233. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/team_teardown.sh +32 -0
  234. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/validate_plan_doc.py +642 -0
  235. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/verify_harness.sh +96 -0
  236. phase_loop_runtime/skills_bundle/codex-phase-loop/README.md +1 -0
  237. phase_loop_runtime/skills_bundle/codex-phase-loop/SKILL.md +248 -0
  238. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/README.md +1 -0
  239. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/SKILL.md +150 -0
  240. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/assets/review_prompt.md +19 -0
  241. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  242. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/roadmap-template.md +193 -0
  243. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  244. phase_loop_runtime/skills_bundle/codex-plan-detailed/README.md +1 -0
  245. phase_loop_runtime/skills_bundle/codex-plan-detailed/SKILL.md +119 -0
  246. phase_loop_runtime/skills_bundle/codex-plan-detailed/assets/review_prompt.md +25 -0
  247. phase_loop_runtime/skills_bundle/codex-plan-phase/README.md +1 -0
  248. phase_loop_runtime/skills_bundle/codex-plan-phase/SKILL.md +307 -0
  249. phase_loop_runtime/skills_bundle/codex-plan-phase/assets/review_prompt.md +17 -0
  250. phase_loop_runtime/skills_bundle/codex-plan-phase/scripts/validate_plan_doc.py +1092 -0
  251. phase_loop_runtime/skills_bundle/codex-run-train/README.md +1 -0
  252. phase_loop_runtime/skills_bundle/codex-run-train/SKILL.md +75 -0
  253. phase_loop_runtime/skills_bundle/codex-skill-editor/README.md +1 -0
  254. phase_loop_runtime/skills_bundle/codex-skill-editor/SKILL.md +94 -0
  255. phase_loop_runtime/skills_bundle/codex-skill-editor/assets/editor_prompt.md +72 -0
  256. phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/README.md +1 -0
  257. phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/SKILL.md +114 -0
  258. phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  259. phase_loop_runtime/skills_bundle/codex-task-contextualizer/README.md +1 -0
  260. phase_loop_runtime/skills_bundle/codex-task-contextualizer/SKILL.md +102 -0
  261. phase_loop_runtime/skills_bundle/codex-task-contextualizer/references/task-templates.md +103 -0
  262. phase_loop_runtime/skills_bundle/gemini-advisor-board/README.md +1 -0
  263. phase_loop_runtime/skills_bundle/gemini-advisor-board/SKILL.md +38 -0
  264. phase_loop_runtime/skills_bundle/gemini-advisor-panel/README.md +1 -0
  265. phase_loop_runtime/skills_bundle/gemini-advisor-panel/SKILL.md +38 -0
  266. phase_loop_runtime/skills_bundle/gemini-execute-detailed/README.md +1 -0
  267. phase_loop_runtime/skills_bundle/gemini-execute-detailed/SKILL.md +144 -0
  268. phase_loop_runtime/skills_bundle/gemini-execute-phase/README.md +1 -0
  269. phase_loop_runtime/skills_bundle/gemini-execute-phase/SKILL.md +198 -0
  270. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  271. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  272. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  273. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  274. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  275. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  276. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  277. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/state.py +274 -0
  278. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  279. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/team_teardown.sh +32 -0
  280. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/validate_plan_doc.py +642 -0
  281. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/verify_harness.sh +96 -0
  282. phase_loop_runtime/skills_bundle/gemini-phase-loop/README.md +1 -0
  283. phase_loop_runtime/skills_bundle/gemini-phase-loop/SKILL.md +111 -0
  284. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/README.md +1 -0
  285. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/SKILL.md +147 -0
  286. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/assets/review_prompt.md +19 -0
  287. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  288. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/roadmap-template.md +193 -0
  289. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  290. phase_loop_runtime/skills_bundle/gemini-plan-detailed/README.md +1 -0
  291. phase_loop_runtime/skills_bundle/gemini-plan-detailed/SKILL.md +119 -0
  292. phase_loop_runtime/skills_bundle/gemini-plan-detailed/assets/review_prompt.md +25 -0
  293. phase_loop_runtime/skills_bundle/gemini-plan-phase/README.md +1 -0
  294. phase_loop_runtime/skills_bundle/gemini-plan-phase/SKILL.md +217 -0
  295. phase_loop_runtime/skills_bundle/gemini-plan-phase/assets/review_prompt.md +17 -0
  296. phase_loop_runtime/skills_bundle/gemini-plan-phase/scripts/validate_plan_doc.py +1092 -0
  297. phase_loop_runtime/skills_bundle/gemini-run-train/README.md +1 -0
  298. phase_loop_runtime/skills_bundle/gemini-run-train/SKILL.md +75 -0
  299. phase_loop_runtime/skills_bundle/gemini-skill-editor/README.md +1 -0
  300. phase_loop_runtime/skills_bundle/gemini-skill-editor/SKILL.md +87 -0
  301. phase_loop_runtime/skills_bundle/gemini-skill-editor/assets/editor_prompt.md +72 -0
  302. phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/README.md +1 -0
  303. phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/SKILL.md +109 -0
  304. phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  305. phase_loop_runtime/skills_bundle/gemini-task-contextualizer/README.md +1 -0
  306. phase_loop_runtime/skills_bundle/gemini-task-contextualizer/SKILL.md +98 -0
  307. phase_loop_runtime/skills_bundle/gemini-task-contextualizer/references/task-templates.md +103 -0
  308. phase_loop_runtime/skills_bundle/opencode-advisor-board/README.md +1 -0
  309. phase_loop_runtime/skills_bundle/opencode-advisor-board/SKILL.md +38 -0
  310. phase_loop_runtime/skills_bundle/opencode-advisor-panel/README.md +1 -0
  311. phase_loop_runtime/skills_bundle/opencode-advisor-panel/SKILL.md +38 -0
  312. phase_loop_runtime/skills_bundle/opencode-execute-detailed/README.md +1 -0
  313. phase_loop_runtime/skills_bundle/opencode-execute-detailed/SKILL.md +144 -0
  314. phase_loop_runtime/skills_bundle/opencode-execute-phase/README.md +1 -0
  315. phase_loop_runtime/skills_bundle/opencode-execute-phase/SKILL.md +196 -0
  316. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  317. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  318. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  319. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  320. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  321. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  322. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  323. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/state.py +274 -0
  324. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  325. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/team_teardown.sh +32 -0
  326. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/validate_plan_doc.py +642 -0
  327. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/verify_harness.sh +96 -0
  328. phase_loop_runtime/skills_bundle/opencode-phase-loop/README.md +1 -0
  329. phase_loop_runtime/skills_bundle/opencode-phase-loop/SKILL.md +113 -0
  330. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/README.md +1 -0
  331. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/SKILL.md +147 -0
  332. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/assets/review_prompt.md +19 -0
  333. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  334. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/roadmap-template.md +193 -0
  335. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  336. phase_loop_runtime/skills_bundle/opencode-plan-detailed/README.md +1 -0
  337. phase_loop_runtime/skills_bundle/opencode-plan-detailed/SKILL.md +119 -0
  338. phase_loop_runtime/skills_bundle/opencode-plan-detailed/assets/review_prompt.md +25 -0
  339. phase_loop_runtime/skills_bundle/opencode-plan-phase/README.md +1 -0
  340. phase_loop_runtime/skills_bundle/opencode-plan-phase/SKILL.md +211 -0
  341. phase_loop_runtime/skills_bundle/opencode-plan-phase/assets/review_prompt.md +17 -0
  342. phase_loop_runtime/skills_bundle/opencode-plan-phase/scripts/validate_plan_doc.py +1092 -0
  343. phase_loop_runtime/skills_bundle/opencode-run-train/README.md +1 -0
  344. phase_loop_runtime/skills_bundle/opencode-run-train/SKILL.md +75 -0
  345. phase_loop_runtime/skills_bundle/opencode-skill-editor/README.md +1 -0
  346. phase_loop_runtime/skills_bundle/opencode-skill-editor/SKILL.md +87 -0
  347. phase_loop_runtime/skills_bundle/opencode-skill-editor/assets/editor_prompt.md +72 -0
  348. phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/README.md +1 -0
  349. phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/SKILL.md +109 -0
  350. phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  351. phase_loop_runtime/skills_bundle/opencode-task-contextualizer/README.md +1 -0
  352. phase_loop_runtime/skills_bundle/opencode-task-contextualizer/SKILL.md +98 -0
  353. phase_loop_runtime/skills_bundle/opencode-task-contextualizer/references/task-templates.md +103 -0
  354. phase_loop_runtime/state.py +156 -0
  355. phase_loop_runtime/state_degradation.py +136 -0
  356. phase_loop_runtime/state_ops.py +676 -0
  357. phase_loop_runtime/train_ledger.py +215 -0
  358. phase_loop_runtime/train_roadmap.py +339 -0
  359. phase_loop_runtime/train_runner.py +1197 -0
  360. phase_loop_runtime/verification_evidence.py +650 -0
  361. phase_loop_runtime/verification_evidence_validator.py +75 -0
  362. phase_loop_runtime/visual_evidence_validator.py +36 -0
  363. phase_loop_runtime/worker_pool.py +123 -0
  364. phase_loop_runtime/worktree_index.py +270 -0
  365. phase_loop_runtime-0.2.0.data/data/share/phase-loop-runtime/protocol/protocol.md +2475 -0
  366. phase_loop_runtime-0.2.0.dist-info/METADATA +106 -0
  367. phase_loop_runtime-0.2.0.dist-info/RECORD +370 -0
  368. phase_loop_runtime-0.2.0.dist-info/WHEEL +5 -0
  369. phase_loop_runtime-0.2.0.dist-info/entry_points.txt +9 -0
  370. phase_loop_runtime-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,553 @@
1
+ """CS-0.10c -- local-file `LeaseStore` + soft leases.
2
+
3
+ Implements the `LeaseStore` side of the CS-0.10b coordination contract
4
+ (`consiliency_contract`'s `lease.schema.json` / `lease-event.schema.json` /
5
+ `lease-store-protocol.schema.json`, contract >=0.2.0) so a fleet of local
6
+ agents working the same repo(s) in parallel can claim path-sets without
7
+ stepping on each other. Full design: `COORDINATION-deconfliction.md` on
8
+ consiliency-portal (`plans/unification/consiliency-standardization/`).
9
+
10
+ Two layers, kept deliberately separate (mirrors the contract's own split):
11
+
12
+ * :func:`project` -- a PURE function folding an ordered append-only
13
+ lease-event stream down to the current-lease view at a given instant. It
14
+ takes events only, nothing else, by construction (the sole-truth guardrail
15
+ from `coordination-channel-protocol.schema.json`: a coordination message may
16
+ PROMPT an actor to call a store op, but never itself mutates lease state --
17
+ there is no code path here for a message to even reach the projection).
18
+ This is the function the contract's `lease-*`/`coordination-*` conformance
19
+ vectors are asserted against directly (see
20
+ `tests/test_lease_store_conformance.py`).
21
+ * :class:`LeaseStore` -- the local-file backend: appends schema-valid
22
+ lease-events to `.consiliency/leases/events.jsonl` and answers
23
+ `acquire`/`renew`/`release`/`query` by re-running `project`/`project_all`
24
+ over that log. SOFT MODE ONLY: this backend never declares an atomic
25
+ backend (`atomic_backend = False`, always), so a requested hard lease
26
+ degrades to soft here per the contract's
27
+ `atomicity.degrade_without_atomic_backend` -- a real atomic compare-and-set
28
+ hard lease needs an off-device/coordinator backend (CS-0.10d, deferred).
29
+ Give-way policy is REROUTE, not block: an `acquire()` that collides with an
30
+ active lease (same `lease_id`, or an overlapping `scope` held by someone
31
+ else) returns a conflict result carrying the blocking lease so the caller
32
+ can go work something else -- it never raises, blocks, or preempts. TTL is
33
+ authoritative: an unrenewed lease past `heartbeat_at + ttl_seconds`
34
+ (exclusive boundary) is already free, so a dead holder can never freeze a
35
+ path forever.
36
+ """
37
+ from __future__ import annotations
38
+
39
+ import json
40
+ from dataclasses import dataclass
41
+ from datetime import datetime, timedelta
42
+ from pathlib import Path
43
+ from typing import Any, Mapping, Sequence
44
+
45
+ from jsonschema import Draft202012Validator
46
+ from referencing import Registry, Resource
47
+
48
+ from consiliency_contract import load_schema
49
+
50
+ from .consiliency_layout import consiliency_root
51
+ from .models import utc_now
52
+
53
+ try:
54
+ import fcntl
55
+ except ImportError: # pragma: no cover -- POSIX-only advisory lock
56
+ fcntl = None # type: ignore[assignment]
57
+
58
+ LEASES_DIRNAME = "leases"
59
+ EVENTS_FILENAME = "events.jsonl"
60
+
61
+ #: The failure modes the contract's operation_semantics pin (used to build
62
+ #: `protocol_descriptor()` and as the closed set every `reason` we return
63
+ #: comes from).
64
+ FAILURE_MODES: tuple[str, ...] = ("conflict", "not-holder", "not-found", "expired")
65
+
66
+
67
+ def leases_root(repo: Path) -> Path:
68
+ return consiliency_root(repo) / LEASES_DIRNAME
69
+
70
+
71
+ def events_path(repo: Path) -> Path:
72
+ return leases_root(repo) / EVENTS_FILENAME
73
+
74
+
75
+ # ---------------------------------------------------------------------------
76
+ # Vendored schemas / validators (built lazily, never copied -- CS-0.5/0.11
77
+ # convention: all shapes come from the installed `consiliency_contract`).
78
+ # ---------------------------------------------------------------------------
79
+
80
+
81
+ def lease_schema() -> dict[str, Any]:
82
+ return load_schema("lease")
83
+
84
+
85
+ def lease_event_schema() -> dict[str, Any]:
86
+ return load_schema("lease_event")
87
+
88
+
89
+ def lease_store_protocol_schema() -> dict[str, Any]:
90
+ return load_schema("lease_store_protocol")
91
+
92
+
93
+ def lease_validator() -> Draft202012Validator:
94
+ return Draft202012Validator(lease_schema())
95
+
96
+
97
+ def lease_event_validator() -> Draft202012Validator:
98
+ """`lease-event.schema.json`'s `lease` property `$ref`s `lease.schema.json`
99
+ by `$id` -- wire the two schemas together via a `referencing` registry so
100
+ an acquire event's nested lease snapshot is validated too."""
101
+ lease = lease_schema()
102
+ registry = Registry().with_resources([(lease["$id"], Resource.from_contents(lease))])
103
+ return Draft202012Validator(lease_event_schema(), registry=registry)
104
+
105
+
106
+ def protocol_descriptor() -> dict[str, Any]:
107
+ """The contract 0.2.0-pinned `LeaseStore` protocol descriptor this module
108
+ implements. Every field `lease-store-protocol.schema.json` pins is a
109
+ `const` (or a closed enum) -- building this and validating it against
110
+ that schema is a conformance self-check that our declared semantics
111
+ haven't drifted from the contract."""
112
+ return {
113
+ "schema": "consiliency.lease_store_protocol.v1",
114
+ "operations": ["acquire", "renew", "release", "query"],
115
+ "operation_semantics": {
116
+ "acquire": {
117
+ "request": "lease_id, holder, ttl_seconds, mode, scope, phase",
118
+ "response": "granted-lease | conflict",
119
+ "idempotent": False,
120
+ "holder_only": False,
121
+ "rejects": list(FAILURE_MODES),
122
+ },
123
+ "renew": {
124
+ "request": "lease_id, holder",
125
+ "response": "renewed-lease | rejected",
126
+ "idempotent": True,
127
+ "holder_only": True,
128
+ "rejects": list(FAILURE_MODES),
129
+ },
130
+ "release": {
131
+ "request": "lease_id, holder",
132
+ "response": "released | rejected",
133
+ "idempotent": True,
134
+ "holder_only": True,
135
+ "rejects": list(FAILURE_MODES),
136
+ },
137
+ "query": {
138
+ "request": "lease_id | scope",
139
+ "response": "current-lease | empty",
140
+ "idempotent": True,
141
+ "holder_only": False,
142
+ "rejects": list(FAILURE_MODES),
143
+ },
144
+ },
145
+ "failure_modes": list(FAILURE_MODES),
146
+ "source_of_truth": "lease-store",
147
+ "atomicity": {"hard_requires_atomic_acquire": True, "degrade_without_atomic_backend": "soft"},
148
+ "expiry": {
149
+ "ttl_authoritative": True,
150
+ "heartbeat_renews": True,
151
+ "auto_expiry": True,
152
+ "expires_at_formula": "heartbeat_at + ttl_seconds",
153
+ "boundary": "exclusive",
154
+ },
155
+ "granularity_ladder": {"default": "path-set", "opt_in": "symbol", "out_of_scope": "line"},
156
+ "backends": ["local-file", "portal", "coordinator"],
157
+ }
158
+
159
+
160
+ # ---------------------------------------------------------------------------
161
+ # Timestamps
162
+ # ---------------------------------------------------------------------------
163
+
164
+
165
+ def _parse_ts(value: str) -> datetime:
166
+ # matches the rest of the runtime's convention (state_degradation.py,
167
+ # observability.py, ...): plain `fromisoformat("...Z")` raises on 3.10.
168
+ return datetime.fromisoformat(value.replace("Z", "+00:00"))
169
+
170
+
171
+ def _expires_at(heartbeat_at: str, ttl_seconds: int) -> datetime:
172
+ return _parse_ts(heartbeat_at) + timedelta(seconds=ttl_seconds)
173
+
174
+
175
+ def is_expired(lease: Mapping[str, Any], now: str) -> bool:
176
+ """Exclusive boundary (`lease-store-protocol.schema.json`'s
177
+ `expiry.boundary`): a lease is held over `[acquired_at, expires_at)`; at
178
+ `now == expires_at` it is ALREADY expired."""
179
+ return _parse_ts(now) >= _expires_at(str(lease["heartbeat_at"]), int(lease["ttl_seconds"]))
180
+
181
+
182
+ # ---------------------------------------------------------------------------
183
+ # The pure conformance core
184
+ # ---------------------------------------------------------------------------
185
+
186
+
187
+ def resolve_effective_mode(mode: str, atomic_backend: bool) -> str:
188
+ """Hard mode is granted only over an atomic backend
189
+ (`atomicity.hard_requires_atomic_acquire`); anything else degrades to
190
+ soft (`atomicity.degrade_without_atomic_backend`)."""
191
+ return "hard" if mode == "hard" and atomic_backend else "soft"
192
+
193
+
194
+ def project(
195
+ events: Sequence[Mapping[str, Any]],
196
+ lease_id: str,
197
+ now: str,
198
+ *,
199
+ atomic_backend: bool = False,
200
+ ) -> dict[str, Any] | None:
201
+ """Fold an ordered append-only lease-event stream (the flat
202
+ `coordination-scenario.schema.json` event shape -- `event/lease_id/holder/
203
+ at` plus `ttl_seconds/mode/scope/phase` on an `acquire`) down to the
204
+ current-lease view for one `lease_id`, evaluated at `now`.
205
+
206
+ Pure and events-only by construction: there is no parameter here a
207
+ coordination message could ever occupy, which is what makes the
208
+ sole-truth guardrail structural rather than a runtime check.
209
+ """
210
+ state: dict[str, Any] | None = None
211
+ for event in events:
212
+ if event.get("lease_id") != lease_id:
213
+ continue
214
+ kind = event.get("event")
215
+ if kind == "acquire":
216
+ state = {
217
+ "schema": "consiliency.lease.v1",
218
+ "lease_id": lease_id,
219
+ "holder": event["holder"],
220
+ "acquired_at": event["at"],
221
+ "ttl_seconds": event["ttl_seconds"],
222
+ "heartbeat_at": event["at"],
223
+ "mode": resolve_effective_mode(event.get("mode", "soft"), atomic_backend),
224
+ "scope": dict(event["scope"]),
225
+ "phase": event["phase"],
226
+ }
227
+ elif kind == "renew":
228
+ if state is not None:
229
+ state = {**state, "heartbeat_at": event["at"]}
230
+ elif kind in ("release", "expire"):
231
+ state = None
232
+ if state is not None and is_expired(state, now):
233
+ state = None
234
+ return state
235
+
236
+
237
+ def project_all(
238
+ events: Sequence[Mapping[str, Any]],
239
+ now: str,
240
+ *,
241
+ atomic_backend: bool = False,
242
+ ) -> dict[str, dict[str, Any]]:
243
+ """`project()` every `lease_id` the stream ever mentions; the result is
244
+ exactly the map of currently ACTIVE leases (an expired/released/never-
245
+ granted `lease_id` is simply absent, not present-with-null)."""
246
+ ordered_ids: list[str] = []
247
+ seen: set[str] = set()
248
+ for event in events:
249
+ lease_id = event.get("lease_id")
250
+ if isinstance(lease_id, str) and lease_id not in seen:
251
+ seen.add(lease_id)
252
+ ordered_ids.append(lease_id)
253
+ active: dict[str, dict[str, Any]] = {}
254
+ for lease_id in ordered_ids:
255
+ view = project(events, lease_id, now, atomic_backend=atomic_backend)
256
+ if view is not None:
257
+ active[lease_id] = view
258
+ return active
259
+
260
+
261
+ def _ever_acquired(events: Sequence[Mapping[str, Any]], lease_id: str) -> bool:
262
+ return any(event.get("event") == "acquire" and event.get("lease_id") == lease_id for event in events)
263
+
264
+
265
+ # ---------------------------------------------------------------------------
266
+ # Scope overlap -- acquire-on-intent give-way over the path-set granularity
267
+ # ---------------------------------------------------------------------------
268
+
269
+
270
+ def _normalize_selector_prefix(selector: str) -> str:
271
+ """Strip a trailing glob suffix (`/**`, `/*`, `*`) so two path-set
272
+ selectors can be compared as plain path prefixes."""
273
+ value = selector
274
+ while value.endswith("/**") or value.endswith("/*"):
275
+ value = value.rsplit("/", 1)[0]
276
+ return value.rstrip("*").rstrip("/")
277
+
278
+
279
+ def _paths_overlap(a: str, b: str) -> bool:
280
+ prefix_a, prefix_b = _normalize_selector_prefix(a), _normalize_selector_prefix(b)
281
+ if prefix_a == prefix_b:
282
+ return True
283
+ return prefix_a.startswith(prefix_b + "/") or prefix_b.startswith(prefix_a + "/")
284
+
285
+
286
+ def _scope_paths(scope: Mapping[str, Any]) -> list[str]:
287
+ selectors = [str(s) for s in (scope.get("selector") or [])]
288
+ if scope.get("granularity") == "symbol":
289
+ # a symbol selector anchors "<path>::<symbol>" -- only the path
290
+ # component participates in path-set overlap checks.
291
+ return [selector.split("::", 1)[0] for selector in selectors]
292
+ return selectors
293
+
294
+
295
+ def scope_overlaps(a: Mapping[str, Any], b: Mapping[str, Any]) -> bool:
296
+ """Whether two lease scopes claim any of the same ground. `repo`
297
+ granularity always overlaps (it claims everything); `symbol` only
298
+ overlaps on an exact symbol match; `path-set` (the default) overlaps on a
299
+ shared path or path-ancestor relationship."""
300
+ if a.get("granularity") == "repo" or b.get("granularity") == "repo":
301
+ return True
302
+ if a.get("granularity") == "symbol" and b.get("granularity") == "symbol":
303
+ return bool(set(a.get("selector") or []) & set(b.get("selector") or []))
304
+ return any(_paths_overlap(pa, pb) for pa in _scope_paths(a) for pb in _scope_paths(b))
305
+
306
+
307
+ # ---------------------------------------------------------------------------
308
+ # Results
309
+ # ---------------------------------------------------------------------------
310
+
311
+
312
+ @dataclass(frozen=True)
313
+ class AcquireResult:
314
+ granted: bool
315
+ lease: dict[str, Any] | None
316
+ give_way: str | None # "reroute" on conflict; matches COORDINATION-deconfliction.md
317
+ conflict: dict[str, Any] | None # the blocking lease, when granted is False
318
+ degraded: bool # requested hard, granted soft (no atomic backend)
319
+ reason: str | None # one of FAILURE_MODES, or None when granted
320
+
321
+ def to_json(self) -> dict[str, Any]:
322
+ return {
323
+ "granted": self.granted,
324
+ "lease": self.lease,
325
+ "give_way": self.give_way,
326
+ "conflict": self.conflict,
327
+ "degraded": self.degraded,
328
+ "reason": self.reason,
329
+ }
330
+
331
+
332
+ @dataclass(frozen=True)
333
+ class RenewResult:
334
+ renewed: bool
335
+ lease: dict[str, Any] | None
336
+ reason: str | None
337
+
338
+ def to_json(self) -> dict[str, Any]:
339
+ return {"renewed": self.renewed, "lease": self.lease, "reason": self.reason}
340
+
341
+
342
+ @dataclass(frozen=True)
343
+ class ReleaseResult:
344
+ released: bool
345
+ reason: str | None
346
+
347
+ def to_json(self) -> dict[str, Any]:
348
+ return {"released": self.released, "reason": self.reason}
349
+
350
+
351
+ # ---------------------------------------------------------------------------
352
+ # The local-file backend
353
+ # ---------------------------------------------------------------------------
354
+
355
+
356
+ class LeaseStore:
357
+ """Local-file `LeaseStore` backend (CS-0.10c). Single machine, cooperative
358
+ agents. SOFT MODE ONLY: `atomic_backend` is always `False` here, so a
359
+ requested hard lease always degrades to soft -- there is no cross-machine
360
+ (or even cross-process-race-free) atomic compare-and-set available
361
+ locally; that needs the off-device/Portal backend (CS-0.10d).
362
+ """
363
+
364
+ backend = "local-file"
365
+ atomic_backend = False
366
+
367
+ def __init__(self, repo: str | Path):
368
+ self.repo = Path(repo)
369
+
370
+ # -- storage -----------------------------------------------------------
371
+
372
+ def _events_file(self) -> Path:
373
+ return events_path(self.repo)
374
+
375
+ def _read_raw_events(self) -> list[dict[str, Any]]:
376
+ path = self._events_file()
377
+ if not path.exists():
378
+ return []
379
+ events: list[dict[str, Any]] = []
380
+ for line in path.read_text(encoding="utf-8").splitlines():
381
+ line = line.strip()
382
+ if line:
383
+ events.append(json.loads(line))
384
+ return events
385
+
386
+ def _append_raw_event(self, event: Mapping[str, Any]) -> None:
387
+ path = self._events_file()
388
+ path.parent.mkdir(parents=True, exist_ok=True)
389
+ with open(path, "a", encoding="utf-8") as handle:
390
+ if fcntl is not None:
391
+ fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
392
+ try:
393
+ handle.write(json.dumps(dict(event), sort_keys=True) + "\n")
394
+ finally:
395
+ if fcntl is not None:
396
+ fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
397
+
398
+ @staticmethod
399
+ def _to_scenario_event(event: Mapping[str, Any]) -> dict[str, Any]:
400
+ """Flatten a stored `lease-event.schema.json`-shaped event (an
401
+ `acquire` nests the full lease snapshot under `lease`) into the flat
402
+ shape `project()` folds over."""
403
+ flat: dict[str, Any] = {
404
+ "event": event["event"],
405
+ "lease_id": event["lease_id"],
406
+ "holder": event["holder"],
407
+ "at": event["at"],
408
+ }
409
+ lease = event.get("lease")
410
+ if lease is not None:
411
+ flat.update(
412
+ ttl_seconds=lease["ttl_seconds"],
413
+ mode=lease["mode"],
414
+ scope=lease["scope"],
415
+ phase=lease["phase"],
416
+ )
417
+ return flat
418
+
419
+ def _scenario_events(self) -> list[dict[str, Any]]:
420
+ return [self._to_scenario_event(event) for event in self._read_raw_events()]
421
+
422
+ # -- operations ----------------------------------------------------------
423
+
424
+ def acquire(
425
+ self,
426
+ *,
427
+ lease_id: str,
428
+ holder: str,
429
+ ttl_seconds: int,
430
+ mode: str,
431
+ scope: Mapping[str, Any],
432
+ phase: str,
433
+ now: str | None = None,
434
+ ) -> AcquireResult:
435
+ """Acquire-on-intent over a path-set (default granularity). NOT
436
+ idempotent: re-issuing acquire against an already-active `lease_id`
437
+ always conflicts -- extend a held lease with `renew()` instead. On
438
+ any conflict (same `lease_id` already held, or an overlapping scope
439
+ held by someone else) this returns a result carrying the blocking
440
+ lease and `give_way="reroute"` -- it never blocks or raises."""
441
+ now = now or utc_now()
442
+ active = project_all(self._scenario_events(), now, atomic_backend=self.atomic_backend)
443
+
444
+ existing = active.get(lease_id)
445
+ if existing is not None:
446
+ return AcquireResult(
447
+ granted=False, lease=None, give_way="reroute", conflict=existing, degraded=False, reason="conflict"
448
+ )
449
+
450
+ conflicting = next(
451
+ (
452
+ other
453
+ for other_id, other in active.items()
454
+ if other_id != lease_id and other["holder"] != holder and scope_overlaps(scope, other["scope"])
455
+ ),
456
+ None,
457
+ )
458
+ if conflicting is not None:
459
+ return AcquireResult(
460
+ granted=False, lease=None, give_way="reroute", conflict=conflicting, degraded=False, reason="conflict"
461
+ )
462
+
463
+ effective_mode = resolve_effective_mode(mode, self.atomic_backend)
464
+ degraded = mode == "hard" and effective_mode == "soft"
465
+ lease = {
466
+ "schema": "consiliency.lease.v1",
467
+ "lease_id": lease_id,
468
+ "holder": holder,
469
+ "acquired_at": now,
470
+ "ttl_seconds": ttl_seconds,
471
+ "heartbeat_at": now,
472
+ "mode": effective_mode,
473
+ "scope": dict(scope),
474
+ "phase": phase,
475
+ }
476
+ raw_event = {
477
+ "schema": "consiliency.lease_event.v1",
478
+ "event": "acquire",
479
+ "lease_id": lease_id,
480
+ "holder": holder,
481
+ "at": now,
482
+ "lease": lease,
483
+ }
484
+ lease_event_validator().validate(raw_event)
485
+ self._append_raw_event(raw_event)
486
+ return AcquireResult(granted=True, lease=lease, give_way=None, conflict=None, degraded=degraded, reason=None)
487
+
488
+ def renew(self, *, lease_id: str, holder: str, now: str | None = None) -> RenewResult:
489
+ """Idempotent, holder-only: extends `heartbeat_at` so the lease stays
490
+ active past its original TTL. Rejects `not-holder` if someone else
491
+ holds it, `expired`/`not-found` if there's nothing to renew."""
492
+ now = now or utc_now()
493
+ raw_events = self._read_raw_events()
494
+ scenario_events = [self._to_scenario_event(event) for event in raw_events]
495
+ current = project(scenario_events, lease_id, now, atomic_backend=self.atomic_backend)
496
+ if current is None:
497
+ reason = "expired" if _ever_acquired(scenario_events, lease_id) else "not-found"
498
+ return RenewResult(renewed=False, lease=None, reason=reason)
499
+ if current["holder"] != holder:
500
+ return RenewResult(renewed=False, lease=None, reason="not-holder")
501
+
502
+ raw_event = {"schema": "consiliency.lease_event.v1", "event": "renew", "lease_id": lease_id, "holder": holder, "at": now}
503
+ lease_event_validator().validate(raw_event)
504
+ self._append_raw_event(raw_event)
505
+ return RenewResult(renewed=True, lease={**current, "heartbeat_at": now}, reason=None)
506
+
507
+ def release(self, *, lease_id: str, holder: str, now: str | None = None) -> ReleaseResult:
508
+ """Idempotent, holder-only. Releasing an already-free lease (never
509
+ held, already released, or auto-expired) is a no-op success -- that's
510
+ what idempotent means here. Someone else's active lease rejects
511
+ `not-holder`."""
512
+ now = now or utc_now()
513
+ raw_events = self._read_raw_events()
514
+ scenario_events = [self._to_scenario_event(event) for event in raw_events]
515
+ current = project(scenario_events, lease_id, now, atomic_backend=self.atomic_backend)
516
+ if current is None:
517
+ return ReleaseResult(released=True, reason=None)
518
+ if current["holder"] != holder:
519
+ return ReleaseResult(released=False, reason="not-holder")
520
+
521
+ raw_event = {"schema": "consiliency.lease_event.v1", "event": "release", "lease_id": lease_id, "holder": holder, "at": now}
522
+ lease_event_validator().validate(raw_event)
523
+ self._append_raw_event(raw_event)
524
+ return ReleaseResult(released=True, reason=None)
525
+
526
+ def query(
527
+ self,
528
+ *,
529
+ lease_id: str | None = None,
530
+ path: str | None = None,
531
+ scope: Mapping[str, Any] | None = None,
532
+ now: str | None = None,
533
+ ) -> dict[str, Any] | None:
534
+ """The current-lease view, projected from the event log ONLY -- no
535
+ coordination-channel input exists in this signature to mutate it.
536
+ Query by exact `lease_id`, or by `path`/`scope` to find whatever
537
+ active lease (if any) claims that ground."""
538
+ now = now or utc_now()
539
+ scenario_events = self._scenario_events()
540
+ if lease_id is not None:
541
+ return project(scenario_events, lease_id, now, atomic_backend=self.atomic_backend)
542
+
543
+ query_scope = scope if scope is not None else ({"granularity": "path-set", "selector": [path]} if path else None)
544
+ if query_scope is None:
545
+ raise ValueError("query() requires lease_id, path, or scope")
546
+ for other in project_all(scenario_events, now, atomic_backend=self.atomic_backend).values():
547
+ if scope_overlaps(query_scope, other["scope"]):
548
+ return other
549
+ return None
550
+
551
+ def list_active(self, *, now: str | None = None) -> dict[str, dict[str, Any]]:
552
+ now = now or utc_now()
553
+ return project_all(self._scenario_events(), now, atomic_backend=self.atomic_backend)