"""Real-Postgres tests for WHICH rules reach a session (milestone 307 step 5). What mocks can't prove, and what this milestone must not get wrong: 1. **Nothing stops binding.** A rule with no tier, no areas and no edges behaves exactly as it did before tiers existed. That is the one failure this whole design must not produce, and it is asserted first. 2. A conditional rule is invisible to a project that doesn't work in its area, and arrives — binding, not suggested — to one that does. 3. A `co_surfaces` partner arrives with its other half, which is the failure that made merging rule 144 into rule 46 look like the only fix. 4. An explicit suppression outranks an edge. """ import pytest import pytest_asyncio from scribe.models import async_session from scribe.models.project import Project from scribe.models.rulebook import Rulebook from scribe.services import canonical_systems as canonical_svc from scribe.services import rulebooks as rulebooks_svc from scribe.services import systems as systems_svc from tests.helpers import ensure_user pytestmark = [pytest.mark.integration, pytest.mark.usefixtures("_dispose_engine")] @pytest_asyncio.fixture async def world(): """A project with TWO rulebooks, because the two payloads are different sets. `list_always_on_rules` covers always-on rulebooks; `get_applicable_rules` covers SUBSCRIBED ones. Conflating them is easy and would make these tests assert nothing, so the fixture carries one of each and every test says which payload it is about. """ async with async_session() as s: owner = await ensure_user(s, "surfacing_owner") project = Project(user_id=owner.id, title="Surfacing target") s.add(project) await s.flush() ids = {"owner": owner.id, "pid": project.id} await s.commit() always = await rulebooks_svc.create_rulebook(ids["owner"], "Family standards") async with async_session() as s: rb = await s.get(Rulebook, always.id) rb.always_on = True await s.commit() always_topic = await rulebooks_svc.create_topic(always.id, ids["owner"], "git") await rulebooks_svc.create_rule( always_topic.id, ids["owner"], "dev is home", "Work on dev.", ) book = await rulebooks_svc.create_rulebook(ids["owner"], "Subscribed practices") topic = await rulebooks_svc.create_topic(book.id, ids["owner"], "release") plain = await rulebooks_svc.create_rule( topic.id, ids["owner"], "Between batches, keep stacking", "Keep going.", ) await rulebooks_svc.subscribe_project( project_id=ids["pid"], rulebook_id=book.id, user_id=ids["owner"], ) ids.update({ "always": always.id, "always_topic": always_topic.id, "book": book.id, "topic": topic.id, "plain": plain.id, }) return ids async def _titles(ids) -> set[str]: applicable = await rulebooks_svc.get_applicable_rules(ids["pid"], ids["owner"]) return {r["title"] for r in applicable["rules"]} @pytest.mark.integration async def test_a_rule_with_no_tier_no_areas_and_no_edges_binds_exactly_as_before(world): """THE compatibility guarantee. An install upgrades and every rule it already had keeps arriving — no tier set, no areas, no edges, still bound. Getting this wrong would silently stop enforcing rules people rely on, which is worse than any amount of payload bloat.""" always_on = await rulebooks_svc.list_always_on_rules(world["owner"]) assert "dev is home" in {r.title for r in always_on} assert "Between batches, keep stacking" in await _titles(world) @pytest.mark.integration async def test_a_conditional_rule_is_reachable_not_resident(world): """It leaves the session-start payload entirely — that is the point of the tier — and it does NOT reach a project with no matching area.""" # In the ALWAYS-ON book: the tier alone keeps it out of the session-start # payload, which is the whole point of the tier. resident = await rulebooks_svc.create_rule( world["always_topic"], world["owner"], "Release tagging", "Derive the tag.", when_to_apply="when cutting a release", tier="conditional", ) assert resident.tier == "conditional" always_on = await rulebooks_svc.list_always_on_rules(world["owner"]) assert "Release tagging" not in {r.title for r in always_on} # In the SUBSCRIBED book, untagged: the project has no area to reach it by, # so it stays out of the project payload too. Absent for a DIFFERENT reason # than above, which is why both are asserted. await rulebooks_svc.create_rule( world["topic"], world["owner"], "Untagged conditional", "No area yet.", when_to_apply="sometime", tier="conditional", ) assert "Untagged conditional" not in await _titles(world) @pytest.mark.integration async def test_a_conditional_rule_binds_a_project_that_works_in_its_area(world): """The payoff: the tag match carries it in deterministically. The project reaches the area through its own System's canonical_id — its local NAME is irrelevant, which is the whole reason the catalog exists.""" area = await canonical_svc.find_by_name("CI & Release") assert area is not None, "migration 0087 seeds the standard vocabulary" rule = await rulebooks_svc.create_rule( world["topic"], world["owner"], "Release tagging", "Derive the tag.", when_to_apply="when cutting a release", tier="conditional", ) await rulebooks_svc.set_rule_systems(rule.id, world["owner"], [area.id]) # Still absent: the project has no Systems at all yet. assert "Release tagging" not in await _titles(world) # The project names the area with its OWN word, mapped to the same canon. local = await systems_svc.create_system( world["owner"], world["pid"], "CI & runners", description="ours", ) await canonical_svc.set_system_canonical(world["owner"], local.id, area.id) surfaced = await rulebooks_svc.get_applicable_rules(world["pid"], world["owner"]) hit = [r for r in surfaced["rules"] if r["title"] == "Release tagging"] assert hit, "a tagged conditional rule must bind a project working in that area" assert [s["name"] for s in hit[0]["systems"]] == ["CI & Release"] @pytest.mark.integration async def test_co_surfaces_drags_in_the_half_that_would_have_been_missed(world): """Rule 144 was split off rule 46 and folded back the same day because "either rule could surface without the other". This is the edge that makes that unnecessary: the partner arrives even though nothing else selected it, and says why it is here.""" partner = await rulebooks_svc.create_rule( world["topic"], world["owner"], "Version names are labels", "A name decides nothing.", when_to_apply="when naming a build", tier="conditional", ) await rulebooks_svc.add_rule_relation( world["owner"], world["plain"], partner.id, "co_surfaces", note="they fail together", ) surfaced = await rulebooks_svc.get_applicable_rules(world["pid"], world["owner"]) hit = [r for r in surfaced["rules"] if r["title"] == "Version names are labels"] assert hit, "a co_surfaces partner must arrive with its other half" assert hit[0]["via"] == "co_surfaces" @pytest.mark.integration async def test_a_suppression_outranks_an_edge(world): """The edge says these belong together; the suppression says this project does not want that one. An explicit decision beats an inferred one.""" partner = await rulebooks_svc.create_rule( world["topic"], world["owner"], "Muted partner", "Should not arrive.", tier="conditional", ) await rulebooks_svc.add_rule_relation( world["owner"], world["plain"], partner.id, "co_surfaces", ) await rulebooks_svc.suppress_rule_for_project( world["pid"], partner.id, world["owner"], ) surfaced = await rulebooks_svc.get_applicable_rules(world["pid"], world["owner"]) assert "Muted partner" not in {r["title"] for r in surfaced["rules"]}