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