File size: 9,781 Bytes
ecf3aeb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
#!/usr/bin/env python3
"""Evaluator-blind structural audit for the additive Hugging Face candidate."""

from __future__ import annotations

import hashlib
import json
import re
import sys
from pathlib import Path
from urllib.parse import unquote


TEXT_SUFFIXES = {
    ".csv",
    ".css",
    ".html",
    ".js",
    ".json",
    ".lock",
    ".md",
    ".py",
    ".svg",
    ".toml",
    ".txt",
}
MUTABLE_ENTRYPOINTS = {"README.md", "logbook.json", "pages/index.md"}
LINK_RE = re.compile(r"!?\[[^\]]*\]\(([^)]+)\)")
SECRET_PATTERNS = {
    "hugging_face_token": re.compile(r"\bhf_[A-Za-z0-9]{20,}\b"),
    "github_token": re.compile(r"\bgithub_pat_[A-Za-z0-9_]{20,}\b"),
    "bearer_token": re.compile(r"\bBearer\s+[A-Za-z0-9._~-]{16,}\b", re.IGNORECASE),
    "aws_access_key": re.compile(r"\bAKIA[0-9A-Z]{16}\b"),
}


def sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def flatten_pages(node: dict) -> dict[str, str]:
    pages: dict[str, str] = {}
    for child in node.get("children", []):
        pages[child["slug"]] = child["file"]
        pages.update(flatten_pages(child))
    return pages


def parse_judged_manifest(path: Path) -> dict[str, str]:
    result = {}
    for line in path.read_text().splitlines():
        digest, relative = line.split("  ", 1)
        result[relative] = digest
    return result


def resolve_link(space: Path, source: Path, raw_target: str, routes: dict[str, str]) -> Path | None:
    target = raw_target.strip().split(" ", 1)[0].strip("<>")
    if target.startswith(("http://", "https://", "mailto:")) or target == "":
        return None
    if target.startswith("#/"):
        slug = target[2:].split("#", 1)[0]
        if slug not in routes:
            raise ValueError(f"unknown route {target} from {source.relative_to(space)}")
        return space / routes[slug]
    if target.startswith("#"):
        return None
    target = unquote(target.split("#", 1)[0].split("?", 1)[0])
    return (source.parent / target).resolve()


