File size: 11,147 Bytes
088795a | 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 | from __future__ import annotations
import argparse
import json
from dataclasses import dataclass
from pathlib import Path
from urllib.parse import urlparse
ROOT_DIR = Path(__file__).resolve().parent.parent
LOCAL_REPORT = ROOT_DIR / "outputs" / "local-readiness.json"
SITE_REPORT = ROOT_DIR / "outputs" / "site-verification.json"
WORKER_REPORT = ROOT_DIR / "outputs" / "worker-verification.json"
LIVE_PROOF = ROOT_DIR / "outputs" / "live-deployment-proof.json"
@dataclass
class NextStep:
status: str
title: str
detail: str
command: str
def load_json(path: Path) -> object | None:
if not path.exists():
return None
try:
return json.loads(path.read_text(encoding="utf-8"))
except Exception:
return None
def report_checks_pass(path: Path, required: set[str]) -> bool:
payload = load_json(path)
if not isinstance(payload, list):
return False
checks = {str(item.get("name", "")): bool(item.get("ok")) for item in payload if isinstance(item, dict)}
return all(checks.get(name) is True for name in required)
def local_ready(path: Path = LOCAL_REPORT) -> bool:
payload = load_json(path)
return isinstance(payload, dict) and bool(payload.get("ready"))
def live_proof_complete(path: Path = LIVE_PROOF) -> bool:
payload = load_json(path)
return isinstance(payload, dict) and bool(payload.get("complete"))
def placeholder_or_test_url(url: str | None) -> bool:
if not url:
return True
lowered = url.lower().strip()
host = _hostname(lowered)
placeholders = [
"your-space.hf.space",
"your-vercel-app.vercel.app",
"localhost",
"127.0.0.1",
"::1",
]
example_hosts = {"example.com", "example.org", "example.net", "example"}
return any(marker in lowered for marker in placeholders) or host in example_hosts or any(
host.endswith(f".{example_host}") for example_host in example_hosts
)
def _hostname(url: str | None) -> str:
if not url:
return ""
try:
parsed = urlparse(url.strip())
return (parsed.hostname or "").lower()
except ValueError:
return ""
def _is_https_url(url: str | None) -> bool:
if not url:
return False
try:
parsed = urlparse(url.strip())
return parsed.scheme == "https" and bool(parsed.netloc)
except ValueError:
return False
def deployment_url_warnings(worker_url: str | None, vercel_origin: str | None) -> list[str]:
warnings: list[str] = []
worker_host = _hostname(worker_url)
origin_host = _hostname(vercel_origin)
if placeholder_or_test_url(worker_url):
warnings.append("Worker URL still looks like a placeholder, local address, or test URL.")
elif not _is_https_url(worker_url):
warnings.append("Worker URL must be a full HTTPS URL, for example https://your-space.hf.space.")
elif worker_host == "vercel.app" or worker_host.endswith(".vercel.app"):
warnings.append("Worker URL looks like a Vercel site URL. Use the Hugging Face Space or Docker worker URL.")
if placeholder_or_test_url(vercel_origin):
warnings.append("Vercel origin still looks like a placeholder, local address, or test URL.")
elif not _is_https_url(vercel_origin):
warnings.append("Vercel origin must be a full HTTPS URL, for example https://your-app.vercel.app.")
elif origin_host == "hf.space" or origin_host.endswith(".hf.space"):
warnings.append("Vercel origin looks like a Hugging Face worker URL. Use the Vercel production site URL.")
if worker_url and vercel_origin and worker_url.rstrip("/") == vercel_origin.rstrip("/"):
warnings.append("Worker URL and Vercel origin are the same URL; they should be two deployed services.")
return warnings
def deployment_urls_look_real(worker_url: str | None, vercel_origin: str | None) -> bool:
return not deployment_url_warnings(worker_url, vercel_origin)
def looks_like_test_artifact(payload: object) -> bool:
if not isinstance(payload, dict):
return False
text = json.dumps(payload, ensure_ascii=False).lower()
return "pytest-" in text or "your-space.hf.space" in text or "your-vercel-app.vercel.app" in text
def live_proof_matches_urls(path: Path, worker_url: str | None, vercel_origin: str | None) -> bool:
payload = load_json(path)
if not isinstance(payload, dict) or not payload.get("complete"):
return False
if looks_like_test_artifact(payload):
return False
proof_worker = str(payload.get("workerUrl") or "").rstrip("/")
proof_origin = str(payload.get("origin") or "").rstrip("/")
expected_worker = str(worker_url or "").rstrip("/")
expected_origin = str(vercel_origin or "").rstrip("/")
if not deployment_urls_look_real(expected_worker, expected_origin):
return False
return proof_worker == expected_worker and proof_origin == expected_origin
def choose_next_step(
worker_url: str | None = None,
vercel_origin: str | None = None,
code: str = "1234",
local_report: Path = LOCAL_REPORT,
site_report: Path = SITE_REPORT,
worker_report: Path = WORKER_REPORT,
live_proof: Path = LIVE_PROOF,
) -> NextStep:
worker_required = {
"recommended stack documented",
"smoke upload accepted",
"smoke job complete",
"smoke usable text",
"smoke audio url",
"smoke download url",
"smoke audio bytes",
"smoke audio file signature",
"smoke download bytes",
"smoke download file signature",
"scanned smoke upload accepted",
"scanned smoke job complete",
"scanned smoke usable text",
"scanned smoke OCR extraction",
"scanned smoke audio url",
"scanned smoke download url",
"scanned smoke audio bytes",
"scanned smoke audio file signature",
"scanned smoke download bytes",
"scanned smoke download file signature",
}
site_required = {
"site login",
"site platform vercel",
"site worker configured",
"site large PDF ready",
"site production worker ready",
"site hosted limits documented",
"site recommended stack documented",
"site direct cloud fallback disabled",
"site worker diagnostics endpoint",
"site worker reachable from vercel",
"site worker CORS ready",
}
if not local_ready(local_report):
return NextStep(
status="local",
title="Prove local/repo readiness",
detail="Run this before deploying so research, packaging, and local checks are current.",
command=(
"python scripts\\prove_local_readiness.py --refresh-research --check-key-links "
"--check-hf-metadata --hf-metadata-report outputs\\hf-model-metadata.md"
),
)
if not deployment_urls_look_real(worker_url, vercel_origin):
return NextStep(
status="handoff",
title="Create deployment handoff with real URLs",
detail=(
"Replace placeholder/test URLs with the real Hugging Face Space URL and Vercel production URL, "
"then generate exact env vars and proof commands."
),
command=(
"python scripts\\deployment_handoff.py https://your-space.hf.space "
"--origin https://your-vercel-app.vercel.app --code 1234"
),
)
site_ok = report_checks_pass(site_report, site_required)
worker_ok = report_checks_pass(worker_report, worker_required)
if live_proof_complete(live_proof):
if site_ok and worker_ok and live_proof_matches_urls(live_proof, worker_url, vercel_origin):
return NextStep(
status="complete",
title="Deployment proof is complete",
detail="The combined live proof report says the deployed Vercel site and worker satisfy the goal audit.",
command=f"Get-Content {live_proof}",
)
return NextStep(
status="live-proof",
title="Re-run live proof against real deployed URLs",
detail=(
"A complete live proof file exists, but the URLs or saved reports do not prove the current real "
"Vercel site and Hugging Face worker. Re-run the live proof with the real deployed URLs."
),
command=(
f"python scripts\\prove_live_deployment.py {worker_url.rstrip('/')} "
f"--origin {vercel_origin.rstrip('/')} --code {code} "
"--smoke-ocr-engine arabic "
"--check-hf-metadata --hf-metadata-report outputs\\hf-model-metadata.md "
"--proof-out outputs\\live-deployment-proof.json"
),
)
if not (site_ok and worker_ok):
return NextStep(
status="live-proof",
title="Verify live Vercel site and OCR/TTS worker",
detail="This writes site and worker reports, including embedded-text and scanned Arabic OCR smoke tests with audio/download proof.",
command=(
f"python scripts\\prove_live_deployment.py {worker_url.rstrip('/')} "
f"--origin {vercel_origin.rstrip('/')} --code {code} "
"--smoke-ocr-engine arabic "
"--check-hf-metadata --hf-metadata-report outputs\\hf-model-metadata.md "
"--proof-out outputs\\live-deployment-proof.json"
),
)
return NextStep(
status="complete-ready",
title="Run final audit",
detail="Both live reports exist and contain the required checks. The final audit should be complete.",
command=(
"python scripts\\audit_goal_readiness.py "
"--worker-report outputs\\worker-verification.json "
"--site-report outputs\\site-verification.json"
),
)
def main() -> None:
parser = argparse.ArgumentParser(description="Print the next command needed to finish the Arabic audio deployment proof.")
parser.add_argument("--worker-url", help="Live worker URL, for example https://your-space.hf.space")
parser.add_argument("--origin", help="Live Vercel origin, for example https://your-app.vercel.app")
parser.add_argument("--code", default="1234", help="Access code for live verification.")
parser.add_argument("--json", action="store_true", help="Print JSON.")
args = parser.parse_args()
step = choose_next_step(worker_url=args.worker_url, vercel_origin=args.origin, code=args.code)
if args.json:
print(json.dumps(step.__dict__, indent=2))
return
print(step.title)
print(step.detail)
print()
print(step.command)
if __name__ == "__main__":
main()
|