"""★2026-09-03 f7cc45c576c0 dry_28 — 「살 자리 5」였는데 entity_detail 이 force 라 실제로는 하류 59 CP 가 지워질 판이었다.
dry 가 force 의 연쇄를 보여 주고, 전이의 drift_ack 로 자기만 다시 도는 길을 가른다."""
from __future__ import annotations

import pytest

from tools.grounding_audit import canary_run as cr


class TestTheDryShowsTheCascade:
    def test_it_lists_the_completed_downstream_of_each_forced_step(self, monkeypatch):
        monkeypatch.setattr(cr, "completed_steps_of",
                            lambda rid, statuses=None: ["b", "c", "z"])
        got = cr.forced_would_invalidate_of("r", ["a"], downstream_of=lambda s: ["b", "c", "d"])
        assert got == {"a": ["b", "c"]}

    def test_no_forced_step_no_listing(self):
        assert cr.forced_would_invalidate_of("r", []) == {}


class TestDriftIsSplitByTheTransitionsAck:
    def _fake(self, monkeypatch, tmp_path, drifted):
        from tools.grounding_audit import canary_pipeline as cp
        ep = tmp_path / "projects" / "p" / "checkpoints" / "episodes" / "e"
        ep.mkdir(parents=True)
        monkeypatch.setattr(cr.ci, "root_dir", lambda _rid: tmp_path)
        monkeypatch.setattr(cr, "_owner_requirement", lambda _fx: {})
        monkeypatch.setattr(cp, "durable_status", lambda pid, epi, s: "completed")
        monkeypatch.setattr(cp, "contract_drift_of", lambda s, pid, epi, cfg: ("hash" if s in drifted else None))
        monkeypatch.setattr(cp, "code_transition_covers_head", lambda root: True)

    def test_every_drifted_completed_step_is_forced_there_is_no_ack(self, monkeypatch, tmp_path):
        self._fake(monkeypatch, tmp_path, drifted={"entity_detail", "scene_director"})
        got = cr.drift_reentry_of("r", ["entity_detail", "scene_director", "shot_director"], {"fixture": "period_episode"})
        assert got == {"forced": ["entity_detail", "scene_director"]}

    def test_the_old_name_still_answers_forced_only(self, monkeypatch, tmp_path):
        self._fake(monkeypatch, tmp_path, drifted={"entity_detail"})
        assert cr.forced_for_drift_of("r", ["entity_detail"], {"fixture": "period_episode"}) == ["entity_detail"]


class TestATransitionBindsOnlyRealAdoptionEvents:
    def test_an_unknown_adoption_id_stops(self, monkeypatch, tmp_path):
        from tools.grounding_audit import canary_pipeline as cp
        sc = {"mode": "v2_chunk", "fixture": "period_episode", "target": "outdoor_structure_seed",
              "background": "on", "still_recipe": "off", "outdoor": "on"}
        monkeypatch.setattr(cr, "recorded_tip", lambda rid: "a" * 40)
        monkeypatch.setattr(cr, "assert_scenario_matches_run", lambda rid, s: None)
        monkeypatch.setattr(cr, "git_tip", lambda: {"tip": "b" * 40, "clean": True, "dirty_files": []})
        monkeypatch.setattr(cr.ci, "root_dir", lambda rid: tmp_path)
        monkeypatch.setattr(cp, "read_attempts", lambda root: [])
        with pytest.raises(cr.ScopeMismatch):
            cr.record_code_transition("r", why="x", sc=sc, hash_adoptions=["nope"])
