"""D15 (§4.8): effective_decisions must stale-filter name-subject decisions too. The reference bug (CRITICA #1): effective_decisions only filtered decisions whose subject was "phase:N". cte_approved uses the CTE *name* as subject, so a cte_approved from F6 stayed "effective" after a rollback to F4 -- a stale decision that still counted (the exact invariant §4.8 forbids). The fix records the emitting phase on each DecisionRecord (high-water-mark) and effective_decisions falls back to it when the subject is not "phase:N". """ from pathlib import Path from tht.decisions import append_decision from tht.phase import approved_ctes, current_phase, effective_decisions def _walk_to_phase(session: Path, target: int) -> None: """Approva in ordine fino a raggiungere `target` (il fold avanza solo su n==cur).""" while current_phase(session) < target: append_decision(session, type="phase_approved", subject=f"phase:{current_phase(session)}") def test_cte_approved_name_subject_is_stale_after_reopen(tmp_path): s = tmp_path / "sess" s.mkdir() _walk_to_phase(s, 6) # ora in F6 # piano CTE + approvazione di un CTE (subject = NOME del CTE, non "phase:6") (s / "cte_plan.json").write_text('["pazienti_base"]') append_decision(s, type="cte_approved", subject="pazienti_base") assert "pazienti_base" in approved_ctes(s) assert current_phase(s) == 6 # rollback a F4: la cte_approved di F6 deve diventare stale (esclusa dalla vista) append_decision(s, type="phase_reopened", subject="phase:4") assert current_phase(s) == 4 assert "pazienti_base" not in approved_ctes(s), ( "cte_approved (subject a nome) di F6 NON deve contare dopo un reopen a F4" ) types = [d.type for d in effective_decisions(s)] assert "cte_approved" not in types def test_phase_field_recorded_on_append(tmp_path): s = tmp_path / "sess" s.mkdir() _walk_to_phase(s, 4) rec = append_decision(s, type="value_grounded", subject="ablazione") assert rec.phase == 4 # emitting phase recorded for the high-water-mark filter