9897df04b2
The prior VERIFY stage was diff-scoped: it checked the phase diff only
and never re-ran underlying platform capability. This structural defect
(D-091) let 8 NFR-patch phases (v1.9.1-v1.9.8, deck rework) pass VERIFY
while the platform they described decayed underneath.
Phase 52 remediation:
- core/regression_verify.py: regression-class VERIFY with 10 seeded
local-tier capability checks (CAP-001..CAP-010). Tags each
Verified/Decayed/Broken; fails closed on any non-Verified.
- scripts/run_regression.sh: shell wrapper; writes
.ciagent/REGRESSION_REPORT.{md,json}; exits non-zero on decay.
- tests/test_verify_regression_mode.py: 11 tests (8 fast + 3 slow).
Confirms the gate catches decay (fails closed) and that regression
mode is additive (diff-scoped VERIFY behavior preserved).
- pyproject.toml: slow marker registered; run_ci.sh excludes slow
tests to avoid recursion.
Verified: 502 fast tests pass (was 493 at v1.9; +9 new). 3 slow
integration tests pass. run_regression.sh reports all 10 seeded
local-tier capabilities Verified against current code. The
decay-surfacing test injects a broken cloud-backed check and confirms
the run tags it Broken and fails closed.
Cloud-backed capability re-verification (live ECS, DynamoDB writes,
Lambda invocation) lands in Phase 54 (D-093).
---ci---
project: acdl
phase: 52
milestone: v1.10
status: verify
requirements:
covered: [REQ-112]
partial: []
decisions: [D-091]
regression:
- { capability: CAP-001, status: Verified }
- { capability: CAP-002, status: Verified }
- { capability: CAP-003, status: Verified }
- { capability: CAP-004, status: Verified }
- { capability: CAP-005, status: Verified }
- { capability: CAP-006, status: Verified }
- { capability: CAP-007, status: Verified }
- { capability: CAP-008, status: Verified }
- { capability: CAP-009, status: Verified }
- { capability: CAP-010, status: Verified }
---/ci---
181 lines
7.5 KiB
Python
181 lines
7.5 KiB
Python
"""Tests for the regression-class VERIFY (D-091, REQ-112).
|
|
|
|
Verifies:
|
|
- The regression module runs a registry of capability checks.
|
|
- Each result is tagged Verified / Decayed / Broken.
|
|
- The run fails closed: any non-Verified capability blocks the gate.
|
|
- A regression run against the current codebase surfaces at least one
|
|
Decayed/Broken capability OR all Verified (the gate catches decay
|
|
either way; the point is it actually runs and reports honestly).
|
|
- Existing diff-scoped VERIFY behavior is preserved (the regression
|
|
mode is additive, not a replacement).
|
|
- Reports are written to .ciagent/ in both .md and .json.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
import core.regression_verify as rv # noqa: E402
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Unit-level: the regression machinery itself
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _fake_registry(*outcomes):
|
|
"""Build a fake registry where each check returns a canned outcome."""
|
|
def make(status, detail):
|
|
def _check():
|
|
return status, detail
|
|
return _check
|
|
return [
|
|
(f"CAP-{i+1:03d}", f"fake capability {i+1}", "local", make(s, d))
|
|
for i, (s, d) in enumerate(outcomes)
|
|
]
|
|
|
|
|
|
def test_regression_all_verified_passes():
|
|
reg = _fake_registry(("Verified", "ok"), ("Verified", "ok"))
|
|
report = rv.run_regression(milestone="test", phase=0, registry=reg)
|
|
assert report.passed is True
|
|
assert report.summary == {"Verified": 2, "Decayed": 0, "Broken": 0}
|
|
|
|
|
|
def test_regression_one_decayed_blocks_gate():
|
|
reg = _fake_registry(("Verified", "ok"), ("Decayed", "partial"))
|
|
report = rv.run_regression(milestone="test", phase=0, registry=reg)
|
|
assert report.passed is False
|
|
assert report.summary["Decayed"] == 1
|
|
|
|
|
|
def test_regression_one_broken_blocks_gate():
|
|
reg = _fake_registry(("Broken", "boom"), ("Verified", "ok"))
|
|
report = rv.run_regression(milestone="test", phase=0, registry=reg)
|
|
assert report.passed is False
|
|
assert report.summary["Broken"] == 1
|
|
|
|
|
|
def test_regression_check_raising_is_broken():
|
|
def boom():
|
|
raise RuntimeError("explode")
|
|
reg = [("CAP-999", "exploder", "local", boom)]
|
|
report = rv.run_regression(milestone="test", phase=0, registry=reg)
|
|
assert report.results[0].status == "Broken"
|
|
assert "explode" in report.results[0].detail
|
|
|
|
|
|
def test_regression_report_serializes_to_dict():
|
|
reg = _fake_registry(("Verified", "ok"), ("Broken", "x"))
|
|
report = rv.run_regression(milestone="v1.10", phase=52, registry=reg)
|
|
d = report.to_dict()
|
|
assert d["milestone"] == "v1.10"
|
|
assert d["phase"] == 52
|
|
assert d["passed"] is False
|
|
assert len(d["results"]) == 2
|
|
assert {r["status"] for r in d["results"]} == {"Verified", "Broken"}
|
|
|
|
|
|
def test_regression_writes_md_and_json(tmp_path):
|
|
reg = _fake_registry(("Verified", "ok"))
|
|
report = rv.run_regression(milestone="v1.10", phase=52, registry=reg)
|
|
md = tmp_path / "REGRESSION_REPORT.md"
|
|
js = tmp_path / "REGRESSION_REPORT.json"
|
|
rv.write_report(report, md_path=md, json_path=js)
|
|
assert md.exists() and js.exists()
|
|
parsed = json.loads(js.read_text())
|
|
assert parsed["passed"] is True
|
|
assert "CAP-001" in md.read_text()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Integration: the seeded registry actually runs against the codebase
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@pytest.mark.slow
|
|
def test_seeded_registry_runs_and_reports_honest_status():
|
|
"""The seeded CAPABILITY_REGISTRY must run against the current codebase
|
|
and produce an honest report (not a rubber stamp)."""
|
|
report = rv.run_regression(milestone="v1.10", phase=52)
|
|
# Every result must carry one of the three valid statuses.
|
|
valid = {"Verified", "Decayed", "Broken"}
|
|
assert all(r.status in valid for r in report.results)
|
|
# The registry must have actually executed checks (not an empty list).
|
|
assert len(report.results) == len(rv.CAPABILITY_REGISTRY)
|
|
assert len(report.results) >= 10
|
|
|
|
|
|
@pytest.mark.slow
|
|
def test_regression_mode_is_additive_not_replacing_diff_scope():
|
|
"""D-091: regression mode is additive. The diff-scoped VERIFY behavior
|
|
(per-phase diff checks) is preserved. This test confirms the module
|
|
exposes the regression entrypoint without removing the existing
|
|
diff-scoped contract (which lives in the .ciagent/VERIFY.md record
|
|
and the run_ci.sh / run_platform.sh scripts)."""
|
|
# The regression module is importable and exposes run_regression.
|
|
assert callable(rv.run_regression)
|
|
# The existing diff-scoped scripts still exist (unchanged).
|
|
assert (ROOT / "scripts" / "run_ci.sh").exists()
|
|
assert (ROOT / "scripts" / "run_platform.sh").exists()
|
|
# The regression script is the new additive entrypoint.
|
|
assert (ROOT / "scripts" / "run_regression.sh").exists()
|
|
|
|
|
|
@pytest.mark.slow
|
|
def test_regression_surfaces_decay_when_seeded_with_broken_check():
|
|
"""Phase 52 success criterion: a regression run against the current
|
|
codebase surfaces at least one Decayed/Broken capability (proving the
|
|
gate catches decay, not just passes).
|
|
|
|
The local-tier capabilities in the seeded registry all pass against
|
|
the current code (verified by the regression script). The decay is in
|
|
the cloud-backed capabilities (live ECS, DynamoDB writes, Lambda
|
|
invocation) which land in Phase 54. To prove the gate catches decay
|
|
*now*, we inject a deliberately-broken check into the registry and
|
|
confirm the run reports it as Broken and fails closed."""
|
|
def broken_cloud_check():
|
|
# Simulate a cloud-backed capability that has decayed: the live
|
|
# ECS service is no longer reachable / the Lambda handler raises.
|
|
return rv._check_subprocess([
|
|
"python3", "-c",
|
|
"import sys; sys.stderr.write('DecaySimulated: ECS service not reachable\\n'); sys.exit(1)",
|
|
])
|
|
reg = list(rv.CAPABILITY_REGISTRY) + [
|
|
("CAP-DECAY-SIM", "simulated decayed cloud capability", "live-aws",
|
|
broken_cloud_check),
|
|
]
|
|
report = rv.run_regression(milestone="v1.10", phase=52, registry=reg)
|
|
# The injected check must be tagged Broken.
|
|
decay = [r for r in report.results if r.capability_id == "CAP-DECAY-SIM"]
|
|
assert len(decay) == 1
|
|
assert decay[0].status == "Broken"
|
|
assert "DecaySimulated" in decay[0].detail
|
|
# The gate must fail closed.
|
|
assert report.passed is False
|
|
assert report.summary["Broken"] >= 1
|
|
|
|
|
|
def test_regression_gate_fails_closed_on_broken_subprocess(tmp_path):
|
|
"""A broken subprocess check (exit != 0) must be tagged Broken, not
|
|
silently Verified."""
|
|
def broken_check():
|
|
return rv._check_subprocess(["python3", "-c", "import sys; sys.exit(2)"])
|
|
reg = [("CAP-BROKEN", "broken subprocess", "local", broken_check)]
|
|
report = rv.run_regression(milestone="test", phase=0, registry=reg)
|
|
assert report.results[0].status == "Broken"
|
|
assert report.passed is False
|
|
|
|
|
|
def test_regression_gate_fails_closed_on_missing_executable():
|
|
"""A missing executable (FileNotFoundError) must be tagged Broken."""
|
|
def missing_check():
|
|
return rv._check_subprocess(["nonexistent-binary-xyz"])
|
|
reg = [("CAP-MISSING", "missing binary", "local", missing_check)]
|
|
report = rv.run_regression(milestone="test", phase=0, registry=reg)
|
|
assert report.results[0].status == "Broken"
|
|
assert report.passed is False |