pub enum WsMessage {
SerialIn {
data: String,
},
SerialOut {
data: String,
},
State {
state: GuestState,
},
Launch,
Reset,
Hello {
version: String,
},
KernelChanged {
ok: bool,
mtime: i64,
size: u64,
sha256_prefix: String,
reason: Option<String>,
},
ConfigUpdate {
config: Value,
},
ConfigInvalid {
error: String,
line: Option<u32>,
col: Option<u32>,
},
ScenarioStart {
scenario: String,
},
ScenarioAbort {
reason: String,
},
ScenarioResult {
verdict: String,
scenario: String,
started_at: String,
ended_at: String,
actions: Value,
transcript: Value,
error: Option<String>,
},
}Expand description
Wire-level message exchanged over the /ws endpoint.
Externally tagged via #[serde(tag = "type")], producing JSON of the form
{"type": "SerialIn", "data": "..."}. Byte payloads (SerialIn,
SerialOut) are base64-encoded so the protocol stays JSON-only on the
wire — see 02-CONTEXT.md decision “/ws message protocol — tagged JSON
only”.
Note: #[serde(deny_unknown_fields)] is intentionally NOT applied —
Phase 4 may add variants additively and older clients should ignore
unknown fields gracefully (02-RESEARCH.md Open Question 3).
Phase 4 (additive): ScenarioStart, ScenarioAbort, and
ScenarioResult extend this enum without renaming or reordering any
existing variant. The bootroom run driver awaits a single
ScenarioResult frame per scenario and translates verdict to a
process exit code (RUN-01, RUN-08).
Variants§
SerialIn
Host -> guest. Bytes injected into guest stdin. data is base64.
SerialOut
Guest -> host. Bytes the guest emitted on serial. data is base64.
Browser emits these for the server to log; server may forward in
Phase 4 headless mode.
State
Server -> client. Authoritative guest status pill state. When the
/ws connection is live this overrides the browser’s local view.
Fields
state: GuestStateLaunch
Client -> server. Asks the server (and observers) to log a Launch action; the browser then page-reloads to re-instantiate qemu-wasm.
Reset
Client -> server. Asks the server (and observers) to log a Reset
action; in Phase 2 this is identical to Launch from the
browser’s perspective.
Hello
Server -> client on connect. version is the server’s
CARGO_PKG_VERSION. Mismatched clients log a warning but proceed.
KernelChanged
Server -> client. Watcher detected a kernel rebuild. ok=true means
size-stability and ELF magic both passed; ok=false carries reason
(e.g., "not ELF"). The browser shows a non-intrusive banner; Launch
is user-initiated. WCH-05.
ConfigUpdate
Server -> client. bootroom.toml was edited and re-parsed
successfully. config is the same JSON projection /api/config
returns. CFG-10.
ConfigInvalid
Server -> client. bootroom.toml was edited but re-parse failed.
The last-known-good config remains active. line/col are 1-based
when the error has a TOML span. CFG-10.
ScenarioStart
Browser -> server (reserved). Sent by the scenario engine at scenario
kickoff. Phase 4 does not require the server to act on this frame —
URL-query detection (?scenario=<name>) is the canonical entry point
— but the variant is reserved so future server-driven re-runs
(--watch, v2) get the wire shape for free. Per 04-RESEARCH Open
Question 1: ship now, leave unused.
ScenarioAbort
Server -> client. Defensive cancellation. Phase 4 does not emit this
frame on any code path; reserved so a future per-server outer-timeout
path can request the browser to bail. Per 04-RESEARCH WsMessage
block “Server -> client. Defensive cancellation”.
ScenarioResult
Browser -> server. Final scenario verdict + full transcript. The
bootroom run driver awaits this frame on a oneshot::Receiver
(parked on AppState) and translates verdict to a process exit
code. Schema (RUN-01, RUN-08):
verdict: “pass” | “fail” | “timeout” | “error”scenario: the scenario name as runstarted_at/ended_at: ISO 8601 UTC timestamps with Z suffix (04-RESEARCH Open Question 3: UTC for machine-parseable logs)actions: opaque JSON — per-action verdicts + per-assertion verdictstranscript: opaque JSON — ordered event list (same shape as the--log-fileJSONL stream defined in 04-06)error: optional structured message forverdict∈ {“timeout”, “error”}
The two opaque-JSON fields use serde_json::Value (not concrete
nested structs) so the wire shape is forward-compatible: the
browser engine builds the JSON; the server only forwards bytes to
--log-file and translates verdict. Concrete nested structs
would force schema-version coupling on every event-shape change.