@@ -1504,6 +1504,67 @@ async def test_late_reference_drift_hook_blocks_reference_reads_after_artifacts_ |
| 1504 | 1504 | assert str(temp_dir / "guide") in result.message |
| 1505 | 1505 | |
| 1506 | 1506 | |
| 1507 | +@pytest.mark.asyncio |
| 1508 | +async def test_late_reference_drift_hook_allows_verification_reference_reads_after_artifacts_exist( |
| 1509 | + temp_dir: Path, |
| 1510 | +) -> None: |
| 1511 | + registry = create_default_registry(temp_dir) |
| 1512 | + policy = build_permission_policy( |
| 1513 | + active_mode=PermissionMode.WORKSPACE_WRITE, |
| 1514 | + workspace_root=temp_dir, |
| 1515 | + tool_requirements=registry.get_tool_requirements(), |
| 1516 | + ) |
| 1517 | + dod_store = DefinitionOfDoneStore(temp_dir) |
| 1518 | + dod = create_definition_of_done("Create a multi-file guide from a reference") |
| 1519 | + dod.status = "in_progress" |
| 1520 | + plan_path = temp_dir / "implementation.md" |
| 1521 | + plan_path.write_text( |
| 1522 | + "\n".join( |
| 1523 | + [ |
| 1524 | + "# Implementation Plan", |
| 1525 | + "", |
| 1526 | + "## File Changes", |
| 1527 | + f"- `{temp_dir / 'guide'}`", |
| 1528 | + f"- `{temp_dir / 'guide' / 'chapters'}`", |
| 1529 | + f"- `{temp_dir / 'guide' / 'index.html'}`", |
| 1530 | + f"- `{temp_dir / 'guide' / 'chapters' / '01-getting-started.html'}`", |
| 1531 | + f"- `{temp_dir / 'guide' / 'chapters' / '02-installation.html'}`", |
| 1532 | + "", |
| 1533 | + ] |
| 1534 | + ) |
| 1535 | + ) |
| 1536 | + dod.implementation_plan = str(plan_path) |
| 1537 | + guide_dir = temp_dir / "guide" / "chapters" |
| 1538 | + guide_dir.mkdir(parents=True, exist_ok=True) |
| 1539 | + (temp_dir / "guide" / "index.html").write_text("index") |
| 1540 | + (guide_dir / "01-getting-started.html").write_text("one") |
| 1541 | + (guide_dir / "02-installation.html").write_text("two") |
| 1542 | + dod_path = dod_store.save(dod) |
| 1543 | + session = FakeSession(active_dod_path=str(dod_path), messages=[]) |
| 1544 | + hook = LateReferenceDriftHook( |
| 1545 | + dod_store=dod_store, |
| 1546 | + project_root=temp_dir, |
| 1547 | + session=session, |
| 1548 | + ) |
| 1549 | + |
| 1550 | + result = await hook.pre_tool_use( |
| 1551 | + HookContext( |
| 1552 | + tool_call=ToolCall( |
| 1553 | + id="read-verify-1", |
| 1554 | + name="read", |
| 1555 | + arguments={"file_path": str(temp_dir / "reference" / "index.html")}, |
| 1556 | + ), |
| 1557 | + tool=registry.get("read"), |
| 1558 | + registry=registry, |
| 1559 | + permission_policy=policy, |
| 1560 | + source="verification", |
| 1561 | + ) |
| 1562 | + ) |
| 1563 | + |
| 1564 | + assert result.decision == HookDecision.CONTINUE |
| 1565 | + assert result.message is None |
| 1566 | + |
| 1567 | + |
| 1507 | 1568 | @pytest.mark.asyncio |
| 1508 | 1569 | async def test_late_reference_drift_hook_blocks_excessive_post_build_self_audits( |
| 1509 | 1570 | temp_dir: Path, |