{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/continuum/v1/edge/client_message.schema.json", "title": "EdgeClientMessage", "description": "What a continuum-proxy gateway sends up to continuum-backend's /ws/edge/v1 route. Discriminated on 'type'. Field names are snake_case, matching every other schema in this repository - note that this differs from the camelCase the backend's HTTP routes use, because this wire format is shared with a Rust consumer whose serde structs derive these names directly.", "oneOf": [ { "type": "object", "title": "Hello", "description": "First message on every connection, before anything else is accepted. Carries the edge JWT minted by POST /auth/edge-token.", "properties": { "type": { "const": "hello" }, "gateway_id": { "type": "string" }, "farm_id": { "type": "string" }, "token": { "type": "string" } }, "required": [ "type", "gateway_id", "farm_id", "token" ] }, { "type": "object", "title": "Heartbeat", "properties": { "type": { "const": "heartbeat" }, "seq": { "type": "integer", "minimum": 0, "description": "Monotonic per connection, restarting at 1 on reconnect. Echoed back in heartbeat_ack so the gateway can tell a stalled connection from a slow one." } }, "required": [ "type", "seq" ] }, { "type": "object", "title": "Telemetry", "description": "One snapshot per printer that has something to report, on a timer. Sent as a batch rather than a message per printer: a farm has one uplink and many printers, and the backend wants a coherent picture of the farm at an instant more than it wants the lowest possible latency per machine.", "properties": { "type": { "const": "telemetry" }, "printers": { "type": "array", "items": { "$ref": "./printer_telemetry.schema.json" } } }, "required": [ "type", "printers" ] }, { "type": "object", "title": "CommandResult", "description": "The reply to a server 'command'. Correlated by command_id, which the backend generates - unlike Bambu's sequence_id, Continuum owns both ends of this protocol, so the side that needs the correlation gets to choose it.", "properties": { "type": { "const": "command_result" }, "command_id": { "type": "string" }, "ok": { "type": "boolean" }, "error": { "type": [ "string", "null" ], "description": "Present only when ok is false. A human-readable message, not a code: its only destination is an operator looking at why a command did not take. Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it." } }, "required": [ "type", "command_id", "ok" ] }, { "type": "object", "title": "FrameBatch", "description": "Camera stills already uploaded to object storage, handed over for the AI worker to score. Not yet sent by any gateway - continuum-proxy restreams cameras through go2rtc and has no frame-capture path yet - but the backend consumes it, so it is part of the contract.", "properties": { "type": { "const": "frame_batch" }, "printer_id": { "type": "string" }, "print_job_id": { "type": [ "string", "null" ], "format": "uuid", "description": "Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it." }, "frames": { "type": "array", "items": { "type": "object", "properties": { "object_key": { "type": "string" }, "captured_at": { "type": "string", "format": "date-time" } }, "required": [ "object_key", "captured_at" ] } } }, "required": [ "type", "printer_id", "frames" ] } ] }