Commit Graph
17 Commits
Author SHA1 Message Date
octoturgeandClaude Opus 5 4dd077a002 Add stage to printer telemetry
Bambu's `stg_cur`: what a printer is doing when it is not laying down
plastic - bed levelling, nozzle cleaning, extrusion calibration. Passed
through as the printer's own number rather than decoded, because the
code-to-name table is not something we have. 0 means printing.

It is here because progress without it is ambiguous in a way that reads as a
fault. Confirmed on a P1P (2026-10-11): `percent` climbed to 14% while
`layer` stayed 0 for several minutes, the printer working through stages 54,
14 and 13 before starting layer 1. A console showing 14% at layer 0 with no
stage looks stuck, and an operator would reasonably go and power-cycle a
machine that was working perfectly.

Optional and additive, so gateways that predate it are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-15 21:37:20 +00:00
octoturgeandClaude Opus 5 1437badf4c Add control_print to the edge command set
Pause, resume and stop, as one variant carrying a verb rather than three
near-identical ones - matching the gateway's `PrintControl`, which exists so
there is one method across four vendor implementations instead of three.

Confirmed live on a P1P (2026-10-11), which is also where the two warnings
in the description come from. A stop is a cancel and is not undoable: the
printer reports the cancelled print as FAILED. And `result: "success"` from
the printer means the command was accepted, not that anything changed - all
three verbs answered success while it sat IDLE with nothing to pause - so a
caller that needs to know the state changed has to read telemetry back.

The enum is `pause|resume|stop` and deliberately not `cancel`, even though
`cancel` is Moonraker's word and the Klipper implementation behind this does
send a cancel. A verb only one vendor recognises must not validate on the
wire.

The "no start a print variant" note stays, with its reason corrected:
starting an uploaded file has now been confirmed by hand, so what is missing
is the path around it - nothing uploads a sliced file to a gateway and
nothing decides which printer runs which job.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-11 17:38:42 +00:00
octoturgeandClaude Opus 5 aa0e3f79b9 Write down the backend <-> ai-worker contract
`stream:camera_frames` and `channel:print_alerts` are the only interface
between `continuum-backend` and `continuum-ai-worker`, and there was
nothing specifying either. They had drifted completely: the backend
wrote `farmId`, `printerId`, `r2ObjectKey` and `capturedAt`; the worker
read a single field called `printer_id` and ignored the rest. Every task
was delivered, acknowledged, and attributed to a printer called
"unknown" - and nothing errored, because a stream entry is a flat string
map and asking it for a field that is not there is not a failure. Same
shape as the /ws/edge/v1 drift, found the same way.

`schemas/continuum/v1/ai/` is now canonical, with a TypeBox mirror in
ts-types and a Rust mirror in the worker.

Two things the schemas have to say that JSON Schema does not naturally
express, both of which caused real bugs:

- A Valkey stream entry is a flat field/value map of *strings*. No
  nesting, no numbers, no null. So FrameTask has no numeric or nested
  properties, and a field with no value is omitted from the XADD rather
  than written as `""` - an empty string is a value, and the producer
  was writing one for `print_job_id`, which a consumer would carry into
  a lookup or a uuid column.
- PrintAlert, by contrast, *is* a JSON document, because PUBLISH carries
  one opaque string. It is also fire-and-forget with no persistence, so
  it can only ever be a live-console feed and never the record of a
  detection.

The README's layout section was stale (it still listed four DTOs that
were removed in 2026-09) and did not mention `edge/` or `ai/` at all. It
now names both, says why the inter-service protocols live here, and
notes that their Rust mirrors sit in the single repo that consumes each
rather than in `packages/rust-types`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-11 16:58:57 +00:00
octoturgeandClaude Opus 5 d3d853ec89 Stop committing tsc output, and pin TypeBox exactly
Two cleanups, one cause each.

The root tsconfig.json carried the compilerOptions directly with no `include`,
which made `tsc -b` treat the whole repository as one project: it ignored each
package's `outDir` and emitted .js/.d.ts next to every source file, which then
got committed. So every typecheck dirtied the tree and the build output was in
git. The root is now a solution file that only references the real projects,
shared options moved to tsconfig.base.json, and the six emitted files are
removed from the index. Output goes to packages/ts-types/dist, which was
already gitignored.

@sinclair/typebox is now pinned exactly rather than by caret range. It is
load-bearing in a way a range should not be allowed near: continuum-backend's
Elysia requires it at runtime without declaring it as a dependency at all, and
@continuum/ts-types resolves its own copy from here, so two different versions
mean two structurally incompatible TSchema types and nothing from this package
validates anything there.

Worth recording what that looked like, because the error pointed elsewhere.
Elysia accepts `TSchema | StandardSchemaV1Like`; when the TSchema branch failed
on the version skew, TypeScript reported the *other* branch's failure,
`'~standard' is missing`. That reads as "Elysia wants a Standard Schema
validator, TypeBox is wrong here", which is not true - Elysia is TypeBox-native
and Standard Schema is just the alternative for Zod/Valibot users. The fix was
only ever to make the versions match.

