specfuse-loop 0.3.9__tar.gz → 0.3.10__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.3.9/specfuse_loop.egg-info → specfuse_loop-0.3.10}/PKG-INFO +1 -1
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/pyproject.toml +1 -1
- specfuse_loop-0.3.10/specfuse/loop/data/VERSION +1 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/loop.py +43 -1
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10/specfuse_loop.egg-info}/PKG-INFO +1 -1
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse_loop.egg-info/SOURCES.txt +1 -0
- specfuse_loop-0.3.10/tests/test_degraded_oracle.py +90 -0
- specfuse_loop-0.3.9/specfuse/loop/data/VERSION +0 -1
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/LICENSE +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/NOTICE +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/README.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/setup.cfg +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/LEARNINGS.template.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/docs/getting-started.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/docs/methodology.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/docs/skills.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/gitignore.snippet +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/roadmap.template.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/rules/correlation-ids.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/rules/never-touch.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/rules/result-contract.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/rules/security-boundaries.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/rules/verification-discipline.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/schemas/event.schema.json +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/schemas/events/initiative_created.schema.json +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/schemas/events/spec_validated.schema.json +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/templates/GATE.template.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/templates/PLAN.template.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/templates/WU.template.md +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/verification.yml.example +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/lint_plan.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/scaffold.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/validate_event.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_arm_gate_edits_uncommitted.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_attempt_outcome_emission.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_autosync.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_autosync_consent.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_autosync_firstrun.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_autosync_plugin.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_backend.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_bookkeeping_commit_crash_run.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_closing_deliverable_guard.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_doctor.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_events_redaction.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_gate_eval_intermediate_wiring.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_gate_eval_terminal_wiring.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_init_integration.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_leak_findings_redaction.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_leak_scan.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_learnings_query.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lint_bare_produces_path.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lint_plan_relative_import.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lint_produces_driver_helper.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_auto_archive.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_close_intermediate.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_gate_budget.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_orchestration.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_post_pass_invariant.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_migrate_legacy.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_planned_cost_lint.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_prepare_feature.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_produces_field.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_roadmap_add_skill.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_scaffold_data_in_sync.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_scaffold_docs.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_scaffold_init.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_scaffold_manifest.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_scaffold_resources.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_scaffold_seed_sanity.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_scaffold_upgrade.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_scaffold_wiring.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_skills_vendored_in_sync.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_terminal_flips.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_untracked_feature_folder.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_upgrade_integration.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_validate_event.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_verify_empty_gate_set.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_version_consistency.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_version_skew.py +0 -0
- {specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_wu_execution_metadata.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.3.
|
|
7
|
+
version = "0.3.10"
|
|
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.3.10
|
|
@@ -62,7 +62,7 @@ SPECFUSE_DIR = Path(".specfuse")
|
|
|
62
62
|
REPO_ROOT = SPECFUSE_DIR.parent
|
|
63
63
|
FEATURES_DIR = SPECFUSE_DIR / "features"
|
|
64
64
|
VERIFICATION_PATH = SPECFUSE_DIR / "verification.yml"
|
|
65
|
-
DRIVER_VERSION = "0.3.
|
|
65
|
+
DRIVER_VERSION = "0.3.10"
|
|
66
66
|
# Oldest scaffold layout this driver can drive. init.sh stamps the scaffold's own
|
|
67
67
|
# version into `.specfuse/VERSION`; check_scaffold_version() fails loud at startup if
|
|
68
68
|
# the consumer's scaffold is older than this, pointing at `specfuse upgrade`. Bump
|
|
@@ -507,6 +507,35 @@ def flush_events(events_path: Path, events: list) -> None:
|
|
|
507
507
|
# --------------------------------------------------------------------------- #
|
|
508
508
|
|
|
509
509
|
|
|
510
|
+
# Known signatures of a gate whose oracle SILENTLY DEGRADED — the command
|
|
511
|
+
# exits 0 while its analyzer measured a strict subset of the real check, so a
|
|
512
|
+
# green gate is a hollow pass (issue #134). Each entry is (compiled pattern,
|
|
513
|
+
# human reason). A gate whose output matches while exiting 0 is forced to FAIL
|
|
514
|
+
# so the driver blocks honestly instead of certifying `met` on a subset oracle.
|
|
515
|
+
# These are language/tool-specific data, not detection logic — extend the list
|
|
516
|
+
# as new silent-degradation modes surface; the matching in verify() is generic.
|
|
517
|
+
DEGRADED_ORACLE_MARKERS: list[tuple["re.Pattern[str]", str]] = [
|
|
518
|
+
# Flutter/Dart custom_lint: the analyzer plugin can't resolve/build (e.g.
|
|
519
|
+
# network to pub.dev blocked in the sandbox), so `flutter analyze` drops to
|
|
520
|
+
# core lints only and still exits 0 — every custom_lint/riverpod_lint rule
|
|
521
|
+
# is invisible. Observed on FEAT-2026-0005 (28 warnings CI caught, loop 0).
|
|
522
|
+
(re.compile(r"error occurred while setting up the analyzer plugin",
|
|
523
|
+
re.IGNORECASE),
|
|
524
|
+
"custom_lint analyzer plugin did not load — 'flutter analyze' degraded to "
|
|
525
|
+
"core lints only; the warnings oracle measured a subset of the real check "
|
|
526
|
+
"and cannot certify zero warnings in this environment"),
|
|
527
|
+
]
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def detect_degraded_oracle(stdout: str) -> str | None:
|
|
531
|
+
"""Return an honest reason string if gate output shows a silently degraded
|
|
532
|
+
oracle, else None. Matches against DEGRADED_ORACLE_MARKERS (issue #134)."""
|
|
533
|
+
for pattern, reason in DEGRADED_ORACLE_MARKERS:
|
|
534
|
+
if pattern.search(stdout):
|
|
535
|
+
return reason
|
|
536
|
+
return None
|
|
537
|
+
|
|
538
|
+
|
|
510
539
|
def parse_gate_failure_signature(stdout: str) -> tuple[str, str]:
|
|
511
540
|
"""Extract (failure_class, failure_signature) from gate runner stdout.
|
|
512
541
|
|
|
@@ -1654,6 +1683,14 @@ def verify(wu: WorkUnit, feature_dir: Path,
|
|
|
1654
1683
|
out, _ = proc.communicate(timeout=GATE_TIMEOUT_SECONDS)
|
|
1655
1684
|
ok = proc.returncode == 0
|
|
1656
1685
|
tail = (out or "").strip().splitlines()[-15:]
|
|
1686
|
+
# A green gate whose oracle silently degraded is a hollow pass
|
|
1687
|
+
# (issue #134): force FAIL and name the degradation honestly so the
|
|
1688
|
+
# log reads as "oracle couldn't measure it," not "code is clean."
|
|
1689
|
+
if ok:
|
|
1690
|
+
degraded = detect_degraded_oracle(out or "")
|
|
1691
|
+
if degraded:
|
|
1692
|
+
ok = False
|
|
1693
|
+
tail = tail + [f"DEGRADED ORACLE: {degraded}"]
|
|
1657
1694
|
except subprocess.TimeoutExpired:
|
|
1658
1695
|
try:
|
|
1659
1696
|
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
|
|
@@ -3174,6 +3211,11 @@ def run(
|
|
|
3174
3211
|
|
|
3175
3212
|
print(f"\n[{time.strftime('%H:%M:%S')}] -- {wu.wu_id} "
|
|
3176
3213
|
f"[{wu.type}] model={wu.model} effort={wu.effort}")
|
|
3214
|
+
# Summary line: the WU's title, so the log says WHAT is being
|
|
3215
|
+
# worked, not just its id. Skipped when title just echoes the id
|
|
3216
|
+
# (the parse default when a WU has no `# Title` heading).
|
|
3217
|
+
if wu.title and wu.title != wu.wu_id:
|
|
3218
|
+
print(f" ↳ {wu.title}")
|
|
3177
3219
|
if dry_run:
|
|
3178
3220
|
print(" (dry run — would dispatch)")
|
|
3179
3221
|
wu.status = DONE
|
|
@@ -53,6 +53,7 @@ tests/test_bookkeeping_commit_crash_run.py
|
|
|
53
53
|
tests/test_bookkeeping_commit_hook_crash.py
|
|
54
54
|
tests/test_closing_deliverable_guard.py
|
|
55
55
|
tests/test_cost_tracking.py
|
|
56
|
+
tests/test_degraded_oracle.py
|
|
56
57
|
tests/test_deliverable_presence_gate.py
|
|
57
58
|
tests/test_doctor.py
|
|
58
59
|
tests/test_driver_integration.py
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 Specfuse contributors
|
|
3
|
+
# Licensed under the Apache License, Version 2.0. See LICENSE.
|
|
4
|
+
#
|
|
5
|
+
"""Issue #134 — verify() must FAIL when a gate's oracle silently degraded.
|
|
6
|
+
|
|
7
|
+
A gate command can exit 0 while its analyzer degraded to a strict subset of the
|
|
8
|
+
real check. The observed case: `flutter analyze --fatal-warnings` cannot build a
|
|
9
|
+
`custom_lint` plugin in the loop sandbox (network to pub.dev blocked), prints
|
|
10
|
+
"An error occurred while setting up the analyzer plugin ...", then continues with
|
|
11
|
+
core lints only and exits 0 — "No issues found!". The gate passes; the real CI
|
|
12
|
+
(plugin loads there) fails on warnings the in-loop oracle never saw. A hollow
|
|
13
|
+
pass.
|
|
14
|
+
|
|
15
|
+
verify() must treat a known silent-degradation signature in gate output as a
|
|
16
|
+
gate FAILURE even on exit 0, with an honest message naming the degraded oracle,
|
|
17
|
+
so the driver blocks instead of certifying `met` on a subset check.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import unittest
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
from tests._loop_loader import load_loop
|
|
26
|
+
|
|
27
|
+
loop = load_loop()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def make_wu(wu_type: str = "implementation"):
|
|
31
|
+
return loop.WorkUnit(
|
|
32
|
+
wu_id="FEAT-2026-9999/T01",
|
|
33
|
+
file=Path("/tmp/does-not-matter.md"),
|
|
34
|
+
depends_on=[],
|
|
35
|
+
type=wu_type,
|
|
36
|
+
model="claude-haiku-4-5-20251001",
|
|
37
|
+
status="pending",
|
|
38
|
+
attempts=0,
|
|
39
|
+
title="test fixture",
|
|
40
|
+
body="(body unused)",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# A shell command that reproduces the degraded-oracle shape: emit the analyzer
|
|
45
|
+
# plugin-setup error to stdout, report "No issues found!", then exit 0.
|
|
46
|
+
_DEGRADED_CMD = (
|
|
47
|
+
"printf '%s\\n' "
|
|
48
|
+
"'An error occurred while setting up the analyzer plugin custom_lint' "
|
|
49
|
+
"'No issues found!'; true"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestDegradedOracle(unittest.TestCase):
|
|
54
|
+
|
|
55
|
+
def test_plugin_setup_error_fails_gate_despite_exit_zero(self):
|
|
56
|
+
cfg = {"code": [{"name": "warnings", "command": _DEGRADED_CMD}]}
|
|
57
|
+
ok, msg = loop.verify(make_wu(), Path("/tmp/feat"), cfg=cfg)
|
|
58
|
+
self.assertFalse(
|
|
59
|
+
ok, "exit 0 with a plugin-setup error must FAIL — oracle degraded"
|
|
60
|
+
)
|
|
61
|
+
self.assertIn("### warnings: FAIL", msg)
|
|
62
|
+
self.assertIn("DEGRADED ORACLE", msg)
|
|
63
|
+
|
|
64
|
+
def test_message_is_honest_about_the_cause(self):
|
|
65
|
+
cfg = {"code": [{"name": "warnings", "command": _DEGRADED_CMD}]}
|
|
66
|
+
_, msg = loop.verify(make_wu(), Path("/tmp/feat"), cfg=cfg)
|
|
67
|
+
# The honest reason (not just the echoed marker) reaches the log: names
|
|
68
|
+
# the degradation and that the oracle measured a subset, not a warning.
|
|
69
|
+
self.assertIn("did not load", msg)
|
|
70
|
+
self.assertIn("subset", msg)
|
|
71
|
+
|
|
72
|
+
def test_clean_pass_still_passes(self):
|
|
73
|
+
# Same "No issues found!" success WITHOUT the degradation marker: passes.
|
|
74
|
+
cfg = {"code": [{"name": "warnings",
|
|
75
|
+
"command": "printf '%s\\n' 'No issues found!'; true"}]}
|
|
76
|
+
ok, msg = loop.verify(make_wu(), Path("/tmp/feat"), cfg=cfg)
|
|
77
|
+
self.assertTrue(ok, "clean exit 0 with no degradation marker must pass")
|
|
78
|
+
self.assertIn("### warnings: PASS", msg)
|
|
79
|
+
|
|
80
|
+
def test_marker_does_not_override_a_real_failure(self):
|
|
81
|
+
# Command exits non-zero AND has the marker: still a FAIL (not masked).
|
|
82
|
+
cfg = {"code": [{"name": "warnings",
|
|
83
|
+
"command": _DEGRADED_CMD.replace("; true", "; false")}]}
|
|
84
|
+
ok, msg = loop.verify(make_wu(), Path("/tmp/feat"), cfg=cfg)
|
|
85
|
+
self.assertFalse(ok)
|
|
86
|
+
self.assertIn("### warnings: FAIL", msg)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
unittest.main()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.3.9
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/docs/concepts/ralph-lineage.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/rules/security-boundaries.md
RENAMED
|
File without changes
|
{specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/specfuse/loop/data/rules/verification-discipline.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specfuse_loop-0.3.9 → specfuse_loop-0.3.10}/tests/test_lint_correlation_id_close_intermediate.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|