diff --git a/.plans/trap-sandbox-gateway-plan.md b/.plans/trap-sandbox-gateway-plan.md deleted file mode 100644 index 23b105e..0000000 --- a/.plans/trap-sandbox-gateway-plan.md +++ /dev/null @@ -1,217 +0,0 @@ -# Plan: trap-sandbox gateway + trapos-sandbox client (first iteration) - -## Context - -`tools/trap-sandbox/` is a new TypeScript service that replaces the legacy `tools/mcp-bridge` -(kept only for inspiration). It delivers a **general-purpose Fastify API on port 4444**, organized -into "sandbox modules" (Fastify plugins). The only module in this iteration is the -**`trapos-cloud-gateway`**: a WebSocket gateway giving a CraftOS (trapos) computer and the -sandbox/opencode a generic, bi-directional request/reply protocol. - -On the Lua side, a new `trapos-sandbox` package installs `servers/sandbox.lua` (a boot daemon that -owns one persistent WS connection and bridges WS frames to in-OS events) plus a `sandbox` command. -The legacy `trapos-sandbox` package is renamed to `trapos-sandbox-legacy`. - -**Intended outcome:** after installing the new trapos and setting `sandbox.url`, running -`sandbox health` in-game prints `sandboxOk: true` / `opencodeOk: true|false`. - -This plan was hardened through a full design interview (see **Decision log**). Notably, auth moved -from Basic-Auth-at-upgrade to an **in-protocol `hello` handshake**, which also prepares a future -"one account owns many computers, account stored server-side" model without building it yet. - -## Decision log (grill outcomes) - -| Topic | Decision | -|---|---| -| Event shape | Explicit `event` field: `computer_request|computer_response|gateway_request|gateway_response`; `type` = redux action | -| Bidi scope | All 4 events now; reverse direction (`gateway_request`→`computer_response`) exercised by tests | -| Duplicate trapos id | **Replace existing (newest-wins)** — deliberate deviation from INIT's "reject the duplicate" | -| Identity | **Trust the frame** — gateway uses the client-declared `traposId`; no connection↔frame cross-validation | -| **Auth** | **In-protocol `hello` handshake** — secret in payload; **supersedes** Basic-Auth-at-upgrade + `?id=&label=` query and the "header support" risk | -| Auth reject | **Terminal**: daemon stops reconnecting on `unauthorized`, stores `lastError`; network drops still reconnect | -| Error model | `ok` + `error: { code, message }` with a stable code enum | -| Health disconnected | **Fail fast** with clear error; ~10s command timeout; ~5s opencode probe bound | -| Opencode wiring | `OPENCODE_SERVER_PASSWORD` + default `OPENCODE_URL=http://127.0.0.1:4242`, Basic Auth (opencode link only) | -| Health payload | `{ sandboxOk, opencodeOk, opencodeDetail? }` (detail shown only on failure) | -| Reverse scope (Lua) | Built-in `ping` only; no extensibility fan-out/response path in v1 | -| Future account seam | `accountId` constant `"local"` via `resolveAccount(secret)`; registry keyed by `(accountId, traposId)`; structured error codes | -| Host tooling | Full wiring: `just sandbox-serve`, `npm.just`/`install`/`ci`, `docs/public-ports.md` row for 4444 | -| Meta package | Add `trapos-sandbox` to `trapos` meta + bump version (daemon dormant until `sandbox.url` set) | -| Versions | Fastify `^5` + `@fastify/websocket` `^11`; `engines.node >=20` (Node 24 in use) | -| Tests | 3 layers (Lua-unit, TS-unit, TS-integration) + one full-boot e2e | - -> The "basic password mechanism" from INIT is preserved *in spirit* (a shared secret = -> `SANDBOX_PASSWORD`, mirroring `OPENCODE_SERVER_PASSWORD`) but delivered **in-band via the hello -> frame**, not as a Basic-Auth header. The opencode probe still uses Basic Auth to the opencode -> server (unchanged). - -## Protocol - -JSON frames. Computer-originated carry `traposSenderId`; gateway-originated carry `traposReceiverId`. -`traposId = ":