test_direction_artifact.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. from __future__ import annotations
  2. import pytest
  3. from script_build_host.domain.artifacts import (
  4. ArtifactKind,
  5. DirectionArtifact,
  6. DirectionConstraint,
  7. DirectionGoal,
  8. DirectionPreference,
  9. )
  10. from script_build_host.domain.errors import ProtocolViolation
  11. from script_build_host.repositories.sqlalchemy import _hydrate_artifact
  12. from script_build_host.tools.registry import _save_direction_candidate_schema
  13. EVIDENCE_REF = "script-build://artifact-versions/1"
  14. def _goal(index: int, *, parent: str | None = None) -> DirectionGoal:
  15. return DirectionGoal(
  16. goal_id=f"goal-{index}",
  17. parent_goal_id=parent,
  18. statement=f"goal statement {index}",
  19. rationale=f"goal rationale {index}",
  20. success_criteria=(f"goal criterion {index}",),
  21. )
  22. def test_direction_supports_hierarchy_rules_and_deterministic_projection() -> None:
  23. direction = DirectionArtifact(
  24. goals=(_goal(1), _goal(2, parent="goal-1"), _goal(3)),
  25. constraints=(
  26. DirectionConstraint("constraint-1", "facts remain traceable", "hard boundary"),
  27. ),
  28. preferences=(DirectionPreference("preference-1", "prefer a concise opening", priority=1),),
  29. evidence_refs=(EVIDENCE_REF,),
  30. )
  31. payload = direction.content_payload()
  32. assert payload["schema_version"] == "script-direction/v1"
  33. assert payload["goals"][1]["parent_goal_id"] == "goal-1"
  34. assert payload["constraints"][0]["constraint_id"] == "constraint-1"
  35. assert payload["preferences"][0]["priority"] == 1
  36. assert "### goal-1: goal statement 1" in direction.legacy_markdown
  37. assert "#### goal-2: goal statement 2" in direction.legacy_markdown
  38. assert "## Constraints" in direction.legacy_markdown
  39. assert "## Preferences" in direction.legacy_markdown
  40. assert EVIDENCE_REF in direction.legacy_markdown
  41. def test_direction_accepts_thirty_goal_nodes_and_rejects_thirty_one() -> None:
  42. DirectionArtifact(
  43. goals=tuple(_goal(index) for index in range(1, 31)), evidence_refs=(EVIDENCE_REF,)
  44. )
  45. with pytest.raises(ValueError, match="between one and thirty"):
  46. DirectionArtifact(
  47. goals=tuple(_goal(index) for index in range(1, 32)),
  48. evidence_refs=(EVIDENCE_REF,),
  49. )
  50. @pytest.mark.parametrize(
  51. ("goals", "message"),
  52. [
  53. ((_goal(1, parent="missing"),), "parent must exist"),
  54. (
  55. (
  56. _goal(1),
  57. _goal(2, parent="goal-1"),
  58. _goal(3, parent="goal-2"),
  59. ),
  60. "only two levels",
  61. ),
  62. (
  63. (DirectionGoal("goal-1", "statement", "rationale", success_criteria=()),),
  64. "requires success criteria",
  65. ),
  66. (
  67. (DirectionGoal("goal-1", "statement", "", success_criteria=("check",)),),
  68. "requires a rationale",
  69. ),
  70. ],
  71. )
  72. def test_direction_rejects_invalid_goal_hierarchy(
  73. goals: tuple[DirectionGoal, ...], message: str
  74. ) -> None:
  75. with pytest.raises(ValueError, match=message):
  76. DirectionArtifact(goals=goals, evidence_refs=(EVIDENCE_REF,))
  77. def test_direction_tool_contract_keeps_schema_name_and_removes_model_markdown() -> None:
  78. parameters = _save_direction_candidate_schema()["function"]["parameters"]
  79. assert parameters["properties"]["goals"]["maxItems"] == 30
  80. assert "constraints" in parameters["required"]
  81. assert "preferences" in parameters["required"]
  82. assert "legacy_markdown" not in parameters["properties"]
  83. assert "criteria" not in parameters["properties"]
  84. def test_direction_hydration_rejects_historical_structure_without_compatibility() -> None:
  85. historical_payload = {
  86. "schema_version": "script-direction/v1",
  87. "goals": [{"goal_id": "goal-1", "statement": "old goal", "rationale": "old rationale"}],
  88. "criteria": [{"criterion_id": "criterion-1", "description": "old criterion"}],
  89. "domain_criteria": [],
  90. "evidence_refs": [EVIDENCE_REF],
  91. "legacy_markdown": "# old projection",
  92. }
  93. with pytest.raises(ProtocolViolation, match="current structured contract"):
  94. _hydrate_artifact(ArtifactKind.DIRECTION, historical_payload, "sha256:" + "a" * 64)