# Cloud print job: MQTT-native design (not yet implemented) `OrcaPrinterAgent::start_print` currently finalizes a cloud print job over HTTP (`OrcaCloudServiceAgent::start_cloud_print_job`, `POST /api/v1/printers//print-jobs//start`). This document records the MQTT-native alternative that was designed as the intended replacement, the gap that blocks it today, and why it should not be built by adding fields to `print.gcode_file`. ## Current (implemented) flow 1. `OrcaCloudServiceAgent::upload_gcode_via_cloud` - `POST print-jobs/uploads` -> `{job_id, upload_url, expires_at, max_bytes}` - `PUT upload_url` -> raw G-code straight to R2 (presigned, PUT-only, no bearer token; must not go through the `http_put` helper, which always prefixes `api_base_url` and attaches the cloud session's Authorization header). 2. `OrcaCloudServiceAgent::start_cloud_print_job` - `POST print-jobs//start` with `{filename, start}`. - The gateway HEAD-verifies the R2 object landed, mints a short-lived signed *download* URL (`createPrintJobDownloadToken` in `apps/gateway/src/services/printer-print-jobs.ts`), and relays a `print.project_file` command carrying that URL to the printer - **over the gateway's own relay connection to OrcaSonar, not OrcaSlicer's MQTT session.** OrcaSonar's `executeProjectFile` (`internal/cloud/printfile.go`) downloads from that URL and starts the print. This works today and requires no changes to OrcaCloud or OrcaSonar. ## Why an MQTT-native version is desirable The HTTP finalize call means `start_print`'s cloud path depends on the client's REST session (auth token, network path to the gateway's HTTP API) in addition to its MQTT session. An MQTT-native version would let OrcaSlicer trigger the download-and-start entirely over the connection it already maintains for every other printer command. ## The key finding: OrcaSonar already supports this, transport-agnostically `print.project_file` is not tied to the HTTP `/start` route. OrcaSonar's cloud MQTT message handler intercepts it purely by command name, before routing to the generic per-namespace dispatcher: ```go // internal/cloud/client.go, makeRequestHandler if cmd.Namespace == "print" && cmd.Name == "project_file" { go c.handleProjectFile(ctx, cmd) // downloads `url`, then starts if start=true return } ``` This fires for **any** message that reaches OrcaSonar's own outbound cloud MQTT session on `device//request` - regardless of whether it was published there by the gateway's internal relay (today's `/start` path) or by a client publishing directly. OrcaSlicer's cloud MQTT connection already publishes to that same topic for every other cloud command (`OrcaPrinterAgent::route_send(is_lan=false, ...)` - `set_bed_temp`, `ams_change_filament`, etc.), via the same relay-shard mechanism (`apps/gateway/src/lib/relay-router.ts`). So **OrcaSlicer publishing `print.project_file` itself, over its existing cloud MQTT connection, would already reach `handleProjectFile` and trigger the identical download-then- start behavior - with zero new code on OrcaSonar.** (This only works over the cloud relay session. OrcaSonar's LAN-side/generic dispatcher, `internal/bridge/klipper/adapter.go`, treats `print.project_file` as an unmapped macro call - a no-op in practice. That's fine: R2 upload is a cloud-only feature to begin with.) ## The one real gap: no GET-signed download URL is exposed to the client `handleProjectFile` needs a URL it can `GET`. The `upload_url` returned by `POST print-jobs/uploads` is presigned for `PUT` only - S3 SigV4 signatures are bound to the HTTP method, so it cannot be reused for a download. Minting a download URL/token already exists as a function (`createPrintJobDownloadToken` in `apps/gateway/src/services/printer-print-jobs.ts`) and the exact URL template is already built in `dispatchPrintFileCommand` (`apps/gateway/src/routes/printer.ts`) - it is simply never returned to the API caller today, only used server-side when `/start` builds the relay payload itself. **Required OrcaCloud change:** have `POST print-jobs/uploads` (or a small follow-up call) also mint and return a signed download URL alongside `upload_url`, reusing `createPrintJobDownloadToken` + the existing URL template. This is on the order of ~10 lines in an existing handler, not a new permission model - the caller is already an authenticated, authorized user of that printer, identically to who is authorized to call `/start` today. No OrcaSonar change is required at all. ## Why this should NOT be built into `print.gcode_file` / `start_sdcard_print` `print.gcode_file` (sent by `OrcaPrinterAgent::start_sdcard_print`) is the generic "start this file that is already on the printer" primitive. It is used by the LAN `start_local_print` path today, is meant to stay usable for starting any file already on the SD card by filename alone, and is expected to grow parameters unrelated to cloud upload (e.g. filament mapping) over time. Making `gcode_file` download-aware would require either: - adding cloud-specific fields (`job_id`, a download `url`, ...) to a command that has nothing to do with cloud jobs in the LAN case, forcing all of them to be optional/unused most of the time, or - giving OrcaSonar a side-channel registry of "filenames currently being downloaded" that `gcode_file`'s handler consults - solvable, but an orthogonal change with its own design questions (see "decoupled two-step option" below). Neither is necessary: `print.project_file` already exists as a fully separate, fully-working command for exactly the "not yet on the printer, fetch it first" case, so the cloud upload flow does not need to touch `gcode_file` at all. ## Intended MQTT-native design, once the gap above is closed Replace the HTTP finalize step (`start_cloud_print_job`) with: build and publish, over the cloud MQTT connection (`route_send(is_lan=false, ...)`), ```json {"print": {"command": "project_file", "sequence_id": "...", "url": "", "param": "", "start": true}} ``` `start_sdcard_print` / `print.gcode_file` remains untouched and fully decoupled. ### Decoupled two-step option If a use case ever needs "download now, start later" as an explicit user action (rather than upload-and-immediately-print), the same `print.project_file` command already supports it via `start: false` (download and store only - see `executeProjectFile`'s `start` handling in `internal/cloud/printfile.go`). The later "start" action would then be a perfectly ordinary `print.gcode_file` with `param: `, going through the existing, generic `start_sdcard_print` unmodified. This still requires no protocol changes beyond the download-URL gap above. ## Summary of gaps | Component | Change needed | |---|---| | OrcaCloud (gateway) | Return a signed download URL from `POST print-jobs/uploads` (or a small sibling endpoint), reusing existing `createPrintJobDownloadToken` logic. | | OrcaSonar | None. `print.project_file` handling already does exactly what's needed, transport-agnostically, on the cloud MQTT session. | | OrcaSlicer (this repo) | Once the above lands: replace `start_cloud_print_job`'s HTTP call with a `print.project_file` publish over the cloud MQTT connection. `start_sdcard_print` stays untouched either way. |