specfuse-loop 0.27.1__tar.gz → 0.28.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 (655) hide show
  1. {specfuse_loop-0.27.1/specfuse_loop.egg-info → specfuse_loop-0.28.0}/PKG-INFO +1 -1
  2. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/pyproject.toml +1 -1
  3. specfuse_loop-0.28.0/specfuse/loop/data/VERSION +1 -0
  4. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/monitoring.yml.example +9 -0
  5. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/rules/design-for-diagnosis.md +16 -2
  6. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/lint_monitoring.py +78 -2
  7. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/loop.py +1 -1
  8. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/artifact.py +3 -1
  9. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/cli.py +3 -0
  10. specfuse_loop-0.28.0/specfuse/monitor/durations.py +40 -0
  11. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/providers/azure_app_insights.py +256 -1
  12. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/providers/azure_service_bus.py +4 -25
  13. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0/specfuse_loop.egg-info}/PKG-INFO +1 -1
  14. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse_loop.egg-info/SOURCES.txt +4 -0
  15. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_app_insights_query_schema_check.py +9 -1
  16. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_derive_monitoring_discovery.py +291 -3
  17. specfuse_loop-0.28.0/tests/test_http_status_regression_adapter.py +216 -0
  18. specfuse_loop-0.28.0/tests/test_http_status_regression_end_to_end.py +130 -0
  19. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_monitoring.py +71 -12
  20. specfuse_loop-0.28.0/tests/test_monitor_durations.py +29 -0
  21. specfuse_loop-0.27.1/specfuse/loop/data/VERSION +0 -1
  22. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/LICENSE +0 -0
  23. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/NOTICE +0 -0
  24. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/README.md +0 -0
  25. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/setup.cfg +0 -0
  26. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/__init__.py +0 -0
  27. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/budget.py +0 -0
  28. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/diagnose_invoke.py +0 -0
  29. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/drafting_answers.py +0 -0
  30. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/drafting_invoke.py +0 -0
  31. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/drafting_questions.py +0 -0
  32. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/driver_command.py +0 -0
  33. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/driver_invoke.py +0 -0
  34. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/invoke.py +0 -0
  35. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/monitoring_read.py +0 -0
  36. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/providers/__init__.py +0 -0
  37. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/providers/answers.py +0 -0
  38. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/providers/bugs.py +0 -0
  39. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/providers/feature.py +0 -0
  40. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/providers/findings_autofix.py +0 -0
  41. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/providers/findings_diagnose.py +0 -0
  42. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/providers/triage.py +0 -0
  43. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/queue_read.py +0 -0
  44. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/repo_detect.py +0 -0
  45. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/run.py +0 -0
  46. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/severity_backfill.py +0 -0
  47. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/state.py +0 -0
  48. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/triage_invoke.py +0 -0
  49. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/agent/worktree.py +0 -0
  50. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/__init__.py +0 -0
  51. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/_filelock.py +0 -0
  52. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/_miniyaml.py +0 -0
  53. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/_wu_sections.py +0 -0
  54. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/adopt_feature.py +0 -0
  55. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/agent_policy.py +0 -0
  56. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/arm_eval.py +0 -0
  57. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/arm_sweep.py +0 -0
  58. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/arm_txn.py +0 -0
  59. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/bug_lane.py +0 -0
  60. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/bug_lane_run.py +0 -0
  61. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/bug_lane_state.py +0 -0
  62. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/build_provenance.py +0 -0
  63. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/caller_check.py +0 -0
  64. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/changelog.py +0 -0
  65. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/closing_requirements.py +0 -0
  66. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/cost.py +0 -0
  67. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/criteria_state.py +0 -0
  68. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/CHANGELOG.seed.md +0 -0
  69. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/LEARNINGS.template.md +0 -0
  70. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/docs/concepts/adopting-auto-mode.md +0 -0
  71. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
  72. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/docs/concepts/autonomy-stop-classes.md +0 -0
  73. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
  74. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/docs/getting-started.md +0 -0
  75. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/docs/glossary.md +0 -0
  76. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/docs/methodology.md +0 -0
  77. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/docs/skills.md +0 -0
  78. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/gitignore.snippet +0 -0
  79. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/monitoring-secrets-checklist.md +0 -0
  80. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/monitoring.overrides.yml.example +0 -0
  81. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/roadmap.template.md +0 -0
  82. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/rules/close-discipline.md +0 -0
  83. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/rules/correlation-ids.md +0 -0
  84. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/rules/human-output.md +0 -0
  85. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/rules/never-touch.md +0 -0
  86. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/rules/operator-escalation.md +0 -0
  87. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/rules/planning-discipline.md +0 -0
  88. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/rules/result-contract.md +0 -0
  89. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/rules/security-boundaries.md +0 -0
  90. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/rules/verification-discipline.md +0 -0
  91. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/rules-local/README.md +0 -0
  92. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/schemas/driver-event.schema.json +0 -0
  93. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/schemas/event.schema.json +0 -0
  94. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
  95. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
  96. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
  97. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
  98. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/templates/DECISIONS.template.md +0 -0
  99. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/templates/GATE.template.md +0 -0
  100. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/templates/LEARNINGS-pending.template.md +0 -0
  101. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/templates/PLAN.template.md +0 -0
  102. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/templates/WU.template.md +0 -0
  103. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/verification.yml.example +0 -0
  104. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/data/workflows/specfuse-monitor.yml +0 -0
  105. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/decisions_format.py +0 -0
  106. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/driver_edit.py +0 -0
  107. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/escalation.py +0 -0
  108. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/events_stats.py +0 -0
  109. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/gate_commands.py +0 -0
  110. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/gate_eval.py +0 -0
  111. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/gh_backend.py +0 -0
  112. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/gh_features.py +0 -0
  113. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/heartbeat.py +0 -0
  114. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/judge.py +0 -0
  115. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/labels.py +0 -0
  116. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/learnings_distil.py +0 -0
  117. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/learnings_query.py +0 -0
  118. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/lint_closing.py +0 -0
  119. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/lint_plan.py +0 -0
  120. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/lint_roadmap.py +0 -0
  121. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/notify.py +0 -0
  122. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/notify_escalation.py +0 -0
  123. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/notify_sla.py +0 -0
  124. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/plan_baseline.py +0 -0
  125. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/policy_proposals.py +0 -0
  126. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/policy_review.py +0 -0
  127. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/prerun.py +0 -0
  128. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/prerun_capture.py +0 -0
  129. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/promotion.py +0 -0
  130. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/rearm_migration.py +0 -0
  131. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/red_on_base.py +0 -0
  132. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/replay_spin.py +0 -0
  133. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/roadmap_archive.py +0 -0
  134. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/scaffold.py +0 -0
  135. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/triage.py +0 -0
  136. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/upgrade_merge_gate.py +0 -0
  137. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/loop/validate_event.py +0 -0
  138. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/__init__.py +0 -0
  139. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/adapters.py +0 -0
  140. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/autofix.py +0 -0
  141. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/autofix_invoke.py +0 -0
  142. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/autofix_run.py +0 -0
  143. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/autofix_state.py +0 -0
  144. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/diagnose_cli.py +0 -0
  145. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/diagnosis.py +0 -0
  146. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/fingerprint.py +0 -0
  147. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/issues.py +0 -0
  148. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/providers/__init__.py +0 -0
  149. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/providers/_azure_auth.py +0 -0
  150. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/redaction.py +0 -0
  151. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse/monitor/schedule.py +0 -0
  152. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse_loop.egg-info/dependency_links.txt +0 -0
  153. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse_loop.egg-info/entry_points.txt +0 -0
  154. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse_loop.egg-info/requires.txt +0 -0
  155. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/specfuse_loop.egg-info/top_level.txt +0 -0
  156. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_abandon_feature_skill.py +0 -0
  157. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_abandoned_work_reported.py +0 -0
  158. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_abandoned_wu_strands_gate.py +0 -0
  159. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_adopt_feature.py +0 -0
  160. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_budget.py +0 -0
  161. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_bug_selection_dedup.py +0 -0
  162. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_default_features_root.py +0 -0
  163. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_driver_command.py +0 -0
  164. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_driver_invoke.py +0 -0
  165. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_driver_restart.py +0 -0
  166. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_escalation_on_issue.py +0 -0
  167. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_escalation_run_state.py +0 -0
  168. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_findings_seam.py +0 -0
  169. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_invoke_timeout.py +0 -0
  170. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_invoke_usage.py +0 -0
  171. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_item_worktree.py +0 -0
  172. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_lock.py +0 -0
  173. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_policy_key_ownership.py +0 -0
  174. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_policy_queue.py +0 -0
  175. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_policy_schema.py +0 -0
  176. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_policy_triage_dial.py +0 -0
  177. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_provider_answers.py +0 -0
  178. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_provider_bugs.py +0 -0
  179. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_provider_feature.py +0 -0
  180. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_provider_findings_autofix.py +0 -0
  181. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_provider_findings_diagnose.py +0 -0
  182. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_provider_isolation.py +0 -0
  183. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_provider_triage.py +0 -0
  184. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_queue_read.py +0 -0
  185. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_relays_driver_output.py +0 -0
  186. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_repo_detect.py +0 -0
  187. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_run.py +0 -0
  188. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_run_progress.py +0 -0
  189. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_seam.py +0 -0
  190. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_snapshot_refresh.py +0 -0
  191. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_agent_state.py +0 -0
  192. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_answer_escalation_skill.py +0 -0
  193. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_app_insights_adapters.py +0 -0
  194. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_app_insights_workspace_schema.py +0 -0
  195. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_archive_ref_reconciliation.py +0 -0
  196. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_arm_eval.py +0 -0
  197. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_arm_eval_lint_class.py +0 -0
  198. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_arm_eval_wiring.py +0 -0
  199. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_arm_gate_edits_uncommitted.py +0 -0
  200. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_arm_sweep.py +0 -0
  201. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_arm_sweep_gate.py +0 -0
  202. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_arm_txn.py +0 -0
  203. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_arm_wiring.py +0 -0
  204. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_artifact_redaction.py +0 -0
  205. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_attempt_note_untracked_files.py +0 -0
  206. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_attempt_outcome_contract.py +0 -0
  207. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_attempt_outcome_emission.py +0 -0
  208. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_attention_nonwriting_guard.py +0 -0
  209. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_attention_silence_section.py +0 -0
  210. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_attention_skill_structure.py +0 -0
  211. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_attribution_halt_claims.py +0 -0
  212. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_autoclose_deferral_visibility.py +0 -0
  213. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_autoclose_gate_note.py +0 -0
  214. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_autoclose_stub_states_what_passed.py +0 -0
  215. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_autofix_invoke.py +0 -0
  216. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_autofix_live.py +0 -0
  217. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_autofix_predicate.py +0 -0
  218. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_autofix_run.py +0 -0
  219. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_autofix_state.py +0 -0
  220. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_autosync.py +0 -0
  221. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_autosync_consent.py +0 -0
  222. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_autosync_firstrun.py +0 -0
  223. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_autosync_no_cwd_leak.py +0 -0
  224. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_autosync_plugin.py +0 -0
  225. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_azure_credential_resolution.py +0 -0
  226. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_backend.py +0 -0
  227. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_baseline_escalation_message.py +0 -0
  228. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_baseline_persistence.py +0 -0
  229. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_baseline_probe.py +0 -0
  230. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_baseline_provenance.py +0 -0
  231. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_baseline_tree_hash_key.py +0 -0
  232. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bash_routing.py +0 -0
  233. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bats_suites_gated.py +0 -0
  234. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_binary_verdict.py +0 -0
  235. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_binding_block_advisory.py +0 -0
  236. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_binding_block_allocation.py +0 -0
  237. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_binding_block_budget.py +0 -0
  238. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_blind_run_is_not_drained.py +0 -0
  239. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bookkeeping_commit_crash_run.py +0 -0
  240. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
  241. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bookkeeping_no_verify.py +0 -0
  242. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_brief_covers_every_unit_escalation.py +0 -0
  243. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_ci_pending.py +0 -0
  244. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_guardrails.py +0 -0
  245. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_label_nonfatal.py +0 -0
  246. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_labels_registered.py +0 -0
  247. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_min_severity.py +0 -0
  248. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_no_checks_pending.py +0 -0
  249. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_policy_contract.py +0 -0
  250. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_pr_number_carried.py +0 -0
  251. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_preflight_and_dial.py +0 -0
  252. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_run.py +0 -0
  253. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_spend.py +0 -0
  254. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_state.py +0 -0
  255. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_stopped_outcome_pr_number.py +0 -0
  256. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_superseded.py +0 -0
  257. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_bug_lane_test_paths.py +0 -0
  258. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_build_provenance.py +0 -0
  259. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_caller_check_ratchet.py +0 -0
  260. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_carried_green_invalidated_by_diff.py +0 -0
  261. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_carry_accounting_close_and_judge.py +0 -0
  262. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_changed_file_test_selection.py +0 -0
  263. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_changelog_append_only.py +0 -0
  264. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_changelog_collection.py +0 -0
  265. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_changelog_release_wiring.py +0 -0
  266. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_changelog_schema.py +0 -0
  267. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_ci_conclusion_wait.py +0 -0
  268. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_ci_runs_every_gate.py +0 -0
  269. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_claude_resolution.py +0 -0
  270. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_close_b_pending_learnings.py +0 -0
  271. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_close_records_feature_oracle.py +0 -0
  272. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_close_wu_self_reference.py +0 -0
  273. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_closing_deliverable_guard.py +0 -0
  274. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_closing_guard_contracts.py +0 -0
  275. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_closing_guard_prediction.py +0 -0
  276. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_closing_learnings_destination.py +0 -0
  277. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_closing_requirements.py +0 -0
  278. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_closing_sessions_on_sonnet.py +0 -0
  279. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_coauthor_trailer_allowlist.py +0 -0
  280. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_conditional_reflection.py +0 -0
  281. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_console_encoding.py +0 -0
  282. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_convergent_iteration.py +0 -0
  283. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_convergent_wu_lint.py +0 -0
  284. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_correlation_id_override.py +0 -0
  285. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_correlation_id_two_surfaces.py +0 -0
  286. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_cost_lifetime.py +0 -0
  287. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_cost_tracking.py +0 -0
  288. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_criteria_per_gate_lint.py +0 -0
  289. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_criteria_state.py +0 -0
  290. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_criteria_worklist.py +0 -0
  291. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_decision_citation_lint.py +0 -0
  292. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_decision_followups_discharged.py +0 -0
  293. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_decision_override_lint.py +0 -0
  294. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_decisions_format.py +0 -0
  295. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_default_severity_aliases.py +0 -0
  296. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_degraded_oracle.py +0 -0
  297. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_deliverable_missing_note.py +0 -0
  298. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_deliverable_presence_gate.py +0 -0
  299. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_derive_agent_policy_review_mode.py +0 -0
  300. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_derive_agent_policy_skill.py +0 -0
  301. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_derive_monitoring_skill_registration.py +0 -0
  302. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_design_for_diagnosis_rule.py +0 -0
  303. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_deterministic_refusal_repeat.py +0 -0
  304. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_diagnose_headless.py +0 -0
  305. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_diagnose_issue_skill.py +0 -0
  306. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_diagnosis.py +0 -0
  307. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_diagnosis_roundtrip_live.py +0 -0
  308. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_dispatch_model_from_fix_scope.py +0 -0
  309. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_dispatch_preamble_rules_pointer.py +0 -0
  310. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_dispatch_skeleton.py +0 -0
  311. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_dispatch_skills_index.py +0 -0
  312. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_distilled_accept_step.py +0 -0
  313. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_doctor.py +0 -0
  314. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_doctor_command_preflight.py +0 -0
  315. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_done_feature_gates.py +0 -0
  316. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_draft_feature_answers_mode.py +0 -0
  317. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_drafting_answer_gate.py +0 -0
  318. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_drafting_invoke.py +0 -0
  319. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_drafting_question_issue.py +0 -0
  320. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_drafting_questions.py +0 -0
  321. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_drafting_reply_shape.py +0 -0
  322. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_driver_edit_detection.py +0 -0
  323. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_driver_event_registry_covers_emitters.py +0 -0
  324. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_driver_integration.py +0 -0
  325. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_driver_lock.py +0 -0
  326. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_driver_module_surface.py +0 -0
  327. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_driver_restart_halt_surfaced.py +0 -0
  328. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_driver_restart_halt_wiring.py +0 -0
  329. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_driver_restart_hold.py +0 -0
  330. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_driver_staleness_gate_summary.py +0 -0
  331. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_driver_staleness_warning.py +0 -0
  332. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_dry_run_no_branch_mutation.py +0 -0
  333. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_duration_tracking.py +0 -0
  334. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_empty_files_escalation.py +0 -0
  335. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_ensure_feature_branch.py +0 -0
  336. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_ensure_feature_branch_base.py +0 -0
  337. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_escalation_assignee_policy.py +0 -0
  338. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_escalation_contract.py +0 -0
  339. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_escalation_crash_containment.py +0 -0
  340. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_escalation_dedup_search.py +0 -0
  341. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_escalation_emit.py +0 -0
  342. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_event_gate_full_envelope.py +0 -0
  343. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_events_redaction.py +0 -0
  344. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_events_stats.py +0 -0
  345. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_extra_gates.py +0 -0
  346. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_failure_artifact_model.py +0 -0
  347. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_failure_signature_fail_prefix.py +0 -0
  348. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_failure_signature_names_the_test.py +0 -0
  349. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_failure_signature_skips_log_noise.py +0 -0
  350. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_feature_branch_auto_sync.py +0 -0
  351. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_feature_branch_squash_merge.py +0 -0
  352. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_feature_branch_stale_base.py +0 -0
  353. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_feature_id_resolution.py +0 -0
  354. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_feature_oracle_e2e.py +0 -0
  355. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_feature_provider_drafting_dispatch.py +0 -0
  356. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_feature_review_accumulation.py +0 -0
  357. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_filelock_portable.py +0 -0
  358. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_files_changed_auto_repair.py +0 -0
  359. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_find_pr_no_search_index.py +0 -0
  360. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_fingerprint.py +0 -0
  361. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_fix_bug_cwd_worktree.py +0 -0
  362. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_fix_bug_diff_self_check.py +0 -0
  363. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_fix_bug_headless.py +0 -0
  364. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_fix_bug_reads_comments.py +0 -0
  365. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_fix_unit_insertion_bookkeeping.py +0 -0
  366. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_fix_unit_insertion_e2e.py +0 -0
  367. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_fix_unit_insertion_refused.py +0 -0
  368. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_fold_contract_documented.py +0 -0
  369. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_followups_artifact.py +0 -0
  370. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_force_full_close.py +0 -0
  371. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_frontmatter_scalar_quoting.py +0 -0
  372. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_gate_broad_run.py +0 -0
  373. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_gate_budget.py +0 -0
  374. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_gate_budget_post_dispatch.py +0 -0
  375. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_gate_commands_needs.py +0 -0
  376. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_gate_eval.py +0 -0
  377. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_gate_eval_calibration.py +0 -0
  378. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_gate_eval_intermediate_wiring.py +0 -0
  379. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_gate_eval_repo_flag.py +0 -0
  380. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_gate_eval_terminal_wiring.py +0 -0
  381. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_gate_report_verdict.py +0 -0
  382. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_gh_backend.py +0 -0
  383. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_gh_features.py +0 -0
  384. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_git_env_isolation.py +0 -0
  385. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_gitleaks_pinning.py +0 -0
  386. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_groom_backlog_skill.py +0 -0
  387. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_guard_refusal_failure_class.py +0 -0
  388. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_guard_refusal_failure_excerpt.py +0 -0
  389. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_guard_repair_e2e.py +0 -0
  390. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_guard_repair_prompt.py +0 -0
  391. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_hashed_denylist.py +0 -0
  392. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_hashed_denylist_ci.py +0 -0
  393. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_headless_no_background_tasks.py +0 -0
  394. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_heartbeat.py +0 -0
  395. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_heartbeat_adapter.py +0 -0
  396. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_heartbeat_emission_contract.py +0 -0
  397. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_human_output_rule.py +0 -0
  398. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_human_work_unit.py +0 -0
  399. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_hygiene_boundary_guidance.py +0 -0
  400. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_hyphenated_gate_name.py +0 -0
  401. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_ignored_cleanup_before_probe.py +0 -0
  402. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_init_integration.py +0 -0
  403. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_installed_copy_driver_e2e.py +0 -0
  404. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_interpreter_normalization.py +0 -0
  405. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_judge_close_path.py +0 -0
  406. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_judge_cost.py +0 -0
  407. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_judge_cost_fold_committed.py +0 -0
  408. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_judge_module.py +0 -0
  409. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_judge_path_registry.py +0 -0
  410. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_judge_sees_dropped_produces.py +0 -0
  411. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_judge_terminal_flip_state.py +0 -0
  412. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_judged_event_points_at_findings.py +0 -0
  413. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_label_description_length.py +0 -0
  414. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_label_provisioning_runner_contract.py +0 -0
  415. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_label_registry.py +0 -0
  416. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_label_registry_covers_consumers.py +0 -0
  417. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_language_aware_narrow_selection.py +0 -0
  418. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lazy_baseline_e2e.py +0 -0
  419. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_leak_findings_redaction.py +0 -0
  420. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_leak_scan.py +0 -0
  421. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_leak_scan_content.py +0 -0
  422. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_learnings_curate_vendored_promote.py +0 -0
  423. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_learnings_distil_cli.py +0 -0
  424. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_learnings_query.py +0 -0
  425. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_learnings_staging.py +0 -0
  426. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_learnings_suggest_signature_filter.py +0 -0
  427. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_learnings_weights.py +0 -0
  428. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_legacy_4wu_terminal_flips.py +0 -0
  429. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lifecycle_integration.py +0 -0
  430. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lifetime_accounting.py +0 -0
  431. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_ac_observable.py +0 -0
  432. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_bare_produces_path.py +0 -0
  433. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_boundary_consistency.py +0 -0
  434. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_boundary_extraction.py +0 -0
  435. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_close_intermediate.py +0 -0
  436. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_close_verdict_not_predecided.py +0 -0
  437. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_close_wu.py +0 -0
  438. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_closing.py +0 -0
  439. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_closing_criteria.py +0 -0
  440. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_closing_criteria_pristine.py +0 -0
  441. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_correlation_id.py +0 -0
  442. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
  443. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_covers_load_wu_invariants.py +0 -0
  444. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_feature_oracle_declared.py +0 -0
  445. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_gate_proportionality.py +0 -0
  446. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_oracle_env.py +0 -0
  447. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_plan_contract_fields.py +0 -0
  448. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_plan_errors.py +0 -0
  449. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_plan_next_draft.py +0 -0
  450. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_plan_relative_import.py +0 -0
  451. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_plan_verdict_exempt.py +0 -0
  452. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_planning_sections.py +0 -0
  453. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_produces_driver_helper.py +0 -0
  454. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_produces_satisfiability.py +0 -0
  455. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_roadmap.py +0 -0
  456. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_sections.py +0 -0
  457. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_task_graph_yaml_selection.py +0 -0
  458. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_lint_verification_reference.py +0 -0
  459. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_live_tests_opt_in.py +0 -0
  460. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_load_bearing_close_lint.py +0 -0
  461. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_attempt_evidence.py +0 -0
  462. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_auto_archive.py +0 -0
  463. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_caveman_preamble.py +0 -0
  464. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_close_intermediate.py +0 -0
  465. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_criteria_skeleton.py +0 -0
  466. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_criteria_survival.py +0 -0
  467. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_defaults_by_type.py +0 -0
  468. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_effort.py +0 -0
  469. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_failure_note_cap.py +0 -0
  470. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_files_changed_guard.py +0 -0
  471. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_gate_budget.py +0 -0
  472. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_gate_needs.py +0 -0
  473. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_model_alias.py +0 -0
  474. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_orchestration.py +0 -0
  475. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_post_pass_invariant.py +0 -0
  476. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_reset_preserving_events.py +0 -0
  477. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_smoke_runner.py +0 -0
  478. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_unsandboxed.py +0 -0
  479. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_worklist_injection.py +0 -0
  480. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_loop_zero_token_guard.py +0 -0
  481. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_make_repo_gpgsign_isolation.py +0 -0
  482. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_marker_dual_shape.py +0 -0
  483. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_maven_failure_excerpt.py +0 -0
  484. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_max_attempts_configurable.py +0 -0
  485. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_max_open_prs.py +0 -0
  486. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_migrate_legacy.py +0 -0
  487. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_miniyaml_equivalence.py +0 -0
  488. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_miniyaml_negative.py +0 -0
  489. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_monitor_cli.py +0 -0
  490. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_monitor_issue_lifecycle.py +0 -0
  491. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_monitor_runner_surfaces.py +0 -0
  492. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_monitoring_bootstrap_artifacts.py +0 -0
  493. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_monitoring_cron_dialect.py +0 -0
  494. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_monitoring_example.py +0 -0
  495. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_monitoring_fenced_blocks.py +0 -0
  496. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_monitoring_seed.py +0 -0
  497. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_narrow_fallback_warning.py +0 -0
  498. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_needs_decision_outcome.py +0 -0
  499. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_no_pytest_subprocess.py +0 -0
  500. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_notify.py +0 -0
  501. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_notify_escalation.py +0 -0
  502. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_notify_sla.py +0 -0
  503. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_operator_escalation_rule.py +0 -0
  504. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_oracle_set_declared.py +0 -0
  505. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_oracle_unrunnable_attribution.py +0 -0
  506. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_per_run_item_cap_disclosure.py +0 -0
  507. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_pick_feature_skill.py +0 -0
  508. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_pin_honesty_and_integrity.py +0 -0
  509. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_pin_marker_not_inherited.py +0 -0
  510. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_pin_marker_spawn_sites.py +0 -0
  511. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_plan_baseline.py +0 -0
  512. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_planned_cost_lint.py +0 -0
  513. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_planning_cost_floor.py +0 -0
  514. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_plugin_prose_cross_refs.py +0 -0
  515. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_plugin_qualified_dispatch.py +0 -0
  516. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_policy_budgets_applied.py +0 -0
  517. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_policy_proposals.py +0 -0
  518. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_policy_review.py +0 -0
  519. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_pr_checks_json_field.py +0 -0
  520. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_pr_reference_in_title.py +0 -0
  521. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_pre_existing_claims_cite_evidence.py +0 -0
  522. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_prep_halt_operator_surface.py +0 -0
  523. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_prepare_feature.py +0 -0
  524. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_prepare_scaffold_sync.py +0 -0
  525. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_prerun_capture.py +0 -0
  526. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_prerun_oracles.py +0 -0
  527. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_prerun_wiring.py +0 -0
  528. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_produced_document_verdict_guard.py +0 -0
  529. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_produces_amendment_e2e.py +0 -0
  530. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_produces_bracket_literal.py +0 -0
  531. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_produces_bracket_wildcard.py +0 -0
  532. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_produces_field.py +0 -0
  533. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_produces_in_diff.py +0 -0
  534. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_produces_justification.py +0 -0
  535. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_produces_recursive_glob.py +0 -0
  536. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_produces_repair_note.py +0 -0
  537. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_produces_semantics_unified.py +0 -0
  538. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_produces_shape.py +0 -0
  539. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_progress_forward_note.py +0 -0
  540. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_progress_lines_end_to_end.py +0 -0
  541. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_promoted_issue_not_redispatched.py +0 -0
  542. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_promotion_ledger.py +0 -0
  543. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_provider_escalation_traces.py +0 -0
  544. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_provision_labels.py +0 -0
  545. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_queue_stalled_adapter.py +0 -0
  546. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_rearm_clears_stale_criteria_state.py +0 -0
  547. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_rearm_fold_marker.py +0 -0
  548. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_rearm_migration.py +0 -0
  549. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_reclose_carries_narrow_greens_e2e.py +0 -0
  550. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_red_baseline_never_reused.py +0 -0
  551. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_red_on_base_fetch_and_reason.py +0 -0
  552. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_red_on_base_guardrail.py +0 -0
  553. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_red_on_base_overlay.py +0 -0
  554. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_red_on_base_probe.py +0 -0
  555. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_red_on_base_real_runner.py +0 -0
  556. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_redaction_windows_home.py +0 -0
  557. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_reescalation_posts_comment.py +0 -0
  558. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_reflection_required_signals.py +0 -0
  559. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_replan_end_to_end.py +0 -0
  560. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_replan_event_emission.py +0 -0
  561. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_replan_note_collision.py +0 -0
  562. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_replan_trigger.py +0 -0
  563. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_replan_turn_contract.py +0 -0
  564. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_replay_spin.py +0 -0
  565. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_replay_spin_failing_sets.py +0 -0
  566. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_required_checks.py +0 -0
  567. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_resolve_base.py +0 -0
  568. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_restart_cap_counts_progress.py +0 -0
  569. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_result_block.py +0 -0
  570. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_result_block_audience.py +0 -0
  571. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_result_block_near_miss.py +0 -0
  572. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_result_block_scalar.py +0 -0
  573. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_resume_command_build.py +0 -0
  574. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_retain_on_guard_refusal_docs.py +0 -0
  575. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_roadmap_add_skill.py +0 -0
  576. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_roadmap_archive_cli.py +0 -0
  577. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_roadmap_archive_shim.py +0 -0
  578. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_roadmap_archive_skill.py +0 -0
  579. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_roadmap_archive_skill_delegates.py +0 -0
  580. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_roadmap_link_gate.py +0 -0
  581. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_roadmap_row_parser.py +0 -0
  582. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_ruff_verdict_lines.py +0 -0
  583. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_rule_import_convergence.py +0 -0
  584. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_run_level_failure_breaker.py +0 -0
  585. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_scaffold_data_in_sync.py +0 -0
  586. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_scaffold_doc_hygiene.py +0 -0
  587. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_scaffold_docs.py +0 -0
  588. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_scaffold_init.py +0 -0
  589. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_scaffold_label_provisioning.py +0 -0
  590. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_scaffold_manifest.py +0 -0
  591. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_scaffold_resources.py +0 -0
  592. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_scaffold_seed_sanity.py +0 -0
  593. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_scaffold_skew_halts.py +0 -0
  594. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_scaffold_upgrade.py +0 -0
  595. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_scaffold_wiring.py +0 -0
  596. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_schedule_dialect.py +0 -0
  597. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_seed_documents_prerun_keys.py +0 -0
  598. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_service_bus_dlq_adapter.py +0 -0
  599. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_session_env_preflight.py +0 -0
  600. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_severity_aliases.py +0 -0
  601. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_severity_backfill_apply.py +0 -0
  602. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_severity_backfill_bugs_only.py +0 -0
  603. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_severity_backfill_end_to_end.py +0 -0
  604. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_severity_backfill_limit.py +0 -0
  605. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_severity_backfill_marker.py +0 -0
  606. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_severity_backfill_run.py +0 -0
  607. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_severity_label_projection.py +0 -0
  608. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_severity_reconciliation.py +0 -0
  609. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_severity_rubric.py +0 -0
  610. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_shipped_templates_parse.py +0 -0
  611. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_skill_discovery_links.py +0 -0
  612. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_skills_vendored_in_sync.py +0 -0
  613. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_spec_work_unit_rules.py +0 -0
  614. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_spinning_rearm_gate.py +0 -0
  615. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_spinning_repeat_same_failing_set.py +0 -0
  616. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_spinout_brief_end_to_end.py +0 -0
  617. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_spinout_brief_replan_option.py +0 -0
  618. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_squash_commit_hook_crash.py +0 -0
  619. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_stop_rationale_captured.py +0 -0
  620. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_stranded_halt_cause.py +0 -0
  621. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_surefire_run_level_signature.py +0 -0
  622. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_surefire_verdict_lines.py +0 -0
  623. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_template_closing_shapes.py +0 -0
  624. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_terminal_auto_close_changelog.py +0 -0
  625. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_terminal_flip_ownership.py +0 -0
  626. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_terminal_flips.py +0 -0
  627. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_terminal_gate_message.py +0 -0
  628. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_tiered_verification_e2e.py +0 -0
  629. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_timeout_kill_portable.py +0 -0
  630. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_triage.py +0 -0
  631. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_triage_apply.py +0 -0
  632. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_triage_projects_severity_label.py +0 -0
  633. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_triage_severity_classify.py +0 -0
  634. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_triage_severity_end_to_end.py +0 -0
  635. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_triage_severity_write.py +0 -0
  636. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_triage_skill_contract.py +0 -0
  637. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_triage_skips_agent_escalations.py +0 -0
  638. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_tsc_verdict_lines.py +0 -0
  639. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_umbrella_version_resolution.py +0 -0
  640. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_unreadable_pr_listing_holds_pr_work.py +0 -0
  641. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_unterminated_frontmatter.py +0 -0
  642. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_untracked_feature_folder.py +0 -0
  643. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_untracked_file_absorption.py +0 -0
  644. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_upgrade_integration.py +0 -0
  645. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_upgrade_merge_gate.py +0 -0
  646. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_validate_event.py +0 -0
  647. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_validate_event_driver_types.py +0 -0
  648. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_verdict_coupling.py +0 -0
  649. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_verify_empty_gate_set.py +0 -0
  650. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_version_consistency.py +0 -0
  651. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_version_skew.py +0 -0
  652. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_windows_gate_exec.py +0 -0
  653. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_worktree_restore.py +0 -0
  654. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_wu_execution_metadata.py +0 -0
  655. {specfuse_loop-0.27.1 → specfuse_loop-0.28.0}/tests/test_wu_template_renders_lintable.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfuse-loop
