"""Tests for semantic artifact invalidation and recovery selection.""" from __future__ import annotations from loader.runtime.artifact_invalidation import ( ArtifactInvalidationAssessor, WorkflowRecoveryStrategy, ) from loader.runtime.workflow import ArtifactEvidenceKind def test_artifact_invalidation_requests_plan_refresh_for_plan_only_drift() -> None: assessor = ArtifactInvalidationAssessor() freshness = assessor.assess( task_statement="Implement the runtime report artifact.", clarify_text=None, implementation_text="# Implementation Plan\n- Create report.md only\n", verification_text="# Verification Plan\n- report.md exists\n", acceptance_criteria=["report.md exists"], touched_files=["/tmp/notes.md"], last_verification_result=None, ) assert freshness.stale_plan is True assert freshness.stale_brief is False assert freshness.recovery_strategy == WorkflowRecoveryStrategy.PLAN_REFRESH.value assert "touched_files_outside_plan" in freshness.reason_codes assert any( item.kind == ArtifactEvidenceKind.CONFIRMED_TOUCHPOINT.value and "notes.md" in item.summary for item in freshness.evidence ) assert any( item.kind == ArtifactEvidenceKind.ACCEPTANCE_ANCHOR.value and "report.md exists" in item.summary for item in freshness.evidence ) def test_artifact_invalidation_can_force_full_replan_when_brief_and_plan_drift() -> None: assessor = ArtifactInvalidationAssessor() freshness = assessor.assess( task_statement="Improve Loader runtime workflow discipline.", clarify_text="# Task Brief\n\n## Desired Outcome\n- Improve Loader runtime workflow.\n", implementation_text="# Implementation Plan\n- Touch planned.txt only\n", verification_text="# Verification Plan\n## Acceptance Criteria\n- planned.txt exists.\n", acceptance_criteria=["notes.txt exists in the workspace root."], touched_files=["/tmp/notes.txt"], last_verification_result="failed", ) assert freshness.stale_plan is True assert freshness.stale_brief is True assert freshness.recovery_strategy == WorkflowRecoveryStrategy.FULL_REPLAN.value assert "touchpoints_outside_brief" in freshness.reason_codes assert "acceptance_criteria_outside_plan" in freshness.reason_codes assert any( item.kind == ArtifactEvidenceKind.VERIFICATION_CONTRADICTION.value and "notes.txt exists in the workspace root." in item.summary for item in freshness.evidence ) assert any( item.kind == ArtifactEvidenceKind.CONTRADICTED_ASSUMPTION.value and "notes.txt" in item.summary for item in freshness.evidence ) assert freshness.evidence_summary def test_artifact_invalidation_treats_path_separator_variants_as_same_touchpoint() -> None: assessor = ArtifactInvalidationAssessor() freshness = assessor.assess( task_statement="Build a multi-file nginx guide.", clarify_text=None, implementation_text=( "# Implementation Plan\n" "- Create 01-getting-started.html in the chapters directory.\n" ), verification_text=( "# Verification Plan\n" "## Acceptance Criteria\n" "- 01-getting-started.html exists.\n" ), acceptance_criteria=["01-getting-started.html exists."], touched_files=["/tmp/chapters/01_getting_started.html"], last_verification_result=None, ) assert freshness.stale_plan is False assert freshness.stale_brief is False assert "touched_files_outside_plan" not in freshness.reason_codes def test_artifact_invalidation_allows_supplemental_repair_files_after_failed_verification() -> None: assessor = ArtifactInvalidationAssessor() freshness = assessor.assess( task_statement="Build a multi-file nginx guide.", clarify_text=None, implementation_text=( "# Implementation Plan\n" "- Create index.html.\n" "- Create 01-getting-started.html.\n" "- Create 02-installation.html.\n" ), verification_text=( "# Verification Plan\n" "## Acceptance Criteria\n" "- index.html exists.\n" "- 01-getting-started.html exists.\n" "- 02-installation.html exists.\n" ), acceptance_criteria=[ "index.html exists.", "01-getting-started.html exists.", "02-installation.html exists.", ], touched_files=[ "/tmp/guides/nginx/index.html", "/tmp/guides/nginx/chapters/01-getting-started.html", "/tmp/guides/nginx/chapters/02-installation.html", "/tmp/guides/nginx/styles.css", ], last_verification_result="planned", retry_count=1, planned_artifacts_complete=True, ) assert freshness.stale_plan is False assert freshness.stale_brief is False assert freshness.recovery_strategy == WorkflowRecoveryStrategy.NONE.value assert "touched_files_outside_plan" not in freshness.reason_codes assert any( item.kind == ArtifactEvidenceKind.CONFIRMED_TOUCHPOINT.value and "styles.css" in item.summary for item in freshness.evidence ) def test_artifact_invalidation_treats_child_files_under_planned_directory_as_in_plan() -> None: assessor = ArtifactInvalidationAssessor() freshness = assessor.assess( task_statement="Build a multi-file nginx guide.", clarify_text=None, implementation_text=( "# Implementation Plan\n" "- Create `~/Loader/guides/nginx/index.html`.\n" "- Create `~/Loader/guides/nginx/chapters/`.\n" ), verification_text=( "# Verification Plan\n" "## Acceptance Criteria\n" "- `~/Loader/guides/nginx/index.html` exists.\n" "- Chapter files exist under `~/Loader/guides/nginx/chapters/`.\n" ), acceptance_criteria=[ "~/Loader/guides/nginx/index.html exists.", "Chapter files exist under ~/Loader/guides/nginx/chapters/.", ], touched_files=[ "/private/tmp/session/Loader/guides/nginx/index.html", "/private/tmp/session/Loader/guides/nginx/chapters/03-configuration.html", ], last_verification_result=None, ) assert freshness.stale_plan is False assert freshness.recovery_strategy == WorkflowRecoveryStrategy.NONE.value assert "touched_files_outside_plan" not in freshness.reason_codes def test_artifact_invalidation_keeps_root_level_sibling_files_out_of_plan() -> None: assessor = ArtifactInvalidationAssessor() freshness = assessor.assess( task_statement="Implement the runtime report artifact.", clarify_text=None, implementation_text=( "# Implementation Plan\n" "- Create `/tmp/session/planned.txt`.\n" ), verification_text=( "# Verification Plan\n" "## Acceptance Criteria\n" "- `/tmp/session/planned.txt` exists.\n" ), acceptance_criteria=["/tmp/session/planned.txt exists."], touched_files=["/tmp/session/notes.txt"], last_verification_result=None, ) assert freshness.stale_plan is True assert freshness.recovery_strategy == WorkflowRecoveryStrategy.PLAN_REFRESH.value assert "touched_files_outside_plan" in freshness.reason_codes