From b8462f25fcff699e30e515db48cec812852de49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iwo=20Strzebo=C5=84ski?= Date: Fri, 30 Oct 2026 08:05:04 +0000 Subject: [PATCH] Add job_event and job_event_ack to the gateway protocol A gateway keeps what happened to a job it was handed (started, finished, failed) and re-sends it on every connection until the cloud acknowledges it, so an outcome reached while the cloud was unreachable still arrives. --- packages/ts-types/src/schemas/edge.ts | 24 +++++++++++++++++++ .../v1/edge/client_message.schema.json | 14 +++++++++++ .../v1/edge/server_message.schema.json | 11 +++++++++ 3 files changed, 49 insertions(+) diff --git a/packages/ts-types/src/schemas/edge.ts b/packages/ts-types/src/schemas/edge.ts index ba8fa88..b8d4486 100644 --- a/packages/ts-types/src/schemas/edge.ts +++ b/packages/ts-types/src/schemas/edge.ts @@ -316,6 +316,22 @@ export const EdgeClientMessageSchema = Type.Union([ jpeg_base64: Maybe(Type.String()), error: Maybe(Type.String()), }), + /** + * What happened to a job the gateway was handed. Kept in the gateway's own + * database and re-sent on every connection until answered with a + * `job_event_ack`; the cloud applies it only to a job still in flight and + * applying it twice changes nothing. + */ + Type.Object({ + type: Type.Literal("job_event"), + event_id: Type.Integer(), + job_id: Type.String({ format: "uuid" }), + event: Type.Union([Type.Literal("started"), Type.Literal("finished"), Type.Literal("failed")]), + /** For `failed`: the reason, in words for an operator. */ + detail: Maybe(Type.String()), + /** When the gateway saw it, ms since the Unix epoch. */ + at_ms: Type.Integer(), + }), Type.Object({ type: Type.Literal("frame_batch"), printer_id: Type.String(), @@ -401,6 +417,14 @@ export const EdgeServerMessageSchema = Type.Union([ }, { additionalProperties: false }, ), + /** The cloud has dealt with a `job_event`; the gateway can forget it. */ + Type.Object( + { + type: Type.Literal("job_event_ack"), + event_id: Type.Integer(), + }, + { additionalProperties: false }, + ), Type.Object({ type: Type.Literal("command"), command_id: Type.String(), diff --git a/schemas/continuum/v1/edge/client_message.schema.json b/schemas/continuum/v1/edge/client_message.schema.json index d495c79..3da66e6 100644 --- a/schemas/continuum/v1/edge/client_message.schema.json +++ b/schemas/continuum/v1/edge/client_message.schema.json @@ -195,6 +195,20 @@ }, "required": ["type", "request_id"] }, + { + "type": "object", + "title": "JobEvent", + "description": "What happened to a job this gateway was handed, kept in the gateway's own database and re-sent on every connection until the cloud answers with a JobEventAck - so an outcome reached while the cloud was unreachable (or the gateway restarted) still arrives. The cloud applies it only to a job that is still in flight, so a job it has since cancelled or reassigned is left alone, and applying the same event twice changes nothing.", + "properties": { + "type": { "const": "job_event" }, + "event_id": { "type": "integer", "description": "The gateway's own sequence number for this event; echoed in the JobEventAck." }, + "job_id": { "type": "string", "format": "uuid" }, + "event": { "enum": ["started", "finished", "failed"] }, + "detail": { "type": ["string", "null"], "description": "For failed: the reason, in words for an operator." }, + "at_ms": { "type": "integer", "description": "When the gateway observed it, milliseconds since the Unix epoch." } + }, + "required": ["type", "event_id", "job_id", "event", "at_ms"] + }, { "type": "object", "title": "DiscoveredPrinters", diff --git a/schemas/continuum/v1/edge/server_message.schema.json b/schemas/continuum/v1/edge/server_message.schema.json index ba5d1df..e49d266 100644 --- a/schemas/continuum/v1/edge/server_message.schema.json +++ b/schemas/continuum/v1/edge/server_message.schema.json @@ -122,6 +122,17 @@ "required": ["type", "request_id", "printer_id"], "additionalProperties": false }, + { + "type": "object", + "title": "JobEventAck", + "description": "The cloud has dealt with a JobEvent (applied it, or found it had nothing to change), so the gateway can forget it. Not sent when the cloud could not record it, so the gateway sends it again.", + "properties": { + "type": { "const": "job_event_ack" }, + "event_id": { "type": "integer" } + }, + "required": ["type", "event_id"], + "additionalProperties": false + }, { "type": "object", "title": "ProvisionPrinter",