Define the /ws/edge/v1 gateway uplink protocol

continuum-proxy and continuum-backend each had their own idea of what the
edge WebSocket carried, and the two did not overlap anywhere: the backend
expected an `auth` handshake the gateway never sends, telemetry shaped one
printer per message where the gateway sends a batch, and UUID printer ids a
gateway has no way to know. Neither end had a command path that matched the
other. Nothing failed, because nothing was written down for both sides to
mirror. This is that document.

schemas/continuum/v1/edge/ is canonical: client_message, server_message,
printer_telemetry and printer_command. packages/ts-types gets its first
real TypeBox mirror - the placeholder there said to add one "once something
actually consumes it", and continuum-backend's edge route now does. The
Rust mirror stays in continuum-proxy/src/uplink/protocol.rs rather than
packages/rust-types, since the gateway is its only Rust consumer.

Two decisions worth recording:

Field names are snake_case, unlike the backend's HTTP routes. The other end
is serde-derived Rust, and snake_case is what those structs produce without
a rename attribute on every field. It is also what the rest of this
repository already uses.

Optional fields accept an explicit null as well as being absent. A gateway
omits what it has no value for, which is what these schemas describe - but
serde emits null by default unless every field is annotated, and a single
such field would otherwise fail validation and take a whole farm's
telemetry batch down with it. Strict in what we send, liberal in what we
accept; the contract tests on both sides pin down each half.

Also bumps this package's @sinclair/typebox to ^0.33.12 to match what
continuum-backend already pins. It was on ^0.32.0, which resolved to a
separate 0.32.37 install - two TypeBox versions means two incompatible
TSchema identities, and Elysia rejected every schema from here on those
grounds alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 10:37:13 +00:00
co-authored by Claude Opus 5
parent 4a54ed62f7
commit ff736f0633
11 changed files with 774 additions and 4 deletions
@@ -0,0 +1,34 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/continuum/v1/edge/printer_command.schema.json",
"title": "PrinterCommand",
"description": "The closed set of things the cloud may ask a printer to do. Deliberately small, and deliberately missing the obvious one: there is no 'start a print' variant. The gateway can start an uploaded file, but that code path has never been run against real hardware, and the first thing to exercise it should be a human watching the machine rather than a message arriving from the internet. Add the variant here once it has been confirmed by hand.",
"oneOf": [
{
"type": "object",
"title": "SetFanSpeed",
"properties": {
"action": { "const": "set_fan_speed" },
"percent": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "A percentage on this wire regardless of what the vendor wants. Each gateway converts: for Bambu that means an M106 PWM value over MQTT, which the printer then reports back as one of 16 fan gears - so a value set here does not read back identically."
}
},
"required": ["action", "percent"]
},
{
"type": "object",
"title": "SendGcodeLine",
"properties": {
"action": { "const": "send_gcode_line" },
"line": {
"type": "string",
"description": "A single raw gcode line. By far the most powerful variant, and the one to be most careful about ever exposing to a UI."
}
},
"required": ["action", "line"]
}
]
}