from __future__ import annotations import pytest from gap_pipeline.kernel_models import ( DiffusedProof, JudgeVerdict, MethodPlan, ProofDAG, RenderedVariant, ReplacementPlan, ) def branched_dag() -> ProofDAG: return ProofDAG.model_validate( { "nodes": [ {"node_id": "n1", "claim": "first fact", "dependencies": []}, {"node_id": "n2", "claim": "left branch", "dependencies": ["n1"]}, {"node_id": "n3", "claim": "right branch", "dependencies": ["n1"]}, { "node_id": "n4", "claim": "combine branches", "dependencies": ["n2", "n3"], }, ], "terminal_node_id": "n4", } ) def test_proof_dag_supports_real_branching() -> None: dag = branched_dag() assert dag.nodes[-1].dependencies == ["n2", "n3"] def test_replacement_must_reference_a_real_dag_node() -> None: replacements = ReplacementPlan.model_validate( { "changes": [ { "slot_id": "slot1", "source_node_id": "n9", "description": "constant", "original_value": "1", "replacement_value": "2", "guard_condition": "positive", "guard_justification": "2 is positive", } ], "closure_statement": "No undeclared changes.", } ) with pytest.raises(ValueError, match="unknown nodes"): replacements.validate_against(branched_dag()) def test_replacement_must_target_a_source_leaf() -> None: replacements = ReplacementPlan.model_validate( { "changes": [ { "slot_id": "slot1", "source_node_id": "n2", "description": "derived value", "original_value": "1", "replacement_value": "2", "guard_condition": "positive", "guard_justification": "2 is positive", } ], "closure_statement": "No undeclared changes.", } ) with pytest.raises(ValueError, match="must target source leaf nodes.*n2"): replacements.validate_against(branched_dag()) def test_replacement_slot_id_matches_release_schema() -> None: with pytest.raises(ValueError, match="must be slot1, slot2"): ReplacementPlan.model_validate( { "changes": [ { "slot_id": "s1", "source_node_id": "n1", "description": "constant", "original_value": "1", "replacement_value": "2", "guard_condition": "positive", "guard_justification": "2 is positive", } ], "closure_statement": "No undeclared changes.", } ) def test_replacement_repair_preserves_slot_and_leaf_identity() -> None: previous = ReplacementPlan.model_validate( { "changes": [ { "slot_id": "slot1", "source_node_id": "n1", "description": "constant", "original_value": "1", "replacement_value": "2", "guard_condition": "positive", "guard_justification": "2 is positive", } ], "closure_statement": "No undeclared changes.", } ) changed_target = previous.model_copy(deep=True) changed_target.changes[0].source_node_id = "n2" with pytest.raises(ValueError, match="preserve replacement slot IDs"): changed_target.validate_repair_of(previous) def test_diffusion_preserves_dependencies_and_methods() -> None: dag = branched_dag() methods = MethodPlan.model_validate( { "nodes": [ {"node_id": node.node_id, "method_label": f"method {node.node_id}"} for node in dag.nodes ] } ) diffused = DiffusedProof.model_validate( { "nodes": [ { "node_id": node.node_id, "dependencies": node.dependencies, "method_label": f"method {node.node_id}", "instantiated_claim": f"new {node.claim}", "justification": "valid re-instantiation", } for node in dag.nodes ], "terminal_node_id": "n4", "terminal_answer": "answer", } ) assert diffused.validate_against(dag, methods) is diffused broken = diffused.model_copy(deep=True) broken.nodes[-1].dependencies = ["n3"] with pytest.raises(ValueError, match="dependencies changed"): broken.validate_against(dag, methods) def test_rendered_solution_must_expose_every_node() -> None: dag = branched_dag() methods = MethodPlan.model_validate( { "nodes": [ {"node_id": node.node_id, "method_label": f"method {node.node_id}"} for node in dag.nodes ] } ) diffused = DiffusedProof.model_validate( { "nodes": [ { "node_id": node.node_id, "dependencies": node.dependencies, "method_label": f"method {node.node_id}", "instantiated_claim": f"new {node.claim}", "justification": "valid", } for node in dag.nodes ], "terminal_node_id": "n4", "terminal_answer": "answer", } ).validate_against(dag, methods) variant = RenderedVariant( question="New problem", solution="[n1] first [n2] left [n3] right", node_order=dag.node_ids(), terminal_answer="answer", ) with pytest.raises(ValueError, match="missing node markers.*n4"): variant.validate_against(dag, diffused) def test_accept_verdict_normalizes_explicit_none_sentinel() -> None: verdict = JudgeVerdict( verdict="accept", step_by_step_check="n1 valid", blocking_issues="None detected.", patch_suggestion="N/A", ) assert verdict.blocking_issues == "" assert verdict.patch_suggestion == ""