openspec/changes/archive/2026-09-10-add-stage-bay/reviews/2026-09-10-advise.md
Advise: add-stage-bay
ADVISE: accept READER: agt-fable-5-1-arch-review SPAWN: /Users/dukejones/work/ClientProjects/AllSystemsGo/AICamera/.spawns/add-stage-bay-1789034625-41352-80c40a60
Rigor: architecture. Density: lean. Reader family: Claude (Fable 5.1). Author family: grok.
Read order: packet Why → openspec/specs/hardware-kit/spec.md (empty) → change spec → sim/3090/serve.py, sim/3090/snapshot.py, vite.config.ts, src/routes/bay/+page.svelte, src/lib/bay/*, src/routes/+layout.svelte, src/routes/layout.css, docs/3090-SIM.md.
Blind take written before design.md / tasks.md / proposal.md / steer.md / notes/ were opened.
Blind take (written before design.md)
- Pin. The Tailscale IP
100.103.147.70is hardcoded in two files (serve.pyBIND,vite.config.tsproxy target). One constant, or resolve it:tailscale ip -4at serve start, MagicDNSfractal1in the proxy. - Pin. Derates are declared owned by
docs/3090-SIM.md, yetsnapshot.pyhardcodesfilter_k 0.13 / bandwidth_k 0.30and bakes them into the plate. The doc itself says ×0.29 in the summary and ×~0.30 in the table. Three copies, two values. - Pin. Per-mag
RESIDENTis process-granular, not model-granular: anypython.*(pipeline|diffusers|transformers|sam2)process marks SAM2 and CLIP resident, and klein too. Running SAM2 alone lights all three. The spec scenario only tests idle, so this passes the spec while lying under load. - Pin.
cap 420is a string literal on the glass. The sim doc says the 3090 is a 350 W card.power.limitis one more field on the existingnvidia-smi --query-gpu. - Pin. Plate age lives only in the footer. On Pages the slate still says
HOLDwith a tally, which reads as live-idle. Plate date belongs besidePLATEin the slate. And there is no one-line plate refresh; the plate will rot. - Refuse. Any feed bind other than the Tailscale address. Refuse
0.0.0.0. Refuse a public tunnel or relay to make Pages “live”; mixed content (https Pages → http 100.x) blocks it anyway, and the honest answer is the plate. - Refuse. Treating
vite devas the only live surface forever without saying so. A tailnet-served build (vite preview, any static host on 100.x) will never go live because the fetch isimport.meta.env.DEV-gated. Acceptable now; it must be written down. - Tradeoff. DEV gate vs hostname gate. DEV gate is one line, zero console noise on Pages, and cannot leak a 100.x fetch into a public build. Hostname gate would let a tailnet-hosted build go live but reopens CORS (
Access-Control-Allow-Origin: *is already on the feed) and mixed-content questions. Take DEV; revisit only if a tailnet static host appears. - Concern. Tactic Sans (Reg, ExtExd Black) and Space Age are committed as font binaries in a public Pages repo. No license file beside them. Web-embedding rights for Tactic Sans need checking before this is public.
- Concern. Fold target. The living spec cited is
hardware-kit, the change delta isspecs/stage-bay/.openspec/specs/stage-bay/does not exist. The fold must create it, or the delta silently goes nowhere. Also+layout.sveltelocks onpathname.includes('/bay'); no collision today, butstartsWith(base + '/bay')is the intent.
Compare against design.md, proposal.md, steer.md, tasks.md
Architecture matches the take. Design names the exact reason for the split: Pages is HTTPS, fractal1 is HTTP on 100.x, mixed content blocks it. Steer records it as decided. The feed is Tailscale-bound and the design gives the stronger reason I did not have: UPnP is on, so 0.0.0.0 could be punched through the router. Refusal 6 is answered and strengthened. Accept the data plane as designed.
Design claims the glass does not own derates. Code disagrees. Design: “Derate constants stay in docs/3090-SIM.md — the glass cites them, it does not own them.” The self-critique repeats it. But snapshot.py emits t4000_read with its own literals and the plate carries them. The glass does not own them; the feed does, which is the same problem one hop away. Pin 2 stands. Fix is small: either the feed stops emitting t4000_read and the page imports the numbers from the doc frontmatter at build time, or the doc frontmatter becomes the single source the snapshot reads. Also reconcile 0.29 vs 0.30 in the doc.
Proposal journey promises a “BRICK DARK” slate. Code has no such state. Journey row “Failed / brick dark: fetch timeout. Last plate with a ‘BRICK DARK’ slate.” In +page.svelte a failed pull sets live: false and the slate reads PLATE, indistinguishable from the Pages case where no fetch was ever attempted. The Why says the data plane “has to be honest about live vs plate”; there are three states (live, tried-and-dark, never-tried) and the glass shows two. Pin 5 sharpens into this: add a dark flag, show BRICK DARK in DEV on failure, PLATE plus date on Pages.
Residency (pin 3). Design says “stack catalog with resident” and nothing about granularity. Not contradicted, just unaddressed. The author must answer it: either the glass says one thing (“AI PROCESS ON TUBE”) or pipeline.py writes a per-model manifest the snapshot reads. Not a send-back; the spec scenario as written is met.
tasks.md checks “JSON contract in stage-bay spec”. The spec has no JSON contract; src/lib/bay/types.ts is the contract. Fine, but the checkbox overstates. Tasks are otherwise thin for a landed change: no plate-refresh step, no font-license step, no fold-target step.
Steelman for the author. This is a one-person lab EVF, landed on main in one commit, whose only live consumer is a Mac on the tailnet running vite dev. Hardcoded IP, hardcoded cap, and process-level residency are all correct today on this one brick. Adding a manifest protocol for residency before the pipeline even runs three models at once would be spec-first waste. I agree, which is why this is accept-with-pins and not send-back: the architecture is right and each pin is a local edit, not a redesign.
Why accept, not send-back
Send-back is for a design that contradicts the spec or a data plane that needs redoing. Neither applies. The live-vs-plate split, the Tailscale-only bind, the DEV gate, and the dark lock all hold up against the cited code. The gaps are one design sentence the code does not yet honor (derate ownership), one journey state the glass does not yet render (BRICK DARK), and housekeeping (plate refresh, fold target, fonts). Pinned as unchecked tasks below; none blocks act on the next node.
Pinned to tasks.md
Appended as unchecked items. See tasks.md.