- Complete depth canonical, pack resolver DEPTH_PACKS, pipeline attrs+node_depths+graph_anti, contemplate/pass_manager/contracts propagation (immutable). - Polish anti_unifier, tests (oov, construction, percent), runtime/chat integration. - Governance: AGENTS.md updates, docs/README/handoff_template, skills bootstrap, retire handoff. - Leaves pickup seeds (NEW_SESSION_PROMPT, plan.md, compact) for continuity. - Spine tests green, 3lang depth visible in ctx for he. Refs: plan.md, 2026-07-06-compact.md
180 lines
6.5 KiB
Python
180 lines
6.5 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from generate.construction_affordances import (
|
|
ConstructionProposal,
|
|
all_diagnostic_families,
|
|
lookup_by_organ,
|
|
lookup_by_relation_type,
|
|
lookup_family,
|
|
propose_construction,
|
|
)
|
|
from generate.kernel_facts import SourceSpan
|
|
|
|
|
|
def test_catalog_entries_are_diagnostic_only_and_serving_forbidden() -> None:
|
|
families = all_diagnostic_families()
|
|
assert len(families) == 4
|
|
serving_authorized = {"proportional_change.decrease_to_fraction"}
|
|
for family in families:
|
|
if family.family_id in serving_authorized:
|
|
assert family.diagnostic_only is False
|
|
assert family.serving_allowed is True
|
|
else:
|
|
assert family.diagnostic_only is True
|
|
assert family.serving_allowed is False
|
|
|
|
|
|
def test_catalog_ordering_is_deterministic() -> None:
|
|
families = all_diagnostic_families()
|
|
ids = [f.family_id for f in families]
|
|
assert ids == sorted(ids)
|
|
assert ids == [
|
|
"binding.quantity_entity",
|
|
"partition.percent_partition",
|
|
"proportional_change.decrease_to_fraction",
|
|
"state_change.unary_delta",
|
|
]
|
|
|
|
|
|
def test_lookups_correctness() -> None:
|
|
quantity_entity = lookup_family("binding.quantity_entity")
|
|
assert quantity_entity is not None
|
|
assert lookup_by_organ("quantity_entity_binding") is quantity_entity
|
|
assert lookup_by_relation_type("quantity_entity") is quantity_entity
|
|
|
|
fraction_family = lookup_family("proportional_change.decrease_to_fraction")
|
|
assert fraction_family is not None
|
|
assert lookup_by_organ("fraction_decrease") is fraction_family
|
|
assert lookup_by_relation_type("decrease_to_fraction") is fraction_family
|
|
|
|
partition_family = lookup_family("partition.percent_partition")
|
|
assert partition_family is not None
|
|
assert lookup_by_organ("percent_partition") is partition_family
|
|
assert lookup_by_relation_type("percent_of") is partition_family
|
|
|
|
unary_delta = lookup_family("state_change.unary_delta")
|
|
assert unary_delta is not None
|
|
assert lookup_by_organ("unary_delta_transition") is unary_delta
|
|
assert lookup_by_relation_type("unary_delta") is unary_delta
|
|
|
|
assert lookup_family("invalid_family_id") is None
|
|
assert lookup_by_organ("invalid_organ") is None
|
|
assert lookup_by_relation_type("invalid_relation") is None
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("family_id", "relation_type", "candidate_organ", "required_roles"),
|
|
(
|
|
(
|
|
"binding.quantity_entity",
|
|
"quantity_entity",
|
|
"quantity_entity_binding",
|
|
{
|
|
"quantity",
|
|
"entity",
|
|
"quantity_kind",
|
|
"provenance_span",
|
|
"local_binding_relation",
|
|
},
|
|
),
|
|
(
|
|
"proportional_change.decrease_to_fraction",
|
|
"decrease_to_fraction",
|
|
"fraction_decrease",
|
|
{"base_quantity", "scale", "state_entity", "transition"},
|
|
),
|
|
(
|
|
"partition.percent_partition",
|
|
"percent_of",
|
|
"percent_partition",
|
|
{"whole", "part", "scale"},
|
|
),
|
|
(
|
|
"state_change.unary_delta",
|
|
"unary_delta",
|
|
"unary_delta_transition",
|
|
{"action_cue", "delta_quantity", "changed_object", "direction"},
|
|
),
|
|
),
|
|
)
|
|
def test_propose_construction_is_preassessment_and_catalog_backed(
|
|
family_id: str,
|
|
relation_type: str,
|
|
candidate_organ: str,
|
|
required_roles: set[str],
|
|
) -> None:
|
|
span = SourceSpan("surface cue", 0, 11)
|
|
|
|
proposal = propose_construction(family_id, (span,))
|
|
|
|
assert proposal.family_id == family_id
|
|
assert proposal.relation_type == relation_type
|
|
assert proposal.candidate_organ == candidate_organ
|
|
assert proposal.evidence_spans == (span,)
|
|
assert proposal.status == "proposed"
|
|
assert proposal.missing_roles == ()
|
|
assert proposal.active_hazards == ()
|
|
if family_id == "proportional_change.decrease_to_fraction":
|
|
assert proposal.diagnostic_only is False
|
|
assert proposal.serving_allowed is True
|
|
else:
|
|
assert proposal.diagnostic_only is True
|
|
assert proposal.serving_allowed is False
|
|
assert {role.role for role in proposal.role_obligations if role.required} == required_roles
|
|
|
|
|
|
def test_propose_construction_rejects_unknown_family_ids() -> None:
|
|
span = SourceSpan("surface cue", 0, 11)
|
|
|
|
with pytest.raises(KeyError):
|
|
propose_construction("invalid_family_id", (span,))
|
|
|
|
|
|
def test_construction_proposal_cannot_carry_assessment_authority() -> None:
|
|
span = SourceSpan("test text", 0, 9)
|
|
ConstructionProposal(
|
|
family_id="proportional_change.decrease_to_fraction",
|
|
relation_type="decrease_to_fraction",
|
|
candidate_organ="fraction_decrease",
|
|
evidence_spans=(span,),
|
|
status="proposed",
|
|
)
|
|
|
|
for status in ("partial", "closed", "refused"):
|
|
with pytest.raises(ValueError, match="must remain 'proposed'"):
|
|
ConstructionProposal(
|
|
family_id="proportional_change.decrease_to_fraction",
|
|
relation_type="decrease_to_fraction",
|
|
candidate_organ="fraction_decrease",
|
|
evidence_spans=(span,),
|
|
status=status, # type: ignore[arg-type]
|
|
)
|
|
|
|
assert "missing_roles" not in ConstructionProposal.__dataclass_fields__
|
|
assert "active_hazards" not in ConstructionProposal.__dataclass_fields__
|
|
|
|
with pytest.raises(TypeError, match="unexpected keyword argument 'missing_roles'"):
|
|
ConstructionProposal(
|
|
family_id="proportional_change.decrease_to_fraction",
|
|
relation_type="decrease_to_fraction",
|
|
candidate_organ="fraction_decrease",
|
|
evidence_spans=(span,),
|
|
status="proposed",
|
|
missing_roles=("base_quantity_unbound",),
|
|
)
|
|
|
|
def test_assess_with_depth_3lang():
|
|
# direct call with depth to exercise root-aware path (AC3)
|
|
from generate.problem_frame_contracts import assess_contracts
|
|
from generate.problem_frame_builder import build_problem_frame
|
|
import inspect
|
|
sig = inspect.signature(assess_contracts)
|
|
assert 'depth' in sig.parameters
|
|
# real frame + depth, assert param used and no crash (real call)
|
|
frame = build_problem_frame("A school has 100 students.")
|
|
assessments = assess_contracts(frame, depth={"n": {"root": "test-root"}})
|
|
assert isinstance(assessments, tuple)
|
|
# real root note from enrich
|
|
assert any("[root:test-root]" in (getattr(a, "explanation", "") or "") for a in assessments)
|