From 53379e40f207a55a1f2242848c50ead3072ac50f Mon Sep 17 00:00:00 2001 From: Shay Date: Tue, 19 May 2026 11:11:45 -0700 Subject: [PATCH] test(grounding): pin source-order contract for discourse adapter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sidecar characterization that freezes the deterministic source ordering of the existing aggregated teaching index, cross-pack chains, and narrative/example composer outputs. No dependency on the discourse planner contract — this is the bridge that protects the next two phases (ResponseMode classification + structured GroundedFact accessors) from source-order drift. 5 tests pin: aggregated teaching index key order, cross-pack subject and object views, narrative composer source ordering, example composer source ordering. Authored in worktree 3721; landed here so the main-line sequencing (characterization -> ResponseMode -> accessors -> planner -> wiring) can proceed against a stable substrate. --- .../test_grounding_source_characterization.py | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 tests/test_grounding_source_characterization.py diff --git a/tests/test_grounding_source_characterization.py b/tests/test_grounding_source_characterization.py new file mode 100644 index 00000000..cd16861e --- /dev/null +++ b/tests/test_grounding_source_characterization.py @@ -0,0 +1,155 @@ +"""Grounding-source characterization for future discourse planning. + +These tests deliberately avoid the discourse planner contract. They freeze +the deterministic source ordering that a future GroundingBundle adapter can +consume without re-learning the current surface composers' hidden order. +""" + +from __future__ import annotations + +from chat.cross_pack_grounding import ( + cross_pack_chains_for_object, + cross_pack_chains_for_subject, +) +from chat.example_surface import example_grounded_surface +from chat.narrative_surface import narrative_grounded_surface +from chat.teaching_grounding import _all_chains_index + + +def test_teaching_chain_index_has_stable_key_order_for_discourse_sources() -> None: + """The aggregated teaching view is keyed by stable subject/intent pairs. + + A future discourse adapter may sort these keys before selecting support + and relation moves. Pinning representative keys protects that substrate + without depending on any new DiscoursePlan type. + """ + chains = _all_chains_index() + observed = [ + (key, chain.chain_id, chain.corpus_id, chain.connective, chain.object) + for key, chain in sorted(chains.items()) + if key[0] in {"knowledge", "light", "memory", "mother", "parent", "truth"} + ] + + assert observed == [ + ( + ("knowledge", "cause"), + "cause_knowledge_requires_evidence", + "cognition_chains_v1", + "requires", + "evidence", + ), + ( + ("light", "cause"), + "cause_light_reveals_truth", + "cognition_chains_v1", + "reveals", + "truth", + ), + ( + ("light", "verification"), + "verification_light_reveals_truth", + "cognition_chains_v1", + "reveals", + "truth", + ), + ( + ("memory", "verification"), + "verification_memory_requires_recall", + "cognition_chains_v1", + "requires", + "recall", + ), + ( + ("mother", "cause"), + "cause_mother_precedes_daughter", + "relations_chains_v2", + "precedes", + "daughter", + ), + ( + ("parent", "cause"), + "cause_parent_precedes_child", + "relations_chains_v1", + "precedes", + "child", + ), + ( + ("truth", "cause"), + "cause_truth_grounds_knowledge", + "cognition_chains_v1", + "grounds", + "knowledge", + ), + ( + ("truth", "verification"), + "verification_truth_requires_evidence", + "cognition_chains_v1", + "requires", + "evidence", + ), + ] + + +def test_cross_pack_subject_helpers_return_canonical_discourse_order() -> None: + """Cross-pack subject helpers sort by ``(intent, connective, object)``.""" + family = cross_pack_chains_for_subject("family") + parent = cross_pack_chains_for_subject("parent") + + assert [(c.chain_id, c.intent, c.connective, c.object) for c in family] == [ + ("cause_family_grounds_identity", "cause", "grounds", "identity"), + ("cause_family_supports_memory", "cause", "supports", "memory"), + ] + assert [(c.chain_id, c.intent, c.connective, c.object) for c in parent] == [ + ("cause_parent_grounds_understanding", "cause", "grounds", "understanding"), + ] + + +def test_cross_pack_object_helpers_return_canonical_discourse_order() -> None: + """Cross-pack object helpers sort by ``(intent, subject, connective)``.""" + parent = cross_pack_chains_for_object("parent") + family = cross_pack_chains_for_object("family") + + assert [(c.chain_id, c.intent, c.subject, c.connective) for c in parent] == [ + ( + "verification_understanding_requires_parent", + "verification", + "understanding", + "requires", + ), + ] + assert [(c.chain_id, c.intent, c.subject, c.connective) for c in family] == [ + ( + "verification_identity_requires_family", + "verification", + "identity", + "requires", + ), + ] + + +def test_narrative_clause_order_is_stable_for_mixed_sources() -> None: + """Narrative surfaces expose the order future relation moves must preserve.""" + surface = narrative_grounded_surface("family", max_clauses=8) + assert surface is not None + + assert surface.index("family grounds identity") < surface.index( + "family grounds parent" + ) + assert surface.index("family grounds parent") < surface.index( + "family supports memory" + ) + assert "cross_pack_chains_v1 + relations_chains_v1" in surface + + +def test_example_clause_order_is_stable_for_mixed_sources() -> None: + """Example surfaces expose reverse-chain order for future example moves.""" + surface = example_grounded_surface("parent", max_examples=8) + assert surface is not None + + assert surface.index("child follows parent") < surface.index( + "family grounds parent" + ) + assert surface.index("family grounds parent") < surface.index( + "understanding requires parent" + ) + assert "cross_pack_chains_v1 + relations_chains_v1" in surface