tenseleyflow/loader / fe0c9d8

Browse files

Allow verification reference reads

Authored by espadonne
SHA
fe0c9d8a23c467672901bd35398b8675113df646
Parents
dda85b4
Tree
e8a5800

2 changed files

StatusFile+-
M src/loader/runtime/hooks.py 2 0
M tests/test_permissions.py 61 0
src/loader/runtime/hooks.pymodified
@@ -830,6 +830,8 @@ class LateReferenceDriftHook(BaseToolHook):
830830
     async def pre_tool_use(self, context: HookContext) -> HookResult:
831831
         if context.tool_call.name not in _OBSERVATION_TOOLS:
832832
             return HookResult()
833
+        if context.source == "verification":
834
+            return HookResult()
833835
 
834836
         completed_scope = self._completed_artifact_scope()
835837
         if completed_scope is not None:
tests/test_permissions.pymodified
@@ -1504,6 +1504,67 @@ async def test_late_reference_drift_hook_blocks_reference_reads_after_artifacts_
15041504
     assert str(temp_dir / "guide") in result.message
15051505
 
15061506
 
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
+
15071568
 @pytest.mark.asyncio
15081569
 async def test_late_reference_drift_hook_blocks_excessive_post_build_self_audits(
15091570
     temp_dir: Path,