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