bend: dual-port worker (8320 wire + 8321 http) — playground onramp

Each gpu-worker.lsp now listens on both wire-TCP (existing :8320) and
HTTP/1.1+CORS (new :8321), sharing one handle-request dispatcher. Lets
a tab on https://lumbda.com/playground/ POST to its own machine via
http://localhost:8321/ — browsers permit localhost from HTTPS origins
without TLS, so no proxy, no cert, no fox-owned infra required for the
decentralized run-your-own-bend story.

main() forks at startup: child runs http-run-loop on :8321, parent
keeps existing run-loop on :8320. Adding a new op-head to handle-request
exposes it over both transports automatically. Binary modes
(BSHK/BCGB/BSCP/BSRT/BSB3) stay wire-only — they exist for native
callers who already cache the binary locally; browser callers send
S-expression recipes the worker dispatches the same way.

Two latent defects fixed to make CPU-only and Python-tier hosts work:
- vram-used-mib now file-exists? guards /usr/bin/nvidia-smi. Python
  tier's spawn-process-stdio raises FileNotFoundError on missing
  binary, not returning #f as the prior code expected, which crashed
  every worker on a CPU-only laptop.
- fork-self return discriminated via (number? pid) not (eq? pid 0).
  Python tier's (eq? 0 #f) returns #t because == conflates int 0
  with bool False; pre-existing run-loop has the same risk but
  C/asm tier (identity eq?) masks it for the production case.

Phase 2 (server-side factory ops: compile uploaded .lsp recipes into
.bin before bending — the foxhop champion-circuit workflow) deferred
until authentication lands; today a worker on the public internet
would let any caller occupy our GPU.

Operational Caddy + DNS proposals in plans/bend-http-deploy.md cover
the personal-remote-access endpoint chain (proxy.unturf.com edge →
ai.foxhop.net Caddy → 3090-ai:8321) gated by trusted-IP allowlist —
applied separately.

Also codifies the playground "CSS Grid only, never flexbox" rule in
CLAUDE.md: all www/ and wasm/ stylesheets are already grid-only;
documenting the invariant so future edits don't drift.

Tests: smoke-bend-http.sh — (ping)→(ok pong), unknown-op fallback,
OPTIONS CORS preflight — all PASS. Wire path unchanged, verified
round-trip via 8-digit-prefix framing.
This commit is contained in:
russell@unturf.com 2026-06-14 17:32:46 -04:00
parent e34fb1f7dc
commit 88e16c0ce2
No known key found for this signature in database
8 changed files with 771 additions and 30 deletions

View file

@ -35,6 +35,48 @@ Then ask fox about our mission.
- Every implementation (Python, C, GNU asm) carries a dedicated architecture diagram
- When explaining architecture, draft or reference a dot diagram first
## Web styling — CSS Grid only, never flexbox
Every page in `www/` and `wasm/` (homepage, whitepaper, playground,
REPL, bend demo, 404) lays out multi-child regions with **CSS Grid**.
Flexbox is **banned** as a layout primitive. One layout language across
every page; no mode-switching in our head while we read or edit.
**Forbidden** anywhere in our CSS (source or generated):
- `display: flex`, `display: inline-flex`
- `flex-direction`, `flex-wrap`, `flex-flow`
- `flex-grow`, `flex-shrink`, `flex-basis`, shorthand `flex:`
- `order` (use grid-area / source order instead)
**Allowed** (works for grid too — keep on grid containers only):
- `gap`, `row-gap`, `column-gap`
- `align-items`, `justify-items`, `place-items`
- `align-content`, `justify-content`, `place-content`
- `align-self`, `justify-self`, `place-self`
**Patterns that look like they need flex but don't:**
- Horizontal toolbar → `display: grid; grid-auto-flow: column; gap: …`
- Tab row → same as above; sticky positioning composes fine
- Centered single child → `display: grid; place-items: center`
- Sidebar + main → `display: grid; grid-template-columns: auto 1fr`
- Wrapping chip cloud → `display: grid; grid-template-columns: repeat(auto-fit, minmax(N, max-content))`
Every source CSS file under `www/` and `wasm/{app,repl,dist,dist-repl}/`
opens with the banner `/* No flexbox. Every multi-child layout uses
CSS Grid. */`. Keep that banner intact when editing; add it when
introducing a new stylesheet.
**Audit before commit** when CSS changed:
```bash
grep -rn 'display:[[:space:]]*\(inline-\)\?flex\|flex-direction\|flex-wrap\|flex-grow\|flex-shrink\|flex-basis' www/ wasm/ && exit 1 || echo "grid-only OK"
```
A hit fails our audit. Convert to grid before committing.
## Implementations
| Impl | Path | Build | Test | REPL |
@ -48,12 +90,18 @@ Then ask fox about our mission.
`examples/cuda-fanout/` ships `(bend ...)` — runtime decides per call
whether to evaluate locally or ship to a CUDA worker over our wire
protocol. Two wire modes:
protocol. **Each worker listens on two ports out of the box:**
- **S-expression mode** (text) — for small payloads. Slow above ~1k
inputs because parser cost dominates.
- **Binary mode** (magic `BSHK` + raw bytes) — for huge payloads. 150x
faster than S-exp at 1M inputs; bends past host hashlib by 12x.
- **8320 — wire-TCP** (length-prefixed S-expressions + binary magic
`BSHK`/`BCGB`/`BSCP`/`BSRT`/`BSB3` blobs). The native path: used
by `bend.lsp`, asm clients, and anything that can open a raw
socket. Fastest. Binary mode is 150× faster than S-exp at 1M inputs.
- **8321 — HTTP/1.1 + CORS** (POST body is the S-expression, response
body is the result text). The browser path: lets a tab on
`https://lumbda.com/playground/` POST to `http://localhost:8321/`
via the browser's localhost-exception (no TLS required, no proxy
needed). Same `handle-request` dispatcher fires on both ports, so
every op-head ships once and lights up on both transports.
Workers run on any tier (`make gpu-worker LUMBDA={c,python,asm}`).
C tier ~9x faster than Python on small calls; binary mode equalizes
@ -67,6 +115,19 @@ to dispatch real-scale candidate scoring to a GPU worker. The Phase B
1-8 secp256k1 arithmetic landed on the foxhop side this session, so
the substrate has every piece it needs.
**Phase 2 (deferred until auth lands):** server-side factory ops
that take an `.lsp` recipe via HTTP, compile a `.bin` on the worker
filesystem, then bend it. Killer use case: upload a foxhop champion
ECDSA circuit recipe from the playground/REPL and get the result
back from a GPU cluster. Blocked on authentication — today a worker
without auth would let any caller occupy our GPU.
**Run your own bend** (the decentralized story we encourage): any
user runs `make gpu-worker LUMBDA=python` on their machine and
pastes `http://localhost:8321/` into the playground bend field.
CPU works (kernels fall back to host execution); GPU faster. No
fox-owned infra required.
## Test Suites
- Python unit/integration: `tests.py` (571 tests)