core/tests/test_compose_relations.py
Shay b5d6ad6510 feat(compositionality): compose_relations operator lifts lane 68.8% → 100%
Closes the residual `novel_pair_under_seen_relation` pattern that
neither `transitive_walk` nor `multi_relation_walk` could synthesise.

- new `compose_relations(triples, head, frame, relation)` operator —
  pure lookup, returns both `R(head, ?)` and `R(frame, ?)` tails
- new `FRAME_TRANSFER` intent + `_FRAME_TRANSFER_RE` regex tried
  before generic TRANSITIVE_QUERY so "in Y" isn't truncated; handles
  "X belong to in Y" → belongs_to normalisation
- pipeline wiring: `_maybe_compose_relations`, `_fold_compose_into_surface`,
  `_serialize_compose` (folded into operator_invocation so trace_hash
  stays bit-identical across replay)
- regression: inference_closure, multi_step_reasoning,
  cross_domain_transfer all still 100% on public + holdouts

discourse_paragraph v2:
- per-sentence grammar rubric (length, capitalization, subject
  alignment) gated on `require_per_sentence_grammar`
- scaling cases at 10 / 20 / 50 sentences — 3/3 pass, 100% per-sentence
- 3 runtime round-trip cases (`mode: runtime_roundtrip`) that prime
  vault, ask question, verify bit-identical across two fresh runtimes
- new `per_sentence_grammar_pass_rate` lane metric

Long-form replay benchmark (benchmarks/replay_vs_llm.py):
- `replay_determinism_report(prompts, runs, priming)` — CORE-only
- `compare_to_llm(prompts, llm_callable)` — BYO API client, no
  provider lock-in; reports per-prompt determinism on both sides
- ships with default cognition-pack prompts; 100% bit-identical at runs=3

Lanes green: cognition 121/121, runtime 19/19, teaching 17/17,
packs 6/6, compositionality 16/16 + 10/10, inference_closure 20/20 +
12/12, multi_step_reasoning 15/15 + 10/10, cross_domain_transfer
10/10 + 8/8, discourse_paragraph v1 12/12 + v2 6/6.
2026-05-16 22:44:06 -07:00

92 lines
3.3 KiB
Python

"""Unit tests for compose_relations operator and FRAME_TRANSFER intent.
Covers the compositionality lane's `novel_pair_under_seen_relation`
pattern: given R(A, a_val) and R(B, b_val), the probe "What does A R
in B?" should yield both tails.
"""
from __future__ import annotations
from generate.intent import IntentTag, classify_intent
from generate.operators import FrameComposeResult, compose_relations
class TestComposeRelations:
def test_returns_both_tails_when_both_edges_present(self):
triples = (
("truth", "grounds", "judgment"),
("knowledge", "grounds", "inference"),
)
result = compose_relations(
triples, head="truth", frame="knowledge", relation="grounds"
)
assert result.subject_tail == "judgment"
assert result.frame_tail == "inference"
def test_returns_none_for_missing_edge(self):
triples = (("truth", "grounds", "judgment"),)
result = compose_relations(
triples, head="truth", frame="knowledge", relation="grounds"
)
assert result.subject_tail == "judgment"
assert result.frame_tail is None
def test_case_insensitive_inputs(self):
triples = (("Truth", "Grounds", "Judgment"),)
result = compose_relations(
triples, head="TRUTH", frame="knowledge", relation="GROUNDS"
)
assert result.head == "truth"
assert result.subject_tail == "judgment"
def test_first_write_wins_for_duplicate_heads(self):
triples = (
("truth", "grounds", "judgment"),
("truth", "grounds", "second"),
)
result = compose_relations(
triples, head="truth", frame="truth", relation="grounds"
)
assert result.subject_tail == "judgment"
def test_pure_function_replay_deterministic(self):
triples = (
("truth", "grounds", "judgment"),
("knowledge", "grounds", "inference"),
)
a = compose_relations(triples, "truth", "knowledge", "grounds")
b = compose_relations(triples, "truth", "knowledge", "grounds")
assert a == b
def test_as_dict_is_json_safe(self):
result = FrameComposeResult(
head="truth",
frame="knowledge",
relation="grounds",
subject_tail="judgment",
frame_tail="inference",
)
d = result.as_dict()
assert d["head"] == "truth"
assert d["frame_tail"] == "inference"
class TestFrameTransferIntent:
def test_classifies_frame_transfer_form(self):
intent = classify_intent("What does truth ground in knowledge?")
assert intent.tag is IntentTag.FRAME_TRANSFER
assert intent.subject == "truth"
assert intent.relation == "grounds"
assert intent.frame == "knowledge"
def test_belong_to_in_form_normalises_to_belongs_to(self):
intent = classify_intent("What does recognition belong to in naming?")
assert intent.tag is IntentTag.FRAME_TRANSFER
assert intent.subject == "recognition"
assert intent.relation == "belongs_to"
assert intent.frame == "naming"
def test_does_not_match_single_entity_probe(self):
intent = classify_intent("What does wisdom precede?")
assert intent.tag is IntentTag.TRANSITIVE_QUERY
assert intent.frame is None