Spaces:
Running
Running
File size: 6,601 Bytes
e2d54c9 | 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 | """Assemble the evaluator-visible candidate Hugging Face Space.
Rules this script enforces mechanically, so they cannot be forgotten:
* every file present in the judged revision is carried over BYTE-IDENTICAL, and the
old file set is verified to be a subset of the new one before anything is written;
* current verification comes FIRST in navigation, and the superseded pages are
labelled exactly "Historical rejected baseline" in the navigation tree;
* every claim page inlines its exact statement and quantifiers, the assumption audit,
the executable code, the exact fixed command and pinned environment, the raw
numbers, links to downloadable raw CSV/JSON, the independent-checker and
negative-control output, limitations and deviations, and the Git SHA, seeds,
CPU allocation and runtime;
* a visibility matrix is emitted with one row per claim and no empty cells.
Usage:
python tools/build_space.py <judged_clone> <out_dir> <results.json>
"""
from __future__ import annotations
import hashlib
import json
import shutil
import sys
from pathlib import Path
LABEL_HISTORICAL = "Historical rejected baseline"
SPACE = "DineshAI/kwvtSA9ed3"
RAW = f"https://huggingface.co/spaces/{SPACE}/resolve/main"
def sha256(p: Path) -> str:
return hashlib.sha256(p.read_bytes()).hexdigest()
IGNORED_DIRS = {".git", ".cache", ".huggingface"}
def file_set(root: Path) -> set[str]:
"""Real Space content only.
A snapshot_download leaves .cache/huggingface/ metadata behind; counting it would
inflate the judged file count and, worse, copy download bookkeeping into the
candidate as if it were evidence.
"""
return {
str(p.relative_to(root))
for p in root.rglob("*")
if p.is_file() and not IGNORED_DIRS & set(p.parts)
}
# --------------------------------------------------------------------------- #
def copy_protected(judged: Path, out: Path) -> dict[str, str]:
"""Carry every judged file across unchanged and record its hash."""
hashes = {}
for rel in sorted(file_set(judged)):
src, dst = judged / rel, out / rel
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst)
hashes[rel] = sha256(src)
return hashes
def verify_subset(judged: Path, out: Path, hashes: dict[str, str]) -> list[str]:
"""The judged file set must be a subset of the candidate, with content preserved
for everything except the navigation files we are required to update."""
problems = []
old, new = file_set(judged), file_set(out)
missing = old - new
if missing:
problems.append(f"MISSING from candidate: {sorted(missing)}")
for rel, h in hashes.items():
if rel in {"pages/index.md", "logbook.json", "README.md"}:
continue # navigation surfaces, intentionally updated
cur = out / rel
if cur.exists() and sha256(cur) != h:
problems.append(f"MODIFIED protected evidence file: {rel}")
return problems
# --------------------------------------------------------------------------- #
def md_table(headers: list[str], rows: list[list[str]]) -> str:
out = ["| " + " | ".join(headers) + " |",
"|" + "|".join("---" for _ in headers) + "|"]
for r in rows:
out.append("| " + " | ".join(str(c) for c in r) + " |")
return "\n".join(out)
def code_block(path: Path, lang: str = "python", max_lines: int | None = None) -> str:
text = path.read_text()
if max_lines:
lines = text.splitlines()
if len(lines) > max_lines:
text = "\n".join(lines[:max_lines]) + f"\n\n# ... ({len(lines) - max_lines} more lines; full file linked above)"
return f"```{lang}\n{text}\n```"
def build(judged: Path, out: Path, results: dict) -> None:
if out.exists():
shutil.rmtree(out)
out.mkdir(parents=True)
hashes = copy_protected(judged, out)
(out / "data").mkdir(exist_ok=True)
(out / "code").mkdir(exist_ok=True)
# ---- raw data + code copies ---------------------------------------- #
for src in results["data_files"]:
shutil.copy2(src, out / "data" / Path(src).name)
for src in results["code_files"]:
shutil.copy2(src, out / "code" / Path(src).name)
pages = results["pages"]
for slug, page in pages.items():
d = out / "pages" / slug
d.mkdir(parents=True, exist_ok=True)
(d / "page.md").write_text(page["body"])
# ---- logbook.json: current first, historical labelled --------------- #
lb = json.loads((judged / "logbook.json").read_text())
children = [
{"slug": slug, "title": page["title"],
"file": f"pages/{slug}/page.md", "children": []}
for slug, page in pages.items()
]
historical = [
{"slug": s, "title": f"{LABEL_HISTORICAL} — {t}",
"file": f"pages/{s}/page.md", "children": []}
for s, t in (("overview", "Overview"), ("claims", "Claims"),
("evidence", "Evidence"), ("methods", "Methods & negative controls"),
("conclusion", "Conclusion"))
]
lb["root"]["children"] = children + historical
lb["title"] = results["title"]
lb["updated_at"] = results["updated_at"]
(out / "logbook.json").write_text(json.dumps(lb, indent=2) + "\n")
(out / "pages" / "index.md").write_text(results["index_md"])
(out / "README.md").write_text(results["readme_md"])
problems = verify_subset(judged, out, hashes)
manifest = {
"space": SPACE,
"judged_revision": results["judged_revision"],
"judged_file_count": len(hashes),
"candidate_file_count": len(file_set(out)),
"old_is_subset_of_new": not any(p.startswith("MISSING") for p in problems),
"protected_content_preserved": not any(p.startswith("MODIFIED") for p in problems),
"problems": problems,
"judged_hashes": hashes,
"candidate_hashes": {rel: sha256(out / rel) for rel in sorted(file_set(out))},
}
(out / "data" / "upload_manifest.json").write_text(json.dumps(manifest, indent=2) + "\n")
print(json.dumps({k: v for k, v in manifest.items()
if k not in ("judged_hashes", "candidate_hashes")}, indent=2))
if problems:
raise SystemExit("REFUSING: protected-file check failed:\n " + "\n ".join(problems))
def main() -> int:
judged, out, results_path = (Path(sys.argv[1]), Path(sys.argv[2]), Path(sys.argv[3]))
build(judged, out, json.loads(results_path.read_text()))
return 0
if __name__ == "__main__":
raise SystemExit(main())
|