specfuse-loop 0.6.0__tar.gz → 0.7.0__tar.gz

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 (247) hide show
  1. {specfuse_loop-0.6.0/specfuse_loop.egg-info → specfuse_loop-0.7.0}/PKG-INFO +5 -1
  2. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/pyproject.toml +11 -1
  3. specfuse_loop-0.7.0/specfuse/loop/closing_requirements.py +232 -0
  4. specfuse_loop-0.7.0/specfuse/loop/data/VERSION +1 -0
  5. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/monitoring.yml.example +6 -1
  6. specfuse_loop-0.7.0/specfuse/loop/data/rules/close-discipline.md +119 -0
  7. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/templates/WU.template.md +2 -5
  8. specfuse_loop-0.7.0/specfuse/loop/data/workflows/specfuse-monitor.yml +42 -0
  9. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/labels.py +13 -0
  10. specfuse_loop-0.7.0/specfuse/loop/lint_closing.py +388 -0
  11. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/lint_monitoring.py +72 -7
  12. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/lint_plan.py +16 -1
  13. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/loop.py +209 -42
  14. specfuse_loop-0.7.0/specfuse/monitor/__init__.py +3 -0
  15. specfuse_loop-0.7.0/specfuse/monitor/adapters.py +44 -0
  16. specfuse_loop-0.7.0/specfuse/monitor/artifact.py +82 -0
  17. specfuse_loop-0.7.0/specfuse/monitor/cli.py +584 -0
  18. specfuse_loop-0.7.0/specfuse/monitor/fingerprint.py +37 -0
  19. specfuse_loop-0.7.0/specfuse/monitor/issues.py +289 -0
  20. specfuse_loop-0.7.0/specfuse/monitor/providers/__init__.py +9 -0
  21. specfuse_loop-0.7.0/specfuse/monitor/providers/_azure_auth.py +52 -0
  22. specfuse_loop-0.7.0/specfuse/monitor/providers/azure_app_insights.py +326 -0
  23. specfuse_loop-0.7.0/specfuse/monitor/providers/azure_service_bus.py +341 -0
  24. specfuse_loop-0.7.0/specfuse/monitor/redaction.py +97 -0
  25. specfuse_loop-0.7.0/specfuse/monitor/schedule.py +265 -0
  26. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0/specfuse_loop.egg-info}/PKG-INFO +5 -1
  27. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse_loop.egg-info/SOURCES.txt +33 -0
  28. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse_loop.egg-info/entry_points.txt +1 -0
  29. specfuse_loop-0.7.0/specfuse_loop.egg-info/requires.txt +12 -0
  30. specfuse_loop-0.7.0/tests/test_app_insights_adapters.py +248 -0
  31. specfuse_loop-0.7.0/tests/test_artifact_redaction.py +125 -0
  32. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_autosync.py +41 -10
  33. specfuse_loop-0.7.0/tests/test_autosync_no_cwd_leak.py +155 -0
  34. specfuse_loop-0.7.0/tests/test_azure_credential_resolution.py +131 -0
  35. specfuse_loop-0.7.0/tests/test_closing_guard_contracts.py +91 -0
  36. specfuse_loop-0.7.0/tests/test_closing_requirements.py +200 -0
  37. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_derive_monitoring_discovery.py +23 -10
  38. specfuse_loop-0.7.0/tests/test_dispatch_skeleton.py +325 -0
  39. specfuse_loop-0.7.0/tests/test_failure_artifact_model.py +109 -0
  40. specfuse_loop-0.7.0/tests/test_fingerprint.py +203 -0
  41. specfuse_loop-0.7.0/tests/test_heartbeat_adapter.py +193 -0
  42. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_init_integration.py +1 -0
  43. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_label_registry.py +10 -2
  44. specfuse_loop-0.7.0/tests/test_label_registry_covers_consumers.py +128 -0
  45. specfuse_loop-0.7.0/tests/test_lint_closing.py +249 -0
  46. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_monitoring.py +25 -4
  47. specfuse_loop-0.7.0/tests/test_monitor_cli.py +768 -0
  48. specfuse_loop-0.7.0/tests/test_monitor_issue_lifecycle.py +317 -0
  49. specfuse_loop-0.7.0/tests/test_monitor_runner_surfaces.py +241 -0
  50. specfuse_loop-0.7.0/tests/test_monitoring_cron_dialect.py +324 -0
  51. specfuse_loop-0.7.0/tests/test_queue_stalled_adapter.py +356 -0
  52. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_scaffold_data_in_sync.py +17 -1
  53. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_scaffold_init.py +1 -0
  54. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_scaffold_resources.py +1 -0
  55. specfuse_loop-0.7.0/tests/test_schedule_dialect.py +191 -0
  56. specfuse_loop-0.7.0/tests/test_service_bus_dlq_adapter.py +263 -0
  57. specfuse_loop-0.6.0/specfuse/loop/data/VERSION +0 -1
  58. specfuse_loop-0.6.0/specfuse/loop/data/rules/close-discipline.md +0 -115
  59. specfuse_loop-0.6.0/specfuse_loop.egg-info/requires.txt +0 -7
  60. specfuse_loop-0.6.0/tests/test_closing_guard_contracts.py +0 -172
  61. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/LICENSE +0 -0
  62. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/NOTICE +0 -0
  63. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/README.md +0 -0
  64. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/setup.cfg +0 -0
  65. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/__init__.py +0 -0
  66. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/_filelock.py +0 -0
  67. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/_miniyaml.py +0 -0
  68. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/_wu_sections.py +0 -0
  69. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/adopt_feature.py +0 -0
  70. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/LEARNINGS.template.md +0 -0
  71. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
  72. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
  73. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/docs/getting-started.md +0 -0
  74. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/docs/methodology.md +0 -0
  75. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/docs/skills.md +0 -0
  76. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/gitignore.snippet +0 -0
  77. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/monitoring-secrets-checklist.md +0 -0
  78. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/monitoring.overrides.yml.example +0 -0
  79. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/roadmap.template.md +0 -0
  80. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/rules/correlation-ids.md +0 -0
  81. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/rules/design-for-diagnosis.md +0 -0
  82. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/rules/never-touch.md +0 -0
  83. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/rules/operator-escalation.md +0 -0
  84. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/rules/planning-discipline.md +0 -0
  85. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/rules/result-contract.md +0 -0
  86. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/rules/security-boundaries.md +0 -0
  87. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/rules/verification-discipline.md +0 -0
  88. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/rules-local/README.md +0 -0
  89. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/schemas/event.schema.json +0 -0
  90. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
  91. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
  92. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
  93. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
  94. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/templates/GATE.template.md +0 -0
  95. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/templates/PLAN.template.md +0 -0
  96. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/data/verification.yml.example +0 -0
  97. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/escalation.py +0 -0
  98. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/events_stats.py +0 -0
  99. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/gate_eval.py +0 -0
  100. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/gh_backend.py +0 -0
  101. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/gh_features.py +0 -0
  102. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/scaffold.py +0 -0
  103. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse/loop/validate_event.py +0 -0
  104. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse_loop.egg-info/dependency_links.txt +0 -0
  105. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/specfuse_loop.egg-info/top_level.txt +0 -0
  106. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_accept_hedged_close_skill.py +0 -0
  107. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_adopt_feature.py +0 -0
  108. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_arm_gate_edits_uncommitted.py +0 -0
  109. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_attempt_outcome_contract.py +0 -0
  110. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_attempt_outcome_emission.py +0 -0
  111. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_attention_nonwriting_guard.py +0 -0
  112. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_attention_skill_structure.py +0 -0
  113. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_autoclose_deferral_visibility.py +0 -0
  114. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_autosync_consent.py +0 -0
  115. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_autosync_firstrun.py +0 -0
  116. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_autosync_plugin.py +0 -0
  117. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_backend.py +0 -0
  118. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_baseline_escalation_message.py +0 -0
  119. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_baseline_persistence.py +0 -0
  120. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_baseline_probe.py +0 -0
  121. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_bash_routing.py +0 -0
  122. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_bats_suites_gated.py +0 -0
  123. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_bookkeeping_commit_crash_run.py +0 -0
  124. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
  125. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_bookkeeping_no_verify.py +0 -0
  126. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_claude_resolution.py +0 -0
  127. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_close_wu_self_reference.py +0 -0
  128. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_closing_deliverable_guard.py +0 -0
  129. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_closing_guard_prediction.py +0 -0
  130. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_console_encoding.py +0 -0
  131. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_cost_tracking.py +0 -0
  132. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_degraded_oracle.py +0 -0
  133. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_deliverable_presence_gate.py +0 -0
  134. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_derive_monitoring_skill_registration.py +0 -0
  135. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_design_for_diagnosis_rule.py +0 -0
  136. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_doctor.py +0 -0
  137. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_done_feature_gates.py +0 -0
  138. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_driver_integration.py +0 -0
  139. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_driver_lock.py +0 -0
  140. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_duration_tracking.py +0 -0
  141. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_empty_files_escalation.py +0 -0
  142. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_ensure_feature_branch.py +0 -0
  143. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_ensure_feature_branch_base.py +0 -0
  144. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_escalation_contract.py +0 -0
  145. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_escalation_emit.py +0 -0
  146. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_events_redaction.py +0 -0
  147. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_events_stats.py +0 -0
  148. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_extra_gates.py +0 -0
  149. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_feature_id_resolution.py +0 -0
  150. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_filelock_portable.py +0 -0
  151. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_force_full_close.py +0 -0
  152. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_gate_eval.py +0 -0
  153. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_gate_eval_calibration.py +0 -0
  154. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_gate_eval_intermediate_wiring.py +0 -0
  155. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_gate_eval_terminal_wiring.py +0 -0
  156. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_gh_backend.py +0 -0
  157. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_gh_features.py +0 -0
  158. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_git_env_isolation.py +0 -0
  159. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_gitleaks_pinning.py +0 -0
  160. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_hashed_denylist.py +0 -0
  161. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_hashed_denylist_ci.py +0 -0
  162. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_interpreter_normalization.py +0 -0
  163. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_leak_findings_redaction.py +0 -0
  164. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_leak_scan.py +0 -0
  165. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_leak_scan_content.py +0 -0
  166. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_learnings_query.py +0 -0
  167. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_legacy_4wu_terminal_flips.py +0 -0
  168. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lifecycle_integration.py +0 -0
  169. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lifetime_accounting.py +0 -0
  170. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_bare_produces_path.py +0 -0
  171. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_close_intermediate.py +0 -0
  172. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_close_wu.py +0 -0
  173. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_correlation_id.py +0 -0
  174. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
  175. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_oracle_env.py +0 -0
  176. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_plan_errors.py +0 -0
  177. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_plan_next_draft.py +0 -0
  178. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_plan_relative_import.py +0 -0
  179. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_plan_verdict_exempt.py +0 -0
  180. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_planning_sections.py +0 -0
  181. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_produces_driver_helper.py +0 -0
  182. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_sections.py +0 -0
  183. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_task_graph_yaml_selection.py +0 -0
  184. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_lint_verification_reference.py +0 -0
  185. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_attempt_evidence.py +0 -0
  186. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_auto_archive.py +0 -0
  187. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_caveman_preamble.py +0 -0
  188. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_close_intermediate.py +0 -0
  189. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_defaults_by_type.py +0 -0
  190. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_effort.py +0 -0
  191. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_failure_note_cap.py +0 -0
  192. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_files_changed_guard.py +0 -0
  193. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_gate_budget.py +0 -0
  194. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_model_alias.py +0 -0
  195. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_orchestration.py +0 -0
  196. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_post_pass_invariant.py +0 -0
  197. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_reset_preserving_events.py +0 -0
  198. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_smoke_runner.py +0 -0
  199. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_unsandboxed.py +0 -0
  200. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_loop_zero_token_guard.py +0 -0
  201. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_migrate_legacy.py +0 -0
  202. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_miniyaml_equivalence.py +0 -0
  203. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_miniyaml_negative.py +0 -0
  204. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_monitoring_bootstrap_artifacts.py +0 -0
  205. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_monitoring_example.py +0 -0
  206. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_monitoring_fenced_blocks.py +0 -0
  207. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_monitoring_seed.py +0 -0
  208. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_operator_escalation_rule.py +0 -0
  209. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_planned_cost_lint.py +0 -0
  210. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_planning_cost_floor.py +0 -0
  211. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_prepare_feature.py +0 -0
  212. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_prepare_scaffold_sync.py +0 -0
  213. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_produces_field.py +0 -0
  214. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_produces_in_diff.py +0 -0
  215. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_provision_labels.py +0 -0
  216. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_redaction_windows_home.py +0 -0
  217. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_resolve_base.py +0 -0
  218. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_result_block.py +0 -0
  219. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_roadmap_add_skill.py +0 -0
  220. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_roadmap_archive_skill.py +0 -0
  221. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_roadmap_row_parser.py +0 -0
  222. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_scaffold_doc_hygiene.py +0 -0
  223. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_scaffold_docs.py +0 -0
  224. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_scaffold_label_provisioning.py +0 -0
  225. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_scaffold_manifest.py +0 -0
  226. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_scaffold_seed_sanity.py +0 -0
  227. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_scaffold_upgrade.py +0 -0
  228. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_scaffold_wiring.py +0 -0
  229. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_skill_discovery_links.py +0 -0
  230. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_skills_vendored_in_sync.py +0 -0
  231. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_spinning_rearm_gate.py +0 -0
  232. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_squash_commit_hook_crash.py +0 -0
  233. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_template_closing_shapes.py +0 -0
  234. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_terminal_flip_ownership.py +0 -0
  235. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_terminal_flips.py +0 -0
  236. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_timeout_kill_portable.py +0 -0
  237. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_untracked_feature_folder.py +0 -0
  238. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_untracked_file_absorption.py +0 -0
  239. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_upgrade_integration.py +0 -0
  240. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_upgrade_merge_gate.py +0 -0
  241. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_validate_event.py +0 -0
  242. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_verdict_coupling.py +0 -0
  243. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_verify_empty_gate_set.py +0 -0
  244. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_version_consistency.py +0 -0
  245. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_version_skew.py +0 -0
  246. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_windows_gate_exec.py +0 -0
  247. {specfuse_loop-0.6.0 → specfuse_loop-0.7.0}/tests/test_wu_execution_metadata.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfuse-loop
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Local-first executor for the Specfuse Plan + Work Unit gate-cycle methodology.
5
5
  Author: Specfuse contributors