3
- Version: 0.27.1
3
+ Version: 0.28.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "specfuse-loop"
7
- version = "0.27.1"
7
+ version = "0.28.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"
@@ -0,0 +1 @@
1
+ 0.28.0
@@ -81,6 +81,15 @@ components:
81
81
  # Use for any component that serves HTTP traffic directly.
82
82
  - type: http-5xx
83
83
 
84
+ # http-status-regression: alerts when a deployment raises the rate of
85
+ # server-fault 4xx responses compared with the window before it.
86
+ - type: http-status-regression
87
+ deploy_signal:
88
+ kind: app-version
89
+ window_after_deploy: 1h
90
+ # codes defaults to [403, 405, 415]; this one also opts in to 401.
91
+ codes: [401, 403, 405, 415]
92
+
84
93
  # heartbeat: alerts when the component stops reporting in at all —
85
94
  # the check that catches "it didn't error, it just went silent."
86
95
  - type: heartbeat
@@ -7,8 +7,8 @@ Licensed under the Apache License, Version 2.0. See LICENSE.
7
7
 
8
8
  This rule governs the *target application's* code — not this loop's own
9
9
  execution. It states what a deployed component must do so the check types a
10
- `monitoring.yml` admits (`dlq`, `error-logs`, `http-5xx`, `heartbeat`,
11
- `invariant`) produce a diagnosable finding instead of a bare alert. A check that
10
+ `monitoring.yml` admits (`dlq`, `error-logs`, `http-5xx`,
11
+ `http-status-regression`, `heartbeat`, `invariant`, `queue-stalled`) produce a diagnosable finding instead of a bare alert. A check that
12
12
  fires without one of these properties tells an operator only that something is
