Files
FabledGrimoire/tests/test_family_adoption.py
T
bvandeusenandClaude Opus 5.5 79de8938b9 docs(6209): forty test files — design cascade and stylesheet, embeddings, family canon, forge, backup round trips, inception, placement, platforms, moments, rule move and verification, search scope, facets
Module and test docstrings state what is pinned without the record number
or step that introduced it. Incident accounts become the failure shape in
the present tense: the pipefail head, the code_shapes import, the export
order, the facet dialects, the cross-kind repeat, the write-once kind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:40:25 -04:00

187 lines
8.3 KiB
Python

"""The adoption ledger without a database.
The parts that decide are pure and pinned here: what an assessment needs
before it can be recorded, what each ground of the conflict order needs —
including that every ground ABOVE the deciding one was said not to apply —
when an answer needs a recheck, how a losing side is folded into the note,
and which reference an owed task points at. Beside them: the outcomes and
the order the agent is told are the ones enforced. The state machine against
Postgres is in tests/test_integration_family_adoption.py.
"""
from __future__ import annotations
import pytest
from scribe.mcp.server import _READ_ONLY_TOOLS, _WRITE_TOOLS
from scribe.services.family_adoption import (
CONFLICT_KEYS, CONFLICT_ORDER, OUTCOME_KEYS, _fold_section, _reference_line,
assessment_problems, conflict_problems, needs_recheck,
)
from tests.helpers import tool_doc
# --- what an assessment needs ------------------------------------------------------
@pytest.mark.parametrize("outcome", OUTCOME_KEYS)
def test_every_outcome_needs_a_reason(outcome):
problems = assessment_problems(outcome=outcome, reason=" ", evidence=["src/x.py"])
assert len(problems) == 1 and outcome in problems[0]
def test_adopted_needs_evidence_naming_where():
assert assessment_problems(outcome="adopted", reason="done", evidence=[]) == [
"adopted needs evidence naming where the project does it"]
assert assessment_problems(outcome="adopted", reason="done", evidence=[" "]) != []
assert assessment_problems(outcome="adopted", reason="done", evidence=["ci run 12"]) == []
@pytest.mark.parametrize("outcome", ("exempt", "variant", "owed"))
def test_the_other_outcomes_need_no_evidence(outcome):
assert assessment_problems(outcome=outcome, reason="a fact", evidence=None) == []
def test_an_unknown_outcome_is_refused_by_name():
problems = assessment_problems(outcome="ignored", reason="x", evidence=[])
assert problems and all(k in problems[0] for k in OUTCOME_KEYS)
# --- the conflict order, branch by branch ----------------------------------------
def _checked(upto: str) -> dict:
return {k: f"{k} did not decide" for k in CONFLICT_KEYS[:CONFLICT_KEYS.index(upto)]}
GOOD = {
"operator_stance": dict(evidence=["rule: one keystore per app"]),
"covers_failure": dict(evidence=["incident #12: update refused, signature mismatch"]),
"split_by_condition": dict(conditions={"canon": "the app self-updates",
"other": "a store installs it"}),
"most_recent_complete": dict(evidence=["verified on a device 2026-10-01"]),
}
@pytest.mark.parametrize("ground", CONFLICT_KEYS)
def test_each_ground_holds_when_its_needs_are_met(ground):
kw = {"evidence": None, "conditions": None, **GOOD[ground]}
assert conflict_problems(ground=ground, grounds_checked=_checked(ground),
fold="their reasoning", **kw) == []
@pytest.mark.parametrize("ground", CONFLICT_KEYS[1:])
def test_a_ground_is_refused_while_any_ground_above_it_is_unanswered(ground):
above = CONFLICT_KEYS[:CONFLICT_KEYS.index(ground)]
for skipped in above:
checked = {k: v for k, v in _checked(ground).items() if k != skipped}
problems = conflict_problems(ground=ground, grounds_checked=checked,
fold="x", **{"evidence": None, "conditions": None,
**GOOD[ground]})
assert len(problems) == 1 and f"'{skipped}'" in problems[0]
def test_the_first_ground_needs_nothing_checked_above_it():
assert conflict_problems(ground="operator_stance", grounds_checked=None, fold="x",
evidence=["rule 4"], conditions=None) == []
@pytest.mark.parametrize("ground", ("operator_stance", "covers_failure", "most_recent_complete"))
def test_grounds_one_two_and_four_need_their_evidence(ground):
problems = conflict_problems(ground=ground, grounds_checked=_checked(ground), fold="x",
evidence=[" "], conditions=None)
assert len(problems) == 1 and "evidence" in problems[0]
def test_a_split_needs_both_conditions():
for conds in (None, {"canon": "self-updates"}, {"canon": "", "other": "store"}):
problems = conflict_problems(ground="split_by_condition",
grounds_checked=_checked("split_by_condition"),
fold="x", evidence=None, conditions=conds)
assert len(problems) == 1 and "conditions" in problems[0]
def test_the_losing_reasoning_is_required():
problems = conflict_problems(ground="operator_stance", grounds_checked=None, fold=" ",
evidence=["rule 4"], conditions=None)
assert len(problems) == 1 and "never dropped" in problems[0]
def test_an_unknown_ground_names_the_order():
problems = conflict_problems(ground="seniority", grounds_checked=None, fold="x",
evidence=[], conditions=None)
assert problems == [f"ground must be one of, in order: {', '.join(CONFLICT_KEYS)}"]
# --- recheck -------------------------------------------------------------------------
@pytest.mark.parametrize("row_status,row_version,idea_status,idea_version,expected", [
("adopted", 1, "canon", 2, True),
("owed", 1, "canon", 2, True),
("adopted", 2, "canon", 2, False),
# An undone revision leaves answers AHEAD of the idea: still not agreeing.
("adopted", 3, "canon", 2, True),
("unassessed", None, "canon", 2, False),
("adopted", 1, "retired", 2, False),
])
def test_needs_recheck(row_status, row_version, idea_status, idea_version, expected):
assert needs_recheck(row_status, row_version, idea_status, idea_version) is expected
# --- folding the losing side into the note ------------------------------------------
@pytest.mark.parametrize("ground,heading", [
("operator_stance", "### Alternative — two's approach"),
("covers_failure", "### Trap — two's approach"),
("most_recent_complete", "### Alternative — two's approach"),
("split_by_condition", "### When a store installs it — two's approach"),
])
def test_the_losing_side_is_folded_as_its_ground_says(ground, heading):
text = _fold_section(ground=ground, fold="Their reasoning.",
conditions=GOOD["split_by_condition"]["conditions"],
canon_title="one", other_title="two", when="2026-10-06")
assert text.startswith(heading)
assert "Their reasoning." in text and "one's approach" in text
# --- the reference an owed task points at -----------------------------------------
REFS = [
{"id": 10, "title": "Update installer", "language": "kotlin"},
{"id": 11, "title": "Update installer", "language": "dart"},
]
def test_the_reference_in_the_projects_language_is_named():
line = _reference_line(REFS, ["dart"], idea_id=5)
assert "#11" in line and "#10" not in line
def test_no_reference_in_the_language_names_the_ones_that_exist():
line = _reference_line(REFS, ["python"], idea_id=5)
assert "python" in line and "#10" in line and "#11" in line
def test_no_reference_at_all_points_at_the_idea():
assert "#5" in _reference_line([], ["python"], idea_id=5)
# --- the product text and the doors ---------------------------------------------------
def test_the_outcomes_the_agent_is_told_are_the_ones_enforced():
"""The outcomes and the order are product text. The tool
docstrings must name every outcome and every ground the service checks,
in the service's order."""
assess_doc = tool_doc("scribe.mcp.tools.family", "assess_family_adoption")
for n, key in enumerate(OUTCOME_KEYS, start=1):
assert f"{n}. {key} —" in assess_doc, f"outcome {n} is not taught as {key}"
resolve_doc = tool_doc("scribe.mcp.tools.family", "resolve_family_conflict")
for n, key in enumerate(CONFLICT_KEYS, start=1):
assert f"{n}. {key} —" in resolve_doc, f"ground {n} is not taught as {key}"
assert [c["fold_as"] for c in CONFLICT_ORDER] == ["alternative", "trap", "condition", "alternative"]
def test_every_adoption_tool_is_classified():
reads = {"get_family_adoption", "list_family_adoptions"}
writes = {"assess_family_adoption", "resolve_family_conflict", "revise_family_idea",
"set_family_references", "set_family_topic"}
assert reads <= _READ_ONLY_TOOLS
assert writes <= _WRITE_TOOLS