6
6
  License: Apache-2.0
@@ -19,6 +19,10 @@ Requires-Python: >=3.10
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE
21
21
  License-File: NOTICE
22
+ Provides-Extra: azure
23
+ Requires-Dist: azure-identity>=1.15; extra == "azure"
24
+ Requires-Dist: azure-servicebus>=7.11; extra == "azure"
25
+ Requires-Dist: azure-monitor-query>=1.2; extra == "azure"
22
26
  Provides-Extra: dev
23
27
  Requires-Dist: PyYAML>=6.0; extra == "dev"
24
28
  Requires-Dist: ruff>=0.6; extra == "dev"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "specfuse-loop"
7
- version = "0.6.0"
7
+ version = "0.7.0"
8
8
  description = "Local-first executor for the Specfuse Plan + Work Unit gate-cycle methodology."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -33,6 +33,15 @@ dependencies = []
33
33
  # _miniyaml compare its output against yaml.safe_load on every real file.
34
34
  # ruff/bandit/coverage are for the reference implementation's own gates.
35
35
  [project.optional-dependencies]
36
+ # The Azure provider SDKs. Kept OUT of the runtime dependencies on purpose: the
37
+ # package imports them lazily inside the transport factories, so `specfuse-loop`
38
+ # installs with zero runtime dependencies and the harvester's core stays
39
+ # provider-agnostic. Only an operator actually harvesting from Azure needs these.
40
+ azure = [
41
+ "azure-identity>=1.15",
42
+ "azure-servicebus>=7.11",
43
+ "azure-monitor-query>=1.2",
44
+ ]
36
45
  dev = [
37
46
  "PyYAML>=6.0",
38
47
  "ruff>=0.6",
@@ -64,6 +73,7 @@ select = ["E4", "E7", "E9", "F", "PLW1510", "B", "BLE001", "S110", "TRY004"]
64
73
  specfuse-loop = "specfuse.loop.loop:main"
65
74
  specfuse-lint = "specfuse.loop.lint_plan:main"
66
75
  specfuse-monitor-lint = "specfuse.loop.lint_monitoring:main"
76
+ specfuse-monitor = "specfuse.monitor.cli:main"
67
77
  specfuse-stats = "specfuse.loop.events_stats:main"
68
78
 
69
79
  [tool.setuptools.packages.find]
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright 2026 Specfuse contributors
4
+ # Licensed under the Apache License, Version 2.0. See LICENSE.
5
+ #
6
+ """Closing-ceremony requirement registry (FEAT-2026-0054/T01).
7
+
8
+ One machine-readable home for every closing-artifact requirement that the
9
+ post-squash guards in ``specfuse/loop/loop.py`` enforce for ``close``,
10
+ ``close-intermediate``, and ``plan-next`` work units. The guards import the
11
+ constants and helpers below instead of spelling headings, filenames, and
12
+ verdict values inline, so a later lint mode and skeleton writer can read
13
+ ``CLOSING_REQUIREMENTS`` and stay in lockstep with what the guards actually
14
+ check — durable rule FEAT-2026-0070/G1-CLOSE-INTERMEDIATE.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import re
20
+ from dataclasses import dataclass, field
21
+ from typing import Optional
22
+
23
+ # --------------------------------------------------------------------------- #
24
+ # Shared literal values — the single spelling every guard body imports #
25
+ # --------------------------------------------------------------------------- #
26
+
27
+ VERDICT_VALUES = frozenset({"met", "met_locally", "partially_met", "not_met"})
28
+
29
+ RETROSPECTIVE_FILENAME = "RETROSPECTIVE.md"
30
+ LEARNINGS_PATH = ".specfuse/LEARNINGS.md"
31
+ ROADMAP_PATH = ".specfuse/roadmap.md"
32
+ DOCS_PREFIX = "docs/"
33
+ NOTHING_GENERALIZES_PHRASE = "nothing generalizes"
34
+
35
+ COST_ANALYSIS_HEADING = "Cost analysis"
36
+ COST_ANALYSIS_HEADING_RE = re.compile(
37
+ r"^##+ " + re.escape(COST_ANALYSIS_HEADING), re.MULTILINE | re.IGNORECASE,
38
+ )
39
+
40
+ FAILURE_CLASS_HEADING = "Failure-class breakdown"
41
+ FAILURE_CLASS_HEADING_LEVEL = 3
42
+ FAILURE_CLASS_HEADING_MARKDOWN = "#" * FAILURE_CLASS_HEADING_LEVEL + " " + FAILURE_CLASS_HEADING
43
+ FAILURE_CLASS_HEADING_RE = re.compile(
44
+ rf"^#{{{FAILURE_CLASS_HEADING_LEVEL}}} {re.escape(FAILURE_CLASS_HEADING)}\b",
45
+ re.MULTILINE,
46
+ )
47
+ NO_FAILURES_SENTINEL = (
48
+ f"{FAILURE_CLASS_HEADING_MARKDOWN}\n\n(no non-passing attempts in scope)\n"
49
+ )
50
+
51
+ GATE_SECTION_HEADING_LEVELS = "1,3"
52
+
53
+
54
+ def gate_section_heading_re(gate_n: int) -> re.Pattern:
55
+ """Regex matching the `## Gate N` / `### Gate N` retrospective section."""
56
+ return re.compile(
57
+ rf"^#{{{GATE_SECTION_HEADING_LEVELS}}} Gate {gate_n}\b", re.MULTILINE,
58
+ )
59
+
60
+
61
+ GATE_REVIEW_FILENAME_TEMPLATE = "GATE-{next_gate:02d}-REVIEW.md"
62
+
63
+
64
+ def gate_review_filename(next_gate: int) -> str:
65
+ """Filename of the review doc a plan-next WU must draft for the next gate."""
66
+ return GATE_REVIEW_FILENAME_TEMPLATE.format(next_gate=next_gate)
67
+
68
+
69
+ DEFERRAL_HEADING_TEXT = "What the loop did NOT verify"
70
+ DEFERRAL_HEADING_RE = re.compile(rf"(?m)^#{{1,3}}\s*{re.escape(DEFERRAL_HEADING_TEXT)}.*$")
71
+
72
+ AUTOCLOSE_DEBT_MARKER_RE = re.compile(r"<!--\s*specfuse:autoclose-debt\s+gate=(\d+)")
73
+
74
+
75
+ # --------------------------------------------------------------------------- #
76
+ # Requirement records #
77
+ # --------------------------------------------------------------------------- #
78
+
79
+
80
+ @dataclass(frozen=True)
81
+ class Requirement:
82
+ """One closing-artifact requirement, as data.
83
+
84
+ ``applies_when`` names the condition under which the requirement fires:
85
+ ``always``, ``verdict_met``, ``failures_present``, or
86
+ ``autoclose_debt_marker``. ``phase`` is ``pre-squash`` (checked by
87
+ ``assert_closing_deliverables`` right after the WU's own squash) or
88
+ ``post-pass`` (checked by ``verify_post_pass_invariants`` after the
89
+ gate-boundary state flips run).
90
+ """
91
+
92
+ id: str
93
+ wu_type: str
94
+ phase: str
95
+ description: str
96
+ enforced_by: str
97
+ applies_when: str = "always"
98
+ file: Optional[str] = None
99
+ file_derivation: Optional[str] = None
100
+ heading: Optional[str] = None
101
+ heading_level: Optional[int] = None
102
+ frontmatter_field: Optional[str] = None
103
+ allowed_values: Optional[frozenset] = field(default=None)
104
+
105
+
106
+ CLOSING_REQUIREMENTS: dict[str, list[Requirement]] = {
107
+ "close": [
108
+ Requirement(
109
+ id="close-a", wu_type="close", phase="pre-squash",
110
+ description="RETROSPECTIVE.md exists and is non-empty in the feature dir",
111
+ file=RETROSPECTIVE_FILENAME,
112
+ enforced_by="assert_retrospective_exists",
113
+ ),
114
+ Requirement(
115
+ id="close-b", wu_type="close", phase="pre-squash",
116
+ description=(
117
+ "LEARNINGS.md gains >=1 line in the squash, or RETROSPECTIVE.md "
118
+ f"says '{NOTHING_GENERALIZES_PHRASE}'"
119
+ ),
120
+ file=LEARNINGS_PATH,
121
+ enforced_by="assert_learnings_appended_or_noop",
122
+ ),
123
+ Requirement(
124
+ id="close-c", wu_type="close", phase="pre-squash",
125
+ description=(
126
+ f"A documentation deliverable ({DOCS_PREFIX}*, {ROADMAP_PATH}, "
127
+ f"{LEARNINGS_PATH}, or a {RETROSPECTIVE_FILENAME}) appears in "
128
+ "the squash diff"
129
+ ),
130
+ enforced_by="assert_doc_or_roadmap_diff",
131
+ ),
132
+ Requirement(
133
+ id="close-d", wu_type="close", phase="pre-squash",
134
+ description="verdict frontmatter field is present and in VERDICT_VALUES",
135
+ frontmatter_field="verdict", allowed_values=VERDICT_VALUES,
136
+ enforced_by="assert_verdict_well_formed",
137
+ ),
138
+ Requirement(
139
+ id="close-e", wu_type="close", phase="pre-squash",
140
+ description=f"RETROSPECTIVE.md has a '{COST_ANALYSIS_HEADING}' heading",
141
+ file=RETROSPECTIVE_FILENAME, heading=COST_ANALYSIS_HEADING,
142
+ applies_when="verdict_met",
143
+ enforced_by="assert_cost_analysis_section_when_met",
144
+ ),
145
+ Requirement(
146
+ id="close-f", wu_type="close", phase="pre-squash",
147
+ description=f"RETROSPECTIVE.md has a '{FAILURE_CLASS_HEADING}' heading",
148
+ file=RETROSPECTIVE_FILENAME, heading=FAILURE_CLASS_HEADING,
149
+ heading_level=FAILURE_CLASS_HEADING_LEVEL,
150
+ applies_when="failures_present",
151
+ enforced_by="assert_failure_class_breakdown_when_failures_present",
152
+ ),
153
+ Requirement(
154
+ id="close-g", wu_type="close", phase="post-pass",
155
+ description=(
156
+ "Predecessor auto-close debt markers are named in the terminal "
157
+ f"close's '{DEFERRAL_HEADING_TEXT}' section"
158
+ ),
159
+ file=RETROSPECTIVE_FILENAME,
160
+ applies_when="autoclose_debt_marker",
161
+ enforced_by="assert_autoclose_debt_reconciled",
162
+ ),
163
+ Requirement(
164
+ id="close-h", wu_type="close", phase="post-pass",
165
+ description=(
166
+ "Terminal gate status, roadmap row, and roadmap-archive anchor "
167
+ "flip when verdict=met"
168
+ ),
169
+ applies_when="verdict_met",
170
+ enforced_by="assert_terminal_flips_fired",
171
+ ),
172
+ ],
173
+ "close-intermediate": [
174
+ Requirement(
175
+ id="close-intermediate-a", wu_type="close-intermediate", phase="pre-squash",
176
+ description="RETROSPECTIVE.md has a 'Gate N' heading for this gate",
177
+ file=RETROSPECTIVE_FILENAME, heading="Gate {gate_n}",
178
+ enforced_by="assert_retrospective_gate_section",
179
+ ),
180
+ Requirement(
181
+ id="close-intermediate-b", wu_type="close-intermediate", phase="pre-squash",
182
+ description=(
183
+ "LEARNINGS.md gains >=1 line in the squash, or RETROSPECTIVE.md "
184
+ f"says '{NOTHING_GENERALIZES_PHRASE}'"
185
+ ),
186
+ file=LEARNINGS_PATH,
187
+ enforced_by="assert_learnings_appended_or_noop",
188
+ ),
189
+ Requirement(
190
+ id="close-intermediate-c", wu_type="close-intermediate", phase="pre-squash",
191
+ description=(
192
+ f"A documentation deliverable ({DOCS_PREFIX}*, {ROADMAP_PATH}, "
193
+ f"{LEARNINGS_PATH}, or a {RETROSPECTIVE_FILENAME}) appears in "
194
+ "the squash diff"
195
+ ),
196
+ enforced_by="assert_doc_or_roadmap_diff",
197
+ ),
198
+ Requirement(
199
+ id="close-intermediate-d", wu_type="close-intermediate", phase="pre-squash",
200
+ description=f"RETROSPECTIVE.md has a '{FAILURE_CLASS_HEADING}' heading",
201
+ file=RETROSPECTIVE_FILENAME, heading=FAILURE_CLASS_HEADING,
202
+ heading_level=FAILURE_CLASS_HEADING_LEVEL,
203
+ applies_when="failures_present",
204
+ enforced_by="assert_failure_class_breakdown_when_failures_present",
205
+ ),
206
+ ],
207
+ "plan-next": [
208
+ Requirement(
209
+ id="plan-next-a", wu_type="plan-next", phase="pre-squash",
210
+ description="GATE-(N+1)-REVIEW.md exists and is non-empty, or no next gate",
211
+ file_derivation=GATE_REVIEW_FILENAME_TEMPLATE,
212
+ enforced_by="assert_gate_review_exists",
213
+ ),
214
+ Requirement(
215
+ id="plan-next-b", wu_type="plan-next", phase="pre-squash",
216
+ description="Next gate has >=1 drafted WU in PLAN.md, or PLAN.md/roadmap is terminal",
217
+ enforced_by="assert_next_gate_drafted_or_terminal",
218
+ ),
219
+ ],
220
+ }
221
+
222
+
223
+ def all_requirements() -> list[Requirement]:
224
+ """Flatten CLOSING_REQUIREMENTS into a single list, registry order preserved."""
225
+ out: list[Requirement] = []
226
+ for reqs in CLOSING_REQUIREMENTS.values():
227
+ out.extend(reqs)
228
+ return out
229
+
230
+
231
+ def requirements_for(wu_type: str) -> list[Requirement]:
232
+ return CLOSING_REQUIREMENTS.get(wu_type, [])
@@ -0,0 +1 @@
1
+ 0.7.0
@@ -147,14 +147,19 @@ components:
147
147
 
148
148
  # One heartbeat check, many targets — one per schedule, so a single
149
149
  # silent timer among several is still individually visible instead of
150
- # being averaged away by a single host-wide heartbeat.
150
+ # being averaged away by a single host-wide heartbeat. `dialect` is
151
+ # required whenever `cron` is present — declared, never inferred, so
152
+ # a heartbeat check knows how many fields the expression carries
153
+ # without guessing. Both targets here are standard 5-field cron.
151
154
  - type: heartbeat
152
155
  targets:
153
156
  - name: nightly-reconciliation
154
157
  cron: "0 2 * * *"
158
+ dialect: standard-5
155
159
  timezone: Etc/UTC
156
160
  - name: hourly-cache-warm
157
161
  cron: "0 * * * *"
162
+ dialect: standard-5
158
163
  timezone: Etc/UTC
159
164
 
160
165
  # queue-stalled: catches a consumer that has stopped consuming — no
@@ -0,0 +1,119 @@
1
+ <!--
2
+ Copyright 2026 Specfuse Contributors
3
+ Licensed under the Apache License, Version 2.0. See LICENSE.
4
+ -->
5
+
6
+ # Rule: close discipline
7
+
8
+ Three obligations for `close` and `close-intermediate` WUs, binding at close
9
+ time. Sibling of [`planning-discipline.md`](planning-discipline.md) (plan/arm
10
+ time) and of the per-WU closing obligations in
11
+ [`result-contract.md`](result-contract.md). Provenance: FEAT-2026-0049
12
+ (the specfuse-generator dogfood), where three gates auto-closed at
13
+ `attempts: 0` and the one close forced to run caught two false-`done` WUs and
14
+ an unsatisfiable predicate — 48% of the feature's spend landed after the
15
+ terminal gate had already "auto-closed". A close that only writes prose
16
+ verifies nothing.
17
+
18
+ A close whose acceptance criteria include ANY obligation below is
19
+ **load-bearing**: the plan author sets `auto_close_disabled: true` in that
20
+ WU's frontmatter so the auto-close predicate cannot optimize it away (#189).
21
+
22
+ ## 1. Oracles re-run fresh
23
+
24
+ The close re-runs every oracle the feature's acceptance criteria name — the
25
+ full test command(s), plus any compile/execution gates — fresh, in this
26
+ session, exit codes read directly. Never inherit a producing WU's
27
+ self-report: `done` is a claim, the re-run is the verification. When the
28
+ feature's criteria assert on generated artifacts, regenerate into a clean
29
+ output directory before asserting — stale output satisfies any assertion.
30
+
31
+ > **Provenance.** A WU reported `done` while its source was untouched and its
32
+ > oracle never ran; the driver-side produces-vs-diff guard now refuses that
33
+ > pass (specfuse-loop >= 0.3.21), but only the close's own fresh re-run
34
+ > catches the composite: all WUs individually green while the feature-level
35
+ > oracle fails.
36
+
37
+ ## 2. Hedged-verdict follow-up record
38
+
39
+ On `met_locally`, the close must produce a named record — in the gate review
40
+ or `RETROSPECTIVE.md` — with one entry per unmet criterion:
41
+
42
+ - the criterion, verbatim;
43
+ - why it is unverifiable in this environment;
44
+ - the exact re-run condition that would upgrade the verdict to `met`.
45
+
46
+ The driver (>= 0.3.21) already guarantees the surfaces stay un-flipped on a
47
+ hedged verdict (gate `awaiting_review`, roadmap `active`, PLAN `active`);
48
+ this record is the other half: without it, `met_locally` is a dead end —
49
+ no artifact says what would make it `met`, and the honest hedged verdict
50
+ degrades into a polite synonym for "unknown".
51
+
52
+ ## 3. Consumer-visible contract changes enumerated, human-acknowledged
53
+
54
+ The close enumerates every consumer-visible addition, removal, or rename the
55
+ feature makes across ALL its producing WUs — API surface, generated models,
56
+ published schemas, CLI flags, whatever contract consumers depend on — and
57
+ blocks on explicit human acknowledgment of the list. A feature with no such
58
+ changes writes exactly: `n/a — no consumer-visible contract change` (do not
59
+ fabricate an empty enumeration; the n/a line is the reviewed claim).
60
+
61
+ A close carrying this obligation is always load-bearing
62
+ (`auto_close_disabled: true`).
63
+
64
+ > **Provenance.** A breaking removal from a generated model survived four
65
+ > gates unreviewed because no close surface owned the breaking-change list.
66
+ > A silent breaking change is the most expensive false-done, and it is not
67
+ > language-specific — any generated or published contract has this surface.
68
+
69
+ ## 4. What the driver checks — check it before you report, not after you fail
70
+
71
+ Sections 1–3 are obligations you reason about. The driver additionally
72
+ refuses a closing WU that produces correct content in the wrong shape, and it
73
+ refuses it *after* the WU has run — so a mismatch used to cost a full
74
+ re-dispatch, not a re-arm.
75
+
76
+ That used to mean memorizing a table of literal guard strings. It no longer
77
+ does. Two mechanical surfaces replace the table:
78
+
79
+ - **The skeleton is pre-created at dispatch.** Every `close` /
80
+ `close-intermediate` / `plan-next` WU starts its session with the
81
+ guard-required files and headings already scaffolded in place
82
+ (`RETROSPECTIVE.md`, the `## Gate <N>` / `## Cost analysis` /
83
+ `### Failure-class breakdown` sections, the `GATE-{N+1}-REVIEW.md`
84
+ filename) — see `precreate_dispatch_skeleton` in `specfuse/loop/loop.py`.
85
+ You fill the skeleton in; you do not need to remember its shape from
86
+ scratch.
87
+ - **`specfuse-lint --closing` is the mandatory pre-report check.** Run it
88
+ before emitting your `RESULT` block. It reads the same registry the driver
89
+ itself checks and reports pass/fail per requirement in-session, so a format
90
+ mismatch is caught while you can still fix it, not after the driver refuses.
91
+
92
+ The registry of record — every guard, what it requires, which WU types it
93
+ applies to — lives in `specfuse/loop/closing_requirements.py`. That module
94
+ and `specfuse/loop/lint_closing.py` (the `--closing` implementation) are the
95
+ one place these requirements are enumerated; this rule does not duplicate
96
+ them.
97
+
98
+ **Migration posture.** Already-drafted features need no conversion — the
99
+ skeleton applies at dispatch time for any WU dispatched from here forward,
100
+ regardless of when its body was authored. Older WU bodies that restate guard
101
+ strings inline (pre-dating this section) are inert, not wrong: the driver
102
+ never read that prose, only the actual artifacts. Removing such prose from an
103
+ old WU body is optional cleanup, not required migration work.
104
+
105
+ > **Provenance (issue #265, FEAT-2026-0054).** Guard requirements are
106
+ > enforced in `specfuse/loop/loop.py`'s `assert_*` functions. They were
107
+ > briefly literal-string-documented in this rule (28% of closing-WU spend
108
+ > going to driver-refused attempts, three undocumented guards accounting for
109
+ > 45% of that waste) — but a second copy of the requirements drifts, so
110
+ > FEAT-2026-0054 replaced the copy with the two mechanical surfaces above and
111
+ > made `closing_requirements.py` the single registry both the driver and the
112
+ > lint read.
113
+
114
+ ## Split with project-local rules
115
+
116
+ These are the generic obligations. The concrete grounding — which command is
117
+ the oracle, which surface is "the API", project-specific regeneration
118
+ hazards — is per-project and belongs in that project's
119
+ `.specfuse/rules-local/` (never touched by upgrade), referencing this rule.
@@ -100,11 +100,8 @@ defect gates later.
100
100
  **Close obligations** (REQUIRED only for `close` / `close-intermediate` WUs; omit
101
101
  otherwise — see `.specfuse/rules/close-discipline.md`).
102
102
 
103
- > **Read `close-discipline.md` §4 before drafting a closing WU.** It lists the
104
- > exact strings the driver checks — heading levels, the `GATE-{N+1}-REVIEW.md`
105
- > filename, the `verdict:` frontmatter field. These are matched literally and
106
- > checked *after* the WU runs, so a mismatch costs a full re-dispatch rather than
107
- > a re-arm. 28% of all closing-WU spend has gone to exactly that (#265).
103
+ > Run `specfuse-lint --closing` and confirm it exits 0 before this WU reports
104
+ > `complete` — see `.specfuse/rules/close-discipline.md` §4.
108
105
 
109
106
  The close's acceptance criteria must cover:
110
107
 
@@ -0,0 +1,42 @@
1
+ # specfuse-monitor.yml — GitHub Actions template for the "gh-actions" runner
2
+ # surface (FEAT-2026-0040/T11).
3
+ #
4
+ # This file is a TEMPLATE. It ships under specfuse/loop/data/ and is not
5
+ # installed into this repository's own .github/workflows/ — this repo is a
6
+ # CLI tool with no deployable components and no monitoring.yml. Copy this
7
+ # file into a CONSUMER project's .github/workflows/ once that project has
8
+ # real components dialed to `runner: gh-actions` in its .specfuse/monitoring.yml.
9
+ #
10
+ # SECURITY POSTURE: every credential-bearing value below is a
11
+ # ${{ secrets.* }} reference, never a literal secret. Add the named secrets
12
+ # in the consumer repository's Settings -> Secrets and variables -> Actions
13
+ # before enabling the schedule. See docs/concepts/monitoring-runners.md.
14
+ name: specfuse-monitor
15
+
16
+ on:
17
+ schedule:
18
+ - cron: "0 * * * *"
19
+ workflow_dispatch:
20
+
21
+ permissions:
22
+ issues: write
23
+ contents: read
24
+
25
+ jobs:
26
+ monitor:
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - name: Checkout repository
30
+ uses: actions/checkout@v4
31
+ - name: Set up Python
32
+ uses: actions/setup-python@v5
33
+ with:
34
+ python-version: "3.11"
35
+ - name: Install specfuse-loop
36
+ run: pip install specfuse-loop
37
+ - name: Run monitoring cycle
38
+ run: specfuse-monitor run --runner gh-actions
39
+ env:
40
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41
+ ACME_TELEMETRY_PROD_API_KEY: ${{ secrets.ACME_TELEMETRY_PROD_API_KEY }}
42
+ ACME_BROKER_PROD_CONNECTION_STRING: ${{ secrets.ACME_BROKER_PROD_CONNECTION_STRING }}
@@ -17,6 +17,7 @@ from pathlib import Path
17
17
  from typing import Callable, Optional
18
18
 
19
19
  from specfuse.loop import escalation, gh_features
20
+ from specfuse.monitor import issues
20
21
 
21
22
 
22
23
  @dataclass(frozen=True)
@@ -70,6 +71,18 @@ LABEL_REGISTRY: tuple[LabelSpec, ...] = (
70
71
  description="A pull request is green and waiting on a merge decision",
71
72
  consumer="escalation.py",
72
73
  ),
74
+ # The harvester's findings carry their own label rather than reusing
75
+ # `needs-human`: they are failure artifacts, not operator escalations, and a
76
+ # distinct label lets `gh issue list` scope to exactly that lifecycle. It was
77
+ # missing here until #300 — `gh issue create` rejects an unknown label, so the
78
+ # harvester could not file a single finding on any repository that had not had
79
+ # this label made by hand.
80
+ LabelSpec(
81
+ name=issues.FINDING_LABEL,
82
+ colour="b60205",
83
+ description="A failure artifact reported by specfuse-monitor",
84
+ consumer="monitor/issues.py",
85
+ ),
73
86
  )
74
87
 
75
88