feat(relay): Phase1 waves A2-E — server entry, shared-store data plane, agent runtime, deploy artifacts
RELAY-PHASE1 Wave A2/B/C/D/E (12-agent workflow, all tsc-clean, 314/314 tests pass): - A2: control-plane server.ts entry + boot/redis.ts revocation-bus wiring + start script. - B1: relay-run shared-store EnforceDeps (relay-auth ports over the SAME Postgres+Redis as P3). - B2: registry-backed MtlsVerifier (verifyAgentCert, fail-closed, INV14). - B3: store-backed RouteResolver (subdomain->hostId). - B4: Redis relay:revocations subscriber -> tunnel teardown (INV12). - B5: main-phase1.ts production entry (public bind, real TLS, async-mTLS prefetch bridge) + staging /auth/mint. - B6 (PARTIAL): relay-web operator login + browser DPoP; proof offered via term.dpop.<b64u> subprotocol. - C: agent dist/cli.js build (esbuild) + runTunnel run-loop + CliDeps. - D1: same-origin static serve of relay-web/public from the browser WSS. - E: systemd units + gen-ca/gen-capability-key/issue-tls-cert scripts + deploy/RUNBOOK.md. Adversarial review: all hard invariants PASS. Follow-ups (B7): close DPoP-subprotocol read on browser-server (blocks browser connect); rate-limit /auth/mint (F1); wire activeSessionCount (F2); scrub error logs (F5). Excludes unrelated public/style.css (concurrent iOS job).
This commit is contained in:
@@ -12,6 +12,7 @@ import { APP_SUBPROTOCOL } from 'relay-contracts'
|
||||
import type { UpgradeRequest } from 'term-relay/data-plane/upgrade.js'
|
||||
import type { RelayNode } from 'term-relay/data-plane/relay-node.js'
|
||||
import { wsToWebSocketLike } from '../wiring/socket-pipe.js'
|
||||
import { serveStatic } from './static-web.js'
|
||||
|
||||
export interface BrowserServerOptions {
|
||||
readonly certPath: string
|
||||
@@ -20,6 +21,20 @@ export interface BrowserServerOptions {
|
||||
readonly bindPort: number
|
||||
readonly node: RelayNode
|
||||
readonly landingHtml: string
|
||||
/**
|
||||
* When set, the HTTP handler serves the built relay-web bundle from this directory (D1), SAME
|
||||
* ORIGIN as the WSS (so Origin/CSP stay aligned). When unset, `landingHtml` is served — the
|
||||
* Phase-0 dev fallback. The WS upgrade is unaffected either way (it rides the `upgrade` event).
|
||||
*/
|
||||
readonly staticRoot?: string
|
||||
/**
|
||||
* Optional pre-router (B5): consulted BEFORE `staticRoot`/`landingHtml` on every non-upgrade HTTP
|
||||
* request. Return `true` to claim the request (the hook owns the response — it may finish it
|
||||
* asynchronously); return `false` to fall through to the static/landing behavior below. Default
|
||||
* (undefined) preserves D1's behavior exactly. WS upgrades never reach this hook (they ride the
|
||||
* `upgrade` event), so same-origin `POST /auth/mint` can coexist with the WSS.
|
||||
*/
|
||||
readonly onRequest?: (req: IncomingMessage, res: ServerResponse) => boolean
|
||||
readonly onListening?: () => void
|
||||
readonly onError?: (e: unknown) => void
|
||||
}
|
||||
@@ -56,7 +71,24 @@ function buildUpgradeRequest(req: IncomingMessage): UpgradeRequest {
|
||||
export function startBrowserServer(opts: BrowserServerOptions): Server {
|
||||
const server = createServer(
|
||||
{ cert: readFileSync(opts.certPath), key: readFileSync(opts.keyPath) },
|
||||
(_req: IncomingMessage, res: ServerResponse) => {
|
||||
(req: IncomingMessage, res: ServerResponse) => {
|
||||
// Pre-router (B5): a claimed request is fully owned by the hook (e.g. POST /auth/mint) and
|
||||
// must NOT fall through to static/landing (which would double-write the response).
|
||||
if (opts.onRequest !== undefined && opts.onRequest(req, res)) return
|
||||
// Static-bundle mode (Phase 1): serve relay-web from `staticRoot`, SAME-ORIGIN as the WSS.
|
||||
// Non-upgrade HTTP requests only — WS upgrades never reach this handler (see `upgrade` event).
|
||||
if (opts.staticRoot !== undefined) {
|
||||
const file = serveStatic(opts.staticRoot, req.url ?? '/')
|
||||
if (file) {
|
||||
res.writeHead(file.status, file.headers)
|
||||
res.end(file.body)
|
||||
} else {
|
||||
res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' })
|
||||
res.end('Not Found')
|
||||
}
|
||||
return
|
||||
}
|
||||
// Phase-0 fallback: a single landing page.
|
||||
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' })
|
||||
res.end(opts.landingHtml)
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user