Lane E publish landed: 16 GLBs + thumbs live on 3GOD depot; pipeline fixes

Depot publish executed over the new passwordless tailnet path (allowlist auth,
GOD3_DEPOT=http://100.94.195.115:8788). Two contract bugs fixed en route:
- publish.py: /api/thumb wants JPEG keyed by the GLB name (was posting PNG keyed
  by a .png name -> 404 that mislabelled successful GLB uploads as ERR); thumb
  push now isolated per the MESHGOD house contract (cosmetic, never fails publish).
- validate_manifest.py: send a custom User-Agent (Cloudflare 403s Python-urllib
  on the public path) + honor GOD3_DEPOT override like publish.py.

validate_manifest.py --depot -> 0 errors, 0 warnings. qa.sh --strict all green.
_published.json records the 16 published assets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-14 15:35:28 +10:00
parent ae6017ca11
commit 862bbcd8de
4 changed files with 59 additions and 14 deletions

View File

@ -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=<the depot password>
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.

18
pipeline/_published.json Normal file
View File

@ -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"
]

View File

@ -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")
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:

View File

@ -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