diff --git a/docs/LANES/ROUND3_INSTRUCTIONS.md b/docs/LANES/ROUND3_INSTRUCTIONS.md index 1f837c3..77c4fed 100644 --- a/docs/LANES/ROUND3_INSTRUCTIONS.md +++ b/docs/LANES/ROUND3_INSTRUCTIONS.md @@ -19,9 +19,13 @@ validation, and the last two integration steps** (LANE_F_NOTES §3.4/§3.5). 2. **True closed residential ring → v2** (stays in V2_IDEAS). 3. **F finding #4 (doubled possessives) is CLOSED** — verified 0 across 2,418 shop names / 5 seeds on current main. It predated Lane A's round-1 fix. -4. **John has authorized the depot publish** (2026-07-14). It needs `GOD3_PW` in the env, which - agents don't have and must not hunt for — John runs `pipeline/publish.py` himself (dry-run - verified: 16 GLBs + thumbs ready). After it lands, Lane F adds `--depot` to the QA gate. +4. **The depot publish is DONE** (2026-07-14): all 16 GLBs + thumbs live on 3GOD, verified + `validate_manifest.py --depot` → 0 errors. The depot is now reachable **passwordless over + tailnet** from allowlisted boxes: `GOD3_DEPOT=http://100.94.195.115:8788` (this box + `100.89.131.57` is allowlisted; `GOD3_PW` only needs any non-empty placeholder). Public + `digalot.fyi/3god` stays read-only+locked. Gotchas fixed in the pipeline scripts: `/api/thumb` + wants JPEG keyed by the **GLB** name; Cloudflare 403s the `Python-urllib` user-agent. + **Lane F: add `--depot` to the qa.sh manifest gate now.** 5. **Hero props: free-first on MODELBEAST — the local path is READY, no setup needed.** The round-2 "HF-gated, setup needed" note is stale: `sf3d` (3.7 GB) and `TRELLIS.2-4B` (14 GB) weights are already cached on this box with working venvs. Do not spend the ~$2.70 fal.ai @@ -131,12 +135,7 @@ Priority order, per your own §3 plan: ## Needs a human (John) - ~~Push to origin~~ — **done** (2026-07-14, Fable). -- **Run the depot publish** (authorized; agents don't hold the cred): - ```bash - cd /Users/m3ultra/Documents/procity - export GOD3_PW= - python3 pipeline/publish.py # idempotent; dry-run already verified 16 GLBs + thumbs - ``` +- ~~Run the depot publish~~ — **done** (2026-07-14, John + Fable, via the tailnet path). - **fal.ai spend**: only if MODELBEAST hero-prop rejects need it (≤$2.70, per-prop go). - **A real pointer-lock playtest** (after the current Opus round, per John) — `cd web && python3 -m http.server 8130` → http://localhost:8130?seed=20261990. diff --git a/pipeline/_published.json b/pipeline/_published.json new file mode 100644 index 0000000..7d626cf --- /dev/null +++ b/pipeline/_published.json @@ -0,0 +1,18 @@ +[ + "procity_fit_bookshelf_01.glb", + "procity_fit_box_crate_01.glb", + "procity_fit_clothes_rack_01.glb", + "procity_fit_coffee_table_01.glb", + "procity_fit_counter_01.glb", + "procity_fit_cube_shelf_wide_01.glb", + "procity_fit_record_crate_01.glb", + "procity_fit_wire_shelf_01.glb", + "procity_fit_work_table_01.glb", + "procity_street_bench_01.glb", + "procity_street_bench_modern_01.glb", + "procity_street_bench_wood_01.glb", + "procity_street_food_cart_01.glb", + "procity_street_longbench_01.glb", + "procity_street_park_bench_01.glb", + "procity_street_streetlight_01.glb" +] \ No newline at end of file diff --git a/pipeline/publish.py b/pipeline/publish.py index 6c258e3..3fc585c 100644 --- a/pipeline/publish.py +++ b/pipeline/publish.py @@ -32,6 +32,26 @@ def cookie(): return f"3g={tok}" +def to_jpeg(png_path): + """PNG thumb → JPEG bytes (the depot's /api/thumb accepts jpeg only). + Uses macOS sips (no deps); falls back to PIL if available elsewhere.""" + import subprocess, tempfile + if sys.platform == "darwin": + with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as t: + tmp = t.name + try: + subprocess.run(["sips", "-s", "format", "jpeg", "-s", "formatOptions", "85", + png_path, "--out", tmp], check=True, capture_output=True) + return open(tmp, "rb").read() + finally: + os.unlink(tmp) + from PIL import Image # non-mac fallback + import io + buf = io.BytesIO() + Image.open(png_path).convert("RGB").save(buf, "JPEG", quality=85) + return buf.getvalue() + + def post(path, body, ctype): req = urllib.request.Request(DEPOT + path, data=body, method="POST", headers={"Content-Type": ctype, "Cookie": cookie()}) @@ -59,11 +79,16 @@ def main(): try: st, msg = post("/api/upload?name=" + urllib.parse.quote(name), open(g, "rb").read(), "model/gltf-binary") + # thumb is cosmetic — never fail the publish over it (house contract: + # /api/thumb is keyed by the stored GLB name and wants JPEG) tstat = "" if os.path.exists(thumb): - ts, _ = post("/api/thumb?name=" + urllib.parse.quote(name.replace(".glb", ".png")), - open(thumb, "rb").read(), "image/png") - tstat = f" thumb:{ts}" + try: + ts, _ = post("/api/thumb?name=" + urllib.parse.quote(name), + to_jpeg(thumb), "image/jpeg") + tstat = f" thumb:{ts}" + except Exception as te: + tstat = f" thumb:ERR({te})" ok = st in (200, 201) print(f" {'OK ' if ok else 'ERR'} {name:38} glb:{st}{tstat}") if ok: diff --git a/pipeline/validate_manifest.py b/pipeline/validate_manifest.py index 6cff25e..5a6f4e8 100644 --- a/pipeline/validate_manifest.py +++ b/pipeline/validate_manifest.py @@ -30,7 +30,9 @@ def head_ok(url): # The depot contract documents GET only, not HEAD — probe with a 1-byte Range GET so we don't # download the whole GLB and don't depend on HEAD being supported by the CDN/cache. try: - req = urllib.request.Request(url, headers={"Range": "bytes=0-0"}) + # custom UA: Cloudflare 403s the default Python-urllib agent on the public path + req = urllib.request.Request(url, headers={"Range": "bytes=0-0", + "User-Agent": "procity-validator/1.0"}) with urllib.request.urlopen(req, timeout=12) as r: return r.status in (200, 206) except urllib.error.HTTPError as e: @@ -78,7 +80,8 @@ def main(): print(f"FAIL: manifest does not parse: {e}") sys.exit(1) - depot = m.get("depot", "https://digalot.fyi/3god") + # GOD3_DEPOT overrides for the direct tailnet path (same env publish.py uses) + depot = os.environ.get("GOD3_DEPOT", m.get("depot", "https://digalot.fyi/3god")).rstrip("/") sk = m.get("skins", {}) # facades + type coverage