13
13
  wrong, not what or where.
14
14
 
@@ -108,3 +108,17 @@ an operator has to reproduce the failure from scratch, from a payload alone,
108
108
  with no error, no attempt count, and no correlation ID to trace back to the
109
109
  request that produced it. Context captured at dead-letter time is the
110
110
  difference between a diagnosis and a re-investigation.
111
+
112
+ ## A deployable stamps a version that changes on every deployment
113
+
114
+ An HTTP-serving component stamps a service version onto the telemetry it emits,
115
+ and that version changes on every deployment — the stamp is injected at build or
116
+ release time, not a constant in source that survives a release unchanged.
117
+
118
+ An `http-status-regression` check detects a deployment by the first appearance
119
+ of a new version value, then compares the failure rate of newly-seen client-error
120
+ and server-error statuses after that point with the rate before it. A component
121
+ that stamps no version has no deployment signal at all; one that stamps the same
122
+ value every release shows no new version to detect, so the check never opens its
123
+ post-deployment window and a regression ships unseen. Either way the check
124
+ cannot be evaluated, and discovery reports the gap rather than proposing it.
@@ -35,6 +35,8 @@ from __future__ import annotations
35
35
  import re
36
36
  from pathlib import Path
37
37
 
38
+ from specfuse.monitor.durations import DURATION_GRAMMAR, parse_duration_seconds
39
+
38
40
  from . import _miniyaml
