From f3b32d54a7c3a797b1cc60c75dfc3c1986cedba2 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 30 Oct 2026 07:59:46 -0700 Subject: [PATCH 1/2] Let the Read Be the Check in the checks/ Gates That Probe First The carried Operations and Failure Messages rule says to attempt the operation and turn its failure into a clean refusal, rather than predicting it with an existence test. Each of these gates read a file after an exists() or is_file() probe. Each now reads directly and catches FileNotFoundError, keeping the message the probe gave. The robots.txt read moves ahead of the origin lookup so its except clause can hold the missing-file refusal, and its comment moves with it. Co-Authored-By: Claude Opus 5.5 --- checks/env_docs_gate.py | 6 +++--- checks/media_redactions_gate.py | 8 +++++--- checks/text_pii_gate.py | 13 ++++++++----- checks/url_parity_gate.py | 14 ++++++++------ 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/checks/env_docs_gate.py b/checks/env_docs_gate.py index 96ccb275..40497a70 100755 --- a/checks/env_docs_gate.py +++ b/checks/env_docs_gate.py @@ -80,11 +80,11 @@ def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=(__doc__ or "").partition("\n")[0]) parser.parse_args(argv) - if not DOC.exists(): + try: + doc_text = DOC.read_text(encoding="utf-8") + except FileNotFoundError: print(f"ERROR: {DOC.name} does not exist", file=sys.stderr) return 1 - - doc_text = DOC.read_text(encoding="utf-8") documented_rows = set(DOC_ROW.findall(doc_text)) documented_any = documented_rows | set(DOC_INLINE.findall(doc_text)) diff --git a/checks/media_redactions_gate.py b/checks/media_redactions_gate.py index a6779939..111aefe5 100755 --- a/checks/media_redactions_gate.py +++ b/checks/media_redactions_gate.py @@ -29,10 +29,12 @@ def main(argv: list[str] | None = None) -> int: files = json.loads(MANIFEST.read_text())["files"] failures = [] for name, entry in files.items(): - path = REPO / name - if not path.is_file(): + try: + data = (REPO / name).read_bytes() + except FileNotFoundError: failures.append(f"{name}: missing") - elif hashlib.sha256(path.read_bytes()).hexdigest() != entry.get("result"): + continue + if hashlib.sha256(data).hexdigest() != entry.get("result"): failures.append(f"{name}: not its redacted result") elif declared(entry) != entry.get("declared"): failures.append( diff --git a/checks/text_pii_gate.py b/checks/text_pii_gate.py index b2e35f7e..4e180209 100755 --- a/checks/text_pii_gate.py +++ b/checks/text_pii_gate.py @@ -236,10 +236,11 @@ def scan_line(text: str) -> Iterator[tuple[str, str]]: def excluded_prefixes(root: pathlib.Path) -> tuple[str, ...]: """The archive boundary, read the way the prose gate reads it.""" - path = root / EXCLUDES - if not path.is_file(): + try: + text = (root / EXCLUDES).read_text(encoding="utf-8") + except FileNotFoundError: return () - lines = (line.strip() for line in path.read_text(encoding="utf-8").splitlines()) + lines = (line.strip() for line in text.splitlines()) return tuple(line for line in lines if line and not line.startswith("#")) @@ -270,9 +271,11 @@ def findings(root: pathlib.Path) -> list[Finding]: def load_allow(path: pathlib.Path) -> list[dict]: """The declared values, each checked for a known class, one matcher, and a reason.""" - if not path.is_file(): + try: + text = path.read_text(encoding="utf-8") + except FileNotFoundError: return [] - data = json.loads(path.read_text(encoding="utf-8")) + data = json.loads(text) entries = data.get("allow") if isinstance(data, dict) else None if not isinstance(entries, list): raise SystemExit(f"{path}: needs an object with an allow list") diff --git a/checks/url_parity_gate.py b/checks/url_parity_gate.py index a55d74cc..f03bdfcb 100755 --- a/checks/url_parity_gate.py +++ b/checks/url_parity_gate.py @@ -121,7 +121,11 @@ def check_robots(public): happens, and it catches a sitemap advertised but not built. """ robots = public / "robots.txt" - if not robots.is_file(): + # This uses errors="replace" rather than strict, or invalid UTF-8 raises out of the whole parity run and a gate that exists to report a bad robots.txt stack-traces on one instead. + # A committed static/robots.txt is the file most likely to carry it, and it is the case this check is for. + try: + text = robots.read_text(encoding="utf-8", errors="replace") + except FileNotFoundError: # Naming one cause as the cause sends a reader to check a setting that is already correct. # The likely one is enableRobotsTXT, and a partial build or the wrong output directory reach the same state, which is the same reason the orphan messages name both of their causes. print("robots : missing") @@ -133,9 +137,6 @@ def check_robots(public): ] origin = site_origin(public) - # This uses errors="replace" rather than strict, or invalid UTF-8 raises out of the whole parity run and a gate that exists to report a bad robots.txt stack-traces on one instead. - # A committed static/robots.txt is the file most likely to carry it, and it is the case this check is for. - text = robots.read_text(encoding="utf-8", errors="replace") advertised = re.findall(r"(?mi)^\s*Sitemap:\s*(\S+)\s*$", text) if not advertised: print("robots : built, no Sitemap line") @@ -170,9 +171,10 @@ def site_origin(public): one environment's output against another's and silently match nothing. """ home = public / "index.html" - if not home.is_file(): + try: + text = home.read_text(encoding="utf-8", errors="ignore") + except FileNotFoundError: sys.exit(f"FAIL: {home} is missing - run hugo first") - text = home.read_text(encoding="utf-8", errors="ignore") found = re.search( r'rel=["\']?canonical["\']?\s+href=["\']?(https?://[^/"\'>\s]+)', text ) From 019e75884ffe2cae00c8a105e457ba7d8b48d6c9 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 30 Oct 2026 08:03:41 -0700 Subject: [PATCH 2/2] Read Each Env Template Directly Too The strict review found the template loop probing with exists() before its read, the same class this change removes. Co-Authored-By: Claude Opus 5.5 --- checks/env_docs_gate.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/checks/env_docs_gate.py b/checks/env_docs_gate.py index 40497a70..769a57c3 100755 --- a/checks/env_docs_gate.py +++ b/checks/env_docs_gate.py @@ -95,10 +95,11 @@ def note(name: str, where: str) -> None: declared.setdefault(name, set()).add(where) for path in TEMPLATES: - if not path.exists(): + try: + text = path.read_text(encoding="utf-8") + except FileNotFoundError: print(f"ERROR: template {path} is missing", file=sys.stderr) return 1 - text = path.read_text(encoding="utf-8") rel = path.relative_to(REPO).as_posix() for name in DECL.findall(text): note(name, rel)