feat: submission pipeline up to the approval gate - #2
Merged
Conversation
Implements docs/SUBMISSION_PIPELINE_DESIGN.md: remote users submit a print
artifact, it is validated against the target machine's profile, and valid jobs
wait in a per-machine queue with expected finish times.
Dispatch is deliberately NOT built. `submissions.dispatch()` raises
`DispatchUnavailable`, no route calls it, and no route can enter
`dispatching` / `running` / `finished`. There are still no `/control/*` routes
and the service issues no printer commands. The whole shipped path is
read-and-analysis: it reads the monitor's cache and writes only to the
gateway's own disk.
New modules:
- `profiles.py` machine profile: operator-declared config merged with live
telemetry, each field tagged observed / declared / unknown
- `artifacts.py` read-only inspection of .3mf and .gcode
- `validation.py` the eight checks from the design's §6
- `submissions.py` intake, job state machine, durable store, dispatch stub
- `queueing.py` per-machine queue and expected finish times
Routes: GET /printers/{id}/profile, GET /printers/{id}/queue,
POST /submissions, GET /submissions[/{id}],
POST /submissions/{id}/approve.
Decisions on the design's §12 open blockers, all following its own
recommendation and none of which changes what ships while dispatch is stubbed:
human-in-the-loop approval; opaque requested_by / approved_by (documented as
not authentication); gateway-owned queue; standard-library .3mf parsing.
Recorded in docs/TODO.md.
Two judgement calls worth recording:
- No mesh parsing. A printable .3mf is a *sliced* plate file embedding
Metadata/plate_N.gcode, so the footprint comes from scanning that rather than
3D/3dmodel.model. This sidesteps the 3MF build-transform coordinate
ambiguity, and makes "this project file is not sliced, no printer can run it"
a real check. The footprint is an extent, so slicer placement cannot change
the answer.
- `not_applicable` is a third check outcome, never a pass. The design's §10
data gaps (no AMS trays on either live printer, blank H2D nozzle type) report
"not compared, and why" instead of quietly approving. Undeclared bed_size_mm
and limits behave the same way: there are no built-in defaults, because a
guessed limit is a fabricated machine fact.
Untrusted input is bounded throughout: size caps on upload and scan, a
truncated scan withholds the footprint rather than reporting a partial one as
whole, artifacts are stored under the submission's UUID (never a client path),
no response carries a stored path, and XML with a document type declaration is
refused outright since that is the only place an entity expansion can be
declared.
The backend also now reads each AMS tray's nozzle_temp_min / nozzle_temp_max,
so the filament-window check becomes real as soon as tray data appears.
This commit also carries the previously uncommitted rich read-only telemetry
work already recorded as done in docs/TODO.md; the two files touch the same
structures and could not be separated cleanly after the fact.
`uv run ruff check .` and `uv run pytest -q` (114 tests) pass. Tests build
their own .3mf and .gcode fixtures and use fake backends; nothing touches
hardware.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UQvsfEeDitEyNzbCwrcEdD
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements
docs/SUBMISSION_PIPELINE_DESIGN.md: remote users submit a print artifact, it is validated against the target machine's profile, and valid jobs wait in a per-machine queue with expected finish times.Dispatch is deliberately not built.
submissions.dispatch()raisesDispatchUnavailable, no route calls it, and no route can enterdispatching/running/finished. There are still no/control/*routes and the service issues no printer commands. Everything shipped here is read-and-analysis: it reads the monitor's cache and writes only to the gateway's own disk.New modules
profiles.pyobserved/declared/unknownartifacts.py.3mfand.gcodevalidation.pysubmissions.pyqueueing.pyRoutes:
GET /printers/{id}/profile,GET /printers/{id}/queue,POST /submissions,GET /submissions[/{id}],POST /submissions/{id}/approve.Decisions on the design's §12 open blockers
All follow the design's own recommendation, and none changes what ships while dispatch is stubbed. Recorded in
docs/TODO.md.ac_authis not wired here, sorequested_by/approved_byare opaque strings recorded in job history. Documented as not authentication..3mfparsing — standard library. No new dependency beyondpython-multipartfor the upload form.Two judgement calls worth reviewing
.3mfis a sliced plate file embeddingMetadata/plate_N.gcode, so the footprint comes from scanning that rather than3D/3dmodel.model. This sidesteps the 3MF build-transform coordinate ambiguity and makes "this project file is not sliced, no printer can run it" a real check. The footprint is an extent, so slicer placement cannot change the answer.not_applicableis a third check outcome, never a pass. The design's §10 data gaps (no AMS trays on either live printer, blank H2D nozzle type) report "not compared, and why" instead of quietly approving. Undeclaredbed_size_mm/limitsbehave the same way — there are no built-in defaults, because a guessed limit is a fabricated machine fact.Safety of untrusted input
Size caps on upload and scan; a truncated scan withholds the footprint rather than reporting a partial one as whole; artifacts stored under the submission's UUID, never a client path; no response carries a stored path; XML with a document type declaration is refused outright, since that is the only place an entity expansion can be declared.
Note for the reviewer
This branch also carries the previously uncommitted rich read-only telemetry work already recorded as done in
docs/TODO.md. It touches the same structures (AmsTrayReading,monitor.status()details, the same test fixtures) and could not be split out cleanly after the fact. It is live on the deployed service today but was absent frommain.After merging
printers.local.yamlhas noprofile:blocks on either printer. Until they are added the profile-dependent checks abstain withnot_applicable. Declarenozzle_typeon the H2D, whose live field is blank.bambu-server.127.0.0.1:8012with no proxy route, so/submissionsis not reachable off-host yet.uv run ruff check .anduv run pytest -q(114 tests) pass. Tests build their own.3mf/.gcodefixtures and use fake backends; nothing touches hardware.🤖 Generated with Claude Code
https://claude.ai/code/session_01UQvsfEeDitEyNzbCwrcEdD