from __future__ import annotations import pytest from script_build_host.domain.artifacts import DirectionConstraint from script_build_host.domain.task_contracts import ( PlannerCriterionInput, ScriptTaskKind, TaskContractError, ) from script_build_host.domain.task_policy import TaskPolicyCatalog def test_task_policy_injects_protected_kind_and_direction_constraint_criteria() -> None: catalog = TaskPolicyCatalog() criteria = catalog.criteria( task_id="paragraph-task", task_kind=ScriptTaskKind.PARAGRAPH, business=( PlannerCriterionInput( client_key="business-quality", description="the opening is memorable", hard=False, ), ), direction_constraints=( DirectionConstraint( constraint_id="facts-traceable", statement="Every factual claim remains traceable", rationale="hard publication rule", ), ), ) assert criteria[0].criterion_id == "paragraph-content-complete" assert criteria[0].hard is True assert criteria[1].criterion_id == "direction-constraint-facts-traceable" assert criteria[1].hard is True assert criteria[2].description == "the opening is memorable" assert criteria[2].hard is False def test_retrieval_criteria_are_typed_and_host_rendered() -> None: criteria = TaskPolicyCatalog().criteria( task_id="external", task_kind=ScriptTaskKind.EXTERNAL_RETRIEVAL, business=( PlannerCriterionInput( client_key="coverage", criterion_type="concept_coverage", params={"concepts": ["占器械", "玩手机", "双重标准"], "minimum_matches": 2}, ), ), ) assert criteria[1].description == "Evidence covers at least 2 of: 占器械, 玩手机, 双重标准" def test_retrieval_rejects_free_text_synthesis_contract_before_dispatch() -> None: with pytest.raises(TaskContractError, match="TASK_CAPABILITY_MISMATCH"): TaskPolicyCatalog().criteria( task_id="external", task_kind=ScriptTaskKind.EXTERNAL_RETRIEVAL, business=( PlannerCriterionInput( client_key="persona-analysis", description="write a persona analysis and element mapping document", ), ), )