def main() -> None:
    if len(sys.argv) != 4:
        raise SystemExit(
            "usage: audit_candidate_space.py CANDIDATE_DIR JUDGED_MANIFEST OUTPUT_DIR"
        )
    space = Path(sys.argv[1]).resolve()
    judged_manifest_path = Path(sys.argv[2]).resolve()
    output = Path(sys.argv[3]).resolve() / "release"
    output.mkdir(parents=True, exist_ok=True)
    failures: list[str] = []

    try:
        logbook = json.loads((space / "logbook.json").read_text())
    except (OSError, json.JSONDecodeError) as exc:
        raise SystemExit(f"invalid candidate logbook: {exc}")
    routes = flatten_pages(logbook["root"])
    routes[logbook["root"]["slug"]] = logbook["root"]["file"]
    children = logbook["root"].get("children", [])
    if not children or children[0].get("slug") != "current-cumulative-verification":
        failures.append("current_cumulative_not_first")

    judged = parse_judged_manifest(judged_manifest_path)
    candidate_paths = {
        path.relative_to(space).as_posix(): path
        for path in space.rglob("*")
        if path.is_file()
    }
    missing_old_paths = sorted(set(judged) - set(candidate_paths))
    if missing_old_paths:
        failures.append("judged_file_set_not_subset")
    changed_historical_pages = []
    for relative, expected in judged.items():
        if relative.startswith("pages/") and relative != "pages/index.md":
            path = candidate_paths.get(relative)
            if path is not None and sha256(path) != expected:
                changed_historical_pages.append(relative)
    if changed_historical_pages:
        failures.append("historical_page_changed")

    json_failures = []
    for relative, path in candidate_paths.items():
        if path.suffix == ".json":
            try:
                json.loads(path.read_text())
            except (UnicodeDecodeError, json.JSONDecodeError):
                json_failures.append(relative)
    if json_failures:
        failures.append("invalid_json")

    opened: list[str] = []
    broken_links: list[dict[str, str]] = []
    queue = [space / "README.md", space / "pages/index.md"]
    seen: set[Path] = set()
    while queue:
        source = queue.pop(0).resolve()
        if source in seen:
            continue
        seen.add(source)
        if not source.is_file() or space not in source.parents:
            broken_links.append({"source": "entrypoint", "target": str(source)})
            continue
        opened.append(source.relative_to(space).as_posix())
        if source.suffix not in {".md", ".html"}:
            continue
        text = source.read_text(errors="strict")
        for raw_target in LINK_RE.findall(text):
            try:
                resolved = resolve_link(space, source, raw_target, routes)
            except ValueError:
                broken_links.append(
                    {"source": source.relative_to(space).as_posix(), "target": raw_target}
                )
                continue
            if resolved is None:
                continue
            if not resolved.is_file() or (resolved != space and space not in resolved.parents):
                broken_links.append(
                    {"source": source.relative_to(space).as_posix(), "target": raw_target}
                )
                continue
            if resolved.suffix in {".md", ".html"}:
                queue.append(resolved)
            elif resolved.relative_to(space).as_posix() not in opened:
                opened.append(resolved.relative_to(space).as_posix())
    if broken_links:
        failures.append("broken_reachable_links")

    cumulative = (space / "pages/current-cumulative-verification/page.md").read_text()
    visibility_rows = {}
    for line in cumulative.splitlines():
        match = re.match(r"\|\s*([1-5])\s*\|(.+)\|", line)
        if match and "Canonical page" not in line:
            cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
            if len(cells) == 9:
                visibility_rows[match.group(1)] = cells
    if set(visibility_rows) != set("12345"):
        failures.append("visibility_matrix_claim_rows")
    elif any(
        any(value in {"", "No", "Missing", "INCONCLUSIVE"} for value in row[1:])
        for row in visibility_rows.values()
    ):
        failures.append("visibility_matrix_missing_cell")

    required_opened = {
        "README.md",
        "pages/index.md",
        "pages/current-cumulative-verification/page.md",
        "pages/current-claim-3-celeba-verification/page.md",
        "pages/current-claim-4-molecular-verification/page.md",
        "code/run_logdiff.py",
        "code/check_baseline_artifacts.py",
        "code/check_claim3_release.py",
        "code/check_claim4_evidence.py",
        "code/audit_candidate_space.py",
        "environment/pyproject.toml",
        "environment/uv.lock",
        "evidence/run-07daf77f-summary.json",
    }
    missing_from_traversal = sorted(required_opened - set(opened))
    if missing_from_traversal:
        failures.append("required_evidence_not_reachable")

    changed_or_new = []
    for relative, path in sorted(candidate_paths.items()):
        if relative not in judged or sha256(path) != judged[relative]:
            changed_or_new.append(relative)
    non_text_uploads = [
        relative for relative in changed_or_new if Path(relative).suffix not in TEXT_SUFFIXES
    ]
    if non_text_uploads:
        failures.append("non_text_candidate_change")

    secret_hits = []
    for relative in changed_or_new:
        path = candidate_paths[relative]
        if path.suffix not in TEXT_SUFFIXES:
            continue
        text = path.read_text(errors="strict")
        for name, pattern in SECRET_PATTERNS.items():
            if pattern.search(text):
                secret_hits.append({"path": relative, "pattern": name})
    if secret_hits:
        failures.append("secret_pattern")

    candidate_manifest = [
        f"{sha256(path)}  {relative}" for relative, path in sorted(candidate_paths.items())
    ]
    upload_allowlist = [
        relative
        for relative in changed_or_new
        if Path(relative).suffix in TEXT_SUFFIXES
        and relative not in {"release/candidate_manifest.sha256", "release/upload_allowlist.txt"}
    ]
    (output / "candidate_manifest.sha256").write_text("\n".join(candidate_manifest) + "\n")
    (output / "upload_allowlist.txt").write_text("\n".join(upload_allowlist) + "\n")

    result = {
        "checker": "evaluator-blind candidate traversal and release audit",
        "status": "PASS" if not failures else "FAIL",
        "failures": failures,
        "canonical_entrypoint": "README.md -> pages/index.md -> current cumulative verification",
        "files_opened": opened,
        "conclusions_not_verified": [],
        "broken_links": broken_links,
        "missing_from_traversal": missing_from_traversal,
        "judged_manifest_entries": len(judged),
        "candidate_file_entries": len(candidate_paths),
        "old_file_set_subset": not missing_old_paths,
        "missing_old_paths": missing_old_paths,
        "historical_pages_unchanged": not changed_historical_pages,
        "changed_historical_pages": changed_historical_pages,
        "json_files_checked": sum(path.suffix == ".json" for path in candidate_paths.values()),
        "invalid_json_files": json_failures,
        "visibility_claim_rows": len(visibility_rows),
        "non_text_uploads": non_text_uploads,
        "secret_hits": secret_hits,
        "upload_allowlist_entries": len(upload_allowlist),
    }
    (output / "candidate_audit.json").write_text(json.dumps(result, indent=2) + "\n")
    print(json.dumps(result, indent=2))
    if failures:
        raise SystemExit(1)


if __name__ == "__main__":
    main()