From 3d17bed32243156fa3d71aea66cee4c836fbac06 Mon Sep 17 00:00:00 2001 From: Yaojia Wang Date: Sat, 4 Jul 2026 13:49:09 +0200 Subject: [PATCH] docs(relay): Phase 0 runnable-relay build spec (PLAN_RELAY_RUN_PHASE0.md) File-level plan for a single-host `npm start` relay: new relay-run/ package wiring the real P4/P5 + in-memory control-plane + self-signed TLS + agent + relay-web. Seam files to read, 9-step build order, verify steps. Executable spec for a fresh build session; reuses the e2e/harness wiring. --- docs/PLAN_RELAY_RUN_PHASE0.md | 52 +++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 docs/PLAN_RELAY_RUN_PHASE0.md diff --git a/docs/PLAN_RELAY_RUN_PHASE0.md b/docs/PLAN_RELAY_RUN_PHASE0.md new file mode 100644 index 0000000..2348301 --- /dev/null +++ b/docs/PLAN_RELAY_RUN_PHASE0.md @@ -0,0 +1,52 @@ +# Phase 0 — single-host runnable relay (build spec) + +> Goal: a single `npm start` that boots an **end-to-end relay you can click through locally** — +> real P4(crypto)+P5(auth) wiring, in-memory stores, self-signed TLS, agent dialing `localhost`, +> `relay-web` served. No Postgres/Redis/KMS. This is the executable spec for the Phase 0 milestone +> in [DEPLOY_RELAY.md](./DEPLOY_RELAY.md) §4. Reuses the wiring proven in `e2e/harness/`. +> +> **Run this build in a FRESH session** (recommend `/compact` first). Read the seam files named below +> before writing — the exact interface shapes must be confirmed against the code, not assumed. + +## Deliverable +A new package `relay-run/` (top-level, symlink node_modules like `e2e/`) with one entry +`relay-run/src/main.ts` that `npm start` runs, plus small wiring modules. Result: browser at +`https://localhost:8443/` → authenticates → live terminal to a shell on the same machine, spliced +through the real relay-node, E2E-encrypted (relay sees only ciphertext). + +## Seam files to read FIRST (confirm exact shapes) +- `term-relay/data-plane/ws-like.ts` — the `WebSocketLike` interface the relay-node consumes. +- `term-relay/data-plane/agent-listener.ts` — `AgentListener`/`AgentTunnel` shapes (how an agent mux attaches). +- `term-relay/data-plane/relay-node.ts` + `upgrade.ts` + `authz-port.ts` — `createRelayNode`, `authorizeUpgrade`, the `Authorizer` port (`onUpgrade`/`onReattach`). +- `term-relay/mux/mux-session.ts` — `createMuxSession`, `MuxStreamHandle` (frame the agent side). +- `control-plane/src/main.ts` — `buildControlPlane`/overrides (`stores`, `verifier`, `bus`, `kmsResolver`), `createMemoryStores`, `inProcessCaSigner`. +- `agent/src/cli.ts` + `agent/src/e2e/hostEndpoint.ts` + `transport/dial.ts` — how the agent dials + runs the E2E host endpoint + loopback splice. +- `e2e/harness/world.ts` — the REFERENCE wiring: it already composes real P5 `onUpgrade` + P4 handshake + fakes. Lift the `EnforceDeps`/registry/bucket/audit construction from here (and `e2e/harness/fakes.ts`). + +## Build steps (order) +1. **Scaffold `relay-run/`**: `package.json` (type module, `"start":"tsx src/main.ts"`, dep on `tsx`, `ws`), `tsconfig.json`, and `node_modules` symlinks to all relay packages + `ws`/`tsx` (mirror `e2e/`’s manual-symlink approach — avoids `npm install`). Add `relay-run/node_modules` to `.gitignore` coverage (root already ignores `node_modules/`). +2. **Self-signed TLS**: generate a localhost cert at boot (node `selfsigned` or a checked-in dev cert) for the browser WSS listener; a throwaway CA for agent mTLS. +3. **Control-plane (in-memory)**: boot `buildControlPlane` with `overrides = { stores: createMemoryStores(), verifier: , bus: in-memory, kmsResolver: inProcessCaSigner }`. Listen on `:8080`. Seed one account + issue a pairing code programmatically at boot. +4. **P5 EnforceDeps**: build the real `EnforceDeps` (hosts/sessions/revocation/buckets/audit + `stepUpPolicyFor: () => NO_STEPUP_POLICY`) backed by the in-memory control-plane registries — copy the construction from `e2e/harness`. Configure the P5 verify key (`configureVerifyKey`). +5. **`WebSocketLike` adapter**: wrap `ws` `WebSocket` → the `WebSocketLike` interface (send/close/onmessage/onclose per ws-like.ts). Browser WSS server on `:8443`; on upgrade, build the `UpgradeRequest` (extract token from subprotocol/cookie, Origin, DPoP header) and call `authorizeUpgrade` (→ real `onUpgrade`). **Close F2 here**: pass a resolved principal when the host policy requires step-up (Phase 0 default policy = not-required, so `null` is fine). +6. **`AgentListener` adapter**: mTLS `tls.Server` on `:8444`; verify the agent client cert against the dev CA (`verifyAgentCert`), attach each as an `AgentTunnel` + `createMuxSession`. Wire `createRelayNode({ config, agentListener, authorizer })` and the opaque splice. +7. **Agent**: build the CLI (`dist/cli.js`) or run `agent` in-process; enroll against control-plane `POST /enroll` with the seeded pairing code (→ SPIFFE cert + `hostContentSecret` + subdomain); dial `wss://localhost:8444` (mTLS) and run the E2E host endpoint splicing to a local shell (`LOCAL_TARGET_URL` / node-pty). +8. **relay-web**: `npm --prefix relay-web run build`; serve the bundle from the browser listener (same origin as WSS so Origin/CSWSH passes). Wire the login → P5 capability-token mint (Phase 0: a dev endpoint that mints a token after a stub human-auth, or reuse `issueCapabilityToken` directly). +9. **`main.ts`**: bring up 3→5→6→7→8 in order; print the URL + a ready-to-use token/subdomain. + +## Verify +- `npm start` in `relay-run/` boots without error; console prints the local URL + subdomain. +- Browser: open `https://localhost:8443/?...`, authenticate, see a live shell; type `whoami` → output. +- Kill/restart the agent → session survives per design (reconnect); `revoke()` → stream tears down. +- Add a smoke test (Vitest) that boots the world in-process and round-trips one command E2E through + the real relay-node (extend the `e2e/harness` pattern with the real `WebSocketLike` adapter). + +## Explicitly NOT in Phase 0 +Postgres/Redis (in-memory only), real KMS (dev in-process signer), wildcard TLS/multi-tenant subdomain +routing, horizontal scaling, F6 replay-epoch transport (preview stays inert), metering. Those are +Phase 1/2 in DEPLOY_RELAY.md. + +## Security notes (keep even in dev) +- Real Origin/CSWSH check on the WSS upgrade (don’t stub it off for localhost — set allowedOrigins to + the dev origin). Real capability-token verify + DPoP (no bypass). Self-signed certs are dev-only; + never reuse the dev CA anywhere real. hostContentSecret 0600, never logged.