# LAN の HTTP アクセスで `crypto.randomUUID()` が WebSocket 接続開始を阻害する

## 概要

Workspace WebUI の Vite dev server を `--host` 付きで LAN に公開し、別端末から
`http://192.168.1.32:5173` にアクセスすると、Worker Console の protocol 状態が
`connecting` のまま進まなかった。

原因は WebSocket や Vite proxy ではなく、WebSocket multiplexing の相関 ID 生成に
`crypto.randomUUID()` を直接使っていることである。`http://localhost` はブラウザから
potentially trustworthy origin として扱われる一方、LAN IP 上の平文 HTTP は secure
context ではない。`crypto.randomUUID()` は secure context 限定なので、LAN アクセスでは
接続処理が WebSocket の生成前に例外終了する。

## 発生経路

Worker Console の `connectProtocolTransport` は、最初に `protocolState` を
`"connecting"` に設定してから `WorkspaceMultiplexer.subscribe()` を呼ぶ。

- `web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte`
  - `protocolState = "connecting"`
  - 直後に `workspaceMultiplexer(...).subscribe(...)`
- `web/workspace/src/lib/workspace/multiplexer.ts`
  - `subscribe()` の先頭で `const clientId = crypto.randomUUID()`
  - その後の `#ensureConnected()` で初めて `new WebSocket(url)` を実行する

このため `crypto.randomUUID()` が利用できないブラウザでは、画面状態だけが
`connecting` に更新された後に同期例外が発生し、WebSocket request 自体が送信されない。
接続失敗を示す `closed` や diagnostic にも遷移しないため、見た目からは WebSocket の
接続待ちに見える。

同じ multiplexer では `crypto.randomUUID()` を次の3用途に使っている。

1. ブラウザ内で subscription を区別する `clientId`
2. `subscribe_events` request と response を対応付ける `request_id`
3. `unsubscribe_events` の `request_id`

いずれも暗号化や認証のためではなく、ローカル Map のキーまたはプロトコル上の相関 ID
である。secure-context-only API を要求する必要性はない。

## 切り分け結果

開発ホスト上から次の条件で `/api/w/<workspace-id>/protocol/ws` に WebSocket upgrade を
送ると、backend への直接接続と Vite の `5173` proxy 経由の両方で
`101 Switching Protocols` が返った。

- `Host: 192.168.1.32:5173`
- `Origin: http://192.168.1.32:5173`

したがって、現在の Vite 設定にある `proxy["/api"].ws = true` と、loopback に bind した
backend への proxy はこの現象の直接原因ではない。

## 改善案

- `WorkspaceMultiplexer` の client/request correlation ID は、module-local の単調増加
  sequence から生成する。これらは1ページ・1接続内で重複しなければよく、暗号学的乱数や
  永続的なglobal uniquenessは不要なので、`crypto`、`Math.random()`、時刻依存のfallbackを
  使わない。
- `WorkspaceMultiplexer.subscribe()` の同期初期化失敗を Console の diagnostic/state に
  反映し、初期値の `connecting` に留まらないようにする。
- LAN 上の平文 HTTP を開発時の対応経路とするなら、insecure context から subscription
  初期化できることを regression test として固定する。
- 本番相当の LAN 公開では HTTPS を使う。Passkey/WebAuthn も secure context を要求し、
  現在の開発設定は `rp_id = localhost` なので、LAN origin を正式対応する場合は auth の
  origin/RP ID 設計も別途必要になる。

## 補足

`--host` は Vite の listener を LAN に bind するだけであり、配信 origin を secure
context に変えるものではない。localhost で正常に動くことだけでは、LAN IP の HTTP
アクセスでも同じブラウザ API が利用できることを証明できない。

## Resolution (2026-08-06)

`WorkspaceMultiplexer` の client/request ID をmodule-localの単調増加sequenceへ変更した。
これによりWebSocket接続開始経路からsecure-context限定の `crypto.randomUUID()` 依存を除去した。
IDはlocal correlationにのみ使われ、認証・認可・capability authorityには使われない。