39
41
 
40
42
  __all__ = ("validate_monitoring", "main", "CRON_DIALECTS")
@@ -43,7 +45,15 @@ RUNNER_VALUES = frozenset({"local", "gh-actions", "in-cluster"})
43
45
  DIAGNOSE_VALUES = frozenset({"manual", "auto"})
44
46
  AUTOFIX_VALUES = frozenset({"off", "on"})
45
47
  CHECK_TYPES = frozenset(
46
- {"dlq", "error-logs", "http-5xx", "heartbeat", "invariant", "queue-stalled"}
48
+ {
49
+ "dlq",
50
+ "error-logs",
51
+ "http-5xx",
52
+ "http-status-regression",
53
+ "heartbeat",
54
+ "invariant",
55
+ "queue-stalled",
56
+ }
47
57
  )
48
58
  HARVEST_MODE_VALUES = frozenset({"peek", "quarantine"})
49
59
 
@@ -250,6 +260,8 @@ def _check_checks(checks: object, component_label: str) -> list[str]:
250
260
  f"'queue-stalled' check requires 'targets' — each target "
251
261
  f"needs 'subscription' and 'function'"
252
262
  )
263
+ elif check_type == "http-status-regression":
264
+ findings.extend(_check_http_status_regression(check, component_label, index))
253
265
  elif check_type == "invariant":
