Revert "feat(lvl5): P1 — 5-rung ladder (L5=abra recipe lint) + de-capped level semantics"
All checks were successful
continuous-integration/drone/push Build is passing

This reverts commit e219a7891d.
This commit is contained in:
autonomic-bot
2026-06-11 07:46:57 +00:00
parent 589943f46e
commit cd62743055
12 changed files with 336 additions and 1065 deletions

View File

@ -1,14 +1,8 @@
"""Unit tests for the level ladder (harness.level) — phase lvl5 semantics.
"""Unit tests for the Phase-3 level ladder (harness.level), plan-phase3-results-ux.md §4.1 / R1.
Pure function — no I/O. Proves the operator-decided rule (plan-phase-lvl5-lint-rung.md,
DECISIONS.md phase lvl5):
level = max i such that rung_i == "pass" and every rung j < i is "pass" or "skip"
— a real FAIL blocks, an UNVERIFIED rung blocks exactly like a fail, an INTENTIONAL skip is
climbed past. Includes the mission's four worked examples verbatim, and the old N/A cases
(single-published-version recipe, non-backup-capable recipe) now climbing past their former
caps. Run cold with: cc-ci-run -m pytest tests/unit/test_level.py -q
Pure function — no I/O. Proves the YunoHost gap-caps-the-level semantics, including the U0 gate
acceptance: a recipe that climbs through L4 reports 4, and one that fails at L2 is capped at 1
(the level just below the failed rung). Run cold with: cc-ci-run -m pytest tests/unit/test_level.py -q
"""
from __future__ import annotations
@ -25,115 +19,69 @@ def _rungs(
upgrade="pass",
backup_restore="pass",
functional="pass",
lint="pass",
):
return {
"install": install,
"upgrade": upgrade,
"backup_restore": backup_restore,
"functional": functional,
"lint": lint,
}
# ---- the ladder: five essential rungs, top is L5 (lint) ----
# ---- the ladder: four essential rungs, top is L4 (functional) ----
def test_full_clean_climb_is_L5():
assert L.compute_level(_rungs()) == 5
def test_full_clean_climb_to_L4():
# All four essential rungs pass → L4 (the top; integration/recipe-local are optional, not leveled).
lvl, reason = L.compute_level(_rungs())
assert lvl == 4
assert reason == ""
def test_ladder_is_five_rungs_lint_on_top():
assert L.RUNGS == ("install", "upgrade", "backup_restore", "functional", "lint")
assert "lint" in L.RUNG_LABEL[5]
def test_fails_at_L2_capped_at_L1():
# GATE: upgrade fails → capped at L1 even though higher rungs would pass.
lvl, reason = L.compute_level(_rungs(upgrade="fail", backup_restore="pass", functional="pass"))
assert lvl == 1
assert "L2" in reason and "FAILED" in reason
# ---- mission worked examples (operator Q&A 2026-06-11, verbatim) ----
def test_mission_example_fail_blocks():
# install ✔, upgrade ✘, backup ✔, functional ✔, lint ✔ → level 1 (fail blocks).
assert L.compute_level(_rungs(upgrade="fail")) == 1
def test_mission_example_intentional_skip_climbs():
# install ✔, upgrade ✔, backup skip (not capable), functional ✔, lint ✔ → level 5
# (previously capped at 2 — the confusing part the operator removed).
assert L.compute_level(_rungs(backup_restore="skip")) == 5
def test_mission_example_unverified_blocks():
# install ✔, upgrade ✔, backup UNVER (harness error), functional ✔, lint ✔ → level 2
# (we cannot claim what we didn't check).
assert L.compute_level(_rungs(backup_restore="unver")) == 2
def test_mission_example_unverified_top_rung_not_earned():
# all four ✔, lint unver (abra missing) → level 4.
assert L.compute_level(_rungs(lint="unver")) == 4
# ---- blocking semantics ----
# ---- L0 / install ----
def test_install_fail_is_L0():
assert L.compute_level(_rungs(install="fail")) == 0
lvl, reason = L.compute_level(_rungs(install="fail"))
assert lvl == 0
assert "L1" in reason and "FAILED" in reason
def test_install_unver_is_L0():
assert L.compute_level(_rungs(install="unver")) == 0
# ---- gap-caps semantics: a higher pass can't rescue a lower gap ----
def test_higher_pass_never_rescues_a_fail():
# everything above a failed rung is dead, however green.
assert L.compute_level(_rungs(upgrade="fail", backup_restore="pass", functional="pass")) == 1
def test_higher_pass_does_not_rescue_lower_na():
# backup/restore N/A (stateless app) caps at L2 even though functional would pass.
lvl, reason = L.compute_level(_rungs(backup_restore="na", functional="pass"))
assert lvl == 2
assert "L3" in reason and "N/A" in reason
def test_lint_fail_blocks_at_4():
assert L.compute_level(_rungs(lint="fail")) == 4
def test_upgrade_na_caps_at_L1():
# only one published version → no upgrade possible → N/A caps at L1 (upgrade is essential).
lvl, reason = L.compute_level(_rungs(upgrade="na"))
assert lvl == 1
assert "L2" in reason and "N/A" in reason
def test_unver_blocks_even_after_a_skip():
# skip at L2 is climbed past, but the unver at L3 still blocks → level 1.
assert L.compute_level(_rungs(upgrade="skip", backup_restore="unver")) == 1
def test_functional_na_caps_at_L3():
# no recipe-specific functional tests → functional N/A caps at L3.
lvl, reason = L.compute_level(_rungs(functional="na"))
assert lvl == 3
assert "L4" in reason and "N/A" in reason
# ---- intentional-skip climbing (the de-cap) ----
def test_single_version_recipe_climbs_past_upgrade_skip():
# old rule: upgrade N/A capped at L1. New rule: skip is climbed past → full climb 5.
assert L.compute_level(_rungs(upgrade="skip")) == 5
def test_stateless_recipe_climbs_past_backup_skip_to_lint():
assert L.compute_level(_rungs(upgrade="skip", backup_restore="skip")) == 5
def test_skip_does_not_count_as_pass():
# ALL skips → nothing passed → level 0 (a skip climbs, but never earns).
assert (
L.compute_level(
_rungs(
install="skip",
upgrade="skip",
backup_restore="skip",
functional="skip",
lint="skip",
)
)
== 0
)
def test_skip_then_pass_earns_the_higher_rung():
# skip at L4, pass at L5 → level 5 (the skip below doesn't stop the climb).
assert L.compute_level(_rungs(functional="skip")) == 5
def test_trailing_skip_keeps_last_pass():
# passes up to L3, skips above → level stays 3 (skips never raise).
assert L.compute_level(_rungs(functional="skip", lint="skip")) == 3
def test_functional_fail_caps_at_L3():
lvl, reason = L.compute_level(_rungs(functional="fail"))
assert lvl == 3
assert "L4" in reason and "FAILED" in reason
# ---- input validation ----
@ -141,7 +89,7 @@ def test_trailing_skip_keeps_last_pass():
def test_invalid_status_raises():
bad = _rungs()
bad["functional"] = "na" # the OLD vocabulary is no longer valid — every N/A is classified
bad["functional"] = "passed" # not in the vocabulary
try:
L.compute_level(bad)
except ValueError:
@ -149,16 +97,6 @@ def test_invalid_status_raises():
raise AssertionError("expected ValueError on invalid rung status")
def test_missing_rung_raises():
bad = _rungs()
del bad["lint"]
try:
L.compute_level(bad)
except ValueError:
return
raise AssertionError("expected ValueError on missing rung")
# ---- helpers: backup_restore_status ----
@ -166,8 +104,8 @@ def test_backup_restore_status_pass():
assert L.backup_restore_status("pass", "pass", True) == "pass"
def test_backup_restore_status_not_capable_is_intentional_skip():
assert L.backup_restore_status("skip", "skip", False) == "skip"
def test_backup_restore_status_not_capable_is_na():
assert L.backup_restore_status("skip", "skip", False) == "na"
def test_backup_restore_status_fail_on_either():
@ -175,20 +113,16 @@ def test_backup_restore_status_fail_on_either():
assert L.backup_restore_status("fail", "pass", True) == "fail"
def test_backup_restore_partial_is_unverified():
# backup-capable but restore didn't run cleanly (not pass, not fail) → cannot claim L3,
# and the non-run is NOT intentional → unver (blocks the level above it).
assert L.backup_restore_status("pass", "skip", True) == "unver"
assert L.backup_restore_status(None, None, True) == "unver"
def test_backup_restore_partial_is_na():
# backup-capable but restore didn't run cleanly (not pass, not fail) → cannot claim L3
assert L.backup_restore_status("pass", "skip", True) == "na"
# ---- helpers: tier_to_rung ----
def test_tier_to_rung_mapping_defaults_unverified():
def test_tier_to_rung_mapping():
assert L.tier_to_rung("pass") == "pass"
assert L.tier_to_rung("fail") == "fail"
# no intentionality information here — a non-run is unver; derive_rungs upgrades to "skip"
# only on a declared/structural fact, never the other way.
assert L.tier_to_rung("skip") == "unver"
assert L.tier_to_rung(None) == "unver"
assert L.tier_to_rung("skip") == "na"
assert L.tier_to_rung(None) == "na"