CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 50s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m25s
CI & Build / Build & push image (push) Successful in 13s
Last run left one failure, and it was the useful kind: the partner ARRIVED
(the scoping fix in 9c5ab1d worked) but carried no `via`, because it came
through the ordinary query rather than being dragged in by the edge.
The assertion was about to be read as "the edge is broken". It was not. The
edge was never exercised: since 394 an untagged rule in a subscribed rulebook
applies on its own, so the partner was already applicable and there was
nothing left for `co_surfaces` to do. The test had quietly stopped testing
anything — passing the first assertion for a reason unrelated to the
mechanism it names.
Rule 167's case exactly, so the fix is to restore its ability to fail rather
than to soften the assertion. The partner is now TAGGED to an area this
project does not work in, which puts it out of reach of everything except the
edge, and the test asserts that unreachability before drawing the relation.
If the edge ever stops dragging partners in, this fails again — which it
could not have done a commit ago.
The neighbouring suppression test keeps an UNTAGGED partner, deliberately and
now explicitly: that one is about a suppression outranking an edge, so its
partner should be reachable by every route, not none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011cPyzNnegXHr5iRMzzy5KJ
162 lines
7.3 KiB
Python
162 lines
7.3 KiB
Python
"""Real-Postgres tests for WHICH rules reach a session (milestone 307 step 5,
|
|
narrowed by 394).
|
|
|
|
What mocks can't prove, and what this design must not get wrong:
|
|
|
|
1. A rule is invisible to a project that doesn't work in its area, and
|
|
arrives — binding, not suggested — to one that does. Area matching is
|
|
DETERMINISTIC: a tag comparison, never a similarity score.
|
|
2. 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.
|
|
3. An explicit suppression outranks an edge.
|
|
|
|
TWO CLAIMS WERE DROPPED HERE BY MILESTONE 394, and it is worth saying which
|
|
rather than leaving a shorter list. "A rule with no tier binds exactly as
|
|
before" and "a conditional rule is reachable, not resident" were both about
|
|
the always-on tier. There is no tier and no resident payload, so neither
|
|
states anything that can now be true or false — they were not failing, they
|
|
had stopped being claims.
|
|
"""
|
|
import pytest
|
|
import pytest_asyncio
|
|
|
|
from scribe.models import async_session
|
|
from scribe.models.project import Project
|
|
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 — one subscribed, one not.
|
|
|
|
Both are ordinary rulebooks since milestone 394; the fixture used to flag
|
|
one always-on because that was a second, separate way to reach a project.
|
|
Keeping two is still worth it: a rulebook nobody subscribed to must
|
|
contribute nothing, and a fixture with only the subscribed one could not
|
|
tell "correctly scoped" from "returns everything".
|
|
"""
|
|
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")
|
|
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_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",
|
|
)
|
|
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",
|
|
)
|
|
# TAGGED TO AN AREA THIS PROJECT DOES NOT WORK IN, which is what makes the
|
|
# test able to fail at all. Since milestone 394 an UNTAGGED rule in a
|
|
# subscribed rulebook applies on its own, so an untagged partner arrives
|
|
# through the ordinary query and the edge is never exercised — the
|
|
# assertion below passed while proving nothing, which is how this was
|
|
# noticed. Tagging it puts it out of reach of everything except the edge.
|
|
area = await canonical_svc.find_by_name("CI & Release")
|
|
assert area is not None, "migration 0087 seeds the standard vocabulary"
|
|
await rulebooks_svc.set_rule_systems(partner.id, world["owner"], [area.id])
|
|
assert "Version names are labels" not in await _titles(world), (
|
|
"the partner must be unreachable on its own, or this test cannot fail"
|
|
)
|
|
|
|
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."""
|
|
# Untagged on purpose, unlike the partner above: this test is about the
|
|
# SUPPRESSION winning, so the partner should be one that would otherwise
|
|
# arrive by every available route — the ordinary query AND the edge.
|
|
partner = await rulebooks_svc.create_rule(
|
|
world["topic"], world["owner"], "Muted partner", "Should not arrive.",
|
|
)
|
|
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"]}
|