minesweeper leaves the platform; plugin sockets relay ws-to-ws

Minesweeper is now plugins/minesweeper (gitea.officer.dev/plugins/minesweeper).
Removes /api/minesweeper, the Dashboard screen, the officerdev panel, the
permission registry entry, the App.tsx route and the hand-written websocket
provider in server.tsx. Registry entries: 22 -> 21, execution 4 -> 3.

The relay changed transport, and that is the part worth reading.

A sidecar announces exactly one port. That single announcement is the whole
provenance argument in target.ts — the platform watched the port arrive at
registration, which is the only thing separating it from a number a manifest
asserted. But a raw-TCP relay requires that port to BE a raw TCP listener, so
the sidecar cannot also serve HTTP on it, and the plugin's ordinary routes
have to fall back to the typed command vocabulary over the registration
socket. That is the mechanism the fleet has spent months retiring, and
Minesweeper would have been the first thing to go back to it.

Relaying websocket-to-websocket dissolves the conflict. The sidecar serves
HTTP and /ws on the one port it announced, exactly as the pty sidecar already
does; createSidecarProxy handles the HTTP half; and the conversion from frames
to whatever the plugin actually speaks happens inside the plugin, which is the
only process that knows. The relay is still ignorant of the payload — the
ignorance sits one layer up.

Also: the caller's JWT is no longer forwarded upstream. A sidecar has no use
for it and forwarding one puts a bearer token into whatever that process logs.
X-Officer-User carries the only part it needs. (The terminal relay still
forwards its token to the pty sidecar; not touched here.)

Verified: bunx tsgo clean, bun test 1403 pass / 0 fail, bun run docs:check
green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
2026-08-23 17:43:43 +00:00
co-authored by Claude Opus 5
parent 6490dbc368
commit fbdfd3fb89
26 changed files with 96 additions and 1034 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ import { assertPermissionTotality, isExemptApiPath } from './totality';
// switched off 2026-08-13. It was listed here until 2026-08-18 and made this file's own totality tests
// fail for five days — the mirror claiming a provider the server does not serve, which is precisely the
// drift the comment above warns about, caught by the check and then ignored because it was "the baseline".
const REAL_WS = ['terminal', 'chat', 'chat-notify', 'task-runner', 'pipeline', 'sidecar', 'minesweeper'];
const REAL_WS = ['terminal', 'chat', 'chat-notify', 'task-runner', 'pipeline', 'sidecar'];
const realApi = () => [...new Set(PERMISSIONS.flatMap((c) => c.api))];
const surface = () => ({
apiPrefixes: [...realApi(), '/auth', '/landing-page-data', '/waitlist', '/sidecar'],
-19
View File
@@ -328,25 +328,6 @@ const CORE_REGISTRY: Permission[] = [
ws: ['task-runner', 'pipeline'],
routes: ['/jobs'],
},
// Minesweeper X, on a virtual display, streamed to the browser over RFB.
//
// `execution` rather than `app`, and the reason is not that a game is dangerous. The wine process runs
// as the SERVER OWNER on the owner's display — so its File and Extras menus are file dialogs with the
// owner's home behind them, and "granted Minesweeper" would quietly mean "granted a file picker as the
// owner". `plugins/desktop` reached the same conclusion from the same premise.
//
// There is a second reason to keep it here for now: one display and one game means two viewers share a
// board and fight over the mouse. Per-viewer sessions would be a display and a wine prefix each, which
// is cheap for this but is a design decision rather than an implementation detail.
{
key: 'minesweeper',
label: 'Minesweeper',
description: 'A Windows Minesweeper session on a virtual display, as the server owner',
kind: 'execution',
api: ['/minesweeper'],
ws: ['minesweeper'],
routes: ['/minesweeper'],
},
/*
SendOff is `app`, not `confined`, and the distinction is the point.