tenseleyflow/loader / a8b3bca

Browse files

Clarify declared HTML link recovery

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
a8b3bca17a53734d51790aa3d7cef85b74bafdad
Parents
eabd30b
Tree
d31745d

4 changed files

StatusFile+-
M src/loader/runtime/safeguard_services.py 37 0
M src/loader/runtime/tool_batches.py 12 1
M tests/test_safeguard_services.py 5 0
M tests/test_tool_batches.py 5 3
src/loader/runtime/safeguard_services.pymodified
@@ -2,6 +2,7 @@
22
 
33
 from __future__ import annotations
44
 
5
+import os
56
 import re
67
 import shlex
78
 from dataclasses import dataclass
@@ -1535,6 +1536,16 @@ class PreActionValidator:
15351536
             )
15361537
         if declared_preview:
15371538
             suggestion += f". Already-declared local targets include: {declared_preview}"
1539
+        allowed_hrefs = self._declared_html_hrefs_for_file(
1540
+            root,
1541
+            normalized,
1542
+            declared_targets,
1543
+        )
1544
+        if allowed_hrefs:
1545
+            allowed_preview = ", ".join(allowed_hrefs[:6])
1546
+            if len(allowed_hrefs) > 6:
1547
+                allowed_preview += ", ..."
1548
+            suggestion += f". Allowed hrefs from this file include: {allowed_preview}"
15381549
         declared_suggestions = self._suggest_declared_html_targets(
15391550
             declared_targets,
15401551
             undeclared_targets,
@@ -1787,6 +1798,32 @@ class PreActionValidator:
17871798
                 suggestions.append(candidate)
17881799
 
17891800
         return suggestions
1801
+
1802
+    def _declared_html_hrefs_for_file(
1803
+        self,
1804
+        root: Path,
1805
+        file_path: Path,
1806
+        declared_targets: set[str],
1807
+    ) -> list[str]:
1808
+        try:
1809
+            source_directory = file_path.parent.resolve(strict=False)
1810
+            root_index = (root / "index.html").resolve(strict=False)
1811
+        except OSError:
1812
+            source_directory = file_path.parent.expanduser()
1813
+            root_index = (root / "index.html").expanduser()
1814
+
1815
+        hrefs: list[str] = []
1816
+        if file_path.name.lower() != "index.html":
1817
+            hrefs.append(os.path.relpath(root_index, source_directory).replace(os.sep, "/"))
1818
+
1819
+        for target in sorted(declared_targets):
1820
+            target_path = (root / target).resolve(strict=False)
1821
+            href = os.path.relpath(target_path, source_directory).replace(os.sep, "/")
1822
+            if href == "." or href in hrefs:
1823
+                continue
1824
+            hrefs.append(href)
1825
+        return hrefs
1826
+
17901827
     def _validate_path(self, file_path: str) -> ValidationResult:
17911828
         if '\x00' in file_path:
17921829
             return ValidationResult(
src/loader/runtime/tool_batches.pymodified
@@ -1258,12 +1258,23 @@ class ToolBatchRunner:
12581258
             event_content,
12591259
             "Already-declared local targets include:",
12601260
         )
1261
+        allowed_hrefs = _extract_blocked_html_target_list(
1262
+            event_content,
1263
+            "Allowed hrefs from this file include:",
1264
+        )
12611265
 
12621266
         guidance = (
12631267
             "That HTML mutation introduced sibling targets outside the current declared local-link set. "
12641268
             f"Stay on `{target}`."
12651269
         )
1266
-        if closest_targets:
1270
+        if allowed_hrefs:
1271
+            guidance += (
1272
+                " Remove the invented hrefs and use only these exact href values "
1273
+                "from this file, or omit navigation links entirely: "
1274
+                + ", ".join(f"`{candidate}`" for candidate in allowed_hrefs[:6])
1275
+                + "."
1276
+            )
1277
+        elif closest_targets:
12671278
             guidance += (
12681279
                 " Remove the invented hrefs or replace them with the closest declared target(s): "
12691280
                 + ", ".join(f"`{candidate}`" for candidate in closest_targets[:3])
tests/test_safeguard_services.pymodified
@@ -1078,6 +1078,9 @@ def test_pre_action_validator_blocks_chapter_write_with_undeclared_missing_sibli
10781078
         == "HTML page introduces new local targets outside the current declared artifact set"
10791079
     )
10801080
     assert "advanced.html" in result.suggestion
1081
+    assert "Allowed hrefs from this file include:" in result.suggestion
1082
+    assert "../index.html" in result.suggestion
1083
+    assert "installation.html" in result.suggestion
10811084
 
10821085
 
10831086
 def test_pre_action_validator_blocks_chapter_write_with_existing_but_undeclared_sibling(
@@ -1121,6 +1124,8 @@ def test_pre_action_validator_blocks_chapter_write_with_existing_but_undeclared_
11211124
     )
11221125
     assert "04-locations-and-servers.html" in result.suggestion
11231126
     assert "04-advanced-configuration.html" in result.suggestion
1127
+    assert "Allowed hrefs from this file include:" in result.suggestion
1128
+    assert "../index.html" in result.suggestion
11241129
 
11251130
 
11261131
 def test_pre_action_validator_does_not_suggest_unrelated_declared_html_target(
tests/test_tool_batches.pymodified
@@ -7812,13 +7812,15 @@ def test_tool_batch_runner_blocked_html_declared_target_nudge_without_close_matc
78127812
             "introducing new sibling targets that the guide root does not declare; remove or replace "
78137813
             "undeclared hrefs like: troubleshooting.html. "
78147814
             "Already-declared local targets include: chapters/introduction.html, chapters/installation.html, "
7815
-            "chapters/configuration.html."
7815
+            "chapters/configuration.html. Allowed hrefs from this file include: ../index.html, "
7816
+            "installation.html, configuration.html."
78167817
         ),
78177818
     )
78187819
 
78197820
     assert queued
7820
-    assert "Remove the invented hrefs or keep local links within the declared target set" in queued[0]
7821
-    assert "`chapters/installation.html`" in queued[0]
7821
+    assert "use only these exact href values" in queued[0]
7822
+    assert "`installation.html`" in queued[0]
7823
+    assert "`../index.html`" in queued[0]
78227824
     assert "closest declared target(s)" not in queued[0]
78237825
 
78247826