0.34.52 rather than 0.33.x because Elysia 1.4 calls `t.Module`, which 0.34
introduced. continuum-backend pins the identical version and
tests/dependency_identity.test.ts there fails if the two ever drift again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-03 12:16:35 +00:00
octoturgeandClaude Opus 5 ff736f0633 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>
2026-10-03 10:37:13 +00:00
octoturgeandClaude Opus 5 4a54ed62f7 Declare proprietary licensing and enforce permissive-only dependencies
Continuum is intended to be sold, so every dependency has to permit
distribution in closed-source commercial software. Nothing recorded that
before: our own crates and npm packages carried no licence field at all,
which made `cargo deny` report them as `unlicensed` errors and left the
actual policy nowhere.

Crates now declare `LicenseRef-Proprietary` with `publish = false`.
That string is deliberately not a real SPDX identifier, so it must never
appear in a `deny.toml` allow list - a third-party crate claiming it
would be a genuine finding. npm packages use `"license": "UNLICENSED"`,
the equivalent convention there.

`deny.toml` holds the permissive-only allow list. The policy itself, the
audit, and the three categories cargo-deny cannot see (external
binaries, ML model weights, self-hosted servers) are documented in
continuum-common's docs/LICENSES.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-03 10:09:59 +00:00
octoturgeandClaude Sonnet 5 8e36f39609 Remove superseded Material/PrintParameters/PrintJob/Template mirrors
They described the old flat continuum/v1 shape, replaced by the
material/printer/vendor restructure. Nothing outside this repo imported
them (checked continuum-proxy, continuum-ai-worker, continuum-backend,
continuum-app). Left both packages in place as empty shells - add a
hand-written mirror back per new schema file once something actually
consumes it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MWQVtBGxHYLkVw2kBupd5g
2026-09-05 20:54:30 +00:00
octoturgeandClaude Sonnet 5 9d995e8e03 Drop Protobuf; move continuum/v1 domain model to JSON Schema
Nothing imported the proto-generated code (zero references to
continuum-rust-types or @continuum/ts-types's proto export in any
consumer repo), and protobuf's real value - one schema compiled to a
compact binary wire format for both languages - never applied: BE<->proxy
and FE<->BE are both JSON. Also fixes 8 broken $ref paths in the bambulab
schemas and a root tsconfig.json that was sweeping the gitignored
examples/ directory into typecheck.

