Spaces:
Runtime error
Runtime error
File size: 14,830 Bytes
1130366 eb62e9b 1130366 d2aafad 1130366 eb62e9b 6d90266 eb62e9b f691896 6971b8a f691896 6d90266 eb62e9b 1130366 eb62e9b 1130366 eb62e9b 1130366 eb62e9b 1130366 eb62e9b 1130366 eb62e9b 1130366 eb62e9b 1130366 | 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 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | """Deploy / record preflight β is the app ready to ship to the Space and record?
Distinct from `scripts/preflight.py` (which gates the live *model* on the real
stack). This one gates *deployment + recording readiness* and runs fully offline:
β’ local build is sound (imports, builds the UI, core tests pass, no errors)
β’ every file the Space needs is present + the README frontmatter is valid
β’ the prompt won't regress (reference block is lean, ledger is the clean baseline)
β’ the credentials/tooling to actually push exist β and if not, says exactly what
to set and where (this remote session does NOT carry HF_TOKEN by default)
β’ (only if a token is present) the live Space exists and what's deployed
GO β safe to `hf upload` + reboot + record. Run: make deploy-check
"""
from __future__ import annotations
import json
import argparse
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
SPACE = "build-small-hackathon/microfactory-lab"
FIELD_LOG_DATASET = "build-small-hackathon/chief-engineer-field-log"
# Uploaded to the Space = everything EXCEPT these (keeps learn/ + assets/ + data/*.jsonl,
# which the app imports/needs; drops docs, spikes, secrets, caches, runtime/transient files).
# Public-facing learn/finetune docs that stay on the Space: README.md, MODEL_CARD*.md,
# SERVING.md, OLLAMA_PUBLISHING.md. Internal session/budget/iteration logs stay in the
# GitHub repo but are kept out of the Space.
SPACE_IGNORE = [
# Docs are internal by default, but two reference docs are public-facing.
"docs/**",
"!docs/reference/DEPLOYMENT.md",
"!docs/reference/SIMULATION.md",
"spike/**", "field_logs/**", "deliberation_logs/**", ".venv/**", "node_modules/**",
"recordings/**", "**/__pycache__/**", "**/*.pyc", ".git/**", ".gitignore", ".env", ".agents/**",
".codeboarding/**", ".codeboardingignore", "data/policy.json", "data/_generated.glb",
"data/_vprint.gif", "uv.lock", ".pytest_cache/**", "*.cap/**",
# Internal finetune workpapers kept internal.
"learn/finetune/REPORT.md",
"learn/finetune/REPORT_v1.md",
]
_fail: list[str] = []
_warn: list[str] = []
def ok(gate: str, detail: str = "") -> None:
print(f"π’ {gate}{' β ' + detail if detail else ''}")
def warn(gate: str, detail: str) -> None:
_warn.append(gate)
print(f"π‘ {gate} β {detail}")
def fail(gate: str, detail: str) -> None:
_fail.append(gate)
print(f"π΄ {gate} β {detail}")
# ββ D1 Β· local build is sound ββββββββββββββββββββββββββββββββββββββββββββββββ
def d1_build() -> None:
try:
import app # noqa: F401 (also exercises the whole import graph)
if type(getattr(app, "demo", None)).__name__ != "Blocks":
fail("D1 build", "app.demo is not a Gradio Blocks β build() did not run")
return
ok("D1 build", "app imports + builds the UI (StudioβBuildβPrintβReview)")
except Exception as e: # noqa: BLE001
fail("D1 build", f"import/build error: {e!r}")
def d1b_tests() -> None:
r = subprocess.run([sys.executable, str(ROOT / "test_core.py")],
cwd=ROOT, capture_output=True, text=True)
if r.returncode == 0 and "ALL CORE TESTS PASSED" in (r.stdout + r.stderr):
ok("D1 tests", "core tests pass (offline)")
else:
fail("D1 tests", f"test_core.py failed (rc={r.returncode}); run `make test`")
# ββ D2 Β· everything the Space needs is present βββββββββββββββββββββββββββββββ
def d2_files() -> None:
required = ["app.py", "README.md", "requirements.txt", "core", "ingest", "sim",
"scripts", "learn", "data/seed_lessons.jsonl", "data/references.jsonl",
"data/lessons.jsonl"]
missing = [p for p in required if not (ROOT / p).exists()]
if missing:
fail("D2 files", f"missing for the Space: {', '.join(missing)}")
else:
ok("D2 files", "all app + data files present")
if not (ROOT / "assets" / "benchy.glb").exists():
warn("D2 assets", "assets/benchy.glb missing β the hero quick-load won't render")
# ββ D3 Β· README frontmatter the Space build reads ββββββββββββββββββββββββββββ
def d3_frontmatter() -> None:
txt = (ROOT / "README.md").read_text(encoding="utf-8")
m = re.match(r"^---\n(.*?)\n---", txt, re.S)
if not m:
fail("D3 README", "no YAML frontmatter block")
return
fm = m.group(1)
need = {"sdk": "gradio", "app_file": "app.py"}
for k, v in need.items():
if not re.search(rf"^{k}:\s*{re.escape(v)}\s*$", fm, re.M):
fail("D3 README", f"frontmatter `{k}: {v}` missing/wrong")
return
if not re.search(r"^sdk_version:\s*\S+", fm, re.M):
fail("D3 README", "sdk_version missing")
return
sd = re.search(r"^short_description:\s*(.+)$", fm, re.M)
if not sd:
warn("D3 README", "no short_description")
elif len(sd.group(1).strip().strip('"')) > 60:
fail("D3 README", f"short_description >60 chars ({len(sd.group(1).strip())}) β upload will reject")
else:
ok("D3 README", "frontmatter valid (sdk/app_file/sdk_version/short_description)")
# ββ D4 Β· requirements carry the Space (zerogpu) deps βββββββββββββββββββββββββ
def d4_requirements() -> None:
req = (ROOT / "requirements.txt").read_text(encoding="utf-8").lower()
needed = ["gradio", "spaces", "torch", "transformers", "trimesh", "shapely", "pydantic"]
missing = [p for p in needed if p not in req]
if missing:
fail("D4 requirements", f"missing pins: {', '.join(missing)} (Space build/zerogpu needs them)")
else:
ok("D4 requirements", "core + zerogpu deps inlined")
# ββ D5 Β· the prompt won't regress (lean reference block) βββββββββββββββββββββ
def d5_reference_block() -> None:
try:
from ingest.distill import reference_block
worst = max((len(reference_block(m)) for m in ("PLA", "PETG", "ABS", "TPU")), default=0)
if worst == 0:
warn("D5 reference", "reference_block returned nothing β references not loaded?")
elif worst > 12:
fail("D5 reference", f"reference block is {worst} lines/material β prompt flood regression")
else:
ok("D5 reference", f"lean ({worst} lines/material max)")
except Exception as e: # noqa: BLE001
fail("D5 reference", f"reference_block error: {e!r}")
# ββ D6 Β· the deployed ledger is the clean baseline (no demo junk) ββββββββββββ
def d6_ledger() -> None:
srcs: dict[str, int] = {}
for line in (ROOT / "data" / "lessons.jsonl").read_text(encoding="utf-8").splitlines():
if line.strip():
try:
srcs[json.loads(line).get("source", "?")] = srcs.get(json.loads(line).get("source", "?"), 0) + 1
except Exception:
continue
runtime = srcs.get("earned", 0) + srcs.get("sim", 0)
summary = ", ".join(f"{k}:{v}" for k, v in sorted(srcs.items()))
if runtime:
warn("D6 ledger", f"{runtime} runtime lesson(s) in the ledger ({summary}) β "
"reset before upload: git checkout -- data/lessons.jsonl")
else:
ok("D6 ledger", f"clean baseline ({summary})")
# ββ D7 Β· data integrity (valid JSONL, enums) βββββββββββββββββββββββββββββββββ
def d7_data() -> None:
try:
from core.models import MATERIALS, OUTCOMES
bad = 0
for line in (ROOT / "data" / "references.jsonl").read_text(encoding="utf-8").splitlines():
if line.strip():
r = json.loads(line)
if not {"material", "param", "value", "source"} <= set(r):
bad += 1
obs = ROOT / "sim" / "calibration" / "observations.modal.jsonl"
obad = sum(1 for l in obs.read_text().splitlines()
if l.strip() and json.loads(l).get("outcome") not in OUTCOMES) if obs.exists() else 0
if bad or obad:
warn("D7 data", f"{bad} malformed ref rows, {obad} bad-outcome obs rows")
else:
ok("D7 data", "references + calibration obs well-formed")
except Exception as e: # noqa: BLE001
warn("D7 data", f"could not validate: {e!r}")
# ββ D8 Β· credentials + tooling to actually deploy ββββββββββββββββββββββββββββ
def d8_credentials() -> None:
has_hf_cli = shutil.which("hf") is not None
token = (os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
or os.environ.get("HUGGINGFACE_TOKEN"))
whoami = None
if has_hf_cli:
r = subprocess.run(["hf", "auth", "whoami"], capture_output=True, text=True)
if r.returncode == 0 and "Not logged in" not in r.stdout:
whoami = r.stdout.strip().splitlines()[0] if r.stdout.strip() else "ok"
if not has_hf_cli:
fail("D8 hf-cli", "`hf` not installed β `uv pip install -U huggingface_hub`")
if token or whoami:
ok("D8 hf-auth", f"authenticated ({'token env' if token else 'hf login: ' + str(whoami)})")
else:
warn("D8 hf-auth", "NO HF credentials in this session. To deploy: run the `hf upload` "
"from a machine where you've run `hf auth login`, OR set HF_TOKEN "
"in the environment (HF write token, member of build-small-hackathon).")
return bool(token or whoami)
# ββ D9 Β· (token only) what's live on the Space right now βββββββββββββββββββββ
def d9_space(authed: bool) -> None:
if not authed:
warn("D9 space", "skipped β no credentials to query the live Space")
return
try:
from huggingface_hub import HfApi
api = HfApi()
rt = api.get_space_runtime(SPACE)
files = api.list_repo_files(SPACE, repo_type="space")
has_core = "app.py" in files and any(f.startswith("core/") for f in files)
stage = getattr(rt, "stage", "?")
ok("D9 space", f"reachable Β· stage={stage} Β· app.py+core present={has_core} Β· {len(files)} files")
if not has_core:
warn("D9 space", "Space is missing app.py/core β likely the pre-restructure build; push the update")
except Exception as e: # noqa: BLE001
warn("D9 space", f"could not query Space: {e!r}")
# ββ D10 Β· the field-log dataset is set + reachable (Sharing-is-Caring / all-runs) ββ
def d10_dataset(authed: bool) -> None:
if not authed:
warn("D10 dataset", "skipped β no credentials to verify the field-log dataset")
return
try:
from huggingface_hub import HfApi
api = HfApi()
if api.repo_exists(FIELD_LOG_DATASET, repo_type="dataset"):
files = api.list_repo_files(FIELD_LOG_DATASET, repo_type="dataset")
logged = any(f.endswith("interactions.jsonl") for f in files)
ok("D10 dataset", f"{FIELD_LOG_DATASET} exists"
+ (" Β· interactions.jsonl present (runs are logging)" if logged
else " Β· no interactions.jsonl yet (do one BUILD on the Space to confirm)"))
else:
warn("D10 dataset", f"{FIELD_LOG_DATASET} not found β create it, or let CommitScheduler "
"make it on first run (needs HF_TOKEN as a Space secret).")
except Exception as e: # noqa: BLE001
warn("D10 dataset", f"could not verify dataset: {e!r}")
# ββ push: actually update the Space files (gated on green + auth) βββββββββββββ
def push_space(factory_reboot: bool = True) -> None:
"""Upload the app to the Space (everything except SPACE_IGNORE) and reboot.
Only runs after the gates pass + credentials are present."""
try:
from huggingface_hub import HfApi
except Exception as e: # noqa: BLE001
fail("PUSH", f"huggingface_hub unavailable: {e!r}")
return
api = HfApi()
print(f"\nβ« uploading {ROOT.name}/ β {SPACE} (excluding docs, spike, caches, secrets)β¦")
try:
api.upload_folder(repo_id=SPACE, repo_type="space", folder_path=str(ROOT),
ignore_patterns=SPACE_IGNORE,
commit_message="deploy: update Space from deploy_preflight --push")
ok("PUSH", "files uploaded")
if factory_reboot:
api.restart_space(SPACE, factory_reboot=True)
ok("PUSH", "factory reboot requested β Space rebuilding (~1-2 min)")
print(" Next: wait for build, then smoke-test (BUILD shows reasoning not Error; "
"O'Brien/La Forge; reset button; wide UI).")
except Exception as e: # noqa: BLE001
fail("PUSH", f"upload/restart failed: {e!r}")
def main() -> None:
ap = argparse.ArgumentParser(description="Deploy/record readiness gate (+ optional Space push).")
ap.add_argument("--push", action="store_true",
help="after the gates pass, UPDATE the Space files (hf upload) + factory reboot")
ap.add_argument("--no-reboot", action="store_true", help="with --push, skip the factory reboot")
args = ap.parse_args()
print("Deploy / record preflight β " + SPACE)
print("=" * 70)
d1_build()
d1b_tests()
d2_files()
d3_frontmatter()
d4_requirements()
d5_reference_block()
d6_ledger()
d7_data()
authed = d8_credentials()
d9_space(authed)
d10_dataset(authed)
print("=" * 70)
if _fail:
print(f"π΄ NO-GO: fix {len(_fail)} blocker(s) β {', '.join(_fail)}")
sys.exit(1)
if args.push:
if not authed:
print("π΄ --push needs HF credentials (HF_TOKEN or `hf auth login`). Nothing pushed.")
sys.exit(1)
push_space(factory_reboot=not args.no_reboot)
sys.exit(1 if _fail else 0)
if _warn:
print(f"π‘ GO with warnings ({', '.join(_warn)}) β read them; credentials/Space "
"warnings just mean 'deploy from an authenticated machine'. "
"Run with --push (authenticated) to update the Space.")
sys.exit(0)
print("π’ GO β local build clean, files + frontmatter ready, authenticated. "
"Re-run with --push to update the Space + reboot, then smoke-test β record.")
if __name__ == "__main__":
main()
|