254
266
  for field in ("query", "fingerprint_by"):
255
267
  if not check.get(field):
@@ -274,8 +286,63 @@ _TARGET_REQUIRED_FIELDS = {
274
286
  "heartbeat": ("name",),
275
287
  "queue-stalled": ("subscription", "function"),
276
288
  }
289
+ DEPLOY_SIGNAL_KINDS = frozenset({"app-version"})
290
+
291
+
292
+ def _check_http_status_regression(
293
+ check: dict, component_label: str, check_index: int
294
+ ) -> list[str]:
295
+ """Field rules for one `http-status-regression` check (`targets` is
296
+ rejected by `_check_targets`)."""
297
+ where = f"component '{component_label}': checks[{check_index}]"
298
+ prefix = f"{where}: 'http-status-regression' check"
299
+ findings: list[str] = []
300
+
301
+ signal = check.get("deploy_signal")
302
+ if not isinstance(signal, dict) or not signal.get("kind"):
303
+ findings.append(
304
+ f"{prefix} requires 'deploy_signal' — a mapping with a 'kind'"
305
+ )
306
+ elif signal["kind"] not in DEPLOY_SIGNAL_KINDS:
307
+ findings.append(
308
+ f"{prefix} has unknown 'deploy_signal' kind {signal['kind']!r} — "
309
+ f"must be one of {sorted(DEPLOY_SIGNAL_KINDS)}"
310
+ )
311
+
312
+ window = check.get("window_after_deploy")
313
+ if window is None:
314
+ findings.append(f"{prefix} requires 'window_after_deploy'")
315
+ else:
316
+ try:
317
+ parse_duration_seconds(str(window))
318
+ except ValueError:
319
+ findings.append(
320
+ f"{prefix} 'window_after_deploy' value {window!r} is not a "
321
+ f"valid duration — expected {DURATION_GRAMMAR}"
322
+ )
323
+
324
+ codes = check.get("codes")
325
+ if codes is not None:
326
+ if (
327
+ not isinstance(codes, list)
328
+ or not codes
329
+ or not all(
330
+ isinstance(c, int) and not isinstance(c, bool) and 400 <= c <= 499
331
+ for c in codes
332
+ )
333
+ ):
334
+ findings.append(
335
+ f"{prefix} 'codes' must be a non-empty list of integers in 400–499"
336
+ )
337
+ elif len(set(codes)) != len(codes):
338
+ findings.append(f"{prefix} 'codes' must not contain duplicates")
339
+ return findings
340
+
341
+
277
342
  # Check types that must never carry `targets` at all.
