specfuse-loop 0.32.0__tar.gz → 0.33.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.
- {specfuse_loop-0.32.0/specfuse_loop.egg-info → specfuse_loop-0.33.0}/PKG-INFO +1 -1
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/pyproject.toml +1 -1
- specfuse_loop-0.33.0/specfuse/loop/data/VERSION +1 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/concepts/monitoring-runners.md +5 -1
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/concepts/monitoring-schema.md +42 -1
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/concepts/monitoring-to-fix.md +2 -1
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/flows.html +1 -1
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/monitoring.yml.example +27 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/rules/design-for-diagnosis.md +81 -1
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/lint_monitoring.py +90 -9
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/loop.py +1 -1
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/artifact.py +2 -1
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/autofix_state.py +3 -3
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/cli.py +21 -6
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/issues.py +10 -4
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/providers/azure_app_insights.py +458 -28
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0/specfuse_loop.egg-info}/PKG-INFO +1 -1
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse_loop.egg-info/SOURCES.txt +7 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_app_insights_harvest_window.py +7 -4
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_app_insights_query_schema_check.py +7 -0
- specfuse_loop-0.33.0/tests/test_backlog_snapshot_emission_contract.py +93 -0
- specfuse_loop-0.33.0/tests/test_backlog_stalled_adapter.py +174 -0
- specfuse_loop-0.33.0/tests/test_backlog_stalled_end_to_end.py +109 -0
- specfuse_loop-0.33.0/tests/test_datastore_scope_rule.py +112 -0
- specfuse_loop-0.33.0/tests/test_dependency_regression_adapter.py +245 -0
- specfuse_loop-0.33.0/tests/test_dependency_regression_end_to_end.py +141 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_derive_monitoring_discovery.py +249 -2
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_monitoring.py +124 -0
- specfuse_loop-0.33.0/tests/test_monitor_finding_listing_limit.py +154 -0
- specfuse_loop-0.32.0/specfuse/loop/data/VERSION +0 -1
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/LICENSE +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/NOTICE +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/README.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/setup.cfg +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/__init__.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/budget.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/diagnose_invoke.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/drafting_answers.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/drafting_invoke.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/drafting_questions.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/driver_command.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/driver_invoke.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/invoke.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/monitoring_read.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/providers/__init__.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/providers/answers.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/providers/bugs.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/providers/feature.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/providers/findings_autofix.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/providers/findings_diagnose.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/providers/triage.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/queue_read.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/repo_detect.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/run.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/severity_backfill.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/state.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/triage_invoke.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/agent/worktree.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/_filelock.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/_wu_sections.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/agent_policy.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/arm_eval.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/arm_sweep.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/arm_txn.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/bug_lane.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/bug_lane_run.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/bug_lane_state.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/build_provenance.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/caller_check.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/changelog.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/changelog_fragments.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/closing_requirements.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/cost.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/criteria_state.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/CHANGELOG.seed.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/LEARNINGS.template.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/concepts/adopting-auto-mode.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/concepts/autonomy-stop-classes.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/getting-started.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/glossary.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/lifecycles.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/methodology.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/skills.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/gitignore.snippet +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/monitoring-secrets-checklist.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/monitoring.overrides.yml.example +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/roadmap.template.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/rules/close-discipline.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/rules/correlation-ids.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/rules/human-output.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/rules/never-touch.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/rules/operator-escalation.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/rules/planning-discipline.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/rules/result-contract.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/rules/security-boundaries.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/rules/verification-discipline.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/rules-local/README.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/schemas/driver-event.schema.json +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/schemas/event.schema.json +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/templates/DECISIONS.template.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/templates/GATE.template.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/templates/LEARNINGS-pending.template.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/templates/PLAN.template.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/templates/WU.template.md +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/verification.yml.example +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/workflows/specfuse-monitor.yml +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/decisions_format.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/driver_edit.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/escalation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/events_stats.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/gate_commands.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/gen_contract.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/heartbeat.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/ids.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/judge.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/labels.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/layout.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/learnings_distil.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/learnings_query.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/lint_closing.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/lint_plan.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/lint_roadmap.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/notify.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/notify_escalation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/notify_sla.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/plan_baseline.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/policy_proposals.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/policy_review.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/prerun.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/prerun_capture.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/promotion.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/rearm_migration.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/red_on_base.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/replay_spin.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/roadmap_archive.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/scaffold.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/triage.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/validate_event.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/__init__.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/adapters.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/autofix.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/autofix_invoke.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/autofix_run.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/diagnose_cli.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/diagnosis.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/durations.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/fingerprint.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/providers/__init__.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/providers/_azure_auth.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/providers/azure_service_bus.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/redaction.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/monitor/schedule.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_abandon_feature_skill.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_abandoned_work_reported.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_abandoned_wu_strands_gate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_budget.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_bug_selection_dedup.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_default_features_root.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_driver_command.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_driver_invoke.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_driver_restart.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_escalation_on_issue.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_escalation_run_state.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_findings_seam.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_invoke_timeout.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_invoke_usage.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_item_worktree.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_lock.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_policy_key_ownership.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_policy_queue.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_policy_schema.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_policy_triage_dial.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_provider_answers.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_provider_bugs.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_provider_feature.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_provider_findings_autofix.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_provider_findings_diagnose.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_provider_isolation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_provider_triage.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_queue_read.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_relays_driver_output.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_repo_detect.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_run.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_run_progress.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_seam.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_snapshot_refresh.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_agent_state.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_answer_escalation_skill.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_app_insights_adapters.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_app_insights_workspace_schema.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_archive_ref_reconciliation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_arm_eval.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_arm_eval_lint_class.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_arm_eval_terminal.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_arm_eval_wiring.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_arm_gate_edits_uncommitted.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_arm_sweep.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_arm_sweep_gate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_arm_txn.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_arm_wiring.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_artifact_redaction.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_attempt_note_untracked_files.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_attempt_outcome_contract.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_attempt_outcome_emission.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_attention_nonwriting_guard.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_attention_silence_section.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_attention_skill_structure.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_attribution_halt_claims.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_autoclose_deferral_visibility.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_autoclose_gate_note.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_autoclose_stub_states_what_passed.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_autofix_invoke.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_autofix_live.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_autofix_predicate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_autofix_run.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_autofix_state.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_autosync.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_autosync_consent.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_autosync_firstrun.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_autosync_no_cwd_leak.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_autosync_plugin.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_azure_credential_resolution.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_backend.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_baseline_escalation_message.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_baseline_persistence.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_baseline_probe.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_baseline_provenance.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_baseline_tree_hash_key.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bash_routing.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bats_suites_gated.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_binary_verdict.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_binding_block_advisory.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_binding_block_allocation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_binding_block_budget.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_blind_run_is_not_drained.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_block_feature_layout.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_blocked_by_entry_anchor.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bookkeeping_commit_crash_run.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bookkeeping_no_verify.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_brief_covers_every_unit_escalation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_ci_pending.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_guardrails.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_label_nonfatal.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_labels_registered.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_min_severity.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_no_checks_pending.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_policy_contract.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_pr_number_carried.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_preflight_and_dial.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_run.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_spend.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_state.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_stopped_outcome_pr_number.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_superseded.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_bug_lane_test_paths.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_build_provenance.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_caller_check_ratchet.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_carried_green_invalidated_by_diff.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_carry_accounting_close_and_judge.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_changed_file_test_selection.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_changelog_append_only.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_changelog_collection.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_changelog_fragments.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_changelog_release_wiring.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_changelog_schema.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_ci_conclusion_wait.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_ci_runs_every_gate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_claude_resolution.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_close_b_pending_learnings.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_close_records_feature_oracle.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_close_wu_self_reference.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_closed_escalation_labels.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_closing_deliverable_guard.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_closing_guard_contracts.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_closing_guard_prediction.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_closing_learnings_destination.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_closing_requirements.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_closing_sessions_on_sonnet.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_coauthor_trailer_allowlist.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_conditional_reflection.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_console_encoding.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_convergent_iteration.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_convergent_wu_lint.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_correlation_id_override.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_correlation_id_two_surfaces.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_cost_lifetime.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_criteria_per_gate_lint.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_criteria_state.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_criteria_worklist.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_decision_citation_lint.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_decision_followups_discharged.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_decision_override_lint.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_decisions_format.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_default_severity_aliases.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_degraded_oracle.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_deliverable_missing_note.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_derive_agent_policy_review_mode.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_derive_agent_policy_skill.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_derive_monitoring_skill_registration.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_design_for_diagnosis_rule.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_deterministic_refusal_repeat.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_diagnose_fenced_analysis.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_diagnose_headless.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_diagnose_issue_skill.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_diagnosis.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_diagnosis_roundtrip_live.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_diagnosis_severity_end_to_end.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_diagnosis_severity_rubric.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_dispatch_model_from_fix_scope.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_dispatch_preamble_rules_pointer.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_dispatch_skeleton.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_dispatch_skills_index.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_distilled_accept_step.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_doctor.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_doctor_command_preflight.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_done_feature_gates.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_draft_feature_answers_mode.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_drafting_answer_gate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_drafting_invoke.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_drafting_question_issue.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_drafting_questions.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_drafting_reply_shape.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_driver_edit_detection.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_driver_event_registry_covers_emitters.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_driver_module_surface.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_driver_restart_halt_surfaced.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_driver_restart_halt_wiring.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_driver_restart_hold.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_driver_staleness_gate_summary.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_driver_staleness_warning.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_dry_run_no_branch_mutation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_ensure_feature_branch_base.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_escalation_assignee_policy.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_escalation_contract.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_escalation_crash_containment.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_escalation_dedup_search.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_escalation_emit.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_event_gate_full_envelope.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_events_redaction.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_events_stats.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_failure_artifact_model.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_failure_signature_fail_prefix.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_failure_signature_names_the_test.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_failure_signature_skips_log_noise.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_feature_branch_auto_sync.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_feature_branch_squash_merge.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_feature_branch_stale_base.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_feature_id_resolution.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_feature_oracle_e2e.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_feature_provider_drafting_dispatch.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_feature_review_accumulation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_filelock_portable.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_files_changed_auto_repair.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_find_pr_no_search_index.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_fingerprint.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_fix_bug_cwd_worktree.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_fix_bug_diff_self_check.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_fix_bug_headless.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_fix_bug_reads_comments.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_fix_unit_insertion_bookkeeping.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_fix_unit_insertion_e2e.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_fix_unit_insertion_refused.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_floor_skip_summary.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_flows_page.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_fold_contract_documented.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_followups_artifact.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_frontmatter_scalar_quoting.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gate_broad_run.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gate_budget.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gate_budget_post_dispatch.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gate_commands_needs.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gate_eval_intermediate_wiring.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gate_eval_repo_flag.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gate_eval_terminal_wiring.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gate_log_size_budget.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gate_report_verdict.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gen_contract.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_gitleaks_pinning.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_groom_backlog_skill.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_guard_refusal_failure_class.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_guard_refusal_failure_excerpt.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_guard_repair_e2e.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_guard_repair_prompt.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_headless_no_background_tasks.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_heartbeat.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_heartbeat_adapter.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_heartbeat_emission_contract.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_http_status_regression_adapter.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_http_status_regression_end_to_end.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_human_output_rule.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_human_work_unit.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_hygiene_boundary_guidance.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_hyphenated_gate_name.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_ids.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_ids_aliases.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_ids_call_sites.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_ids_migrate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_ids_migrate_all.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_ignored_cleanup_before_probe.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_init_integration.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_installed_copy_driver_e2e.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_interpreter_normalization.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_judge_close_path.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_judge_cost.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_judge_cost_fold_committed.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_judge_editing_terminal_docs.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_judge_module.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_judge_path_registry.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_judge_sees_dropped_produces.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_judge_terminal_flip_state.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_judged_event_points_at_findings.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_keyed_feature_ids_docs.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_keyed_feature_ids_end_to_end.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_label_description_length.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_label_provisioning_runner_contract.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_label_registry.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_label_registry_covers_consumers.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_language_aware_narrow_selection.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_layout.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_layout_end_to_end.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_layout_link.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_layout_no_layout_goldens.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_layout_validation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lazy_baseline_e2e.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_leak_findings_redaction.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_leak_scan.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_learnings_curate_vendored_promote.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_learnings_distil_cli.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_learnings_query.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_learnings_staging.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_learnings_suggest_signature_filter.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_learnings_weights.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lifetime_accounting.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_ac_observable.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_bare_produces_path.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_boundary_consistency.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_boundary_extraction.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_close_verdict_not_predecided.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_closing.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_closing_criteria.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_closing_criteria_pristine.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_covers_load_wu_invariants.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_feature_oracle_declared.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_gate_proportionality.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_judge_edit_acknowledged.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_plan_contract_fields.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_plan_relative_import.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_plan_verdict_exempt.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_planning_sections.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_produces_driver_helper.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_produces_satisfiability.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_roadmap.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_roadmap_unkeyed_rows.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_lint_verification_reference.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_live_tests_opt_in.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_load_bearing_close_lint.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_attempt_evidence.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_auto_archive.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_close_intermediate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_criteria_skeleton.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_criteria_survival.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_gate_budget.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_gate_needs.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_orchestration.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_post_pass_invariant.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_worklist_injection.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_make_repo_gpgsign_isolation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_marker_dual_shape.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_maven_failure_excerpt.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_max_attempts_configurable.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_max_open_prs.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_migrate_all_repo_stays_valid.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_migrate_all_scales.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_migrate_legacy.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitor_check_isolation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitor_cli.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitor_diagnose_auto_warning.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitor_durations.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitor_finding_context_end_to_end.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitor_finding_lifecycle_end_to_end.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitor_finding_meta.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitor_issue_lifecycle.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitor_one_issue_per_fingerprint.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitor_quiet_annotation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitor_runner_surfaces.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitoring_bootstrap_artifacts.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitoring_cron_dialect.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitoring_example.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitoring_fenced_blocks.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_monitoring_seed.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_narrow_fallback_warning.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_narrow_non_py_produces.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_needs_decision_outcome.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_no_pytest_subprocess.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_notify.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_notify_escalation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_notify_sla.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_operator_escalation_rule.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_oracle_set_declared.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_oracle_unrunnable_attribution.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_per_run_item_cap_disclosure.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_pick_feature_skill.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_pin_honesty_and_integrity.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_pin_marker_not_inherited.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_pin_marker_spawn_sites.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_plan_baseline.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_planned_cost_lint.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_planning_cost_floor.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_plugin_prose_cross_refs.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_plugin_qualified_dispatch.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_policy_budgets_applied.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_policy_proposals.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_policy_review.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_pr_checks_json_field.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_pr_reference_in_title.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_pre_existing_claims_cite_evidence.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_prep_halt_operator_surface.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_prepare_feature.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_prepare_scaffold_sync.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_prerun_capture.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_prerun_oracles.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_prerun_wiring.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_produced_document_verdict_guard.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_produces_amendment_e2e.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_produces_bracket_literal.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_produces_bracket_wildcard.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_produces_field.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_produces_in_diff.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_produces_justification.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_produces_recursive_glob.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_produces_repair_note.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_produces_semantics_unified.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_produces_shape.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_progress_forward_note.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_progress_lines_end_to_end.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_promoted_issue_not_redispatched.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_promotion_ledger.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_provider_escalation_traces.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_provision_labels.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_queue_stalled_adapter.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_rearm_clears_stale_criteria_state.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_rearm_close_broad_run.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_rearm_fold_marker.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_rearm_migration.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_rearm_retained_new_files.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_reclose_carries_narrow_greens_e2e.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_red_baseline_never_reused.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_red_on_base_fetch_and_reason.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_red_on_base_guardrail.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_red_on_base_overlay.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_red_on_base_probe.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_red_on_base_real_runner.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_redaction_windows_home.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_rediagnose_on_reopen.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_reescalation_posts_comment.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_reflection_required_signals.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_replan_end_to_end.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_replan_event_emission.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_replan_note_collision.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_replan_trigger.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_replan_turn_contract.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_replay_spin.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_replay_spin_failing_sets.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_repo_changelog_opt_in.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_required_checks.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_resolve_base.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_restart_cap_counts_progress.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_result_block_audience.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_result_block_near_miss.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_result_block_scalar.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_resume_command_build.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_retain_on_guard_refusal_docs.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_roadmap_add_skill.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_roadmap_archive_cli.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_roadmap_archive_shim.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_roadmap_archive_skill_delegates.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_roadmap_link_gate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_ruff_verdict_lines.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_rule_import_convergence.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_run_level_failure_breaker.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_scaffold_data_in_sync.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_scaffold_doc_hygiene.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_scaffold_docs.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_scaffold_init.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_scaffold_label_provisioning.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_scaffold_manifest.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_scaffold_resources.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_scaffold_seed_sanity.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_scaffold_skew_halts.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_scaffold_upgrade.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_scaffold_wiring.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_schedule_dialect.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_seed_documents_prerun_keys.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_service_bus_dlq_adapter.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_session_env_preflight.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_severity_aliases.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_severity_backfill_apply.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_severity_backfill_bugs_only.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_severity_backfill_end_to_end.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_severity_backfill_limit.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_severity_backfill_marker.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_severity_backfill_run.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_severity_label_projection.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_severity_reconciliation.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_severity_rubric.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_shipped_templates_parse.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_skill_discovery_links.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_skills_vendored_in_sync.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_spec_work_unit_rules.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_spend_counts_cache_tokens.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_spinning_rearm_gate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_spinning_repeat_same_failing_set.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_spinout_brief_end_to_end.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_spinout_brief_replan_option.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_stop_rationale_captured.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_stranded_halt_cause.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_surefire_run_level_signature.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_surefire_verdict_lines.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_terminal_auto_close_changelog.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_terminal_flips.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_terminal_gate_message.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_terminal_judge_editing_end_to_end.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_tiered_verification_e2e.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_timeout_kill_portable.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_triage.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_triage_apply.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_triage_projects_severity_label.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_triage_reads_comments.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_triage_severity_classify.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_triage_severity_end_to_end.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_triage_severity_label_conflict.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_triage_severity_write.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_triage_skill_contract.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_triage_skips_agent_escalations.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_tsc_verdict_lines.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_umbrella_version_resolution.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_unreadable_pr_listing_holds_pr_work.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_unterminated_frontmatter.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_untracked_feature_folder.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_untracked_file_absorption.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_upgrade_gitignore_lines.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_upgrade_integration.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_validate_event.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_validate_event_driver_types.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_validate_feature_dirs_only.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_verify_empty_gate_set.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_version_consistency.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_version_skew.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_windows_gate_exec.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_worktree_restore.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_wu_execution_metadata.py +0 -0
- {specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/tests/test_wu_template_renders_lintable.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfuse-loop"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.33.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.33.0
|
{specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/concepts/monitoring-runners.md
RENAMED
|
@@ -86,11 +86,14 @@ operator) it starts 24 hours back instead, and the run summary says so.
|
|
|
86
86
|
The window applies to the checks that read event history: `error-logs` and
|
|
87
87
|
`http-5xx` filter on `ingestion_time()`, so a row stamped just before the
|
|
88
88
|
previous run but ingested after it is read by the next run rather than lost
|
|
89
|
-
between the two.
|
|
89
|
+
between the two. These check types are deliberately not bounded by it:
|
|
90
90
|
|
|
91
91
|
- `heartbeat` needs the most recent heartbeat however old it is;
|
|
92
|
+
- `backlog-stalled` reads each backlog's latest snapshot within a fixed
|
|
93
|
+
7-day lookback, since a stall is a state, not an event;
|
|
92
94
|
- `http-status-regression` compares the windows around each deployment and
|
|
93
95
|
carries its own fixed lookback;
|
|
96
|
+
- `dependency-regression` does the same for the component's dependency calls;
|
|
94
97
|
- `invariant` runs the operator's query as written. A query that reads event
|
|
95
98
|
history should bound itself (for example `| where TimeGenerated > ago(1h)`);
|
|
96
99
|
otherwise every matching row in retention is a finding, deduplicated only by
|
|
@@ -184,6 +187,7 @@ the issue was last updated (UTC), what was seen in this run (count, first, last,
|
|
|
184
187
|
| `dlq` | Message (id, sequence number, subject, enqueued time, delivery count), Dead-letter reason, or the Exception message and top stack frames when the reason is blank, Body shape (the top-level keys, never the values), Peek it |
|
|
185
188
|
| `heartbeat` | Schedule (cron, dialect, timezone, expected firing), Last run (the function's last execution and result), Host last seen, a Host silent line when the component sent no request telemetry across the expected firing, and Earliest heartbeat (the component's first heartbeat, which the grace rule measures from) |
|
|
186
189
|
| `queue-stalled` | Queue (oldest active message age, depth, `stall_after`), Where |
|
|
190
|
+
| `backlog-stalled` | Thresholds (`stall_after`, `silent_after`), Latest snapshot (time, oldest pending age, pending count), Investigate |
|
|
187
191
|
|
|
188
192
|
A section with nothing to say is left out rather than printed empty. A
|
|
189
193
|
`queue-stalled` finding cannot show when the subscription last completed a
|
{specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/concepts/monitoring-schema.md
RENAMED
|
@@ -85,16 +85,18 @@ confidence in the checks grows.
|
|
|
85
85
|
|
|
86
86
|
## Check types
|
|
87
87
|
|
|
88
|
-
Every check has a required `type`, one of
|
|
88
|
+
Every check has a required `type`, one of the neutral values in the table below. This set is
|
|
89
89
|
provider-agnostic by construction: a check type names a concept
|
|
90
90
|
(a dead-letter queue, a heartbeat), never a vendor.
|
|
91
91
|
|
|
92
92
|
| Type | Extra required fields | Meaning |
|
|
93
93
|
|---|---|---|
|
|
94
|
+
| `backlog-stalled` | `targets` | Alerts when a work queue the component keeps in its **own data store** (a transactional outbox, a job table drained by a poller) stops draining. Its rows never reach a broker, so `dlq` and `queue-stalled` cannot see it, and `heartbeat` only proves the poller fired. The component reports each backlog itself as a `BacklogSnapshot` event (the emission contract is in `.specfuse/rules/design-for-diagnosis.md`); the monitor never reads the data store. One finding per (component, backlog `name`) while the latest snapshot's oldest pending item is older than `stall_after`, failure class `stalled-backlog`; with `silent_after` set, a separate `silent-backlog` finding when no snapshot for that backlog arrived within it (the reporter died). The backlog name is the signature, so a stall that lasts many runs stays one issue. `pendingCount` appears in the finding but is never compared against a threshold: this is not volume alerting. A component that has emitted no snapshot at all in the last 7 days yields nothing, since a never-deployed reporter and a dead one look the same. |
|
|
94
95
|
| `dlq` | `harvest_mode` (`peek` or `quarantine`), `targets` | Alerts on messages landing in this component's dead-letter queue. `peek` reads without removing; `quarantine` removes after reading. `targets` is **required**: a dead-lettered message is attributed to a subscription, and a target-less `dlq` check on a multi-subscription host cannot say which one. |
|
|
95
96
|
| `error-logs` | none | Scans structured application logs for error-level entries. |
|
|
96
97
|
| `http-5xx` | none | Alerts when the rolling 5xx rate crosses a threshold. For HTTP-serving components. |
|
|
97
98
|
| `http-status-regression` | `deploy_signal`, `window_after_deploy` | Alerts when a deployment raises the rate of server-fault 4xx responses compared with the window before it. For HTTP-serving components that stamp a version on every deployment. `deploy_signal` is a mapping with a required `kind`; the only accepted `kind` is `app-version`. `window_after_deploy` is a duration (`<integer><unit>`, e.g. `30m`). `codes` is optional: a non-empty list of unique integers in 400–499, defaulting to `[403, 405, 415]`. Other codes are opt-in: `401` because a 401 wave is as often expired client tokens as a broken deployment, and `410`, `412`, `428` because few services return them. A code fires when its **share of the component's requests** in the window after the deployment is at least 3× its share in the equal window before, and it occurs at least 10 times after; both thresholds are fixed constants, not fields, and comparing shares keeps a traffic ramp from reading as a regression. Each finding states both shares and counts, the deployment's first-seen time, the code's top five operations by after-window count, up to three sample operation ids, and a Log Analytics query covering both windows. |
|
|
99
|
+
| `dependency-regression` | `deploy_signal`, `window_after_deploy` | Alerts when a deployment makes the component's calls to one of its dependencies — its own database first — start failing. For components that stamp a version on every deployment and call a data store or another dependency the telemetry records. `deploy_signal` and `window_after_deploy` are the same block `http-status-regression` takes, validated by the same rules. `dependency_types` is optional: a non-empty list of unique dependency type names as the telemetry records them (e.g. `[SQL]`, matched case-insensitively), defaulting to every type the component records. `min_failures` is optional: an integer of at least 1, defaulting to `5`, so one transient failure is not a regression. A (dependency type, target) pair fires when, in the window after the deployment, it has at least `min_failures` failed calls **and** its failure rate (failed calls ÷ all calls to that pair) is at least 3× its rate in the equal window before — the same fixed factor as `http-status-regression`. A pair with no calls or no failures before the deployment has a before-rate of 0, so a new query that fails, or a target that never failed before, fires on the floor alone; comparing rates keeps a traffic ramp from reading as a regression. One finding per (deployment, type, target); its signature is the version, type, target and the top failing result code. Each finding states both rates and counts, the deployment's first-seen time, the failing result codes, up to three sample operation ids, and a Log Analytics query covering both windows. **Failures only, by decision (#3577):** a failed call is discrete, while latency is a distribution comparison closer to metrics; a latency regression surfaces here once it becomes a timeout, which is a failed call. An opt-in `latency_ratio` remains a possible later decision, to be made explicitly rather than by precedent. |
|
|
98
100
|
| `heartbeat` | none | Alerts when the component stops reporting in at all. |
|
|
99
101
|
| `invariant` | `query`, `fingerprint_by` | Runs an operator-supplied query (opaque to this schema — not parsed or executed here) and alerts on unexpected rows. `fingerprint_by` names the field used to dedupe repeat findings into one issue. |
|
|
100
102
|
| `queue-stalled` | `targets` | Alerts when a consumer stops consuming from a subscription — no message failed (so `dlq` sees nothing), the host is still alive (so `heartbeat` sees nothing), and the symptom is a broker coordinate (queue depth / age of oldest message), not a telemetry query, so `invariant` cannot express it either. `targets` is required from birth: a wedged consumer on a multi-subscription host raises the identical "which one" question `dlq`/`heartbeat` targets already answer. |
|
|
@@ -108,6 +110,43 @@ schema (`AppRequests`, `AppExceptions`, `AppEvents`, with `AppRoleName`,
|
|
|
108
110
|
(`requests`, `exceptions`, `customEvents`, `cloud_RoleName`, `timestamp`,
|
|
109
111
|
`customDimensions`), which a workspace does not resolve.
|
|
110
112
|
|
|
113
|
+
## Scope: a component's data store is monitored through the component, never directly
|
|
114
|
+
|
|
115
|
+
The monitor watches component misbehaviour, not metrics or infrastructure.
|
|
116
|
+
A component's data store sits on that boundary; this is where the line falls
|
|
117
|
+
(#3578, also stated in `.specfuse/rules/design-for-diagnosis.md`).
|
|
118
|
+
|
|
119
|
+
**The monitor never connects to a component's data store.** There is no SQL,
|
|
120
|
+
document-store or cache provider, and none will be added: the harvester holds
|
|
121
|
+
telemetry and broker read access only. A data-store connection would mean
|
|
122
|
+
holding credentials to production (often personal) data, coupling a check to a
|
|
123
|
+
schema that a migration can silently break, and adding one provider per engine.
|
|
124
|
+
|
|
125
|
+
**Data-store signals the component owns are in scope, and the component emits
|
|
126
|
+
them as telemetry:**
|
|
127
|
+
|
|
128
|
+
| Signal | Read by |
|
|
129
|
+
|---|---|
|
|
130
|
+
| Backlogs in data-store tables (rows waiting past their expected processing time) | the planned `backlog-stalled` check type (#3576) |
|
|
131
|
+
| Dependency failures that start after a deployment, including data-store calls | the planned `dependency-regression` check type (#3577) |
|
|
132
|
+
| Business-state invariants over persisted data: orphaned rows, records stuck in a state past their deadline, migration drift (the release expects migration N, the store reports M) | the existing `invariant` check |
|
|
133
|
+
|
|
134
|
+
The component computes each invariant itself and emits the result; `invariant`
|
|
135
|
+
queries that telemetry, never the store. This is `invariant`'s sanctioned
|
|
136
|
+
business-state use, not the rate-shaped use FEAT-2026-0069 ruled out.
|
|
137
|
+
|
|
138
|
+
**Data-store platform health is out of scope:** CPU and throughput-unit
|
|
139
|
+
consumption, storage, connections, deadlock rate, replication lag, failover. The
|
|
140
|
+
fix for these lives in infrastructure, not the component's repository, so a
|
|
141
|
+
finding has nowhere to route and autofix cannot act on it. They belong to the
|
|
142
|
+
platform's own infrastructure alerting, owned by whoever owns the
|
|
143
|
+
infrastructure.
|
|
144
|
+
|
|
145
|
+
**The test for any future request:** would the fix be a pull request on the
|
|
146
|
+
component? If yes, it is in scope and the component emits the signal. If the
|
|
147
|
+
fix is a capacity or infrastructure change, it is out of scope. `monitor-lint`
|
|
148
|
+
needs no rule for this: a data-store provider does not exist to be configured.
|
|
149
|
+
|
|
111
150
|
## Check targets
|
|
112
151
|
|
|
113
152
|
`component` and check `targets` are two different axes, and the schema keeps
|
|
@@ -146,11 +185,13 @@ triggers as `targets[]` is what keeps that property satisfiable.
|
|
|
146
185
|
|---|---|---|---|
|
|
147
186
|
| `dlq` | **required** | `subscription` (required), `function` (required) | `subscription` is what the harvester queries; `function` is what a human diagnoses by — a subscription name alone rarely tells an on-call engineer which handler failed. |
|
|
148
187
|
| `queue-stalled` | **required** | `subscription` (required), `function` (required), `stall_after` (optional, `<integer><unit>` with unit `s`/`m`/`h`/`d`, e.g. `15m`) | Same `subscription`/`function` coordinates as `dlq` — same subscription, same on-call-facing handler name. `stall_after` (how long is too long since the last message) must match `<integer><unit>` with unit `s`, `m`, `h` or `d` (e.g. `15m`, `90s`, `2h`, `1d`); the linter rejects any other value — the harvester would refuse it on its first poll. A target with no `stall_after` is skipped and recorded at runtime. Only the grammar is checked, not a range. |
|
|
188
|
+
| `backlog-stalled` | **required** | `name` (required), `stall_after` (required), `silent_after` (optional) | `name` is the `backlogName` the component emits. Both durations use the `<integer><unit>` grammar (unit `s`/`m`/`h`/`d`, e.g. `15m`); the linter rejects any other value. Only the grammar is checked, not a range. |
|
|
149
189
|
| `heartbeat` | optional | `name` (required), `cron` (optional), `dialect` (required when `cron` present), `timezone` (optional) | One target per schedule, so a single silent timer among several stays individually visible. A single-schedule component may omit the list. |
|
|
150
190
|
| `invariant` | **forbidden** | — | `fingerprint_by` is already this check type's enumeration key; permitting `targets` too would give it two competing enumeration keys. |
|
|
151
191
|
| `error-logs` | **forbidden** | — | Role-name keyed and genuinely component-scoped; the validator rejects `targets` here. |
|
|
152
192
|
| `http-5xx` | **forbidden** | — | Same reason as `error-logs`. |
|
|
153
193
|
| `http-status-regression` | **forbidden** | — | Per-component, like `http-5xx`: the deployment it compares across is a component-wide event. |
|
|
194
|
+
| `dependency-regression` | **forbidden** | — | Same reason as `http-status-regression`; the (dependency type, target) pair a finding names comes from the telemetry, not from configuration. |
|
|
154
195
|
|
|
155
196
|
Where `targets` is present it must be a non-empty list of mappings, and every
|
|
156
197
|
entry must carry that check type's required coordinates. An empty list is a
|
{specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/docs/concepts/monitoring-to-fix.md
RENAMED
|
@@ -38,7 +38,8 @@ the shipped GitHub Actions template — see
|
|
|
38
38
|
itself.
|
|
39
39
|
|
|
40
40
|
**What it reads.** `.specfuse/monitoring.yml`: each component's checks
|
|
41
|
-
(`error-logs`, `http-5xx`, `http-status-regression`, `dlq`, `queue-stalled`,
|
|
41
|
+
(`error-logs`, `http-5xx`, `http-status-regression`, `dependency-regression`, `dlq`, `queue-stalled`,
|
|
42
|
+
`backlog-stalled`,
|
|
42
43
|
`heartbeat`, `invariant`), each environment's telemetry and broker bindings.
|
|
43
44
|
Event-history checks read only what arrived since the previous run (the
|
|
44
45
|
watermark in `.specfuse/monitor-watermarks/<env>.json`, or the last 24 hours on
|
|
@@ -289,7 +289,7 @@ const FLOWS = [
|
|
|
289
289
|
entry: "a failure in a deployed component", who: "specfuse monitor run, then specfuse-agent or you", ceremony: "one issue per fingerprint",
|
|
290
290
|
summary: "Work that starts in production. The monitor files one issue per distinct failure; two per-component dials in monitoring.yml decide how far it goes without you. Findings skip triage and the bug lane on purpose: their route to a fix is diagnose, then autofix.",
|
|
291
291
|
stages: [
|
|
292
|
-
{ k: "auto", t: "Detect", c: "specfuse monitor run", d: "Runs each component's checks against its telemetry and broker: error-logs, http-5xx, http-status-regression, dlq, queue-stalled, heartbeat, invariant. Reads only what arrived since the last run (the watermark), or the last 24 hours on a first run. One failing check is reported and the rest still run.", rows: { Reads: ".specfuse/monitoring.yml, App Insights, Service Bus", Scheduled: "by you: cron, CI, or later the in-cluster runner" } },
|
|
292
|
+
{ k: "auto", t: "Detect", c: "specfuse monitor run", d: "Runs each component's checks against its telemetry and broker: error-logs, http-5xx, http-status-regression, dependency-regression, dlq, queue-stalled, backlog-stalled, heartbeat, invariant. Reads only what arrived since the last run (the watermark), or the last 24 hours on a first run. One failing check is reported and the rest still run.", rows: { Reads: ".specfuse/monitoring.yml, App Insights, Service Bus", Scheduled: "by you: cron, CI, or later the in-cluster runner" } },
|
|
293
293
|
{ k: "auto", t: "File", c: "one issue per fingerprint", d: "The fingerprint is component + check + normalised failure. New fingerprint: a new monitoring-finding issue. Same fingerprint, open issue: the occurrence count goes up (at most every 6 hours). The monitor reopens a closed finding but never closes one; a finding unseen for 7 days gets a one-time close-candidate comment. A schedule is not called silent before its component's first heartbeat.", rows: { "Closed issue": "reopened with a comment when it closed within 90 days", "dlq issue": "closing it acknowledges the message; only a new message id reopens it" } },
|
|
294
294
|
{ k: "human", t: "Diagnose", c: "diagnose: manual | auto", d: "manual: you run /diagnose-issue N. auto: specfuse-agent diagnoses each open, undiagnosed finding in a headless session. Either way one comment is posted: root cause, evidence, candidate fix, confidence (0 to 1), fix_scope (small, large, external) and severity (critical, high, medium, low, judged from the evidence); the finding gets one severity label. One diagnosis per issue, except a fresh one after the monitor reopens it following a fix, or when someone adds the rediagnose label.", stops: ["diagnose: manual — every finding waits for you", "diagnose: auto only acts when specfuse-agent is scheduled too"] },
|
|
295
295
|
{ k: "auto", t: "Decide", c: "autofix: \"off\" | \"on\"", d: "Fixed rules, not configuration: autofix off → decline. fix_scope large or external → route to you. confidence below 0.8 → route to you. Already attempted for this fingerprint → decline. Daily cap reached → decline. Otherwise → fire. Severity changes none of these rules: it only orders the queue, most severe first.", stops: ["Routed to a human: an escalation comment says which rule fired"] },
|
|
@@ -90,6 +90,19 @@ components:
|
|
|
90
90
|
# codes defaults to [403, 405, 415]; this one also opts in to 401.
|
|
91
91
|
codes: [401, 403, 405, 415]
|
|
92
92
|
|
|
93
|
+
# dependency-regression: alerts when a deployment makes calls to one of
|
|
94
|
+
# the component's dependencies (its database first) start failing — a
|
|
95
|
+
# migration that did not run, a renamed column, a broken connection
|
|
96
|
+
# string. Same deploy_signal block as http-status-regression. Failures
|
|
97
|
+
# only: a slow query is seen once it times out. A (type, target) pair
|
|
98
|
+
# fires at min_failures failed calls and 3x its pre-deploy failure rate.
|
|
99
|
+
- type: dependency-regression
|
|
100
|
+
deploy_signal:
|
|
101
|
+
kind: app-version
|
|
102
|
+
window_after_deploy: 1h
|
|
103
|
+
dependency_types: [SQL] # default: every type the component records
|
|
104
|
+
min_failures: 5 # default 5
|
|
105
|
+
|
|
93
106
|
# heartbeat: alerts when the component stops reporting in at all —
|
|
94
107
|
# the check that catches "it didn't error, it just went silent."
|
|
95
108
|
- type: heartbeat
|
|
@@ -123,6 +136,20 @@ components:
|
|
|
123
136
|
query: "select count(*) from orders where fulfilled_at is null and created_at < now() - interval '1 day'"
|
|
124
137
|
fingerprint_by: order_id
|
|
125
138
|
|
|
139
|
+
# backlog-stalled: catches a work queue this component keeps in its OWN
|
|
140
|
+
# data store (a transactional outbox, a job table) that has stopped
|
|
141
|
+
# draining. The rows never reach the broker, so `dlq`/`queue-stalled`
|
|
142
|
+
# see nothing. The component reports each backlog itself as a
|
|
143
|
+
# `BacklogSnapshot` event (see .specfuse/rules/design-for-diagnosis.md);
|
|
144
|
+
# `name` is that event's `backlogName`. `stall_after`: the oldest pending
|
|
145
|
+
# item older than this is a finding. `silent_after` (optional): no
|
|
146
|
+
# snapshot at all for this long is a finding — the reporter died.
|
|
147
|
+
- type: backlog-stalled
|
|
148
|
+
targets:
|
|
149
|
+
- name: acme-event-outbox
|
|
150
|
+
stall_after: 15m
|
|
151
|
+
silent_after: 30m
|
|
152
|
+
|
|
126
153
|
- type: heartbeat
|
|
127
154
|
|
|
128
155
|
# acme-functions-host: the motivating shape this feature exists for. One
|
{specfuse_loop-0.32.0 → specfuse_loop-0.33.0}/specfuse/loop/data/rules/design-for-diagnosis.md
RENAMED
|
@@ -7,7 +7,7 @@ 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`,
|
|
10
|
+
`monitoring.yml` admits (`backlog-stalled`, `dependency-regression`, `dlq`, `error-logs`, `http-5xx`,
|
|
11
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.
|
|
@@ -20,6 +20,43 @@ implementations. How a project achieves a property (which library, which
|
|
|
20
20
|
platform's native tracing, which log shipper) belongs in that project's
|
|
21
21
|
`.specfuse/rules-local/`, never here.
|
|
22
22
|
|
|
23
|
+
## A component's data store is monitored through the component, never directly
|
|
24
|
+
|
|
25
|
+
The monitor watches component misbehaviour, not metrics or infrastructure.
|
|
26
|
+
A component's data store sits on that boundary, so the line is drawn here once
|
|
27
|
+
rather than differently in each project.
|
|
28
|
+
|
|
29
|
+
**The monitor never connects to a component's data store.** There is no
|
|
30
|
+
data-store provider for any engine; the harvester holds telemetry and broker
|
|
31
|
+
read access only. Three reasons: credentials (a data store holds production
|
|
32
|
+
data, often personal data), schema coupling (a migration silently breaks a check
|
|
33
|
+
that reads tables directly), and provider sprawl (one adapter per engine).
|
|
34
|
+
|
|
35
|
+
**Data-store signals the component owns are in scope, and the component emits
|
|
36
|
+
them** as telemetry, the same way it emits everything else this rule covers:
|
|
37
|
+
|
|
38
|
+
- backlogs in data-store tables — rows waiting past their expected processing
|
|
39
|
+
time — for the planned `backlog-stalled` check type (#3576);
|
|
40
|
+
- dependency failures that start after a deployment, including calls to the
|
|
41
|
+
data store, for the planned `dependency-regression` check type (#3577);
|
|
42
|
+
- business-state invariants over persisted data — orphaned rows, records stuck
|
|
43
|
+
in a state past their deadline, or migration drift (the running release
|
|
44
|
+
expects migration N while the store reports M). The component computes these
|
|
45
|
+
itself and emits the result; the existing `invariant` check reads that
|
|
46
|
+
telemetry. This is `invariant`'s sanctioned business-state use; a
|
|
47
|
+
rate-shaped signal is not an invariant and belongs to its own check type.
|
|
48
|
+
|
|
49
|
+
**Data-store platform health is out of scope:** CPU and throughput-unit
|
|
50
|
+
consumption, storage, connections, deadlock rate, replication lag, failover.
|
|
51
|
+
The fix for these lives in infrastructure, not in the component's repository,
|
|
52
|
+
so a finding has nowhere to route and an automated fix cannot act on it. They
|
|
53
|
+
belong to the platform's own infrastructure alerting, owned by whoever owns the
|
|
54
|
+
infrastructure.
|
|
55
|
+
|
|
56
|
+
**The test for any future request:** would the fix be a pull request on the
|
|
57
|
+
component? If yes, the signal is in scope and the component emits it. If the fix
|
|
58
|
+
is a capacity or infrastructure change, it is out of scope.
|
|
59
|
+
|
|
23
60
|
## Correlation IDs propagate across component boundaries
|
|
24
61
|
|
|
25
62
|
A single request or message carries one correlation ID from the moment it enters
|
|
@@ -94,6 +131,37 @@ indistinguishable from one whose schedule has stopped — the check reports ever
|
|
|
94
131
|
target as silent from its first run, and the real silence, when it comes, is
|
|
95
132
|
lost in that noise.
|
|
96
133
|
|
|
134
|
+
## Data-store work queues report a backlog snapshot
|
|
135
|
+
|
|
136
|
+
A component that keeps a work queue in its own data store — a transactional
|
|
137
|
+
outbox table, a job table, a pending-work table drained by a poller loop —
|
|
138
|
+
periodically reports the state of each such backlog, in exactly this shape: one
|
|
139
|
+
custom event named `BacklogSnapshot` per backlog it owns, emitted under the
|
|
140
|
+
component's role name, carrying:
|
|
141
|
+
|
|
142
|
+
- `backlogName` — a stable identifier for the backlog (for example
|
|
143
|
+
`event-outbox`), the same string as the `name` of the matching
|
|
144
|
+
`backlog-stalled` target in `monitoring.yml`;
|
|
145
|
+
- `oldestPendingAgeSeconds` — the age in seconds of the oldest unprocessed
|
|
146
|
+
item, `0` when the backlog is empty;
|
|
147
|
+
- `pendingCount` — optional, the number of unprocessed items.
|
|
148
|
+
|
|
149
|
+
Emit on a fixed interval from the component's own process, independently of
|
|
150
|
+
whether the drainer succeeds, so the snapshot keeps arriving while the drainer
|
|
151
|
+
is wedged.
|
|
152
|
+
|
|
153
|
+
A broker-level check cannot see this queue: rows still in an outbox never reach
|
|
154
|
+
the broker, so the broker looks quiet and healthy, and a schedule that fires on
|
|
155
|
+
time proves nothing about whether the rows it should drain are moving. A
|
|
156
|
+
`backlog-stalled` check reads the latest snapshot per backlog and reports when
|
|
157
|
+
its oldest pending item is older than the target's `stall_after`, and, when the
|
|
158
|
+
target sets `silent_after`, when no snapshot for that backlog arrived within it
|
|
159
|
+
— the reporter itself has stopped. `pendingCount` only enriches the finding; it
|
|
160
|
+
is never compared against a threshold, because this is a "work is waiting and
|
|
161
|
+
not moving" signal, not volume alerting. The component reports its own backlog
|
|
162
|
+
so the monitor never needs credentials to, or knowledge of the schema of, the
|
|
163
|
+
data store itself.
|
|
164
|
+
|
|
97
165
|
## DLQ entries carry failure context, not just the payload
|
|
98
166
|
|
|
99
167
|
A message that lands in a dead-letter queue carries, alongside its original
|
|
@@ -122,3 +190,15 @@ that stamps no version has no deployment signal at all; one that stamps the same
|
|
|
122
190
|
value every release shows no new version to detect, so the check never opens its
|
|
123
191
|
post-deployment window and a regression ships unseen. Either way the check
|
|
124
192
|
cannot be evaluated, and discovery reports the gap rather than proposing it.
|
|
193
|
+
|
|
194
|
+
A `dependency-regression` check reads the same version stamp from the
|
|
195
|
+
component's outbound dependency calls — its database first — so the property
|
|
196
|
+
applies to any component that calls a data store, HTTP-serving or not. Each
|
|
197
|
+
call must be recorded with its dependency type, its target and a success flag
|
|
198
|
+
(and the result code where the client has one); a component that catches a
|
|
199
|
+
failed query and degrades still records that call as failed, which is exactly
|
|
200
|
+
what the check looks for. The check is failures only, by decision (#3577): a
|
|
201
|
+
failed call is discrete, while latency is a distribution comparison closer to
|
|
202
|
+
metrics. A query that slows down after a deployment surfaces once it times out,
|
|
203
|
+
because a timeout is a failed call. An opt-in latency ratio stays a possible
|
|
204
|
+
later decision, to be made explicitly rather than by precedent.
|
|
@@ -46,6 +46,8 @@ DIAGNOSE_VALUES = frozenset({"manual", "auto"})
|
|
|
46
46
|
AUTOFIX_VALUES = frozenset({"off", "on"})
|
|
47
47
|
CHECK_TYPES = frozenset(
|
|
48
48
|
{
|
|
49
|
+
"backlog-stalled",
|
|
50
|
+
"dependency-regression",
|
|
49
51
|
"dlq",
|
|
50
52
|
"error-logs",
|
|
51
53
|
"http-5xx",
|
|
@@ -262,6 +264,15 @@ def _check_checks(checks: object, component_label: str) -> list[str]:
|
|
|
262
264
|
)
|
|
263
265
|
elif check_type == "http-status-regression":
|
|
264
266
|
findings.extend(_check_http_status_regression(check, component_label, index))
|
|
267
|
+
elif check_type == "backlog-stalled":
|
|
268
|
+
if check.get("targets") is None:
|
|
269
|
+
findings.append(
|
|
270
|
+
f"component '{component_label}': checks[{index}]: "
|
|
271
|
+
f"'backlog-stalled' check requires 'targets' — each target "
|
|
272
|
+
f"needs 'name' and 'stall_after'"
|
|
273
|
+
)
|
|
274
|
+
elif check_type == "dependency-regression":
|
|
275
|
+
findings.extend(_check_dependency_regression(check, component_label, index))
|
|
265
276
|
elif check_type == "invariant":
|
|
266
277
|
for field in ("query", "fingerprint_by"):
|
|
267
278
|
if not check.get(field):
|
|
@@ -282,6 +293,7 @@ def _check_checks(checks: object, component_label: str) -> list[str]:
|
|
|
282
293
|
# they are role-name keyed and therefore genuinely component-scoped; see
|
|
283
294
|
# `_TARGETLESS_CHECK_TYPES` below.
|
|
284
295
|
_TARGET_REQUIRED_FIELDS = {
|
|
296
|
+
"backlog-stalled": ("name", "stall_after"),
|
|
285
297
|
"dlq": ("subscription", "function"),
|
|
286
298
|
"heartbeat": ("name",),
|
|
287
299
|
"queue-stalled": ("subscription", "function"),
|
|
@@ -289,13 +301,10 @@ _TARGET_REQUIRED_FIELDS = {
|
|
|
289
301
|
DEPLOY_SIGNAL_KINDS = frozenset({"app-version"})
|
|
290
302
|
|
|
291
303
|
|
|
292
|
-
def
|
|
293
|
-
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
rejected by `_check_targets`)."""
|
|
297
|
-
where = f"component '{component_label}': checks[{check_index}]"
|
|
298
|
-
prefix = f"{where}: 'http-status-regression' check"
|
|
304
|
+
def _check_deploy_window(check: dict, prefix: str) -> list[str]:
|
|
305
|
+
"""The deploy-correlated block every deployment-windowed check type shares
|
|
306
|
+
(`http-status-regression`, `dependency-regression`): a `deploy_signal`
|
|
307
|
+
mapping with a known `kind`, and a `window_after_deploy` duration."""
|
|
299
308
|
findings: list[str] = []
|
|
300
309
|
|
|
301
310
|
signal = check.get("deploy_signal")
|
|
@@ -320,6 +329,17 @@ def _check_http_status_regression(
|
|
|
320
329
|
f"{prefix} 'window_after_deploy' value {window!r} is not a "
|
|
321
330
|
f"valid duration — expected {DURATION_GRAMMAR}"
|
|
322
331
|
)
|
|
332
|
+
return findings
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _check_http_status_regression(
|
|
336
|
+
check: dict, component_label: str, check_index: int
|
|
337
|
+
) -> list[str]:
|
|
338
|
+
"""Field rules for one `http-status-regression` check (`targets` is
|
|
339
|
+
rejected by `_check_targets`)."""
|
|
340
|
+
where = f"component '{component_label}': checks[{check_index}]"
|
|
341
|
+
prefix = f"{where}: 'http-status-regression' check"
|
|
342
|
+
findings = _check_deploy_window(check, prefix)
|
|
323
343
|
|
|
324
344
|
codes = check.get("codes")
|
|
325
345
|
if codes is not None:
|
|
@@ -339,9 +359,47 @@ def _check_http_status_regression(
|
|
|
339
359
|
return findings
|
|
340
360
|
|
|
341
361
|
|
|
362
|
+
def _check_dependency_regression(
|
|
363
|
+
check: dict, component_label: str, check_index: int
|
|
364
|
+
) -> list[str]:
|
|
365
|
+
"""Field rules for one `dependency-regression` check (#3577): the shared
|
|
366
|
+
deploy block, an optional `dependency_types` list (default: every type
|
|
367
|
+
the component records) and an optional `min_failures` floor (default 5).
|
|
368
|
+
`targets` is rejected by `_check_targets`."""
|
|
369
|
+
where = f"component '{component_label}': checks[{check_index}]"
|
|
370
|
+
prefix = f"{where}: 'dependency-regression' check"
|
|
371
|
+
findings = _check_deploy_window(check, prefix)
|
|
372
|
+
|
|
373
|
+
types = check.get("dependency_types")
|
|
374
|
+
if types is not None:
|
|
375
|
+
if (
|
|
376
|
+
not isinstance(types, list)
|
|
377
|
+
or not types
|
|
378
|
+
or not all(isinstance(t, str) and t.strip() for t in types)
|
|
379
|
+
):
|
|
380
|
+
findings.append(
|
|
381
|
+
f"{prefix} 'dependency_types' must be a non-empty list of "
|
|
382
|
+
f"dependency type names (e.g. [SQL]) — omit it for every type"
|
|
383
|
+
)
|
|
384
|
+
elif len({t.lower() for t in types}) != len(types):
|
|
385
|
+
findings.append(f"{prefix} 'dependency_types' must not contain duplicates")
|
|
386
|
+
|
|
387
|
+
min_failures = check.get("min_failures")
|
|
388
|
+
if min_failures is not None and (
|
|
389
|
+
not isinstance(min_failures, int)
|
|
390
|
+
or isinstance(min_failures, bool)
|
|
391
|
+
or min_failures < 1
|
|
392
|
+
):
|
|
393
|
+
findings.append(
|
|
394
|
+
f"{prefix} 'min_failures' must be an integer of at least 1, "
|
|
395
|
+
f"got {min_failures!r}"
|
|
396
|
+
)
|
|
397
|
+
return findings
|
|
398
|
+
|
|
399
|
+
|
|
342
400
|
# Check types that must never carry `targets` at all.
|
|
343
401
|
_TARGETLESS_CHECK_TYPES = frozenset(
|
|
344
|
-
{"error-logs", "http-5xx", "http-status-regression", "invariant"}
|
|
402
|
+
{"dependency-regression", "error-logs", "http-5xx", "http-status-regression", "invariant"}
|
|
345
403
|
)
|
|
346
404
|
|
|
347
405
|
|
|
@@ -360,7 +418,8 @@ def _check_targets(
|
|
|
360
418
|
check type is required to interpret. `cron`'s field *values* stay
|
|
361
419
|
opaque; only the field *count* is checked. `timezone` and
|
|
362
420
|
`invariant.query` are untouched by this and remain fully opaque.
|
|
363
|
-
`targets` itself is required for `dlq
|
|
421
|
+
`targets` itself is required for `dlq`, `queue-stalled` and
|
|
422
|
+
`backlog-stalled` (enforced in
|
|
364
423
|
`_check_checks`, since a missing key and an empty/malformed one are
|
|
365
424
|
different findings). `error-logs`, `http-5xx`, and `invariant` must not
|
|
366
425
|
carry `targets` at all; every other check type treats `targets` as
|
|
@@ -405,6 +464,28 @@ def _check_targets(
|
|
|
405
464
|
f"'stall_after' value {target['stall_after']!r} is not a "
|
|
406
465
|
f"valid duration — expected {DURATION_GRAMMAR}"
|
|
407
466
|
)
|
|
467
|
+
if check_type == "backlog-stalled":
|
|
468
|
+
findings.extend(_check_backlog_durations(target, where, t_index))
|
|
469
|
+
return findings
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _check_backlog_durations(target: dict, where: str, t_index: int) -> list[str]:
|
|
473
|
+
"""`stall_after` and the optional `silent_after` of one `backlog-stalled`
|
|
474
|
+
target must parse with the shared duration grammar (#3576). A missing
|
|
475
|
+
`stall_after` is already reported by the required-fields pass."""
|
|
476
|
+
findings: list[str] = []
|
|
477
|
+
for field in ("stall_after", "silent_after"):
|
|
478
|
+
value = target.get(field)
|
|
479
|
+
if value is None or (field == "stall_after" and not value):
|
|
480
|
+
continue
|
|
481
|
+
try:
|
|
482
|
+
parse_duration_seconds(str(value), field)
|
|
483
|
+
except ValueError:
|
|
484
|
+
findings.append(
|
|
485
|
+
f"{where}: targets[{t_index}]: 'backlog-stalled' target "
|
|
486
|
+
f"'{field}' value {value!r} is not a valid duration — "
|
|
487
|
+
f"expected {DURATION_GRAMMAR}"
|
|
488
|
+
)
|
|
408
489
|
return findings
|
|
409
490
|
|
|
410
491
|
|
|
@@ -135,7 +135,7 @@ SPECFUSE_DIR = Path(".specfuse")
|
|
|
135
135
|
REPO_ROOT = SPECFUSE_DIR.parent
|
|
136
136
|
FEATURES_DIR = SPECFUSE_DIR / "features"
|
|
137
137
|
VERIFICATION_PATH = SPECFUSE_DIR / "verification.yml"
|
|
138
|
-
DRIVER_VERSION = "0.
|
|
138
|
+
DRIVER_VERSION = "0.33.0"
|
|
139
139
|
# Oldest scaffold layout this driver can drive. init.sh stamps the scaffold's own
|
|
140
140
|
# version into `.specfuse/VERSION`; check_scaffold_version() fails loud at startup if
|
|
141
141
|
# the consumer's scaffold is older than this, pointing at `specfuse upgrade`. Bump
|
|
@@ -24,6 +24,7 @@ from typing import Mapping, Optional
|
|
|
24
24
|
# requirements without importing from lint_monitoring.py — that module is
|
|
25
25
|
# gate 2's, and this WU only reads its shape (see the WU's "do not touch").
|
|
26
26
|
_TARGET_COORDINATE_FIELDS: Mapping[str, tuple[str, ...]] = {
|
|
27
|
+
"backlog-stalled": ("name",),
|
|
27
28
|
"dlq": ("subscription", "function"),
|
|
28
29
|
"heartbeat": ("name",),
|
|
29
30
|
"queue-stalled": ("subscription", "function"),
|
|
@@ -32,7 +33,7 @@ _TARGET_COORDINATE_FIELDS: Mapping[str, tuple[str, ...]] = {
|
|
|
32
33
|
# Check types that must never carry target coordinates — 0069 rejected
|
|
33
34
|
# `targets` on these, so `fingerprint_by` stays their single enumeration key.
|
|
34
35
|
_TARGETLESS_CHECK_TYPES = frozenset(
|
|
35
|
-
{"error-logs", "http-5xx", "http-status-regression", "invariant"}
|
|
36
|
+
{"dependency-regression", "error-logs", "http-5xx", "http-status-regression", "invariant"}
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
|
|
@@ -57,7 +57,7 @@ import time
|
|
|
57
57
|
from dataclasses import dataclass
|
|
58
58
|
from typing import Callable, Optional
|
|
59
59
|
|
|
60
|
-
from specfuse.monitor.issues import DEFAULT_LIST_LIMIT, FINDING_LABEL, find_finding_issue
|
|
60
|
+
from specfuse.monitor.issues import DEFAULT_LIST_LIMIT, FINDING_LABEL, FINDING_LIST_LIMIT, find_finding_issue
|
|
61
61
|
|
|
62
62
|
# Reserved for FEAT-2026-0042 gate 2: applied to a finding issue once a fired
|
|
63
63
|
# autofix attempt is known to have failed. See module docstring.
|
|
@@ -94,7 +94,7 @@ def has_prior_attempt(runner: Callable, repo: str, fingerprint: str) -> bool:
|
|
|
94
94
|
(the runner raises, the listing is truncated) fails closed: True.
|
|
95
95
|
"""
|
|
96
96
|
try:
|
|
97
|
-
issue = find_finding_issue(runner, repo, fingerprint, limit=
|
|
97
|
+
issue = find_finding_issue(runner, repo, fingerprint, limit=FINDING_LIST_LIMIT)
|
|
98
98
|
except Exception: # noqa: BLE001 - fail closed on any read failure
|
|
99
99
|
return True
|
|
100
100
|
if issue is None:
|
|
@@ -119,7 +119,7 @@ def record_attempt(
|
|
|
119
119
|
under a different convention.
|
|
120
120
|
"""
|
|
121
121
|
now = time.time() if now is None else now
|
|
122
|
-
issue = find_finding_issue(runner, repo, fingerprint, limit=
|
|
122
|
+
issue = find_finding_issue(runner, repo, fingerprint, limit=FINDING_LIST_LIMIT)
|
|
123
123
|
if issue is None:
|
|
124
124
|
raise LookupError(f"no finding issue carries fingerprint {fingerprint!r}")
|
|
125
125
|
|
|
@@ -265,6 +265,8 @@ def _build_adapter(
|
|
|
265
265
|
deploy_signal=check.get("deploy_signal"),
|
|
266
266
|
window_after_deploy=check.get("window_after_deploy"),
|
|
267
267
|
codes=check.get("codes"),
|
|
268
|
+
dependency_types=check.get("dependency_types"),
|
|
269
|
+
min_failures=check.get("min_failures"),
|
|
268
270
|
# The harvest window (#3480): adapters that read event history
|
|
269
271
|
# accept it; those that need older history by design do not.
|
|
270
272
|
since=since,
|
|
@@ -678,11 +680,24 @@ def run_cycle(
|
|
|
678
680
|
continue
|
|
679
681
|
filed_fingerprints.add(fingerprint)
|
|
680
682
|
recorder = _CallRecorder(gh_runner)
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
683
|
+
# #3579: a filing error (a truncated listing, a gh failure)
|
|
684
|
+
# is isolated like a check error (#3478): reported, the
|
|
685
|
+
# watermark held so the window is retried, other
|
|
686
|
+
# components still filed.
|
|
687
|
+
try:
|
|
688
|
+
record_finding(
|
|
689
|
+
fingerprint, artifact, repo, runner=recorder, now=now.timestamp(),
|
|
690
|
+
message_ids=tuple(message_ids.get(fingerprint, ())),
|
|
691
|
+
seen=seen_by_fingerprint.get(fingerprint), ensure_label=ensure_label,
|
|
692
|
+
)
|
|
693
|
+
except Exception as exc: # noqa: BLE001 — isolation is the point
|
|
694
|
+
env_errored = True
|
|
695
|
+
errored_components.add(component_name)
|
|
696
|
+
summary.append(
|
|
697
|
+
f"{env_name}/{component_name}: filing errored — "
|
|
698
|
+
f"{type(exc).__name__}: {exc}"
|
|
699
|
+
)
|
|
700
|
+
continue
|
|
686
701
|
outcome = _classify(recorder)
|
|
687
702
|
if outcome == "created":
|
|
688
703
|
created += 1
|
|
@@ -719,7 +734,7 @@ def run_cycle(
|
|
|
719
734
|
if env_errored:
|
|
720
735
|
any_errored = True
|
|
721
736
|
summary.append(
|
|
722
|
-
f"environment {env_name}: watermark not advanced — a check errored; "
|
|
737
|
+
f"environment {env_name}: watermark not advanced — a check or filing errored; "
|
|
723
738
|
"its window is retried next cycle"
|
|
724
739
|
)
|
|
725
740
|
elif not dry_run:
|
|
@@ -70,6 +70,12 @@ REOPENED_MARKER = "<!-- specfuse:finding-reopened -->"
|
|
|
70
70
|
REDIAGNOSE_LABEL = "rediagnose"
|
|
71
71
|
|
|
72
72
|
DEFAULT_LIST_LIMIT = 100
|
|
73
|
+
# The finding lookups' own limit, apart from DEFAULT_LIST_LIMIT (which triage
|
|
74
|
+
# borrows as a page size). `gh issue list` pages internally and stops when the
|
|
75
|
+
# repo runs out, so a high limit costs nothing on a small repo. 100 was low
|
|
76
|
+
# enough that a busy project crossed it and every new fingerprint raised
|
|
77
|
+
# TruncatedListingError (#3579); a full page at this limit still raises.
|
|
78
|
+
FINDING_LIST_LIMIT = 2000
|
|
73
79
|
DEFAULT_THROTTLE_SECONDS = 6 * 60 * 60
|
|
74
80
|
# A finding with no sighting for this long is annotated as a close candidate
|
|
75
81
|
# (#3502). Elapsed time, not a run count: the meta records when a fingerprint
|
|
@@ -330,7 +336,7 @@ def find_finding_issue(
|
|
|
330
336
|
repo: str,
|
|
331
337
|
fingerprint: str,
|
|
332
338
|
*,
|
|
333
|
-
limit: int =
|
|
339
|
+
limit: int = FINDING_LIST_LIMIT,
|
|
334
340
|
now: Optional[float] = None,
|
|
335
341
|
) -> Optional[dict]:
|
|
336
342
|
"""Return the listing row carrying `fingerprint`'s marker, or None.
|
|
@@ -376,7 +382,7 @@ def record_finding(
|
|
|
376
382
|
*,
|
|
377
383
|
runner: Optional[Callable] = None,
|
|
378
384
|
now: Optional[float] = None,
|
|
379
|
-
limit: int =
|
|
385
|
+
limit: int = FINDING_LIST_LIMIT,
|
|
380
386
|
throttle_seconds: float = DEFAULT_THROTTLE_SECONDS,
|
|
381
387
|
message_ids: Sequence[str] = (),
|
|
382
388
|
seen: Optional[SeenThisRun] = None,
|
|
@@ -501,7 +507,7 @@ def finding_component(body: str) -> Optional[str]:
|
|
|
501
507
|
return match.group(1) if match else None
|
|
502
508
|
|
|
503
509
|
|
|
504
|
-
def list_finding_issues(runner: Callable, repo: str, *, now: float, limit: int =
|
|
510
|
+
def list_finding_issues(runner: Callable, repo: str, *, now: float, limit: int = FINDING_LIST_LIMIT) -> list:
|
|
505
511
|
"""Open finding issues plus those closed inside the lookup window, each
|
|
506
512
|
carrying its `state` — the same single listing `find_finding_issue` reads."""
|
|
507
513
|
return _list_findings(runner, repo, limit=limit, now=now)
|
|
@@ -523,7 +529,7 @@ def annotate_if_quiet(
|
|
|
523
529
|
now: float,
|
|
524
530
|
quiet_after_seconds: float = DEFAULT_QUIET_AFTER_SECONDS,
|
|
525
531
|
runner: Optional[Callable] = None,
|
|
526
|
-
limit: int =
|
|
532
|
+
limit: int = FINDING_LIST_LIMIT,
|
|
527
533
|
existing: Optional[dict] = None,
|
|
528
534
|
) -> Optional[str]:
|
|
529
535
|
"""Annotate an open finding issue as a close candidate once its fingerprint
|