"""★중앙 상한은 계획 때 수가 아니라 **provider 앞에서 실제 의무 수**와 견준다 (Codex 긴급 BLOCK 2026-09-03 05:08).

실측 4398a55dc0bb 2판: dry 때 `grounding_screen` CP 가 없어 선언 하한 5 로 중앙 상한 25 를 열었는데, 같은 live
안에서 screen 이 의무 20 을 세웠다 — 다섯 대상 뒤 step cap 에서 설 자리였다(2026-09-02 밤 attempt 21c47e1b 와 같은 부류)."""
from __future__ import annotations

import json

import pytest

from tools.grounding_audit import canary_run as cr

STEPS = [s for s, u in cr.METERING_UNITS.items() if u == cr.UNIT_CENTRAL]


def _screen(root, n):
    d = root / "projects" / "p" / "checkpoints" / "episodes" / "e" / "grounding_screen"
    d.mkdir(parents=True)
    (d / "manifest.json").write_text(json.dumps({"data": {"counts": {"obligation": n}}}), encoding="utf-8")


@pytest.fixture
def run_root(tmp_path, monkeypatch):
    monkeypatch.setattr(cr.ci, "root_dir", lambda rid: tmp_path)
    return tmp_path


class TestTheGateReadsTheScreenOutput:
    def test_more_obligations_than_planned_recomputes_the_cap_instead_of_stopping(self, run_root):
        """★뒤집음 (Codex 2026-09-03 06:05 「비용 때문에 다시 멈추지 말 것」): 분모가 production 산출이므로 사람 선언을 넘는
        것이 아니다 — 서지 않고 실제 의무 수로 step cap 을 재산정한다. 정지선(counted 문)은 그대로."""
        _screen(run_root, 20)
        got = cr.assert_central_cap_covers("r", {"central_target_count": 5, "fixture": "period_episode"}, set(), STEPS)
        assert got["checked"] is True and got["obligations"] == 20
        assert got["recomputed_logical_cap"] == cr.central_logical_cap("period_episode", obligations=20)["logical_cap"]
        assert got["recomputed_target_count"] == 20

    def test_without_a_fixture_name_it_still_stops(self, run_root):
        _screen(run_root, 20)
        with pytest.raises(cr.ScopeMismatch):
            cr.assert_central_cap_covers("r", {"central_target_count": 5}, set(), STEPS)

    def test_a_plan_that_covers_the_obligations_passes_with_both_numbers(self, run_root):
        _screen(run_root, 20)
        got = cr.assert_central_cap_covers("r", {"central_target_count": 20}, set(), STEPS)
        assert got["checked"] is True and got["obligations"] == 20 and got["central_target_count"] == 20

    def test_without_the_screen_cp_the_declared_floor_stands(self, run_root):
        got = cr.assert_central_cap_covers("r", {"central_target_count": 5}, set(), STEPS)
        assert got["checked"] is False and got["central_target_count"] == 5

    def test_the_central_unit_is_in_the_producer_gate_table(self):
        assert cr.PRODUCER_CAP_GATES[cr.UNIT_CENTRAL] == "assert_central_cap_covers"


class TestThePlanRecordsItsBasis:
    def test_build_plan_writes_the_target_count_it_used(self, monkeypatch):
        monkeypatch.setattr(cr, "obligation_count_of", lambda rid: 20)
        # 설정은 process 가 뜰 때 고정되므로 시험은 축 읽기를 선언값으로 맞춘다
        monkeypatch.setattr(cr, "axis_actual", lambda axis: {"background": "on", "still_recipe": "off", "outdoor": "on"}[axis])
        sc = cr.scenario(mode="v2_chunk", fixture="period_episode", target="outdoor_structure_form_reference",
                         background="on", still_recipe="off", outdoor="on")
        built = cr.build_plan(sc, run_id="r")
        assert built["plan_basis"]["central_target_count"] == 20
        assert built["plan_basis"]["fixture"] == "period_episode"
        assert built["plan_basis"]["central_obligation_count"] == 20
        # ★원고 치수에는 섞이지 않는다 — assert_scope 가 판마다 치수를 대조한다(실측 05:14 재개가 섰다)
        assert "central_target_count" not in built["dimensions"]
        assert built["caps"][cr.CENTRAL_STEP] == cr.central_logical_cap("period_episode", obligations=20)["logical_cap"]