278
- _TARGETLESS_CHECK_TYPES = frozenset({"error-logs", "http-5xx", "invariant"})
343
+ _TARGETLESS_CHECK_TYPES = frozenset(
344
+ {"error-logs", "http-5xx", "http-status-regression", "invariant"}
345
+ )
279
346
 
280
347
 
281
348
  def _check_targets(
@@ -329,6 +396,15 @@ def _check_targets(
329
396
  )
330
397
  if check_type == "heartbeat":
331
398
  findings.extend(_check_cron_dialect(target, where, t_index))
399
+ if check_type == "queue-stalled" and target.get("stall_after") is not None:
400
+ try:
401
+ parse_duration_seconds(str(target["stall_after"]))
402
+ except ValueError:
403
+ findings.append(
404
+ f"{where}: targets[{t_index}]: 'queue-stalled' target "
405
+ f"'stall_after' value {target['stall_after']!r} is not a "
406
+ f"valid duration — expected {DURATION_GRAMMAR}"
407
+ )
332
408
  return findings
333
409
 
334
410
 
@@ -127,7 +127,7 @@ SPECFUSE_DIR = Path(".specfuse")
127
127
  REPO_ROOT = SPECFUSE_DIR.parent
128
128
  FEATURES_DIR = SPECFUSE_DIR / "features"
129
129
  VERIFICATION_PATH = SPECFUSE_DIR / "verification.yml"
130
- DRIVER_VERSION = "0.27.1"
130
+ DRIVER_VERSION = "0.28.0"
131
131
  # Oldest scaffold layout this driver can drive. init.sh stamps the scaffold's own
132
132
  # version into `.specfuse/VERSION`; check_scaffold_version() fails loud at startup if
133
133
  # the consumer's scaffold is older than this, pointing at `specfuse upgrade`. Bump
@@ -31,7 +31,9 @@ _TARGET_COORDINATE_FIELDS: Mapping[str, tuple[str, ...]] = {
31
31
 
32
32
  # Check types that must never carry target coordinates — 0069 rejected
33
33
  # `targets` on these, so `fingerprint_by` stays their single enumeration key.
34
- _TARGETLESS_CHECK_TYPES = frozenset({"error-logs", "http-5xx", "invariant"})
34
+ _TARGETLESS_CHECK_TYPES = frozenset(
35
+ {"error-logs", "http-5xx", "http-status-regression", "invariant"}
36
+ )
35
37
 
36
38
 
37
39
  @dataclass(frozen=True)
@@ -252,6 +252,9 @@ def _build_adapter(cls, *, component: str, environment: Mapping[str, object], ch
252
252
  query=check.get("query"),
253
253
  fingerprint_by=check.get("fingerprint_by"),
254
254
  max_message_count=check.get("max_message_count", 50),
255
+ deploy_signal=check.get("deploy_signal"),
256
+ window_after_deploy=check.get("window_after_deploy"),
257
+ codes=check.get("codes"),
255
258
  )
256
259
  return _call_filtered(cls, **kwargs)
257
260
 
@@ -0,0 +1,40 @@
1
+ #
2
+ # Copyright 2026 Specfuse contributors
3
+ # Licensed under the Apache License, Version 2.0. See LICENSE.
4
+ #
5
+ """The `<integer><unit>` duration grammar, one home for adapter and linter.
6
+
7
+ Imports nothing from any provider SDK: `specfuse.loop` (the linter) imports
8
+ this module, and the linter must stay SDK-free.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from typing import Mapping
15
+
16
+ DURATION_GRAMMAR = "<integer><unit> with unit in s/m/h/d, e.g. '15m'"
17
+
18
+ _DURATION_RE = re.compile(r"^(\d+)([smhd])$")
19
+ _DURATION_MULTIPLIERS: Mapping[str, int] = {
20
+ "s": 1,
21
+ "m": 60,
22
+ "h": 3600,
23
+ "d": 86400,
24
+ }
25
+
26
+
27
+ def parse_duration_seconds(value: str, field: str = "stall_after") -> int:
28
+ """Parse `<integer><unit>` (unit in s/m/h/d) into seconds.
29
+
30
+ Refuses rather than guesses: anything outside the grammar — no unit, an
31
+ unsupported unit, whitespace, a negative sign — raises `ValueError`
32
+ naming the offending value. No coercion, no silent zero.
33
+ """
34
+ match = _DURATION_RE.match(value) if isinstance(value, str) else None
35
+ if not match:
36
+ raise ValueError(
37
+ f"invalid {field} value {value!r} — expected {DURATION_GRAMMAR}"
38
+ )
39
+ magnitude, unit = match.groups()
40
+ return int(magnitude) * _DURATION_MULTIPLIERS[unit]
@@ -27,12 +27,14 @@ folded back into a subsequent query; that is the direction
27
27
 
28
28
  from __future__ import annotations
29
29
 
30
+ import json
30
31
  import re
31
- from datetime import datetime
32
+ from datetime import datetime, timedelta, timezone
32
33
  from typing import Iterable, Iterator, Mapping, Optional, Protocol, Sequence
33
34
 
34
35
  from specfuse.monitor.adapters import resolve_telemetry
35
36
  from specfuse.monitor.artifact import FailureArtifact
37
+ from specfuse.monitor.durations import parse_duration_seconds
36
38
  from specfuse.monitor.redaction import redact_artifact
37
39
 
38
40
  # Credential construction is shared with the other Azure provider and looked up
@@ -165,6 +167,259 @@ class Http5xxAdapter:
165
167
  return redact_artifact(artifact)
166
168
 
167
169
 
170
+ # The regression rule (PLAN.md decision 2): a code fires when its share of the
171
+ # after-window's requests is at least REGRESSION_FACTOR times its share of the
172
+ # equal-length before-window and it occurs at least REGRESSION_MIN_COUNT times
173
+ # after the deployment. Documented constants in v1, not monitoring.yml fields.
174
+ REGRESSION_FACTOR = 3
175
+ REGRESSION_MIN_COUNT = 10
176
+
177
+ # The codes evaluated when `monitoring.yml` names none: forbidden, method not
178
+ # allowed, unsupported media type — the 4xx a deployment produces when it breaks
179
+ # authorization mapping, routing or content negotiation. 401 is deliberately
180
+ # opt-in: a 401 wave is as often expired client tokens as a broken deployment.
181
+ DEFAULT_REGRESSION_CODES = ("403", "405", "415")
182
+
183
+ # How many operations, and how many sample operation ids, a finding names.
184
+ _TOP_OPERATIONS = 5
185
+ _SAMPLE_OPERATIONS = 3
186
+
187
+ # How far back the one query looks for each version's first-seen time. A
188
+ # deployment is only evaluated when its before-window also lies inside it.
189
+ _REGRESSION_LOOKBACK = "7d"
190
+
191
+
192
+ def _http_status_regression_query(
193
+ component: str, window_seconds: int, codes: Sequence[object]
194
+ ) -> str:
195
+ """One query, built only from operator configuration (the component, the
196
+ configured ``codes``, ``window_after_deploy`` and a fixed lookback) —
197
+ never from returned rows or the watermark. Each version's first-seen time
198
+ is found, deployments whose after-window has fully elapsed are kept, and
199
+ per (version, code) the after- and before-window counts come back with
200
+ each window's total request count, the code's top operations by
201
+ after-window count, and a few after-window operation ids. The ratio
202
+ arithmetic is the adapter's.
203
+ """
204
+ window = f"{int(window_seconds)}s"
205
+ code_list = ", ".join(f"'{c}'" for c in codes)
206
+ scope = (
207
+ f"AppRequests | where AppRoleName == '{component}' "
208
+ f"and TimeGenerated > ago({_REGRESSION_LOOKBACK}) "
209
+ "and isnotempty(AppVersion)"
210
+ )
211
+ return (
212
+ f"{scope} "
213
+ "| summarize first_seen = min(TimeGenerated) by AppVersion "
214
+ f"| where first_seen + {window} <= now() "
215
+ f"and first_seen - {window} >= ago({_REGRESSION_LOOKBACK}) "
216
+ "| project AppVersion, first_seen, k = 1 "
217
+ f"| join kind=inner ({scope} "
218
+ "| project TimeGenerated, ResultCode, Name, OperationId, k = 1) on k "
219
+ f"| where TimeGenerated >= first_seen - {window} "
220
+ f"and TimeGenerated < first_seen + {window} "
221
+ "| summarize after = countif(TimeGenerated >= first_seen), "
222
+ "before = countif(TimeGenerated < first_seen), "
223
+ "sample = take_anyif(OperationId, TimeGenerated >= first_seen) "
224
+ "by AppVersion, first_seen, ResultCode, Name "
225
+ "| sort by AppVersion asc, ResultCode asc, after desc "
226
+ "| extend rn = row_number(1, prev(AppVersion) != AppVersion "
227
+ "or prev(ResultCode) != ResultCode) "
228
+ "| summarize after = sum(after), before = sum(before), "
229
+ "endpoints = make_list_if(pack('name', Name, 'after', after, "
230
+ f"'before', before), rn <= {_TOP_OPERATIONS}), "
231
+ f"samples = make_list_if(sample, rn <= {_SAMPLE_OPERATIONS} "
232
+ "and isnotempty(sample)) "
233
+ "by AppVersion, first_seen, ResultCode "
234
+ "| summarize per_code = make_list(pack('code', ResultCode, "
235
+ "'after', after, 'before', before, 'endpoints', endpoints, "
236
+ "'samples', samples)), "
237
+ "after_total = sum(after), before_total = sum(before) "
238
+ "by AppVersion, first_seen "
239
+ "| mv-expand per_code "
240
+ "| project version = AppVersion, first_seen, "
241
+ "code = tostring(per_code.code), after = tolong(per_code.after), "
242
+ "before = tolong(per_code.before), endpoints = per_code.endpoints, "
243
+ "samples = per_code.samples, after_total, before_total "
244
+ f"| where code in ({code_list})"
245
+ )
246
+
247
+
248
+ def _dynamic_list(value: object) -> list:
249
+ """A ``dynamic`` column arrives parsed or as JSON text, depending on the
250
+ client; anything else reads as empty rather than failing the finding."""
251
+ if isinstance(value, str):
252
+ try:
253
+ value = json.loads(value)
254
+ except ValueError:
255
+ return []
256
+ return list(value) if isinstance(value, (list, tuple)) else []
257
+
258
+
259
+ def _share(count: int, total: int) -> str:
260
+ return f"{100 * count / total:.2f}%" if total > 0 else "n/a (no requests)"
261
+
262
+
263
+ def _iso(moment: datetime) -> str:
264
+ return moment.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
265
+
266
+
267
+ def _kql_literal(value: str) -> str:
268
+ return value.replace("\\", "\\\\").replace("'", "\\'")
269
+
270
+
271
+ def _as_utc(value: object) -> Optional[datetime]:
272
+ if isinstance(value, datetime):
273
+ parsed = value
274
+ else:
275
+ try:
276
+ parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
277
+ except ValueError:
278
+ return None
279
+ if parsed.tzinfo is None:
280
+ parsed = parsed.replace(tzinfo=timezone.utc)
281
+ return parsed
282
+
283
+
284
+ def _count(value: object) -> Optional[int]:
285
+ try:
286
+ return int(float(value)) # type: ignore[arg-type]
287
+ except (TypeError, ValueError):
288
+ return None
289
+
290
+
291
+ class HttpStatusRegressionAdapter:
292
+ """``TelemetryAdapter`` for the ``http-status-regression`` check type.
293
+
294
+ One finding per (status code, deployment version) whose after-window has
295
+ fully elapsed at ``reference_time`` and whose share of requests regressed
296
+ by ``REGRESSION_FACTOR`` with at least ``REGRESSION_MIN_COUNT`` after-window
297
+ occurrences. The version is part of ``failure_signature`` so the finding
298
+ is filed once per deployment.
299
+ """
300
+
301
+ def __init__(
302
+ self,
303
+ *,
304
+ component: str,
305
+ environment: Mapping[str, object],
306
+ transport: AppInsightsTransport,
307
+ deploy_signal: object = None,
308
+ window_after_deploy: object = None,
309
+ codes: Optional[Sequence[object]] = None,
310
+ reference_time: Optional[datetime] = None,
311
+ ) -> None:
312
+ self._component = component
313
+ self._telemetry = resolve_telemetry(component, environment)
314
+ self._transport = transport
315
+ self._deploy_signal = deploy_signal
316
+ self._window_after_deploy = window_after_deploy
317
+ self._codes = [str(c) for c in codes] if codes else list(DEFAULT_REGRESSION_CODES)
318
+ self._reference_time = reference_time
319
+
320
+ def fetch_failures(self) -> Iterator[FailureArtifact]:
321
+ window_seconds = parse_duration_seconds(
322
+ self._window_after_deploy, "window_after_deploy" # type: ignore[arg-type]
323
+ )
324
+ reference = _as_utc(self._reference_time) or datetime.now(timezone.utc)
325
+ query = _http_status_regression_query(self._component, window_seconds, self._codes)
326
+ seen: set = set()
327
+ for row in self._transport.run_query(query):
328
+ code = str(row.get("code") or "")
329
+ version = str(row.get("version") or "unknown-version")
330
+ if code not in self._codes:
331
+ continue
332
+ first_seen = _as_utc(row.get("first_seen"))
333
+ counts = [
334
+ _count(row.get(key))
335
+ for key in ("after", "before", "after_total", "before_total")
336
+ ]
337
+ if first_seen is None or None in counts:
338
+ continue
339
+ if first_seen + timedelta(seconds=window_seconds) > reference:
340
+ continue
341
+ after, before, after_total, before_total = counts # type: ignore[misc]
342
+ if (code, version) in seen or not _regressed(
343
+ after, before, after_total, before_total
344
+ ):
345
+ continue
346
+ seen.add((code, version))
347
+ yield self._build_artifact(
348
+ code, version, first_seen, window_seconds,
349
+ after, before, after_total, before_total,
350
+ _dynamic_list(row.get("endpoints")), _dynamic_list(row.get("samples")),
351
+ )
352
+
353
+ def _build_artifact(
354
+ self,
355
+ code: str,
356
+ version: str,
357
+ first_seen: datetime,
358
+ window_seconds: int,
359
+ after: int,
360
+ before: int,
361
+ after_total: int,
362
+ before_total: int,
363
+ endpoints: list,
364
+ samples: list,
365
+ ) -> FailureArtifact:
366
+ window = self._window_after_deploy
367
+ lines = [
368
+ f"{code} rose after deployment {version} "
369
+ f"(first seen {_iso(first_seen)}).",
370
+ f"Share of requests: {_share(before, before_total)} in the {window} "
371
+ f"before ({before} of {before_total}), {_share(after, after_total)} "
372
+ f"in the {window} after ({after} of {after_total}). Fires at "
373
+ f"{REGRESSION_FACTOR}x the before-share with at least "
374
+ f"{REGRESSION_MIN_COUNT} occurrences after.",
375
+ ]
376
+ operations = sorted(
377
+ (e for e in endpoints if isinstance(e, Mapping)),
378
+ key=lambda e: _count(e.get("after")) or 0,
379
+ reverse=True,
380
+ )[:_TOP_OPERATIONS]
381
+ if operations:
382
+ lines += ["", f"Top operations returning {code}:"]
383
+ lines += [
384
+ f" {e.get('name') or '(unnamed)'}: {_count(e.get('after')) or 0} "
385
+ f"after, {_count(e.get('before')) or 0} before"
386
+ for e in operations
387
+ ]
388
+ sample_ids = [str(s) for s in samples if s][:_SAMPLE_OPERATIONS]
389
+ if sample_ids:
390
+ lines += ["", f"Sample operation ids after the deployment: {', '.join(sample_ids)}"]
391
+ span = timedelta(seconds=window_seconds)
392
+ lines += [
393
+ "",
394
+ "Investigate in Log Analytics:",
395
+ "AppRequests",
396
+ f"| where AppRoleName == '{_kql_literal(self._component)}' "
397
+ f"and ResultCode == '{_kql_literal(code)}'",
398
+ f"| where TimeGenerated between (datetime({_iso(first_seen - span)}) "
399
+ f".. datetime({_iso(first_seen + span)}))",
400
+ "| summarize count() by AppVersion, Name, bin(TimeGenerated, 5m)",
401
+ ]
402
+ artifact = FailureArtifact(
403
+ component=self._component,
404
+ check_type="http-status-regression",
405
+ failure_class=code,
406
+ failure_signature=f"{version}:{code}",
407
+ observed_text="\n".join(lines),
408
+ target_coordinates=None,
409
+ )
410
+ return redact_artifact(artifact)
411
+
412
+
413
+ def _regressed(after: int, before: int, after_total: int, before_total: int) -> bool:
414
+ """``after/after_total >= FACTOR * before/before_total``, in integers; a
415
+ before-window with no requests, or none of the code, has a share of 0."""
416
+ if after_total <= 0 or after < REGRESSION_MIN_COUNT:
417
+ return False
418
+ if before_total <= 0 or before <= 0:
419
+ return True
420
+ return after * before_total >= REGRESSION_FACTOR * before * after_total
421
+
422
+
168
423
  class InvariantAdapter:
169
424
  """``TelemetryAdapter`` for the ``invariant`` check type.
170
425
 
@@ -30,6 +30,7 @@ from datetime import datetime
30
30
  from typing import Iterable, Iterator, List, Mapping, Optional, Protocol, Sequence
31
31
 
32
32
  from specfuse.monitor.artifact import FailureArtifact
33
+ from specfuse.monitor.durations import parse_duration_seconds
33
34
  from specfuse.monitor.redaction import redact_artifact
34
35
 
35
36
  # Credential construction is shared with the other Azure provider and looked up
@@ -137,32 +138,10 @@ class ServiceBusDlqAdapter:
137
138
  return redact_artifact(artifact)
138
139
 
139
140
 
140
- _STALL_AFTER_RE = re.compile(r"^(\d+)([smhd])$")
141
- _STALL_AFTER_MULTIPLIERS: Mapping[str, int] = {
142
- "s": 1,
143
- "m": 60,
144
- "h": 3600,
145
- "d": 86400,
146
- }
147
-
148
-
149
141
  def parse_stall_after_seconds(value: str) -> int:
150
- """Parse a `stall_after` value (`<integer><unit>`, unit in s/m/h/d) into
151
- seconds.
152
-
153
- Refuses rather than guesses (the WU's sub-decision 1): anything outside
154
- the grammar — no unit, an unsupported unit, whitespace, a negative sign
155
- — raises `ValueError` naming the offending value. No coercion, no
156
- "assume minutes", no silent zero.
157
- """
158
- match = _STALL_AFTER_RE.match(value)
159
- if not match:
160
- raise ValueError(
161
- f"invalid stall_after value {value!r} — expected "
162
- f"<integer><unit> with unit in s/m/h/d, e.g. '15m'"
163
- )
164
- magnitude, unit = match.groups()
165
- return int(magnitude) * _STALL_AFTER_MULTIPLIERS[unit]
142
+ """Parse a `stall_after` value into seconds. Thin alias over the shared
143
+ grammar in `specfuse.monitor.durations`."""
144
+ return parse_duration_seconds(value, field="stall_after")
166
145
 
167
146
 
168
147
  @dataclass(frozen=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfuse-loop
3
- Version: 0.27.1
3
+ Version: 0.28.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
@@ -129,6 +129,7 @@ specfuse/monitor/autofix_state.py
129
129
  specfuse/monitor/cli.py
130
130
  specfuse/monitor/diagnose_cli.py
131
131
  specfuse/monitor/diagnosis.py
132
+ specfuse/monitor/durations.py
132
133
  specfuse/monitor/fingerprint.py
133
134
  specfuse/monitor/issues.py
134
135
  specfuse/monitor/redaction.py
@@ -386,6 +387,8 @@ tests/test_headless_no_background_tasks.py
386
387
  tests/test_heartbeat.py
387
388
  tests/test_heartbeat_adapter.py
388
389
  tests/test_heartbeat_emission_contract.py
390
+ tests/test_http_status_regression_adapter.py
391
+ tests/test_http_status_regression_end_to_end.py
389
392
  tests/test_human_output_rule.py
390
393
  tests/test_human_work_unit.py
391
394
  tests/test_hygiene_boundary_guidance.py
@@ -480,6 +483,7 @@ tests/test_migrate_legacy.py
480
483
  tests/test_miniyaml_equivalence.py
481
484
  tests/test_miniyaml_negative.py
482
485
  tests/test_monitor_cli.py
486
+ tests/test_monitor_durations.py
483
487
  tests/test_monitor_issue_lifecycle.py
484
488
  tests/test_monitor_runner_surfaces.py
485
489
  tests/test_monitoring_bootstrap_artifacts.py
@@ -67,6 +67,7 @@ _KQL_KEYWORDS = frozenset({
67
67
  "where", "project", "summarize", "extend", "by", "take", "limit",
68
68
  "and", "or", "not", "startswith", "endswith", "contains", "has", "in",
69
69
  "true", "false", "asc", "desc", "order", "sort",
70
+ "join", "kind", "inner", "on", "mv", "expand",
70
71
  })
71
72
 
72
73
  _STRING_LITERAL = re.compile(r"'(?:[^'\\]|\\.)*'|\"(?:[^\"\\]|\\.)*\"")
@@ -74,6 +75,8 @@ _STRING_LITERAL = re.compile(r"'(?:[^'\\]|\\.)*'|\"(?:[^\"\\]|\\.)*\"")
74
75
  # access, not a column) and what follows it (``(`` is a call, a lone ``=``
75
76
  # is an output alias).
76
77
  _IDENT = re.compile(r"(?P<dot>\.\s*)?\b(?P<name>[A-Za-z_][A-Za-z0-9_]*)\b(?=\s*(?P<after>\(|==|=)?)")
78
+ # An output name defined anywhere in the query, so a later stage may read it.
79
+ _ALIAS = re.compile(r"\b([A-Za-z_][A-Za-z0-9_]*)\s*=(?!=)")
77
80
 
78
81
 
79
82
  def schema_violations(query: str) -> list[str]:
@@ -89,12 +92,14 @@ def schema_violations(query: str) -> list[str]:
89
92
  columns = WORKSPACE_SCHEMA.get(table)
90
93
  if columns is None:
91
94
  return [f"table {table!r} is not a workspace table"]
95
+ aliases = set(_ALIAS.findall(rest))
92
96
  problems = []
93
97
  for match in _IDENT.finditer(rest):
94
98
  name, after = match.group("name"), match.group("after")
95
99
  if match.group("dot") or after in ("(", "="):
96
100
  continue
97
- if name.lower() in _KQL_KEYWORDS or name in columns:
101
+ if name.lower() in _KQL_KEYWORDS or name in columns or name in aliases\
102
+ or name in WORKSPACE_SCHEMA:
98
103
  continue
99
104
  problems.append(f"column {name!r} is not a column of {table}")
100
105
  return problems
@@ -104,6 +109,9 @@ _BUILT_IN_QUERIES = {
104
109
  "error-logs": aai._error_logs_query,
105
110
  "http-5xx": aai._http_5xx_query,
106
111
  "heartbeat": aai._heartbeat_query,
112
+ "http-status-regression": lambda component: aai._http_status_regression_query(
113
+ component, 3600, ["401", "403"]
114
+ ),
107
115
  }
108
116
 
109
117