- proto/continuum/v1/*.proto -> schemas/continuum/v1/*.schema.json
- packages/rust-types: hand-written serde structs instead of
  prost-build codegen
- packages/ts-types: drop the generated/ proto barrel export, keep the
  hand-written TypeBox schemas as the TS source of truth
- root package.json: drop build:ts, protoc, ts-proto

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MWQVtBGxHYLkVw2kBupd5g
2026-09-05 14:56:24 +00:00
octoturge f7efe0a273 finished BambuMQTT schemas 2026-09-05 14:34:23 +00:00
octoturge 51dba8e896 Fix broken v2 bambulab schema refs, wire up v2 dispatchers, fix PrintJobStatus JSON mismatch
- v2/common/device/device.schema.json: stray backtick in bed $ref broke resolution
- v2/report/print/print.schema.json: ams $ref pointed at wrong path (missing ams/ subdir)
- v2/common/device/plate.schema.json: $id was copy-pasted from bed.schema.json
- Add v2/request.schema.json and v2/report.schema.json dispatchers - v2's leaf
  schemas were unreachable without them, same oneOf convention as v1
- Rewrite schemas/bambulab/README.md layout section, which still described
  v1's pre-rename paths and claimed v2 was removed
- PrintJob.status serialized as a raw i32 in Rust (prost stores proto3 enums
  as i32) while ts-types' TypeBox schema validates full enum name strings -
  add serde with= on the field so Rust JSON matches TS and protobuf's own
  canonical JSON enum mapping, plus a regression test
2026-08-31 13:50:51 +00:00
octoturge 4cc40c913c Fix broken $refs from the bambulab schema underscore-rename, remove stale v2/
- print.schema.json, _ams.schema.json, _ams_unit.schema.json still
  pointed at the pre-rename filenames (ams.schema.json, ams_unit.schema.json,
  ams_tray.schema.json) after the underscore-prefix split - fixed all
  three, verified with a full jsonschema + referencing validation run
  against real captures (get_version, pushall, a full print/ams/xcam
  report with both empty and loaded trays) plus negative tests.
- _upgrade_state.schema.json: fixed its $id (pointed at report/info/,
  file lives under report/print/) and its title (said "ModuleInfo",
  clearly copy-pasted from _module_info.schema.json). Wired it into
  print.schema.json as the print report's `upgrade_state` property -
  the file existed but nothing referenced it yet.
- Deleted schemas/bambulab/v2/, which had been recreated as stale,
  broken copies of an older v1 (missing the envelope allOf, missing
  the underscore renames, $ids still pointing at v1/ paths) - the
  repo's own README documents v2 as intentionally removed until V2's
  wire format is known to differ from V1's.
- Updated schemas/bambulab/README.md's layout tree and inheritance-
  example reference, both stale after the underscore rename.
- Removed stray tsc -b build output (packages/ts-types/src/*.d.ts/js)
  that isn't meant to be committed (outDir is dist/, gitignored).

Verified: cargo check clean (continuum-proxy, continuum-ai-worker,
rust-types), tsc clean (continuum-backend, continuum-common ts-core),
10/10 JSON Schema validation cases passing.
2026-08-29 08:13:49 +00:00
octoturge bc4ee8690a Drop proto/bambulab/ — plain hand-written Rust structs instead
Reconsidered after actually feeling the friction: this data is JSON over
MQTT (not binary protobuf), only continuum-proxy ever parses it (raw
vendor wire format, never crosses into TS), so none of protobuf's actual
value (cross-language codegen, binary wire efficiency) applied here.
Every gotcha hit while building this (field_attribute's oneof/variant
path collision, needing .bambulab-scoped type_attribute calls, explicit
#[serde(default)] per not-always-present field) came from forcing
prost-build's codegen model onto a plain-JSON, Rust-only use case.

Verified the replacement is strictly simpler, not just smaller: a
hand-written struct's Option<T> field needs zero extra attributes to
handle a missing JSON key (prost's generated String needed an explicit
field_attribute call per field), and #[serde(flatten)] on a hand-written
field has no ambiguity at all (prost-build's oneof/variant path collision
doesn't exist without prost-build's codegen in the picture).

The replacement lives in continuum-proxy: src/printer/bambu_commands/.
continuum.v1 is unaffected — that schema is genuinely cross-language and
proto still earns its keep there.
2026-08-28 22:48:48 +00:00
octoturge 99c1d038f9 Add InfoCommand as one fully-wired real example in bambulab.v1
The rest of proto/bambulab/ was pure boilerplate/comments with nothing
concrete to copy from. This one message (sequence_id + command, matching
Bambu's real get_version request) is wired all the way through: real
proto, real generated Rust struct, lib.rs actually include!'d instead of
commented out. continuum-proxy's bambu_protocol_demo example serializes it
and diffs the output against Bambu's actual wire JSON byte for byte.

Verified with cargo build (this crate) and cargo run --example
bambu_protocol_demo (continuum-proxy) - output matches exactly.
2026-08-28 22:41:42 +00:00
octoturge f0bb8714a3 Document how to wire up bambulab modules in lib.rs once messages exist 2026-08-28 22:39:55 +00:00
octoturge 7505e759e1 Wire up proto/bambulab/ build pipeline (boilerplate — message bodies TBD)
Adds bambulab/v1 and bambulab/v2 to the prost-build pipeline, scoped
separately from continuum.v1's camelCase convention: bambulab.* uses
snake_case, matching Bambu's actual MQTT wire JSON exactly, since this is
edge-only traffic (continuum-proxy) that has to match the printer's real
format rather than a schema we get to design.

Verified the scoping doesn't collide: type_attribute calls are cumulative,
so the existing '.' blanket selector had to become '.continuum' specifically
- keeping it global would stack a second, conflicting #[serde(rename_all)]
onto every bambulab type too and fail to compile.

No message bodies added - proto/bambulab/{common,v1/*,v2/*}.proto are
intentionally just syntax+package+guidance comments (matching what was
already started for v1). build.rs has inline notes on the two real gotchas
already hit while prototyping this: the field_attribute path-matching
oneof/variant collision (solution: hand-write the envelope enum in Rust
instead of modeling it as a proto oneof), and serde needing #[serde(default)]
on fields that aren't always present.
2026-08-28 22:38:20 +00:00
octoturge 34c7c97d4f Fix real codegen bugs: unused proto import, missing runtime deps, no barrel index
protoc warned that print_job.proto imported template.proto without using it
- removed. ts-types/package.json was missing protobufjs and long, which the
ts-proto-generated code imports at runtime - tsc failed with 'cannot find
module'. src/index.ts also re-exports './generated' as a namespace, but
ts-proto wasn't configured to emit an index barrel file for it - added
outputIndex=true to the protoc invocation.

Verified with a clean 'bun run build' (both TS and Rust codegen) and
'tsc -b'.
2026-08-28 18:36:23 +00:00
octoturgeandClaude Sonnet 5 660855ecd6 Initial boilerplate scaffold for continuum-schemas
Protobuf data model (Template/PrintJob/MaterialProfile/PrintParameters)
with codegen targets for TypeBox+TS types (packages/ts-types) and
prost-derived Rust structs (packages/rust-types).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 16:12:03 +00:00