Skip to main content

printobserver_sdk/
contract.rs

1//! Generated by `contract-codegen` from the checked-in contract schemas.
2//!
3//! This file is the Rust client's request and response types.
4//!
5//! Do not edit by hand: change the contracts, regenerate with `just
6//! generate-clients`, and commit what that writes.
7
8use std::collections::BTreeMap;
9
10use serde::{Deserialize, Serialize};
11
12use crate::{Client, ClientError};
13
14/// The version of the server contract these types were generated from: the
15/// version of the workspace this crate is compiled from. A published crate
16/// reports the release version it was built at, and a crate compiled from a
17/// checkout reports that checkout's workspace version.
18pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
19
20/// Every operation this client exposes a method for, which is every operation
21/// the server declares and no other.
22pub const OPERATION_NAMES: [&str; 37] = [
23    "prints",
24    "status",
25    "context",
26    "image",
27    "history",
28    "events",
29    "manifest_get",
30    "look",
31    "manifest_set",
32    "spools",
33    "spool",
34    "printer_history",
35    "preflight",
36    "pause",
37    "resume",
38    "cancel",
39    "start_print",
40    "set_feedrate_factor",
41    "set_flowrate_factor",
42    "set_tool_target_c",
43    "set_bed_target_c",
44    "set_fan_percent",
45    "acknowledge_failure",
46    "close_print",
47    "register_spool",
48    "load_spool",
49    "unload_spool",
50    "adjust_spool",
51    "retire_spool",
52    "recover_status",
53    "recover_lift",
54    "recover_cool",
55    "recover_fan",
56    "recover_heat",
57    "recover_park",
58    "recover_home_xy",
59    "recover_home_z",
60];
61
62/// A field that was sent, null included, as what was sent.
63fn present<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
64where
65    D: serde::Deserializer<'de>,
66    T: Deserialize<'de>,
67{
68    T::deserialize(deserializer).map(Some)
69}
70
71/// One field of an answer as it was sent: left out, sent as null, or a value.
72#[derive(Default)]
73enum Sent<T> {
74    /// Left out.
75    #[default]
76    Absent,
77    /// Sent as null.
78    Null,
79    /// Sent as a value.
80    Value(T),
81}
82
83impl<'de, T: Deserialize<'de>> Deserialize<'de> for Sent<T> {
84    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
85        Ok(Option::<T>::deserialize(deserializer)?.map_or(Self::Null, Self::Value))
86    }
87}
88
89/// What an operator's acknowledgement of a failure event asks for next.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91pub enum AcknowledgementDisposition {
92    /// Carry on printing.
93    #[serde(rename = "continue")]
94    Continue,
95    /// Carry on printing, watched more closely.
96    #[serde(rename = "watch")]
97    Watch,
98    /// The print should stop. Recorded for whoever may cancel it: the
99    /// acknowledgement itself asks nothing of the machine.
100    #[serde(rename = "stop")]
101    Stop,
102}
103
104/// Whether one recovery action is allowed now: `allowed`, and the refusal it
105/// would meet, present exactly when it is not.
106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
107#[serde(try_from = "ActionAllowanceAsSent")]
108pub struct ActionAllowance {
109    /// One shape of the contracts, which say nothing more about it.
110    pub action: RecoveryActionKind,
111    /// Whether it is allowed now.
112    // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
113    pub allowed: bool,
114    /// One shape of the contracts, which say nothing more about it.
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub refusal: Option<RecoveryRejection>,
117}
118
119/// One `ActionAllowance` as it was sent, before its paired fields are held to
120/// each other.
121#[derive(Deserialize)]
122struct ActionAllowanceAsSent {
123    /// One shape of the contracts, which say nothing more about it.
124    action: RecoveryActionKind,
125    /// Whether it is allowed now.
126    // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
127    allowed: bool,
128    /// One shape of the contracts, which say nothing more about it.
129    #[serde(default)]
130    refusal: Sent<RecoveryRejection>,
131}
132
133impl TryFrom<ActionAllowanceAsSent> for ActionAllowance {
134    type Error = String;
135
136    fn try_from(sent: ActionAllowanceAsSent) -> Result<Self, Self::Error> {
137        let refusal = match sent.refusal {
138            Sent::Absent => None,
139            Sent::Value(value) => Some(value),
140            Sent::Null => {
141                return Err("`refusal` is sent as one or not at all, never as null".to_owned());
142            }
143        };
144        if sent.allowed == refusal.is_some() {
145            return Err(
146                "`allowed` is false exactly when `refusal` is sent, and they disagree".to_owned(),
147            );
148        }
149        Ok(Self {
150            action: sent.action,
151            allowed: sent.allowed,
152            refusal,
153        })
154    }
155}
156
157/// What one mutating request left behind it.
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159pub struct ActionAnswer {
160    /// The bounded intervention it opened, when it opened one.
161    #[serde(default, skip_serializing_if = "Option::is_none")]
162    pub intervention: Option<Intervention>,
163    /// What the printer said, when the request reached it and it refused.
164    #[serde(default, skip_serializing_if = "Option::is_none")]
165    pub printer_refusal: Option<String>,
166    /// The record of the request and the decision taken on it. A rejected
167    /// request's rejection is here, carrying its reason, the value asked for
168    /// and the range allowed, so a caller can ask again inside the range.
169    pub record: ActionRecord,
170}
171
172/// An accepted action reached the printer.
173#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
174pub struct ActionExecutedPayload {
175    /// The action's identifier.
176    pub action_id: ActionId,
177    /// The bounded intervention it opened, when it opened one.
178    #[serde(default, skip_serializing_if = "Option::is_none")]
179    pub intervention_id: Option<InterventionId>,
180}
181
182/// A lowercase hyphenated version 7 UUID identifying one requested action.
183pub type ActionId = String;
184
185/// One action of the closed vocabulary, named without its payload.
186///
187/// This is what a safety envelope grants and what a policy rejection names;
188/// the
189/// payload lives on `PrintAction` itself.
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
191pub enum ActionKind {
192    /// Pause the print.
193    #[serde(rename = "pause")]
194    Pause,
195    /// Resume the print.
196    #[serde(rename = "resume")]
197    Resume,
198    /// Cancel the print.
199    #[serde(rename = "cancel")]
200    Cancel,
201    /// Start a print of a named file.
202    #[serde(rename = "start_print")]
203    StartPrint,
204    /// Set the feedrate factor.
205    #[serde(rename = "set_feedrate_factor")]
206    SetFeedrateFactor,
207    /// Set the flowrate factor.
208    #[serde(rename = "set_flowrate_factor")]
209    SetFlowrateFactor,
210    /// Set a tool's target temperature.
211    #[serde(rename = "set_tool_target_c")]
212    SetToolTargetC,
213    /// Set the bed's target temperature.
214    #[serde(rename = "set_bed_target_c")]
215    SetBedTargetC,
216    /// Set the fan percentage.
217    #[serde(rename = "set_fan_percent")]
218    SetFanPercent,
219    /// Acknowledge a failure event.
220    #[serde(rename = "acknowledge_failure")]
221    AcknowledgeFailure,
222    /// Close a print record printobserver left open.
223    #[serde(rename = "close_print")]
224    ClosePrint,
225    /// Register a spool.
226    #[serde(rename = "register_spool")]
227    RegisterSpool,
228    /// Load a spool onto a tool.
229    #[serde(rename = "load_spool")]
230    LoadSpool,
231    /// Unload a spool from its tool.
232    #[serde(rename = "unload_spool")]
233    UnloadSpool,
234    /// Correct a spool's remaining figure.
235    #[serde(rename = "adjust_spool")]
236    AdjustSpool,
237    /// Retire a spool.
238    #[serde(rename = "retire_spool")]
239    RetireSpool,
240}
241
242/// One request, the decision taken on it, and what became of it.
243///
244/// `executed_at` and `outcome` are both optional and are absent together: a
245/// record is written when the decision is taken, which is before — and, for a
246/// rejected request, instead of — anything reaching the printer.
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
248pub struct ActionRecord {
249    /// The decision policy took on it.
250    pub decision: PolicyDecision,
251    /// When it reached the printer, if it did.
252    #[serde(default, skip_serializing_if = "Option::is_none")]
253    pub executed_at: Option<Timestamp>,
254    /// This record's identifier, minted by the store.
255    pub id: ActionId,
256    /// What the printer made of it, if it reached the printer.
257    #[serde(default, skip_serializing_if = "Option::is_none")]
258    pub outcome: Option<ExecutionOutcome>,
259    /// The print this request was made against.
260    pub print_id: PrintId,
261    /// The request itself.
262    pub request: ActionRequest,
263}
264
265/// Policy refused an action.
266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
267pub struct ActionRejectedPayload {
268    /// The action's identifier.
269    pub action_id: ActionId,
270    /// The whole decision, carrying which rejection it was.
271    pub decision: PolicyDecision,
272}
273
274/// One request an actor made, at the instant it made it.
275#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
276pub struct ActionRequest {
277    /// What was asked for.
278    pub action: PrintAction,
279    /// Who asked.
280    pub actor: Actor,
281    /// When they asked.
282    pub requested_at: Timestamp,
283}
284
285/// An actor asked for an action.
286#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
287pub struct ActionRequestedPayload {
288    /// What was asked for.
289    pub action: PrintAction,
290    /// The action's identifier.
291    pub action_id: ActionId,
292    /// Who asked.
293    pub actor: Actor,
294}
295
296/// Who asked for something.
297#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
298pub enum Actor {
299    /// The supervising agent, naming its session.
300    #[serde(rename = "agent")]
301    Agent {
302        /// The supervision session the agent is acting in.
303        session_name: String,
304    },
305    /// A person.
306    #[serde(rename = "operator")]
307    Operator,
308    /// The supervisor itself.
309    #[serde(rename = "system")]
310    System,
311}
312
313/// An actor class, which is what a safety envelope grants actions to.
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
315pub enum ActorClass {
316    /// The supervising agent.
317    #[serde(rename = "agent")]
318    Agent,
319    /// A person.
320    #[serde(rename = "operator")]
321    Operator,
322    /// The supervisor itself.
323    #[serde(rename = "system")]
324    System,
325}
326
327/// One thing an adjustment may change.
328pub type Adjustable = String;
329
330/// The agent's written record of one supervision turn.
331///
332/// This is deliberately not how the agent acts: acting is a
333/// `PrintAction` that policy rules on.
334#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
335pub struct AgentAssessment {
336    /// How sure the agent is.
337    pub confidence: Confidence,
338    /// What the agent did.
339    pub did: String,
340    /// Whether the agent is escalating to a person.
341    pub escalating: bool,
342    /// Whether the print should carry on.
343    pub should_continue: bool,
344    /// One line saying what is happening.
345    pub summary: String,
346    /// Why it did it.
347    pub why: String,
348}
349
350/// The agent wrote down what it made of a turn.
351#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
352pub struct AgentAssessmentPayload {
353    /// What the agent answered with.
354    pub assessment: AgentAssessment,
355    /// The session the turn ran in.
356    pub session_name: String,
357}
358
359/// The bed heater, as a recovery read reports it.
360#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
361pub struct BedTemperature {
362    /// What it reads, in degrees Celsius, `null` when not reported.
363    pub actual_c: Option<f64>,
364    /// What it is driving towards, in degrees Celsius, `null` when not
365    /// reported.
366    pub target_c: Option<f64>,
367}
368
369/// The camera gave no usable frame for a look or a periodic observation,
370/// however many times it was asked.
371///
372/// Written in place of the look or the observation it was for, so that no
373/// frame the camera could not give is handed to the agent as one it did.
374#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
375pub struct CameraDegradedPayload {
376    /// How many frames were fetched, every one of them bad.
377    pub attempts: i64,
378    /// Why the last of them was bad.
379    pub cause: FrameCause,
380    /// What was wrong with each, in words.
381    pub detail: String,
382}
383
384/// Somebody took a fresh look at the print.
385///
386/// Written down before the camera is asked for a frame, so that a frame the
387/// camera would not give is recorded against a look that is already in the
388/// history; the frame itself, when there is one, is this event's image.
389#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
390pub struct CameraLookPayload {
391    /// The events for the print that arrived while it waited, and that it
392    /// handed to whoever looked, oldest first.
393    #[serde(default, skip_serializing_if = "Option::is_none")]
394    pub delivered: Option<Vec<EventId>>,
395    /// How long the look waited for something to happen before it was taken,
396    /// in whole seconds.
397    pub waited_s: i64,
398}
399
400/// The durable identity of one debit, so that a replay can never debit twice.
401#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
402#[serde(tag = "key")]
403pub enum ChargeKey {
404    /// One segment of a print this system watched, on one tool.
405    #[serde(rename = "print")]
406    Print {
407        /// The print.
408        print_id: PrintId,
409        /// Which segment: zero from the print's start, and one more each time
410        /// a mid-print swap or a filament-change signal closes the current
411        /// one.
412        segment: i64,
413        /// The tool, in the printer's own numbering.
414        tool: i64,
415    },
416    /// One print this system never saw, on one tool.
417    #[serde(rename = "unobserved")]
418    Unobserved {
419        /// The file that was printed.
420        file_name: String,
421        /// The file's tally generation, which advances each time the
422        /// printer's
423        /// tally for the file is reset.
424        generation: i64,
425        /// Where the file lives, in the printer's own vocabulary.
426        origin: String,
427        /// The print's ordinal in the file's tally — completed and failed
428        /// prints counted up to and including this one — within that
429        /// generation.
430        print_number: i64,
431        /// The tool, in the printer's own numbering.
432        tool: i64,
433    },
434}
435
436/// How the length a charge debits was worked out.
437#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
438pub enum ChargeMethod {
439    /// A completed print: the length the file's analysis gives.
440    #[serde(rename = "slicer_estimate")]
441    SlicerEstimate,
442    /// The extrusion the file's own moves add up to, up to a position in it.
443    #[serde(rename = "gcode_extrusion")]
444    GcodeExtrusion,
445    /// The analysed length scaled by how far into the file the print got; the
446    /// fallback used only when the file itself could not be read.
447    #[serde(rename = "file_fraction")]
448    FileFraction,
449    /// A completed print this system never saw: the file's analysed length.
450    #[serde(rename = "unobserved_estimate")]
451    UnobservedEstimate,
452}
453
454/// Filament a tool fed after an unconfirmed filament change was held for the
455/// next spool loaded onto that tool, or confirmed on it.
456#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
457pub struct ChargePendedPayload {
458    /// What was held.
459    pub pending: PendingCharge,
460}
461
462/// What a charge took from a spool, and the whole calculation behind it.
463///
464/// Present or absent as one: a debit names its spool exactly when it carries
465/// the grams, the figures they were worked out with, and the spool's figure
466/// either side of them.
467#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
468pub struct ChargedFilament {
469    /// The density the grams were worked out with, in grams per cubic
470    /// centimetre.
471    pub density_g_cm3: f64,
472    /// The diameter the grams were worked out with, in millimetres.
473    pub diameter_mm: f64,
474    /// What the length weighs, in grams; what is subtracted from the spool.
475    pub grams: f64,
476    /// The spool's figure after it, in grams.
477    pub remaining_after_g: f64,
478    /// The spool's figure before the charge, in grams.
479    pub remaining_before_g: f64,
480    /// The spool charged.
481    pub spool_id: SpoolId,
482}
483
484/// One mark a spool command cleared, and the spool it was cleared from.
485#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
486pub struct ClearedMark {
487    /// The reason that no longer stands.
488    pub reason: UnverifiedReason,
489    /// The spool the mark was cleared from.
490    pub spool_id: SpoolId,
491}
492
493/// How sure the agent is.
494///
495/// A closed vocabulary rather than a number, because a number invites a
496/// precision the agent does not have.
497#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
498pub enum Confidence {
499    /// Not sure.
500    #[serde(rename = "low")]
501    Low,
502    /// Fairly sure.
503    #[serde(rename = "medium")]
504    Medium,
505    /// Sure.
506    #[serde(rename = "high")]
507    High,
508}
509
510/// What a context read answers.
511///
512/// The latest image is **materialized** here rather than left as the handle
513/// `PrintContext` carries: the one thing a supervision turn does with an
514/// image is look at it, and a handle is not something anything can open. The
515/// path is absolute on this server's own filesystem, exactly as
516/// `ImageAnswer`'s is, and is absent when there is no image or when the
517/// record is intact and the file is not.
518#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
519pub struct ContextAnswer {
520    /// Everything a supervision turn is given about the print.
521    pub context: PrintContext,
522    /// Where the latest image's bytes are, absolute on this host.
523    #[serde(default, skip_serializing_if = "Option::is_none")]
524    pub image_path: Option<String>,
525}
526
527/// The failure detector's health, and since when it has been so: `since` is
528/// `null` exactly while the status is `unchecked`.
529#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
530#[serde(tag = "status")]
531pub enum DetectorHealth {
532    /// Nothing checks it: no detector health address is configured, or no
533    /// check has been made yet.
534    #[serde(rename = "unchecked")]
535    Unchecked {
536        /// Nothing: an unchecked detector has no instant it became so.
537        since: (),
538    },
539    /// It answered its health check, from `since` on.
540    #[serde(rename = "available")]
541    Available {
542        /// One shape of the contracts, which say nothing more about it.
543        since: Timestamp,
544    },
545    /// It did not answer its health check, from `since` on.
546    #[serde(rename = "unavailable")]
547    Unavailable {
548        /// One shape of the contracts, which say nothing more about it.
549        since: Timestamp,
550    },
551}
552
553/// The failure detector's own health changed.
554///
555/// The printer's rather than any print's, so it is written with no print.
556#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
557pub struct DetectorHealthChangedPayload {
558    /// What it was answered, in words.
559    pub detail: String,
560    /// What the check found.
561    pub status: DetectorStatus,
562}
563
564/// What a check of the failure detector's own health found.
565#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
566pub enum DetectorStatus {
567    /// It answered its health check.
568    #[serde(rename = "available")]
569    Available,
570    /// It did not: refused, reset, answered otherwise, or not in time.
571    #[serde(rename = "unavailable")]
572    Unavailable,
573}
574
575/// The envelope intersected with the print's manifest.
576///
577/// This is what context reports, so that the agent can see its own limits
578/// before it asks. Computing one is the supervision core's; this crate
579/// declares
580/// the shape and nothing that produces it.
581#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
582pub struct EffectiveBounds {
583    /// The range each adjustable may be set to, inclusive.
584    pub allowed: BTreeMap<String, Range>,
585}
586
587/// What this server answers when it will not do what it was asked.
588#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
589pub struct ErrorAnswer {
590    /// One line saying why, in the words of whatever refused it.
591    pub error: String,
592}
593
594/// Whose error a job's error is.
595#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
596pub enum ErrorSource {
597    /// The printer's own message: its firmware stopped the job.
598    #[serde(rename = "printer")]
599    Printer,
600    /// The host's: it lost the line to the printer, or failed in itself.
601    #[serde(rename = "host")]
602    Host,
603}
604
605/// A lowercase hyphenated version 7 UUID identifying one event.
606pub type EventId = String;
607
608/// The name one kind of event is written down under: lowercase `snake_case`,
609/// declared by the domain that owns the event.
610pub type EventKind = String;
611
612/// One event, as the store holds it and the server serves it.
613///
614/// `raw` holds the bytes exactly as received for an externally sourced event
615/// and is absent for an internally raised one — it is what makes the history
616/// auditable when a normalization turns out to be wrong. `print_id` is
617/// optional, because an externally sourced event may name no print this
618/// system
619/// knows.
620#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
621pub struct EventRecord {
622    /// This event's identifier, minted by the store.
623    pub id: EventId,
624    /// The image it arrived with, when it arrived with one.
625    #[serde(default, skip_serializing_if = "Option::is_none")]
626    pub image: Option<ImageRef>,
627    /// Which event this is.
628    pub kind: EventKind,
629    /// What it carries, in the form its kind declares.
630    pub payload: serde_json::Value,
631    /// The print it belongs to, when it belongs to one.
632    #[serde(default, skip_serializing_if = "Option::is_none")]
633    pub print_id: Option<PrintId>,
634    /// The bytes exactly as received, for an externally sourced event.
635    #[serde(default, skip_serializing_if = "Option::is_none")]
636    pub raw: Option<RawBytes>,
637    /// When it was received.
638    pub received_at: Timestamp,
639    /// Where it came from.
640    pub source: EventSource,
641}
642
643/// Where an event came from, as the bare string the domain that raised it
644/// declares for itself.
645pub type EventSource = String;
646
647/// What an events read answers: the events stored after its cursor that pass
648/// its filters, oldest first, and where the next read picks up.
649#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
650pub struct EventsAnswer {
651    /// The last event this read examined, whether or not it passed the
652    /// filters, given as `after` to read on from it; null when the log held
653    /// nothing to start after. Always sent, as null when it is that.
654    pub cursor: Option<EventId>,
655    /// The events, oldest first in the order they were stored, each exactly
656    /// as
657    /// a history read serves it.
658    pub events: Vec<EventRecord>,
659    /// The watched print's end as stored, whatever the filters said, and null
660    /// while it is open. Absent from a read that names no print.
661    #[serde(
662        default,
663        skip_serializing_if = "Option::is_none",
664        deserialize_with = "present"
665    )]
666    pub print_ended: Option<Option<PrintEnded>>,
667}
668
669/// What happened when an accepted action reached the printer.
670#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
671pub enum ExecutionOutcome {
672    /// The printer took it.
673    #[serde(rename = "succeeded")]
674    Succeeded,
675    /// The printer did not, for this reason.
676    #[serde(rename = "failed")]
677    Failed {
678        /// Why it failed.
679        reason: String,
680    },
681}
682
683/// A filament change was signalled during a print.
684#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
685pub struct FilamentChangeSignalledPayload {
686    /// The position the spool was charged up to, and how fresh it is, when
687    /// one
688    /// was known; absent, there is no position and so no claim about one.
689    #[serde(default, skip_serializing_if = "Option::is_none")]
690    pub position: Option<SignalPosition>,
691    /// The kind of the alert that carried the signal.
692    pub source_kind: EventKind,
693    /// The spool that was on it, when one was tracked.
694    #[serde(default, skip_serializing_if = "Option::is_none")]
695    pub spool_id: Option<SpoolId>,
696    /// The tool the change was signalled for, when it is known.
697    #[serde(default, skip_serializing_if = "Option::is_none")]
698    pub tool: Option<i64>,
699}
700
701/// Filament was charged against a spool, or recorded uncharged.
702#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
703pub struct FilamentConsumedPayload {
704    /// The file's analysed length for the tool, in millimetres, when it had
705    /// one.
706    #[serde(default, skip_serializing_if = "Option::is_none")]
707    pub analysed_length_mm: Option<f64>,
708    /// The durable identity of this debit.
709    pub charge_key: ChargeKey,
710    /// The spool charged and the whole calculation of what it was charged;
711    /// absent when nothing was tracked on the tool, and the filament was
712    /// recorded uncharged.
713    #[serde(default, skip_serializing_if = "Option::is_none")]
714    pub charged: Option<ChargedFilament>,
715    /// How much was fed, in millimetres.
716    pub length_mm: f64,
717    /// How the length was worked out.
718    pub method: ChargeMethod,
719    /// Anything a reader needs to make sense of the figure.
720    #[serde(default, skip_serializing_if = "Option::is_none")]
721    pub note: Option<String>,
722    /// How far into the file the charge runs, when that was read.
723    #[serde(default, skip_serializing_if = "Option::is_none")]
724    pub position: Option<FilePosition>,
725    /// The tool that fed it, as its key names it.
726    pub tool: i64,
727}
728
729/// A file name a printer's own file API can be asked for: no path separator,
730/// no NUL byte, no `.` or `..` segment, no drive prefix, and not empty.
731pub type FileName = String;
732
733/// How far into its file a print had got, and when that was read.
734#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
735pub struct FilePosition {
736    /// The byte the printer had read the file up to.
737    pub bytes: i64,
738    /// When the position was read.
739    pub observed_at: Timestamp,
740    /// The file's size in bytes, when it was known.
741    #[serde(default, skip_serializing_if = "Option::is_none")]
742    pub size_bytes: Option<i64>,
743}
744
745/// The printer's tally for a file went down — the file was deleted and
746/// uploaded again — so the file's tally generation advanced, and the new
747/// count
748/// is that generation's growth from zero.
749///
750/// The rebaseline itself marks no spool; only an `unverified` disposition
751/// does.
752#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
753pub struct FileTallyRebaselinedPayload {
754    /// What became of the prints the new generation counts.
755    pub disposition: RebaselineDisposition,
756    /// The file whose tally went down.
757    pub file_name: String,
758    /// The failed prints the new tally counts.
759    pub new_failures: i64,
760    /// The generation it is in now.
761    pub new_generation: i64,
762    /// The completed prints the new tally counts.
763    pub new_successes: i64,
764    /// The failed prints the old tally counted.
765    pub old_failures: i64,
766    /// The generation the tally was in.
767    pub old_generation: i64,
768    /// The completed prints the old tally counted.
769    pub old_successes: i64,
770    /// Where it lives, in the printer's own vocabulary.
771    pub origin: String,
772}
773
774/// The XY rectangle every extruding move of a file lies inside, in
775/// millimetres.
776#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
777pub struct Footprint {
778    /// The largest X.
779    pub max_x: f64,
780    /// The largest Y.
781    pub max_y: f64,
782    /// The smallest X.
783    pub min_x: f64,
784    /// The smallest Y.
785    pub min_y: f64,
786}
787
788/// Why a camera frame was not one to hand the agent.
789#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
790pub enum FrameCause {
791    /// It decoded, but is near-uniform grey: its grey-level spread is below
792    /// the configured blank-frame bound.
793    #[serde(rename = "blank")]
794    Blank,
795    /// It is not a whole image: a strict decode refused it.
796    #[serde(rename = "undecodable")]
797    Undecodable,
798}
799
800/// One tool heater whose target was above zero.
801#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
802pub struct HeatedTool {
803    /// Its target, in degrees Celsius.
804    pub target_c: f64,
805    /// The tool's number.
806    pub tool: i64,
807}
808
809/// One heater, as a source reported it.
810///
811/// Every field is optional, because a source that reports no heater at all
812/// reports none of these; each is a plausibility-ranged reported value in
813/// degrees Celsius.
814#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
815pub struct HeaterSnapshot {
816    /// The temperature the heater is at.
817    #[serde(default, skip_serializing_if = "Option::is_none")]
818    pub actual_c: Option<Reported>,
819    /// The offset applied to this heater's target.
820    #[serde(default, skip_serializing_if = "Option::is_none")]
821    pub offset_c: Option<Reported>,
822    /// The temperature the heater is driving towards.
823    #[serde(default, skip_serializing_if = "Option::is_none")]
824    pub target_c: Option<Reported>,
825}
826
827/// What a history read answers.
828#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
829pub struct HistoryAnswer {
830    /// The print's events, newest first.
831    pub events: Vec<EventRecord>,
832}
833
834/// What an image read answers.
835///
836/// The path is absolute on this server's own filesystem, and is the whole of
837/// the answer about the bytes: nothing here renders them, encodes them or
838/// serves them. It is absent when the record is intact and the file is not,
839/// which is a different answer from there being no such image.
840#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
841pub struct ImageAnswer {
842    /// Where its bytes are, absolute on this host.
843    #[serde(default, skip_serializing_if = "Option::is_none")]
844    pub path: Option<String>,
845    /// The record.
846    pub record: ImageRecord,
847}
848
849/// A lowercase hyphenated version 7 UUID identifying one image.
850pub type ImageId = String;
851
852/// One image, stored beside the event it arrived with.
853///
854/// `relative_path` is relative to the configured state directory, so that the
855/// store stays portable. Materializing an image resolves that to an absolute
856/// path; nothing in this system renders image bytes into JSON.
857#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
858pub struct ImageRecord {
859    /// How many bytes it is.
860    pub byte_len: i64,
861    /// The content type it was served as.
862    pub content_type: String,
863    /// The event it arrived with.
864    pub event_id: EventId,
865    /// When it was fetched.
866    pub fetched_at: Timestamp,
867    /// This image's identifier, minted by the store.
868    pub id: ImageId,
869    /// The print it belongs to.
870    pub print_id: PrintId,
871    /// Where it lives, relative to the configured state directory.
872    pub relative_path: String,
873    /// The SHA-256 of its bytes, lowercase hexadecimal.
874    pub sha256: String,
875    /// Where it was fetched from, when it was fetched from somewhere.
876    #[serde(default, skip_serializing_if = "Option::is_none")]
877    pub source_url: Option<String>,
878}
879
880/// The handle an image travels in context under.
881#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
882pub struct ImageRef {
883    /// The image's identifier.
884    pub id: ImageId,
885    /// The SHA-256 of its bytes, lowercase hexadecimal.
886    pub sha256: String,
887}
888
889/// One adjustment made for a bounded time.
890///
891/// `prior_value` is read from the printer snapshot taken before the change,
892/// and
893/// is absent when the printer reported none — in which case expiry restores
894/// nothing and the outcome says so rather than guessing a default.
895#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
896pub struct Intervention {
897    /// The action that asked for it.
898    pub action_id: ActionId,
899    /// What it changed.
900    pub adjustable: Adjustable,
901    /// When it was applied.
902    pub applied_at: Timestamp,
903    /// What it was changed to.
904    pub applied_value: f64,
905    /// When it stops standing.
906    pub expires_at: Timestamp,
907    /// This intervention's identifier, minted by the store.
908    pub id: InterventionId,
909    /// What became of it.
910    pub outcome: InterventionOutcome,
911    /// The print it was made against.
912    pub print_id: PrintId,
913    /// What the printer reported before the change, if it reported anything.
914    #[serde(default, skip_serializing_if = "Option::is_none")]
915    pub prior_value: Option<f64>,
916    /// When the prior value was put back, if it was.
917    #[serde(default, skip_serializing_if = "Option::is_none")]
918    pub restored_at: Option<Timestamp>,
919}
920
921/// A bounded intervention expired.
922#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
923pub struct InterventionExpiredPayload {
924    /// What it had changed.
925    pub adjustable: Adjustable,
926    /// The intervention's identifier.
927    pub intervention_id: InterventionId,
928    /// What became of it.
929    pub outcome: InterventionOutcome,
930}
931
932/// A lowercase hyphenated version 7 UUID identifying one bounded
933/// intervention.
934pub type InterventionId = String;
935
936/// What became of a bounded change.
937#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
938pub enum InterventionOutcome {
939    /// It is still in force.
940    #[serde(rename = "still_active")]
941    StillActive,
942    /// The prior value was put back.
943    #[serde(rename = "restored")]
944    Restored,
945    /// There was no prior value to put back.
946    #[serde(rename = "restore_unavailable")]
947    RestoreUnavailable,
948    /// Putting the prior value back failed.
949    #[serde(rename = "restore_failed")]
950    RestoreFailed {
951        /// Why it failed.
952        reason: String,
953    },
954    /// Another intervention replaced it before it expired.
955    #[serde(rename = "superseded")]
956    Superseded {
957        /// The intervention that replaced it.
958        by: InterventionId,
959    },
960}
961
962/// A job's error, and whose it is.
963#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
964pub struct JobError {
965    /// Whose it is.
966    pub source: ErrorSource,
967    /// The error, as the source reported it.
968    pub text: String,
969}
970
971/// What a sliced job declares about itself and about what may be adjusted.
972///
973/// An adjustable the manifest does not name takes the envelope's own range; a
974/// manifest range wider than the envelope's is narrowed to the envelope's and
975/// the narrowing is recorded on the print. A manifest may only narrow.
976#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
977pub struct JobManifest {
978    /// The range each named adjustable may take, inclusive.
979    pub allowed: BTreeMap<String, Range>,
980    /// The file this manifest is about, as the slicer named it.
981    pub file_name: String,
982    /// The material the job is sliced for.
983    pub material: String,
984    /// Whatever else the slicer recorded.
985    pub metadata: BTreeMap<String, String>,
986    /// The nozzle the job is sliced for, in millimetres.
987    pub nozzle_diameter_mm: f64,
988    /// The slicer profile the job was sliced with.
989    pub slicer_profile: String,
990}
991
992/// The job a printer reports it is running.
993///
994/// Every field but `state` is optional: an absent one means the source did
995/// not
996/// report it.
997#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
998pub struct JobSnapshot {
999    /// How far through the print is, as a fraction from zero to one.
1000    ///
1001    /// Normalized to a fraction here regardless of how the source expresses
1002    /// it.
1003    #[serde(default, skip_serializing_if = "Option::is_none")]
1004    pub completion: Option<Reported>,
1005    /// The error the source reports, when it reports one, and whose it is.
1006    #[serde(default, skip_serializing_if = "Option::is_none")]
1007    pub error: Option<JobError>,
1008    /// The whole print's estimated duration, in whole seconds.
1009    #[serde(default, skip_serializing_if = "Option::is_none")]
1010    pub estimated_print_time_s: Option<i64>,
1011    /// The filament the source's analysis of the file says each tool uses;
1012    /// empty when the source reported none.
1013    #[serde(default, skip_serializing_if = "Option::is_none")]
1014    pub filament: Option<Vec<ToolFilament>>,
1015    /// The name of the file being printed, as the source reported it.
1016    #[serde(default, skip_serializing_if = "Option::is_none")]
1017    pub file_name: Option<String>,
1018    /// Where the file lives, in the source's own vocabulary.
1019    #[serde(default, skip_serializing_if = "Option::is_none")]
1020    pub file_origin: Option<String>,
1021    /// How far into the file the printer has read, in bytes, when the source
1022    /// reported it.
1023    #[serde(default, skip_serializing_if = "Option::is_none")]
1024    pub file_position_bytes: Option<i64>,
1025    /// How long the print has left, in whole seconds.
1026    #[serde(default, skip_serializing_if = "Option::is_none")]
1027    pub print_time_left_s: Option<i64>,
1028    /// How long the print has been running, in whole seconds.
1029    #[serde(default, skip_serializing_if = "Option::is_none")]
1030    pub print_time_s: Option<i64>,
1031    /// The file's size in bytes.
1032    #[serde(default, skip_serializing_if = "Option::is_none")]
1033    pub size_bytes: Option<i64>,
1034    /// The state the source reports the job to be in.
1035    pub state: PrinterState,
1036}
1037
1038/// What the last print laid down, read from its own G-code up to the last
1039/// file position recorded for it.
1040#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1041pub struct LastPrint {
1042    /// Where in X and Y it laid material.
1043    pub footprint: Footprint,
1044    /// The print.
1045    pub print_id: PrintId,
1046    /// The tallest part it laid down, in millimetres.
1047    pub tallest_mm: f64,
1048}
1049
1050/// One spool loaded on one tool, as a print started.
1051#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1052pub struct LoadedSpool {
1053    /// The spool loaded on it.
1054    pub spool_id: SpoolId,
1055    /// The tool, in the printer's own numbering.
1056    pub tool: i64,
1057}
1058
1059/// One fresh look at a print.
1060///
1061/// An answer rather than a record, so it admits fields beside its own the way
1062/// every answer of the server does: a client that could not open the frame's
1063/// path says so in a field of its own beside the rest of the answer.
1064#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1065pub struct Look {
1066    /// The events for this print that arrived while the look waited, oldest
1067    /// first; the look returned early when there were any.
1068    #[serde(default, skip_serializing_if = "Option::is_none")]
1069    pub arrived: Option<Vec<EventRecord>>,
1070    /// Whether the print is paused and the pause is the detector's, which is
1071    /// what an adjustment asked for now would be applied under.
1072    pub detector_paused: bool,
1073    /// The look itself, as it was written into the print's history, carrying
1074    /// the frame as its image when there is one.
1075    pub event: EventRecord,
1076    /// The frame the camera gave, absent when no camera is configured or it
1077    /// gave none — which the history records as a port failure.
1078    #[serde(default, skip_serializing_if = "Option::is_none")]
1079    pub frame: Option<ImageRef>,
1080    /// The absolute path the frame's bytes are at, on the supervisor's host.
1081    #[serde(default, skip_serializing_if = "Option::is_none")]
1082    pub image_path: Option<String>,
1083    /// The job the printer reports now, absent when it could not be read.
1084    #[serde(default, skip_serializing_if = "Option::is_none")]
1085    pub job: Option<JobSnapshot>,
1086    /// The printer's state now, absent when it could not be read.
1087    #[serde(default, skip_serializing_if = "Option::is_none")]
1088    pub printer: Option<PrinterSnapshot>,
1089}
1090
1091/// An external body arrived that could not be read.
1092///
1093/// This kind always carries its `raw` bytes, and it exists so that an alert
1094/// this system cannot read is written down rather than dropped.
1095#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1096pub struct MalformedExternalEventPayload {
1097    /// One line saying why the body could not be read.
1098    pub detail: String,
1099}
1100
1101/// What a manifest read or write answers.
1102#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1103pub struct ManifestAnswer {
1104    /// The manifest, when the print has one.
1105    #[serde(default, skip_serializing_if = "Option::is_none")]
1106    pub manifest: Option<JobManifest>,
1107    /// Every range this manifest asked wider than the envelope allows,
1108    /// narrowed
1109    /// to the envelope's — recorded on the print, so nobody has to wonder
1110    /// later
1111    /// which bound applied.
1112    pub narrowings: Vec<ManifestNarrowing>,
1113}
1114
1115/// One adjustable whose manifest range was wider than the envelope's.
1116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1117pub struct ManifestNarrowing {
1118    /// The adjustable that was narrowed.
1119    pub adjustable: Adjustable,
1120    /// The range that stands.
1121    pub applied: Range,
1122    /// The range the manifest asked for.
1123    pub requested: Range,
1124}
1125
1126/// Obico reported a print failure.
1127///
1128/// The two instants are optional because Obico's own field for each is a Unix
1129/// timestamp number, an empty string, or absent, and the last two both mean
1130/// the
1131/// producer reported no instant. An absent field here is that, never an epoch
1132/// date standing in for it.
1133#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1134pub struct ObicoFailureAlertPayload {
1135    /// When the print ended, when Obico reported an instant for it.
1136    #[serde(default, skip_serializing_if = "Option::is_none")]
1137    pub ended_at: Option<Timestamp>,
1138    /// The file being printed, when Obico named one.
1139    #[serde(default, skip_serializing_if = "Option::is_none")]
1140    pub file_name: Option<String>,
1141    /// Whether Obico called it a warning rather than a failure.
1142    pub is_warning: bool,
1143    /// Obico's own identifier for the print, when it named one.
1144    #[serde(default, skip_serializing_if = "Option::is_none")]
1145    pub obico_print_id: Option<i64>,
1146    /// Whether Obico paused the print itself.
1147    pub print_paused: bool,
1148    /// When the print started, when Obico reported an instant for it.
1149    #[serde(default, skip_serializing_if = "Option::is_none")]
1150    pub started_at: Option<Timestamp>,
1151}
1152
1153/// The kind of printer notification Obico sent, normalized.
1154#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1155pub enum ObicoNotificationType {
1156    /// A print started.
1157    #[serde(rename = "started")]
1158    Started,
1159    /// A print finished.
1160    #[serde(rename = "done")]
1161    Done,
1162    /// A print was cancelled.
1163    #[serde(rename = "cancelled")]
1164    Cancelled,
1165    /// A print was paused.
1166    #[serde(rename = "paused")]
1167    Paused,
1168    /// A print was resumed.
1169    #[serde(rename = "resumed")]
1170    Resumed,
1171    /// The printer is waiting for a filament change.
1172    #[serde(rename = "filament_change")]
1173    FilamentChange,
1174    /// A heater cooled down.
1175    #[serde(rename = "heater_cooled")]
1176    HeaterCooled,
1177    /// A heater reached its target.
1178    #[serde(rename = "heater_target")]
1179    HeaterTarget,
1180}
1181
1182/// Obico sent a printer notification.
1183///
1184/// The two instants are optional for the same reason
1185/// `ObicoFailureAlertPayload`'s are, and are absent along with the rest of
1186/// the print's fields when the notification is about no print at all.
1187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1188pub struct ObicoPrinterNotificationPayload {
1189    /// When the print ended, when Obico reported an instant for it.
1190    #[serde(default, skip_serializing_if = "Option::is_none")]
1191    pub ended_at: Option<Timestamp>,
1192    /// The file being printed, when the notification is about one.
1193    #[serde(default, skip_serializing_if = "Option::is_none")]
1194    pub file_name: Option<String>,
1195    /// Which notification it is.
1196    pub notification_type: ObicoNotificationType,
1197    /// Obico's own identifier for the print, when the notification is about
1198    /// one.
1199    #[serde(default, skip_serializing_if = "Option::is_none")]
1200    pub obico_print_id: Option<i64>,
1201    /// When the print started, when Obico reported an instant for it.
1202    #[serde(default, skip_serializing_if = "Option::is_none")]
1203    pub started_at: Option<Timestamp>,
1204}
1205
1206/// An operator acknowledged an event.
1207#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1208pub struct OperatorAcknowledgementPayload {
1209    /// The event being acknowledged.
1210    pub acknowledged_event_id: EventId,
1211    /// What the operator asked for next.
1212    pub disposition: AcknowledgementDisposition,
1213}
1214
1215/// The print paused under the printer, and whether resuming it is still safe.
1216///
1217/// A recovery action executed while it was paused moved the head, and
1218/// `OctoPrint` restores no position on resume, so the rest of the layer would
1219/// print at the wrong height: such a print is not resumable.
1220#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1221pub struct PausedPrint {
1222    /// The print.
1223    pub print_id: PrintId,
1224    /// Whether it may be resumed.
1225    // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
1226    pub resumable: bool,
1227}
1228
1229/// Filament a tool fed after a filament change nobody has confirmed, held
1230/// until
1231/// somebody says which spool fed it.
1232///
1233/// A length rather than grams, because the grams follow the spool that takes
1234/// it.
1235#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1236pub struct PendingCharge {
1237    /// The file's analysed length for the tool, in millimetres, when it had
1238    /// one.
1239    #[serde(default, skip_serializing_if = "Option::is_none")]
1240    pub analysed_length_mm: Option<f64>,
1241    /// The print segment the filament was fed in, which names the print and
1242    /// the tool that fed it.
1243    pub charge_key: PrintSegmentKey,
1244    /// How much was fed, in millimetres.
1245    pub length_mm: f64,
1246    /// How that length was worked out.
1247    pub method: ChargeMethod,
1248    /// Anything a reader needs to make sense of the figure.
1249    #[serde(default, skip_serializing_if = "Option::is_none")]
1250    pub note: Option<String>,
1251    /// When it was held.
1252    pub pended_at: Timestamp,
1253    /// How far into the file the segment ran, when that was read.
1254    #[serde(default, skip_serializing_if = "Option::is_none")]
1255    pub position: Option<FilePosition>,
1256}
1257
1258/// One pending charge a spool command took.
1259#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1260pub struct PendingTaken {
1261    /// The key it is charged under.
1262    pub charge_key: ChargeKey,
1263    /// The print it was fed in.
1264    pub print_id: PrintId,
1265}
1266
1267/// The supervisor looked in on an active print on its own schedule, with no
1268/// detector having said anything about it.
1269///
1270/// What a failure detector never alerts on — a progress that stopped moving,
1271/// a
1272/// heater whose target fell to nothing mid-print, a fan cut at a bridge, a
1273/// wall
1274/// going thin — is still on the machine and in the camera's frame, so every
1275/// active print is given one of these each interval and a turn on it. It
1276/// carries what the printer reported as it was taken; the frame the camera
1277/// gave
1278/// at the same moment, untagged by any detector, is this event's image.
1279#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1280pub struct PeriodicObservationPayload {
1281    /// How often these are taken, in whole seconds.
1282    pub interval_s: i64,
1283    /// The job the printer reported as the observation was taken, absent when
1284    /// it could not be read.
1285    #[serde(default, skip_serializing_if = "Option::is_none")]
1286    pub job: Option<JobSnapshot>,
1287    /// The printer's state as the observation was taken, absent when it could
1288    /// not be read — which the history records as a port failure.
1289    #[serde(default, skip_serializing_if = "Option::is_none")]
1290    pub printer: Option<PrinterSnapshot>,
1291}
1292
1293/// The decision policy took on one request.
1294#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1295pub enum PolicyDecision {
1296    /// The request may proceed.
1297    #[serde(rename = "accepted")]
1298    Accepted,
1299    /// The request may not, for this reason.
1300    #[serde(rename = "rejected")]
1301    Rejected(RejectionReason),
1302}
1303
1304/// A port failed while one event was being handled.
1305///
1306/// The event is named rather than implied, so that a reader holding an
1307/// event's
1308/// identifier reaches every failure recorded while that event was being
1309/// handled. A failure recorded here is one the handling survived: the event
1310/// is
1311/// already in the history by the time any of these sites is reached, and the
1312/// loop goes on to handle the next event.
1313#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1314pub struct PortFailurePayload {
1315    /// What the port said about it, in the port's own words.
1316    pub detail: String,
1317    /// The event whose handling reached the failing call.
1318    pub event_id: EventId,
1319    /// Where it failed.
1320    pub site: PortFailureSite,
1321}
1322
1323/// Where a port failed while an event was being handled.
1324///
1325/// A closed set of exactly the sites at which a failure has nowhere else to
1326/// be
1327/// recorded. The printer's action methods record theirs on the
1328/// `ActionRecord` the request minted, and a restoring
1329/// call records its own on the `Intervention` it was
1330/// expiring; those are not sites here, because a second record of them would
1331/// be
1332/// a second version of one fact.
1333#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1334pub enum PortFailureSite {
1335    /// Reading the printer's own state.
1336    #[serde(rename = "printer_snapshot")]
1337    PrinterSnapshot,
1338    /// Reading the job the printer reports it is running.
1339    #[serde(rename = "printer_job")]
1340    PrinterJob,
1341    /// Writing the image the event arrived with.
1342    #[serde(rename = "image_write")]
1343    ImageWrite,
1344    /// Running the supervision turn the event prompted.
1345    #[serde(rename = "supervision_turn")]
1346    SupervisionTurn,
1347    /// Taking a fresh frame from the camera, for a look at the print or for a
1348    /// periodic observation of it.
1349    #[serde(rename = "camera_look")]
1350    CameraLook,
1351    /// Telling the detector that paused the print its detection was handled.
1352    #[serde(rename = "detector_acknowledgement")]
1353    DetectorAcknowledgement,
1354}
1355
1356/// How fresh the position a filament change charged up to is.
1357#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1358pub enum PositionFreshness {
1359    /// Read from the job as the signal was handled: where the spool stopped.
1360    #[serde(rename = "read_at_signal")]
1361    ReadAtSignal,
1362    /// The last position recorded before the signal, the job reporting none
1363    /// as it was handled: the spool may have fed past it.
1364    #[serde(rename = "last_recorded")]
1365    LastRecorded,
1366}
1367
1368/// What a filament check found for one print.
1369#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1370pub struct Preflight {
1371    /// When the check was made.
1372    pub checked_at: Timestamp,
1373    /// The file the print is of.
1374    pub file_name: String,
1375    /// What it found for each tool.
1376    pub tools: Vec<PreflightTool>,
1377    /// The worst of the tools' verdicts, in the order insufficient,
1378    /// unverified spool, no analysis, warned, overridden, unchecked, passed.
1379    pub verdict: PreflightVerdict,
1380}
1381
1382/// What a filament check answers.
1383#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1384pub struct PreflightAnswer {
1385    /// What the check found, read now.
1386    pub preflight: Preflight,
1387}
1388
1389/// The filament check before a start was made.
1390#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1391pub struct PreflightCheckedPayload {
1392    /// The start it was made for, when it was made for one.
1393    #[serde(default, skip_serializing_if = "Option::is_none")]
1394    pub action_id: Option<ActionId>,
1395    /// What the check found.
1396    pub preflight: Preflight,
1397}
1398
1399/// What a print needs from one spool, in grams: both figures a comparison
1400/// with what the spool holds uses, or neither.
1401#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1402pub struct PreflightNeed {
1403    /// The margin asked for beyond that.
1404    pub buffer_g: f64,
1405    /// What the print needs from the spool, by the file's analysis and the
1406    /// spool's own diameter and density.
1407    pub required_g: f64,
1408}
1409
1410/// What a check that refuses a start found for one tool.
1411#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1412#[serde(tag = "verdict")]
1413pub enum PreflightObjection {
1414    /// The spool holds less than the print needs and its buffer.
1415    #[serde(rename = "insufficient")]
1416    Insufficient {
1417        /// The spool's marks at the check, oldest first, so a spool both
1418        /// short
1419        /// and unverified says both.
1420        #[serde(default, skip_serializing_if = "Option::is_none")]
1421        marks: Option<Vec<UnverifiedMark>>,
1422        /// What the print needs from it.
1423        need: PreflightNeed,
1424        /// What the spool holds, in grams.
1425        remaining_g: f64,
1426        /// The spool.
1427        spool: PreflightSpool,
1428    },
1429    /// The spool's figure is in doubt.
1430    #[serde(rename = "unverified_spool")]
1431    UnverifiedSpool {
1432        /// Every reason the spool's figure is in doubt, oldest first; never
1433        /// empty.
1434        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
1435        marks: Vec<UnverifiedMark>,
1436        /// What the print needs from it, when the file's analysis says.
1437        #[serde(default, skip_serializing_if = "Option::is_none")]
1438        need: Option<PreflightNeed>,
1439        /// What the spool holds, in grams.
1440        remaining_g: f64,
1441        /// The spool.
1442        spool: PreflightSpool,
1443    },
1444    /// The file has no analysis, so what the spool must hold is unknown.
1445    #[serde(rename = "no_analysis")]
1446    NoAnalysis {
1447        /// What the spool holds, in grams.
1448        remaining_g: f64,
1449        /// The spool.
1450        spool: PreflightSpool,
1451    },
1452}
1453
1454/// The spool a check judged, named beside its identifier so a reader need
1455/// not look it up.
1456#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1457pub struct PreflightSpool {
1458    /// The spool's identifier.
1459    pub id: SpoolId,
1460    /// The spool's name.
1461    pub name: String,
1462}
1463
1464/// What a filament check found for one tool.
1465#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1466pub struct PreflightTool {
1467    /// What the check found for it, with the evidence that finding rests on.
1468    pub result: PreflightToolResult,
1469    /// The tool, in the printer's own numbering.
1470    pub tool: i64,
1471}
1472
1473/// What a filament check found for one tool, tagged by its verdict, each
1474/// verdict carrying exactly the evidence it rests on.
1475#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1476#[serde(tag = "verdict")]
1477pub enum PreflightToolResult {
1478    /// The spool holds what the print needs and its buffer, and its figure is
1479    /// not in doubt.
1480    #[serde(rename = "passed")]
1481    Passed {
1482        /// What the print needs from it.
1483        need: PreflightNeed,
1484        /// What the spool holds, in grams.
1485        remaining_g: f64,
1486        /// The spool.
1487        spool: PreflightSpool,
1488    },
1489    /// No tracked spool is loaded on the tool, so nothing was checked.
1490    #[serde(rename = "unchecked")]
1491    Unchecked,
1492    /// The spool holds less than the print needs and its buffer.
1493    #[serde(rename = "insufficient")]
1494    Insufficient {
1495        /// The spool's marks at the check, oldest first, so a spool both
1496        /// short
1497        /// and unverified says both.
1498        #[serde(default, skip_serializing_if = "Option::is_none")]
1499        marks: Option<Vec<UnverifiedMark>>,
1500        /// What the print needs from it.
1501        need: PreflightNeed,
1502        /// What the spool holds, in grams.
1503        remaining_g: f64,
1504        /// The spool.
1505        spool: PreflightSpool,
1506    },
1507    /// The spool's figure is in doubt.
1508    #[serde(rename = "unverified_spool")]
1509    UnverifiedSpool {
1510        /// Every reason the spool's figure is in doubt, oldest first; never
1511        /// empty.
1512        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
1513        marks: Vec<UnverifiedMark>,
1514        /// What the print needs from it, when the file's analysis says.
1515        #[serde(default, skip_serializing_if = "Option::is_none")]
1516        need: Option<PreflightNeed>,
1517        /// What the spool holds, in grams.
1518        remaining_g: f64,
1519        /// The spool.
1520        spool: PreflightSpool,
1521    },
1522    /// `OctoPrint`'s analysis of the file had not appeared, so what the spool
1523    /// must hold is unknown.
1524    #[serde(rename = "no_analysis")]
1525    NoAnalysis {
1526        /// What the spool holds, in grams.
1527        remaining_g: f64,
1528        /// The spool.
1529        spool: PreflightSpool,
1530    },
1531    /// The start went ahead despite this objection, because an operator said
1532    /// so.
1533    #[serde(rename = "overridden")]
1534    Overridden {
1535        /// What the check objected to.
1536        objection: PreflightObjection,
1537    },
1538    /// The check is set to warn, and let the start go ahead despite this
1539    /// objection.
1540    #[serde(rename = "warned")]
1541    Warned {
1542        /// What the check objected to.
1543        objection: PreflightObjection,
1544    },
1545}
1546
1547/// What a filament check found, for one tool or for a whole print.
1548#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1549pub enum PreflightVerdict {
1550    /// The spool holds what the print needs and its buffer.
1551    #[serde(rename = "passed")]
1552    Passed,
1553    /// No tracked spool is loaded on the tool, so nothing was checked.
1554    #[serde(rename = "unchecked")]
1555    Unchecked,
1556    /// The spool holds less than the print needs and its buffer.
1557    #[serde(rename = "insufficient")]
1558    Insufficient,
1559    /// The spool's figure is in doubt.
1560    #[serde(rename = "unverified_spool")]
1561    UnverifiedSpool,
1562    /// `OctoPrint`'s analysis of the file had still not appeared when the
1563    /// start's wait for it elapsed, or has not appeared yet when the check is
1564    /// only read.
1565    #[serde(rename = "no_analysis")]
1566    NoAnalysis,
1567    /// The start went ahead despite the check, because the actor said so.
1568    #[serde(rename = "overridden")]
1569    Overridden,
1570    /// The check is set to warn, and let an insufficient, unverified or
1571    /// unanalysed start go ahead.
1572    #[serde(rename = "warned")]
1573    Warned,
1574}
1575
1576/// The whole vocabulary an actor may ask for, and there is no other.
1577#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1578#[serde(tag = "action")]
1579pub enum PrintAction {
1580    /// Pause the print.
1581    #[serde(rename = "pause")]
1582    Pause {
1583        /// Who is asking.
1584        actor: Actor,
1585        /// Why the actor is asking for this.
1586        reason: String,
1587    },
1588    /// Resume the print.
1589    #[serde(rename = "resume")]
1590    Resume {
1591        /// Who is asking.
1592        actor: Actor,
1593        /// Why the actor is asking for this.
1594        reason: String,
1595    },
1596    /// Cancel the print.
1597    #[serde(rename = "cancel")]
1598    Cancel {
1599        /// Who is asking.
1600        actor: Actor,
1601        /// Why the actor is asking for this.
1602        reason: String,
1603    },
1604    /// Start a print of a named file, with a manifest.
1605    #[serde(rename = "start_print")]
1606    StartPrint {
1607        /// Who is asking.
1608        actor: Actor,
1609        /// The file to print, validated as a name a file API can be asked
1610        /// for.
1611        file_name: FileName,
1612        /// The manifest this print is bounded by.
1613        manifest: JobManifest,
1614        /// Start despite an insufficient or unverified spool, or a file
1615        /// `OctoPrint` has not analysed; the start's own reason is the
1616        /// override's reason.
1617        #[serde(default, skip_serializing_if = "Option::is_none")]
1618        override_filament_check: Option<bool>,
1619        /// Why the actor is asking for this.
1620        reason: String,
1621    },
1622    /// Set the feedrate factor, where one means one hundred percent.
1623    #[serde(rename = "set_feedrate_factor")]
1624    SetFeedrateFactor {
1625        /// Who is asking.
1626        actor: Actor,
1627        /// How long the change stands for, in whole seconds.
1628        #[serde(default, skip_serializing_if = "Option::is_none")]
1629        duration_s: Option<i64>,
1630        /// The multiplier asked for.
1631        factor: f64,
1632        /// Why the actor is asking for this.
1633        reason: String,
1634    },
1635    /// Set the flowrate factor, where one means one hundred percent.
1636    #[serde(rename = "set_flowrate_factor")]
1637    SetFlowrateFactor {
1638        /// Who is asking.
1639        actor: Actor,
1640        /// How long the change stands for, in whole seconds.
1641        #[serde(default, skip_serializing_if = "Option::is_none")]
1642        duration_s: Option<i64>,
1643        /// The multiplier asked for.
1644        factor: f64,
1645        /// Why the actor is asking for this.
1646        reason: String,
1647    },
1648    /// Set one tool's target temperature.
1649    #[serde(rename = "set_tool_target_c")]
1650    SetToolTargetC {
1651        /// Who is asking.
1652        actor: Actor,
1653        /// How long the change stands for, in whole seconds.
1654        #[serde(default, skip_serializing_if = "Option::is_none")]
1655        duration_s: Option<i64>,
1656        /// Why the actor is asking for this.
1657        reason: String,
1658        /// The target temperature asked for, in degrees Celsius.
1659        target_c: f64,
1660        /// The tool, in the printer's own numbering.
1661        tool: i64,
1662    },
1663    /// Set the bed's target temperature.
1664    #[serde(rename = "set_bed_target_c")]
1665    SetBedTargetC {
1666        /// Who is asking.
1667        actor: Actor,
1668        /// How long the change stands for, in whole seconds.
1669        #[serde(default, skip_serializing_if = "Option::is_none")]
1670        duration_s: Option<i64>,
1671        /// Why the actor is asking for this.
1672        reason: String,
1673        /// The target temperature asked for, in degrees Celsius.
1674        target_c: f64,
1675    },
1676    /// Set the part-cooling fan percentage.
1677    #[serde(rename = "set_fan_percent")]
1678    SetFanPercent {
1679        /// Who is asking.
1680        actor: Actor,
1681        /// How long the change stands for, in whole seconds.
1682        #[serde(default, skip_serializing_if = "Option::is_none")]
1683        duration_s: Option<i64>,
1684        /// The percentage asked for.
1685        percent: f64,
1686        /// Why the actor is asking for this.
1687        reason: String,
1688    },
1689    /// Acknowledge a failure event, with a disposition.
1690    #[serde(rename = "acknowledge_failure")]
1691    AcknowledgeFailure {
1692        /// Who is asking.
1693        actor: Actor,
1694        /// What to do next.
1695        disposition: AcknowledgementDisposition,
1696        /// The event being acknowledged.
1697        event_id: EventId,
1698        /// Why the actor is asking for this.
1699        reason: String,
1700    },
1701    /// Close a print record printobserver left open, with the outcome the
1702    /// operator judges it ended with. It asks nothing of the machine and
1703    /// charges no spool: the record's consumption was never seen to end.
1704    #[serde(rename = "close_print")]
1705    ClosePrint {
1706        /// Who is asking.
1707        actor: Actor,
1708        /// How the print ended, in the operator's judgement.
1709        outcome: PrintOutcome,
1710        /// Why the actor is asking for this.
1711        reason: String,
1712    },
1713}
1714
1715/// Everything a supervision turn is given about one print.
1716#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1717pub struct PrintContext {
1718    /// The limits in force, so that the agent can see them before it asks.
1719    pub bounds: EffectiveBounds,
1720    /// The failure detector's health, and since when it has been so.
1721    #[serde(default, skip_serializing_if = "Option::is_none")]
1722    pub detector: Option<DetectorHealth>,
1723    /// The interventions still active.
1724    pub interventions: Vec<Intervention>,
1725    /// The job, when a snapshot could be taken.
1726    #[serde(default, skip_serializing_if = "Option::is_none")]
1727    pub job: Option<JobSnapshot>,
1728    /// The most recent image, when there is one.
1729    #[serde(default, skip_serializing_if = "Option::is_none")]
1730    pub latest_image: Option<ImageRef>,
1731    /// The manifest, when the print has one.
1732    #[serde(default, skip_serializing_if = "Option::is_none")]
1733    pub manifest: Option<JobManifest>,
1734    /// The print itself.
1735    pub print: PrintRecord,
1736    /// The printer, when a snapshot could be taken.
1737    #[serde(default, skip_serializing_if = "Option::is_none")]
1738    pub printer: Option<PrinterSnapshot>,
1739    /// A bounded list of this print's events, newest first.
1740    pub recent_events: Vec<EventRecord>,
1741}
1742
1743/// One print's end, as its `print_ended` event stored it.
1744#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1745pub struct PrintEnded {
1746    /// The `print_ended` event.
1747    pub event_id: EventId,
1748    /// How the print ended.
1749    pub outcome: PrintOutcome,
1750    /// When that event was received.
1751    pub received_at: Timestamp,
1752}
1753
1754/// A print ended.
1755#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1756pub struct PrintEndedPayload {
1757    /// How far into its file it last read, when that was read.
1758    #[serde(default, skip_serializing_if = "Option::is_none")]
1759    pub last_position: Option<FilePosition>,
1760    /// How it ended.
1761    pub outcome: PrintOutcome,
1762    /// Why it ended, in the words the end was recorded with.
1763    pub reason: String,
1764    /// The state the printer was in as it ended.
1765    pub state: PrinterState,
1766}
1767
1768/// A lowercase hyphenated version 7 UUID identifying one print.
1769pub type PrintId = String;
1770
1771/// How a print ended.
1772#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1773pub enum PrintOutcome {
1774    /// It ran to its end.
1775    #[serde(rename = "completed")]
1776    Completed,
1777    /// Somebody cancelled it.
1778    #[serde(rename = "cancelled")]
1779    Cancelled,
1780    /// It stopped on an error.
1781    #[serde(rename = "failed")]
1782    Failed,
1783    /// It stopped with no cancel, no error the printer reported and no
1784    /// finish. Charged as a failed print is: what the file's moves extruded
1785    /// up
1786    /// to the last file position seen.
1787    #[serde(rename = "interrupted")]
1788    Interrupted,
1789}
1790
1791/// One print, and the record supervision keys from.
1792///
1793/// The provider's own print id is carried beside this record's identifier
1794/// rather than as it, because a print may be observed before the provider
1795/// that reports it has one.
1796#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1797pub struct PrintRecord {
1798    /// Why it ended, if it has.
1799    #[serde(default, skip_serializing_if = "Option::is_none")]
1800    pub end_reason: Option<String>,
1801    /// When it ended, if it has.
1802    #[serde(default, skip_serializing_if = "Option::is_none")]
1803    pub ended_at: Option<Timestamp>,
1804    /// The file being printed, as the source reported it.
1805    #[serde(default, skip_serializing_if = "Option::is_none")]
1806    pub file_name: Option<String>,
1807    /// This print's identifier, minted by the store.
1808    pub id: PrintId,
1809    /// The longest running time, in whole seconds, the printer reported for
1810    /// this print's job at a read that found it printing rather than paused.
1811    ///
1812    /// The time a job has spent printing only grows, pause or no pause, so a
1813    /// job of this print's file reporting less — by more than
1814    /// `listing::JOB_IDENTITY_TOLERANCE_S` — is a later job. Absent until a
1815    /// read finds the job printing.
1816    #[serde(default, skip_serializing_if = "Option::is_none")]
1817    pub job_print_time_s: Option<i64>,
1818    /// When the printer's job this print records began, as the printer's own
1819    /// running time put it at the latest read that found the job running or
1820    /// paused: the instant of that read, less the running time it reported.
1821    ///
1822    /// `OctoPrint` counts a pause into the running time until the job resumes
1823    /// and then takes the pause back out, so a pause moves this later: a read
1824    /// putting it further than `listing::JOB_IDENTITY_TOLERANCE_S` from the
1825    /// one
1826    /// recorded resets it, rather than it being fixed once. Absent when no
1827    /// read has
1828    /// found this print's job reporting a running time — a print opened by an
1829    /// alert or a start before any read, and every print recorded before this
1830    /// was.
1831    #[serde(default, skip_serializing_if = "Option::is_none")]
1832    pub job_started_at: Option<Timestamp>,
1833    /// Every manifest range this print narrowed to the envelope's.
1834    pub narrowings: Vec<ManifestNarrowing>,
1835    /// When the print was opened.
1836    pub opened_at: Timestamp,
1837    /// The provider's own identifier for the print, whichever provider
1838    /// reported it, when one has.
1839    #[serde(default, skip_serializing_if = "Option::is_none")]
1840    pub provider_print_id: Option<i64>,
1841    /// The state the print is in.
1842    pub state: PrinterState,
1843}
1844
1845/// One segment of a print this system watched, on one tool: the key of a
1846/// `ChargeKey::Print` debit, and the only key filament held pending can be
1847/// under.
1848#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1849pub struct PrintSegmentKey {
1850    /// The print.
1851    pub print_id: PrintId,
1852    /// Which segment, numbered as `ChargeKey::Print` numbers it.
1853    pub segment: i64,
1854    /// The tool, in the printer's own numbering.
1855    pub tool: i64,
1856}
1857
1858/// A print started.
1859#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1860pub struct PrintStartedPayload {
1861    /// The file being printed, when it is known.
1862    #[serde(default, skip_serializing_if = "Option::is_none")]
1863    pub file_name: Option<String>,
1864    /// The spool loaded on each tool as it started.
1865    pub loaded: Vec<LoadedSpool>,
1866    /// The filament check made before it started, when one was.
1867    #[serde(default, skip_serializing_if = "Option::is_none")]
1868    pub preflight: Option<Preflight>,
1869    /// The action that started it; absent when the print was observed rather
1870    /// than started through this system.
1871    #[serde(default, skip_serializing_if = "Option::is_none")]
1872    pub started_by: Option<ActionId>,
1873}
1874
1875/// A print that was already open when spool tracking was installed: it is
1876/// never charged, and this says why it carries no charge.
1877#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1878pub struct PrintUntrackedPayload {
1879    /// Why the print carries no charge.
1880    pub reason: String,
1881}
1882
1883/// The printer's connection changed into or out of `offline` or `error`: a
1884/// serial disconnect, a reconnect, a printer error.
1885///
1886/// The printer's rather than any print's, so it is written with no print.
1887#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1888pub struct PrinterConnectionChangedPayload {
1889    /// What the watch read, in words.
1890    pub detail: String,
1891    /// The state the previous read found.
1892    pub from: PrinterState,
1893    /// The state this read found.
1894    pub to: PrinterState,
1895}
1896
1897/// The printer reset: its firmware restarted, and whatever it held is gone.
1898///
1899/// Recorded on each reset the printer pushed, one while idle included, which
1900/// changes nothing a read of its state reports. The printer's rather than any
1901/// print's, so it is written with no print; `during_print` names the print it
1902/// happened during, when one was running.
1903#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1904pub struct PrinterResetPayload {
1905    /// What the watch knew of it, in words.
1906    pub detail: String,
1907    /// The print that was running when it reset, `null` when none was.
1908    #[serde(default, skip_serializing_if = "Option::is_none")]
1909    pub during_print: Option<PrintId>,
1910}
1911
1912/// A printer, as a source reported it at one instant.
1913#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1914pub struct PrinterSnapshot {
1915    /// The bed heater, when the printer reports one.
1916    #[serde(default, skip_serializing_if = "Option::is_none")]
1917    pub bed: Option<HeaterSnapshot>,
1918    /// The chamber heater, when the printer reports one.
1919    #[serde(default, skip_serializing_if = "Option::is_none")]
1920    pub chamber: Option<HeaterSnapshot>,
1921    /// The state the printer is in.
1922    pub connection: PrinterState,
1923    /// The part-cooling fan, in percent.
1924    #[serde(default, skip_serializing_if = "Option::is_none")]
1925    pub fan_percent: Option<Reported>,
1926    /// The feedrate multiplier, where one means one hundred percent.
1927    #[serde(default, skip_serializing_if = "Option::is_none")]
1928    pub feedrate_factor: Option<Reported>,
1929    /// The flowrate multiplier, where one means one hundred percent.
1930    #[serde(default, skip_serializing_if = "Option::is_none")]
1931    pub flowrate_factor: Option<Reported>,
1932    /// The instant this observation was taken.
1933    pub observed_at: Timestamp,
1934    /// The tool heaters, indexed by tool number.
1935    pub tools: Vec<HeaterSnapshot>,
1936}
1937
1938/// The state a source reports a printer or a print to be in.
1939///
1940/// The `unknown` arm exists so that a state nobody anticipated is recorded
1941/// carrying the source's own word for it rather than lost.
1942#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1943pub enum PrinterState {
1944    /// Connected and idle.
1945    #[serde(rename = "operational")]
1946    Operational,
1947    /// Printing, but paused.
1948    #[serde(rename = "paused")]
1949    Paused,
1950    /// Printing.
1951    #[serde(rename = "printing")]
1952    Printing,
1953    /// Cancelling a print.
1954    #[serde(rename = "cancelling")]
1955    Cancelling,
1956    /// In an error state.
1957    #[serde(rename = "error")]
1958    Error,
1959    /// Not reachable.
1960    #[serde(rename = "offline")]
1961    Offline,
1962    /// A state this vocabulary does not name, in the source's own word for
1963    /// it.
1964    #[serde(rename = "unknown")]
1965    Unknown(String),
1966}
1967
1968/// What the prints read answers: every print, and the one the printer is
1969/// running.
1970///
1971/// An open print's recorded `state` is the state it was opened in, which is
1972/// `printing` whether or not the machine is paused now; what the machine is
1973/// doing is the status read's answer.
1974#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1975pub struct PrintsAnswer {
1976    /// The print the printer's current job belongs to, present exactly when
1977    /// the
1978    /// printer reports a job it is printing or has paused. Absent when the
1979    /// printer could not be read.
1980    #[serde(default, skip_serializing_if = "Option::is_none")]
1981    pub active: Option<PrintId>,
1982    /// Every print this server holds, ended or not, most recently opened
1983    /// first.
1984    pub prints: Vec<PrintRecord>,
1985}
1986
1987/// An inclusive pair of 64-bit floats.
1988#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1989pub struct Range {
1990    /// The highest value the range admits, inclusive.
1991    pub max: f64,
1992    /// The lowest value the range admits, inclusive.
1993    pub min: f64,
1994}
1995
1996/// Bytes exactly as received, base64-encoded.
1997pub type RawBytes = String;
1998
1999/// What became of the prints a file's new tally generation counts.
2000#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2001pub enum RebaselineDisposition {
2002    /// They were accounted as prints this system never saw.
2003    #[serde(rename = "accounted")]
2004    Accounted,
2005    /// It could not be told which of them this system already settled, so the
2006    /// loaded spool was marked unverified rather than charged on a guess.
2007    #[serde(rename = "unverified")]
2008    Unverified,
2009}
2010
2011/// The whole recovery vocabulary, and there is no other.
2012///
2013/// The two assertions are the operator's word for what printobserver cannot
2014/// see: `clearance_asserted` that the nozzle is clear of the part, and
2015/// `bed_clear_asserted` that homing Z will meet nothing on the bed. Each is
2016/// false when left out.
2017#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2018#[serde(tag = "action")]
2019pub enum RecoveryAction {
2020    /// Move the nozzle away from the bed by `mm`.
2021    #[serde(rename = "recover_lift")]
2022    RecoverLift {
2023        /// How far, in millimetres: above zero and at most
2024        /// `recovery.max_lift_mm`.
2025        mm: f64,
2026    },
2027    /// Set every heater's target to zero.
2028    #[serde(rename = "recover_cool")]
2029    RecoverCool,
2030    /// Set the part-cooling fan.
2031    #[serde(rename = "recover_fan")]
2032    RecoverFan {
2033        /// The percentage, from 0 to 100.
2034        percent: f64,
2035    },
2036    /// Heat one tool, within the safety envelope's range for it.
2037    #[serde(rename = "recover_heat")]
2038    RecoverHeat {
2039        /// The operator's word that the nozzle is clear of the part.
2040        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
2041        #[serde(default, skip_serializing_if = "Option::is_none")]
2042        clearance_asserted: Option<bool>,
2043        /// The target, in degrees Celsius.
2044        target_c: f64,
2045        /// The tool, in the printer's own numbering.
2046        tool: i64,
2047    },
2048    /// Move the head to `recovery.park_xy`.
2049    #[serde(rename = "recover_park")]
2050    RecoverPark {
2051        /// The operator's word that the nozzle is clear of the part.
2052        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
2053        #[serde(default, skip_serializing_if = "Option::is_none")]
2054        clearance_asserted: Option<bool>,
2055    },
2056    /// Home X and Y.
2057    #[serde(rename = "recover_home_xy")]
2058    RecoverHomeXy {
2059        /// The operator's word that the nozzle is clear of the part.
2060        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
2061        #[serde(default, skip_serializing_if = "Option::is_none")]
2062        clearance_asserted: Option<bool>,
2063    },
2064    /// Home Z.
2065    #[serde(rename = "recover_home_z")]
2066    RecoverHomeZ {
2067        /// The operator's word that homing Z will meet nothing on the bed.
2068        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
2069        #[serde(default, skip_serializing_if = "Option::is_none")]
2070        bed_clear_asserted: Option<bool>,
2071    },
2072}
2073
2074/// One recovery action, named without its fields: what a grant names and what
2075/// a refusal of an ungranted request names.
2076#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2077pub enum RecoveryActionKind {
2078    /// Move the nozzle away from the bed.
2079    #[serde(rename = "recover_lift")]
2080    RecoverLift,
2081    /// Every heater's target to zero.
2082    #[serde(rename = "recover_cool")]
2083    RecoverCool,
2084    /// Set the part-cooling fan.
2085    #[serde(rename = "recover_fan")]
2086    RecoverFan,
2087    /// Heat one tool.
2088    #[serde(rename = "recover_heat")]
2089    RecoverHeat,
2090    /// Move the head to the configured park place.
2091    #[serde(rename = "recover_park")]
2092    RecoverPark,
2093    /// Home X and Y.
2094    #[serde(rename = "recover_home_xy")]
2095    RecoverHomeXy,
2096    /// Home Z.
2097    #[serde(rename = "recover_home_z")]
2098    RecoverHomeZ,
2099}
2100
2101/// What a recovery action answers, carried out or refused.
2102///
2103/// A refusal is the same shape and deliberately so: `decision` is either
2104/// `executed`, carrying what the printer made of it, or `rejected`, carrying
2105/// the reason, and the two events are what the request wrote.
2106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2107pub struct RecoveryAnswer {
2108    /// What was asked for.
2109    pub action: RecoveryAction,
2110    /// What became of it.
2111    pub decision: RecoveryDecision,
2112    /// What became of it, in a sentence for a person: why it was refused and
2113    /// what would let it go ahead, or what it did.
2114    pub detail: String,
2115    /// The `recovery_requested` event, and the `recovery_executed` or
2116    /// `recovery_rejected` beside it.
2117    pub events: Vec<EventRecord>,
2118}
2119
2120/// What became of a recovery request: carried out with the printer's outcome,
2121/// or refused — one or the other, never both and never neither.
2122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2123pub enum RecoveryDecision {
2124    /// It reached the printer, and this is what the printer made of it.
2125    #[serde(rename = "executed")]
2126    Executed(ExecutionOutcome),
2127    /// It was refused, and reached nothing.
2128    #[serde(rename = "rejected")]
2129    Rejected(RecoveryRejection),
2130}
2131
2132/// A recovery action reached the printer, and what the printer made of it.
2133#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2134pub struct RecoveryExecutedPayload {
2135    /// What was asked for.
2136    pub action: RecoveryAction,
2137    /// Who asked.
2138    pub actor: Actor,
2139    /// What the printer made of it.
2140    pub outcome: ExecutionOutcome,
2141    /// Why.
2142    pub reason: String,
2143}
2144
2145/// One field of a recovery action a bound is held to.
2146#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2147pub enum RecoveryField {
2148    /// A lift's distance, in millimetres.
2149    #[serde(rename = "mm")]
2150    Mm,
2151    /// The fan's percentage.
2152    #[serde(rename = "percent")]
2153    Percent,
2154    /// A heat's tool.
2155    #[serde(rename = "tool")]
2156    Tool,
2157    /// A heat's target, in degrees Celsius.
2158    #[serde(rename = "target_c")]
2159    TargetC,
2160    /// The operator's word that the nozzle is clear of the part.
2161    #[serde(rename = "clearance_asserted")]
2162    ClearanceAsserted,
2163    /// The operator's word that homing Z will meet nothing on the bed.
2164    #[serde(rename = "bed_clear_asserted")]
2165    BedClearAsserted,
2166}
2167
2168/// Where the print head is, in millimetres, as the printer reported it.
2169#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2170pub struct RecoveryPosition {
2171    /// X.
2172    pub x: f64,
2173    /// Y.
2174    pub y: f64,
2175    /// Z.
2176    pub z: f64,
2177}
2178
2179/// A recovery action was refused, and reached nothing.
2180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2181pub struct RecoveryRejectedPayload {
2182    /// What was asked for.
2183    pub action: RecoveryAction,
2184    /// Who asked.
2185    pub actor: Actor,
2186    /// Why.
2187    pub reason: String,
2188    /// Why it was refused.
2189    pub rejection: RecoveryRejection,
2190}
2191
2192/// Why a recovery request was refused, each a variant of its own so a
2193/// consumer tells them apart by matching rather than by reading words.
2194#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2195pub enum RecoveryRejection {
2196    /// The asking actor's class is not granted this action.
2197    #[serde(rename = "actor_may_not_request")]
2198    ActorMayNotRequest {
2199        /// The action asked for.
2200        action: RecoveryActionKind,
2201    },
2202    /// A print is running, or being cancelled: nothing may move the printer
2203    /// under it.
2204    #[serde(rename = "print_running")]
2205    PrintRunning {
2206        /// The state the printer reported.
2207        state: PrinterState,
2208    },
2209    /// The printer could not be read, or reports itself offline or in error.
2210    #[serde(rename = "printer_unreachable")]
2211    PrinterUnreachable,
2212    /// Nothing printobserver did has told it where Z is, so no move that
2213    /// depends on the nozzle's height above the part may be made.
2214    #[serde(rename = "z_untrusted")]
2215    ZUntrusted,
2216    /// The nozzle is not far enough above the tallest part the last print
2217    /// laid down.
2218    #[serde(rename = "insufficient_clearance")]
2219    InsufficientClearance {
2220        /// The clearance asked above it, in millimetres.
2221        margin_mm: f64,
2222        /// Where the printer reports the nozzle, in millimetres.
2223        nozzle_z_mm: f64,
2224        /// The tallest part the last print laid down, in millimetres.
2225        tallest_mm: f64,
2226    },
2227    /// A value the request carries is outside the range allowed for it.
2228    #[serde(rename = "out_of_bounds")]
2229    OutOfBounds {
2230        /// The range allowed, inclusive.
2231        allowed: Range,
2232        /// The field.
2233        field: RecoveryField,
2234        /// The value asked for.
2235        requested: f64,
2236    },
2237    /// This action needs an assertion the request did not make.
2238    #[serde(rename = "assertion_required")]
2239    AssertionRequired {
2240        /// The assertion it needs.
2241        field: RecoveryField,
2242    },
2243    /// The configuration states nothing of this printer's recovery facts, so
2244    /// no recovery action can be checked.
2245    #[serde(rename = "recovery_not_configured")]
2246    RecoveryNotConfigured,
2247    /// The printer cannot do what this action needs: a move its adapter does
2248    /// not support, or a nozzle position it cannot read.
2249    #[serde(rename = "printer_cannot")]
2250    PrinterCannot {
2251        /// The action asked for.
2252        action: RecoveryActionKind,
2253        /// What the printer cannot do, in words.
2254        detail: String,
2255    },
2256}
2257
2258/// A recovery action was asked for.
2259///
2260/// The printer's rather than any print's, so it is written with no print.
2261#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2262pub struct RecoveryRequestedPayload {
2263    /// What was asked for.
2264    pub action: RecoveryAction,
2265    /// Who asked.
2266    pub actor: Actor,
2267    /// Why.
2268    pub reason: String,
2269}
2270
2271/// What a recovery read answers: everything a recovery is checked against,
2272/// and what each action would meet now.
2273#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2274pub struct RecoveryStatus {
2275    /// Each action, and whether it is allowed now.
2276    pub actions: Vec<ActionAllowance>,
2277    /// What the last print laid down, `null` when there is no print with a
2278    /// file to read or its file could not be read.
2279    pub last_print: Option<LastPrint>,
2280    /// The print paused under the printer, `null` when none is.
2281    pub paused_print: Option<PausedPrint>,
2282    /// Where the head is, read only while Z is trusted; `null` otherwise or
2283    /// when it could not be read.
2284    pub position: Option<RecoveryPosition>,
2285    /// The state the printer reports; `offline` when it could not be read.
2286    pub state: PrinterState,
2287    /// Every heater.
2288    pub temperatures: RecoveryTemperatures,
2289    /// Where Z is homed and whether that sweeps the bed, `null` when
2290    /// recovery is not configured.
2291    pub z_home: Option<ZHome>,
2292    /// Whether Z is trusted.
2293    pub z_trust: ZTrust,
2294}
2295
2296/// Every heater, as a recovery read reports it.
2297#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2298pub struct RecoveryTemperatures {
2299    /// The bed, `null` when the printer reports none.
2300    pub bed: Option<BedTemperature>,
2301    /// Each tool.
2302    pub tools: Vec<ToolTemperature>,
2303}
2304
2305/// Why a request was refused.
2306///
2307/// Each rejection is a distinct variant, so a consumer distinguishes them by
2308/// matching rather than by reading a message.
2309#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2310pub enum RejectionReason {
2311    /// The value asked for is outside the range allowed for that adjustable.
2312    #[serde(rename = "out_of_bounds")]
2313    OutOfBounds {
2314        /// The adjustable that was asked for.
2315        adjustable: Adjustable,
2316        /// The range that was allowed.
2317        allowed: Range,
2318        /// The value that was asked for.
2319        requested: f64,
2320    },
2321    /// This actor class may not request this action at all.
2322    #[serde(rename = "actor_may_not_request")]
2323    ActorMayNotRequest {
2324        /// The action they asked for.
2325        action: ActionKind,
2326        /// The class of the actor that asked.
2327        actor_class: ActorClass,
2328    },
2329    /// There is no active print to act on.
2330    #[serde(rename = "no_active_print")]
2331    NoActivePrint,
2332    /// The printer is not in a state this action is valid from.
2333    #[serde(rename = "invalid_from_state")]
2334    InvalidFromState {
2335        /// The state the printer is in.
2336        state: PrinterState,
2337    },
2338    /// The agent's minimum interval has not elapsed.
2339    #[serde(rename = "min_interval_not_elapsed")]
2340    MinIntervalNotElapsed {
2341        /// The minimum interval, in whole seconds.
2342        interval_s: i64,
2343        /// How long it has been since the last agent action, in whole
2344        /// seconds.
2345        since_last_s: i64,
2346    },
2347    /// The adjustable is not one this printer has.
2348    #[serde(rename = "unsupported_adjustable")]
2349    UnsupportedAdjustable {
2350        /// The adjustable this printer cannot express.
2351        adjustable: Adjustable,
2352    },
2353    /// A start was refused because the spool on one tool holds less than the
2354    /// print needs and its buffer.
2355    #[serde(rename = "insufficient_filament")]
2356    InsufficientFilament {
2357        /// The margin asked for beyond that, in grams.
2358        buffer_g: f64,
2359        /// What the spool holds, in grams.
2360        remaining_g: f64,
2361        /// What the print needs from it, in grams.
2362        required_g: f64,
2363        /// The spool loaded on it.
2364        spool_id: SpoolId,
2365        /// That spool's name.
2366        spool_name: String,
2367        /// The tool whose spool is short.
2368        tool: i64,
2369        /// The spool's oldest mark, when the short spool is also unverified.
2370        #[serde(default, skip_serializing_if = "Option::is_none")]
2371        unverified: Option<UnverifiedReason>,
2372    },
2373    /// A start was refused because the spool on one tool is unverified.
2374    #[serde(rename = "unverified_spool")]
2375    UnverifiedSpool {
2376        /// The spool's oldest mark.
2377        reason: UnverifiedReason,
2378        /// The spool loaded on it.
2379        spool_id: SpoolId,
2380        /// That spool's name.
2381        spool_name: String,
2382        /// The tool whose spool is unverified.
2383        tool: i64,
2384    },
2385    /// A start was refused because `OctoPrint`'s analysis of the file had not
2386    /// appeared when the start's wait for it elapsed.
2387    #[serde(rename = "analysis_unavailable")]
2388    AnalysisUnavailable {
2389        /// The file that has no analysis.
2390        file_name: String,
2391        /// How long the start waited for one, in whole seconds.
2392        waited_s: i64,
2393    },
2394    /// A close was refused because the print record has already ended.
2395    #[serde(rename = "print_already_ended")]
2396    PrintAlreadyEnded,
2397    /// A close was refused because the printer shows a job of the record's
2398    /// file printing, paused or cancelling.
2399    #[serde(rename = "print_still_active")]
2400    PrintStillActive {
2401        /// The state the printer reported that job in.
2402        state: PrinterState,
2403    },
2404    /// A close was refused because an intervention for the print has neither
2405    /// expired nor been restored.
2406    #[serde(rename = "intervention_still_active")]
2407    InterventionStillActive {
2408        /// The intervention still standing.
2409        intervention_id: InterventionId,
2410    },
2411    /// A resume was refused because a recovery action was executed while the
2412    /// print was paused: the head moved, and `OctoPrint` restores no position
2413    /// on resume, so the rest of the layer would print at the wrong height.
2414    #[serde(rename = "print_not_resumable")]
2415    PrintNotResumable {
2416        /// The `recovery_executed` event that made it so.
2417        recovery_event_id: EventId,
2418    },
2419    /// A spool command could not be carried out.
2420    #[serde(rename = "spool_refused")]
2421    SpoolRefused {
2422        /// Why, in a form a client acts on without reading `message`.
2423        cause: SpoolRefusalCause,
2424        /// Why, in a sentence naming what to do instead, for a person.
2425        message: String,
2426    },
2427}
2428
2429/// A value as a source reported it, flagged when it is outside the
2430/// plausibility range this crate declares for its field.
2431#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2432pub struct Reported {
2433    /// One shape of the contracts, which say nothing more about it.
2434    pub out_of_range: bool,
2435    /// One shape of the contracts, which say nothing more about it.
2436    pub value: f64,
2437}
2438
2439/// The position a filament change charged up to, and how fresh it is.
2440#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2441pub struct SignalPosition {
2442    /// Whether it was read when the signal was handled, or is the last one
2443    /// recorded before it.
2444    pub freshness: PositionFreshness,
2445    /// The position.
2446    pub position: FilePosition,
2447}
2448
2449/// A spool's remaining figure was corrected.
2450#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2451pub struct SpoolAdjustedPayload {
2452    /// Who corrected it.
2453    pub actor: Actor,
2454    /// Every mark the correction cleared.
2455    #[serde(default, skip_serializing_if = "Option::is_none")]
2456    pub cleared_marks: Option<Vec<ClearedMark>>,
2457    /// The scale reading the figure was worked out from, when it was given as
2458    /// one.
2459    #[serde(default, skip_serializing_if = "Option::is_none")]
2460    pub gross_g: Option<f64>,
2461    /// Why.
2462    pub reason: String,
2463    /// Its figure after, in grams.
2464    pub remaining_after_g: f64,
2465    /// Its figure before, in grams, after any pending charge it took.
2466    pub remaining_before_g: f64,
2467    /// The spool corrected.
2468    pub spool_id: SpoolId,
2469    /// The pending charges the correction took first, each also its own
2470    /// `filament_consumed` event under its key.
2471    #[serde(default, skip_serializing_if = "Option::is_none")]
2472    pub took_pending: Option<Vec<PendingTaken>>,
2473}
2474
2475/// What one spool's read answers: the spool, and the newest of the printer's
2476/// own events that name it.
2477#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2478pub struct SpoolAnswer {
2479    /// The newest timeline events naming it, newest first.
2480    pub recent: Vec<EventRecord>,
2481    /// The spool, retired or not.
2482    pub spool: SpoolRecord,
2483}
2484
2485/// The whole vocabulary of the spool ledger, and there is no other.
2486#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2487#[serde(tag = "command")]
2488pub enum SpoolCommand {
2489    /// Register a spool.
2490    #[serde(rename = "register_spool")]
2491    RegisterSpool {
2492        /// Who is asking.
2493        actor: Actor,
2494        /// The filament's colour.
2495        #[serde(default, skip_serializing_if = "Option::is_none")]
2496        color: Option<String>,
2497        /// The material's density, in grams per cubic centimetre; the known
2498        /// density of the material when absent, and a material with none
2499        /// known
2500        /// is refused.
2501        #[serde(default, skip_serializing_if = "Option::is_none")]
2502        density_g_cm3: Option<f64>,
2503        /// The filament's diameter, in millimetres; 1.75 when absent.
2504        #[serde(default, skip_serializing_if = "Option::is_none")]
2505        diameter_mm: Option<f64>,
2506        /// The net filament on the spool when full, in grams.
2507        initial_g: f64,
2508        /// The material, such as `PLA`.
2509        material: String,
2510        /// The operator's own label for it.
2511        name: String,
2512        /// Why the actor is asking for this.
2513        reason: String,
2514        /// The filament on it now, in grams; the full figure when absent.
2515        #[serde(default, skip_serializing_if = "Option::is_none")]
2516        remaining_g: Option<f64>,
2517        /// The empty spool's own weight, in grams.
2518        #[serde(default, skip_serializing_if = "Option::is_none")]
2519        tare_g: Option<f64>,
2520    },
2521    /// Load a spool onto a tool.
2522    ///
2523    /// Onto an occupied tool it is a swap, and the spool there is unloaded in
2524    /// the same write. Naming the spool already on the tool confirms it was
2525    /// never swapped, which is accepted only while that tool holds a pending
2526    /// charge or that spool holds a filament-change mark.
2527    #[serde(rename = "load_spool")]
2528    LoadSpool {
2529        /// Who is asking.
2530        actor: Actor,
2531        /// Why the actor is asking for this.
2532        reason: String,
2533        /// The spool to load.
2534        spool_id: SpoolId,
2535        /// The tool, in the printer's own numbering.
2536        tool: i64,
2537    },
2538    /// Unload a spool from its tool.
2539    #[serde(rename = "unload_spool")]
2540    UnloadSpool {
2541        /// Who is asking.
2542        actor: Actor,
2543        /// Why the actor is asking for this.
2544        reason: String,
2545        /// The spool to unload.
2546        spool_id: SpoolId,
2547    },
2548    /// Correct a spool's remaining figure, from exactly one of the two; a
2549    /// command naming both or neither is refused as `spool_refused`.
2550    #[serde(rename = "adjust_spool")]
2551    AdjustSpool {
2552        /// Who is asking.
2553        actor: Actor,
2554        /// A scale reading of the spool, in grams, which needs its tare.
2555        #[serde(default, skip_serializing_if = "Option::is_none")]
2556        gross_g: Option<f64>,
2557        /// Why the actor is asking for this.
2558        reason: String,
2559        /// The filament left, in grams.
2560        #[serde(default, skip_serializing_if = "Option::is_none")]
2561        remaining_g: Option<f64>,
2562        /// The spool to correct.
2563        spool_id: SpoolId,
2564    },
2565    /// Retire a spool, after which it refuses every command.
2566    #[serde(rename = "retire_spool")]
2567    RetireSpool {
2568        /// Who is asking.
2569        actor: Actor,
2570        /// Why the actor is asking for this.
2571        reason: String,
2572        /// The spool to retire.
2573        spool_id: SpoolId,
2574    },
2575}
2576
2577/// What a spool command answers, accepted or refused.
2578///
2579/// A refusal is the same shape and deliberately so: the decision carries the
2580/// grant or the ledger's own reason, and the events are the record the
2581/// refusal
2582/// wrote.
2583#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2584pub struct SpoolCommandAnswer {
2585    /// The decision taken on the command.
2586    pub decision: PolicyDecision,
2587    /// Every event it wrote, in the order they were written.
2588    pub events: Vec<EventRecord>,
2589    /// The spool it was about, as the command left it, when there is one.
2590    #[serde(default, skip_serializing_if = "Option::is_none")]
2591    pub spool: Option<SpoolRecord>,
2592}
2593
2594/// A spool command was refused.
2595#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2596pub struct SpoolCommandRejectedPayload {
2597    /// The command, as it was asked for.
2598    pub command: SpoolCommand,
2599    /// The whole decision, carrying which refusal it was.
2600    pub decision: PolicyDecision,
2601}
2602
2603/// One field of a spool command, as the command names it.
2604#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2605pub enum SpoolField {
2606    /// The operator's own label for the spool.
2607    #[serde(rename = "name")]
2608    Name,
2609    /// The material, such as `PLA`.
2610    #[serde(rename = "material")]
2611    Material,
2612    /// The filament's diameter, in millimetres.
2613    #[serde(rename = "diameter_mm")]
2614    DiameterMm,
2615    /// The material's density, in grams per cubic centimetre.
2616    #[serde(rename = "density_g_cm3")]
2617    DensityGCm3,
2618    /// The net filament on the spool when full, in grams.
2619    #[serde(rename = "initial_g")]
2620    InitialG,
2621    /// The filament on the spool now, in grams.
2622    #[serde(rename = "remaining_g")]
2623    RemainingG,
2624    /// The empty spool's own weight, in grams.
2625    #[serde(rename = "tare_g")]
2626    TareG,
2627    /// A scale reading of the spool, in grams.
2628    #[serde(rename = "gross_g")]
2629    GrossG,
2630}
2631
2632/// A lowercase hyphenated version 7 UUID identifying one registered spool.
2633pub type SpoolId = String;
2634
2635/// Where a spool is in its life.
2636///
2637/// One state rather than a loaded tool beside a retirement instant, because a
2638/// retired spool is on no tool: retiring a loaded spool takes it off.
2639#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2640#[serde(tag = "state")]
2641pub enum SpoolLifecycle {
2642    /// Registered, and loaded on no tool.
2643    #[serde(rename = "shelved")]
2644    Shelved,
2645    /// Loaded on one tool.
2646    #[serde(rename = "loaded")]
2647    Loaded {
2648        /// The tool, in the printer's own numbering.
2649        tool: i64,
2650    },
2651    /// Retired: it takes no further command, and is on no tool.
2652    #[serde(rename = "retired")]
2653    Retired {
2654        /// When it was retired.
2655        at: Timestamp,
2656    },
2657}
2658
2659/// A spool was loaded onto a tool, or confirmed as never having left it.
2660#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2661pub struct SpoolLoadedPayload {
2662    /// Who loaded it.
2663    pub actor: Actor,
2664    /// Every mark the load cleared, on either spool.
2665    #[serde(default, skip_serializing_if = "Option::is_none")]
2666    pub cleared_marks: Option<Vec<ClearedMark>>,
2667    /// Why.
2668    pub reason: String,
2669    /// The spool the load swapped off the tool, when it was occupied.
2670    #[serde(default, skip_serializing_if = "Option::is_none")]
2671    pub replaced: Option<SpoolId>,
2672    /// The spool loaded.
2673    pub spool_id: SpoolId,
2674    /// The pending charges the load took, each also its own
2675    /// `filament_consumed` event under its key.
2676    #[serde(default, skip_serializing_if = "Option::is_none")]
2677    pub took_pending: Option<Vec<PendingTaken>>,
2678    /// The tool, in the printer's own numbering.
2679    pub tool: i64,
2680}
2681
2682/// One spool the operator registered, and what is known of it now.
2683#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2684pub struct SpoolRecord {
2685    /// The filament's colour, when the operator gave one.
2686    #[serde(default, skip_serializing_if = "Option::is_none")]
2687    pub color: Option<String>,
2688    /// The material's density, in grams per cubic centimetre; above zero.
2689    pub density_g_cm3: f64,
2690    /// The filament's diameter, in millimetres; above zero.
2691    pub diameter_mm: f64,
2692    /// This spool's identifier, minted when it was registered.
2693    pub id: SpoolId,
2694    /// The net filament on the spool when it was full, in grams; above zero.
2695    pub initial_g: f64,
2696    /// Where the spool is in its life: on the shelf, loaded on a tool, or
2697    /// retired — and never both of the last two.
2698    pub lifecycle: SpoolLifecycle,
2699    /// The material, as given and trimmed, such as `PLA`; never empty.
2700    pub material: String,
2701    /// The operator's own label for the spool; never empty.
2702    pub name: String,
2703    /// When the spool was registered.
2704    pub registered_at: Timestamp,
2705    /// The filament left, in grams.
2706    ///
2707    /// Always the initial figure less every charge plus every correction. A
2708    /// charge is never refused or clamped, so this may go below zero.
2709    pub remaining_g: f64,
2710    /// The empty spool's own weight, in grams, when the operator gave it.
2711    #[serde(default, skip_serializing_if = "Option::is_none")]
2712    pub tare_g: Option<f64>,
2713    /// How far the remaining figure can be trusted.
2714    pub trust: SpoolTrust,
2715    /// When anything about the spool last changed.
2716    pub updated_at: Timestamp,
2717}
2718
2719/// Why the spool ledger refused a command, each cause carrying what a client
2720/// needs to choose the next valid request.
2721#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2722#[serde(tag = "cause")]
2723pub enum SpoolRefusalCause {
2724    /// No spool was ever registered under this identifier.
2725    #[serde(rename = "unknown_spool")]
2726    UnknownSpool {
2727        /// The identifier the command named.
2728        spool_id: SpoolId,
2729    },
2730    /// The spool is retired, and takes no further command.
2731    #[serde(rename = "retired_spool")]
2732    RetiredSpool {
2733        /// The retired spool.
2734        spool_id: SpoolId,
2735    },
2736    /// The spool named is already on the tool, and nothing is pending there
2737    /// or
2738    /// marked on it for a load to confirm.
2739    #[serde(rename = "nothing_to_confirm")]
2740    NothingToConfirm {
2741        /// The tool the spool is already on.
2742        tool: i64,
2743    },
2744    /// A figure the command gave is not one the field can hold: not a number,
2745    /// or out of the field's range.
2746    #[serde(rename = "invalid_adjustment_figure")]
2747    InvalidAdjustmentFigure {
2748        /// The field the figure was given for.
2749        field: SpoolField,
2750        /// The figure given; always a number, because a figure JSON cannot
2751        /// carry is refused as it would be written rather than written as
2752        /// `null`.
2753        value: f64,
2754    },
2755    /// A scale reading was given for a spool with no tare, so it cannot be
2756    /// turned into a figure.
2757    #[serde(rename = "tare_required")]
2758    TareRequired {
2759        /// The spool with no tare.
2760        spool_id: SpoolId,
2761    },
2762    /// The material has no known density, so the command needs one.
2763    #[serde(rename = "density_required")]
2764    DensityRequired {
2765        /// The material, as given and trimmed.
2766        material: String,
2767        /// The spool, when the command was about one that exists; absent for
2768        /// a registration.
2769        #[serde(default, skip_serializing_if = "Option::is_none")]
2770        spool_id: Option<SpoolId>,
2771    },
2772    /// A field the command needs was empty or absent.
2773    #[serde(rename = "missing_field")]
2774    MissingField {
2775        /// The field; for a correction naming neither figure, `remaining_g`,
2776        /// which `gross_g` may stand in for.
2777        field: SpoolField,
2778    },
2779    /// The command gave fields only one of which it may carry.
2780    #[serde(rename = "conflicting_fields")]
2781    ConflictingFields {
2782        /// Every field given that conflicts, in the command's own order.
2783        fields: Vec<SpoolField>,
2784    },
2785    /// The command named a tool the printer cannot have.
2786    #[serde(rename = "unknown_tool")]
2787    UnknownTool {
2788        /// The tool named.
2789        tool: i64,
2790    },
2791    /// The spool is loaded on no tool, so there is nothing to unload.
2792    #[serde(rename = "not_loaded")]
2793    NotLoaded {
2794        /// The spool.
2795        spool_id: SpoolId,
2796    },
2797    /// The ledger refused the write the command made, because the spools
2798    /// changed under it; reading them again shows how.
2799    #[serde(rename = "ledger_refused")]
2800    LedgerRefused,
2801}
2802
2803/// A spool was registered.
2804#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2805pub struct SpoolRegisteredPayload {
2806    /// Who registered it.
2807    pub actor: Actor,
2808    /// Why.
2809    pub reason: String,
2810    /// The spool, as it was registered.
2811    pub spool: SpoolRecord,
2812}
2813
2814/// A spool was retired.
2815#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2816pub struct SpoolRetiredPayload {
2817    /// Who retired it.
2818    pub actor: Actor,
2819    /// Why.
2820    pub reason: String,
2821    /// Its figure as it was retired, in grams.
2822    pub remaining_g: f64,
2823    /// The spool retired.
2824    pub spool_id: SpoolId,
2825}
2826
2827/// How far a spool's remaining figure can be trusted.
2828#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2829#[serde(tag = "state")]
2830pub enum SpoolTrust {
2831    /// Every charge against the spool was taken exactly.
2832    #[serde(rename = "verified")]
2833    Verified,
2834    /// At least one thing happened that could not be charged exactly.
2835    #[serde(rename = "unverified")]
2836    Unverified {
2837        /// Every reason the figure is in doubt, oldest first, at most one per
2838        /// distinct reason; never empty.
2839        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
2840        marks: Vec<UnverifiedMark>,
2841    },
2842}
2843
2844/// A spool was unloaded from its tool.
2845#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2846pub struct SpoolUnloadedPayload {
2847    /// Who unloaded it.
2848    pub actor: Actor,
2849    /// Why.
2850    pub reason: String,
2851    /// The spool unloaded.
2852    pub spool_id: SpoolId,
2853    /// The tool it left.
2854    pub tool: i64,
2855}
2856
2857/// A spool was marked as one whose figure is in doubt.
2858#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2859pub struct SpoolUnverifiedPayload {
2860    /// Why its figure is in doubt.
2861    pub reason: UnverifiedReason,
2862    /// The spool marked.
2863    pub spool_id: SpoolId,
2864}
2865
2866/// What the spools read answers.
2867///
2868/// `at_a_glance` is one line per spool, in the order `spools` lists them,
2869/// composed here so that what is left on each spool is the first thing a
2870/// reader of either rendering meets rather than a figure inside a whole
2871/// record. Its name is what puts it first: both renderings walk an answer's
2872/// fields in name order, ahead of `pending` and `spools`.
2873#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2874pub struct SpoolsAnswer {
2875    /// One line per spool in `spools`, in the same order.
2876    pub at_a_glance: Vec<String>,
2877    /// Every charge held after a filament change nobody has confirmed, oldest
2878    /// first: what the next spool loaded on that tool is charged.
2879    pub pending: Vec<PendingCharge>,
2880    /// Every spool, retired ones only when asked for, most recently
2881    /// registered
2882    /// first.
2883    pub spools: Vec<SpoolRecord>,
2884}
2885
2886/// What one restart put back the way it found it.
2887///
2888/// A supervisor that has been restarted adopts whatever the store holds
2889/// rather
2890/// than starting empty, and each of these is one of those adoptions. They are
2891/// recorded rather than merely done, because a print that carried on across a
2892/// restart and one that was started again look identical afterwards unless
2893/// the
2894/// history says which happened.
2895#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2896pub enum StartupOutcome {
2897    /// A print left open was adopted as the print this supervisor is
2898    /// watching.
2899    #[serde(rename = "print_adopted")]
2900    PrintAdopted,
2901    /// A session left open was resumed rather than replaced.
2902    #[serde(rename = "session_resumed")]
2903    SessionResumed {
2904        /// The session's own name in the harness.
2905        session_name: String,
2906    },
2907    /// An intervention already past its expiry was expired on start.
2908    #[serde(rename = "intervention_expired")]
2909    InterventionExpired {
2910        /// What it had changed.
2911        adjustable: Adjustable,
2912        /// The intervention that had outlived its bound.
2913        intervention_id: InterventionId,
2914        /// What became of putting the prior value back.
2915        outcome: InterventionOutcome,
2916    },
2917}
2918
2919/// A supervisor reconciled one thing the store held when it started.
2920#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2921pub struct StartupReconciliationPayload {
2922    /// What was reconciled.
2923    pub outcome: StartupOutcome,
2924    /// The print it is about.
2925    pub print_id: PrintId,
2926}
2927
2928/// What a status read answers.
2929#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2930pub struct StatusAnswer {
2931    /// The failure detector's health, and since when it has been so.
2932    pub detector: DetectorHealth,
2933    /// The bounded interventions still in force.
2934    pub interventions: Vec<Intervention>,
2935    /// The job, when a snapshot could be taken.
2936    #[serde(default, skip_serializing_if = "Option::is_none")]
2937    pub job: Option<JobSnapshot>,
2938    /// The print.
2939    pub print: PrintRecord,
2940    /// The printer, when a snapshot could be taken.
2941    #[serde(default, skip_serializing_if = "Option::is_none")]
2942    pub printer: Option<PrinterSnapshot>,
2943    /// The supervision session watching it, when one is.
2944    #[serde(default, skip_serializing_if = "Option::is_none")]
2945    pub session: Option<SupervisionSession>,
2946}
2947
2948/// The supervision session keyed to one print.
2949#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2950pub struct SupervisionSession {
2951    /// Why it was closed, if it was.
2952    #[serde(default, skip_serializing_if = "Option::is_none")]
2953    pub close_reason: Option<String>,
2954    /// When the session was closed, if it was.
2955    #[serde(default, skip_serializing_if = "Option::is_none")]
2956    pub closed_at: Option<Timestamp>,
2957    /// When the session was opened.
2958    pub created_at: Timestamp,
2959    /// The identity the harness ran the session under.
2960    pub harness_identity: String,
2961    /// When the last turn ran.
2962    pub last_turn_at: Timestamp,
2963    /// The print this session watches.
2964    pub print_id: PrintId,
2965    /// The session's own name in the harness.
2966    pub session_name: String,
2967}
2968
2969/// A supervision session was closed.
2970#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2971pub struct SupervisionSessionClosedPayload {
2972    /// Why it was closed.
2973    pub close_reason: String,
2974    /// The session's own name in the harness.
2975    pub session_name: String,
2976}
2977
2978/// A supervision session was opened.
2979#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2980pub struct SupervisionSessionOpenedPayload {
2981    /// The identity the harness ran it under.
2982    pub harness_identity: String,
2983    /// The session's own name in the harness.
2984    pub session_name: String,
2985}
2986
2987/// What a printer history read answers: one page of the printer's own events.
2988#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2989pub struct TimelineAnswer {
2990    /// The events, newest first.
2991    pub events: Vec<EventRecord>,
2992    /// The cursor for the following, older page, given as `before`; absent
2993    /// when there is none.
2994    #[serde(default, skip_serializing_if = "Option::is_none")]
2995    pub next: Option<EventId>,
2996}
2997
2998/// An instant in UTC, as an RFC 3339 string with a zero offset.
2999pub type Timestamp = String;
3000
3001/// The filament one tool feeds, as a length and, when it is known, a volume.
3002#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3003pub struct ToolFilament {
3004    /// The length of filament fed, in millimetres.
3005    pub length_mm: f64,
3006    /// The tool, in the printer's own numbering.
3007    pub tool: i64,
3008    /// The volume of filament fed, in cubic centimetres, when the source
3009    /// reported one.
3010    #[serde(default, skip_serializing_if = "Option::is_none")]
3011    pub volume_cm3: Option<f64>,
3012}
3013
3014/// One tool heater, as a recovery read reports it.
3015#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3016pub struct ToolTemperature {
3017    /// What it reads, in degrees Celsius, `null` when not reported.
3018    pub actual_c: Option<f64>,
3019    /// What it is driving towards, in degrees Celsius, `null` when not
3020    /// reported.
3021    pub target_c: Option<f64>,
3022    /// The tool, in the printer's own numbering.
3023    pub tool: i64,
3024}
3025
3026/// A print this system never saw was found in the printer's own tally.
3027#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3028pub struct UnaccountedPrintPayload {
3029    /// Whether it was charged against a spool.
3030    pub charged: bool,
3031    /// The file that was printed.
3032    pub file_name: String,
3033    /// When it was printed, when the printer says.
3034    #[serde(default, skip_serializing_if = "Option::is_none")]
3035    pub printed_at: Option<Timestamp>,
3036    /// Whether it completed.
3037    pub success: bool,
3038}
3039
3040/// The printer heated or moved with no job running, soon after a print ended
3041/// `interrupted` or `failed`.
3042///
3043/// A transition rather than a reading: written once when such activity
3044/// begins,
3045/// and again only when what it reports changes. The printer's rather than any
3046/// print's, so it is written with no print; `last_print_id` names the print
3047/// it
3048/// came after.
3049#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3050pub struct UnattendedPrinterActivityPayload {
3051    /// The bed's target, when it is above zero; `null` otherwise.
3052    #[serde(default, skip_serializing_if = "Option::is_none")]
3053    pub bed_target_c: Option<f64>,
3054    /// What the watch read, in words.
3055    pub detail: String,
3056    /// The print that ended before it.
3057    pub last_print_id: PrintId,
3058    /// Whether the print head moved since the last position read, `null` when
3059    /// no position could be read.
3060    #[serde(default, skip_serializing_if = "Option::is_none")]
3061    pub moved: Option<bool>,
3062    /// Every tool whose target is above zero.
3063    pub tools: Vec<HeatedTool>,
3064}
3065
3066/// One reason a spool's figure is in doubt, and since when.
3067#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3068pub struct UnverifiedMark {
3069    /// Why the figure is in doubt.
3070    pub reason: UnverifiedReason,
3071    /// When the spool was marked for it.
3072    pub since: Timestamp,
3073}
3074
3075/// Why a spool's figure is in doubt.
3076#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3077#[serde(tag = "reason")]
3078pub enum UnverifiedReason {
3079    /// A print ran on the spool's tool and nothing could be charged, because
3080    /// the file had no analysis — an SD-card file, for one.
3081    #[serde(rename = "uncharged_print")]
3082    UnchargedPrint {
3083        /// The print nothing could be charged for.
3084        print_id: PrintId,
3085    },
3086    /// A print this system never saw ran and did not complete.
3087    #[serde(rename = "unaccounted_print")]
3088    UnaccountedPrint {
3089        /// The file that was printed.
3090        file_name: String,
3091        /// When it was printed.
3092        printed_at: Timestamp,
3093    },
3094    /// A filament change was signalled and nobody has said which spool is now
3095    /// loaded. The spool was charged up to a position read when the signal
3096    /// was
3097    /// handled.
3098    #[serde(rename = "filament_change_unconfirmed")]
3099    FilamentChangeUnconfirmed {
3100        /// The print the change was signalled during.
3101        print_id: PrintId,
3102    },
3103    /// A filament change was signalled, nobody has said which spool is now
3104    /// loaded, and no fresh position could be read at the signal: the spool
3105    /// was
3106    /// charged only up to the last recorded position, so its figure may be
3107    /// overstated.
3108    #[serde(rename = "filament_change_unconfirmed_position")]
3109    FilamentChangeUnconfirmedPosition {
3110        /// The print the change was signalled during.
3111        print_id: PrintId,
3112    },
3113    /// A file's print tally was reset, and it cannot be told which of the
3114    /// prints since the re-upload were already settled.
3115    #[serde(rename = "tally_reset")]
3116    TallyReset {
3117        /// The file whose tally was reset.
3118        file_name: String,
3119        /// Where the file lives, in the printer's own vocabulary.
3120        origin: String,
3121    },
3122}
3123
3124/// Where the machine homes Z, and whether doing so sweeps the bed.
3125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3126pub struct ZHome {
3127    /// Whether homing Z brings a gantry down over the whole bed.
3128    // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
3129    pub sweeps_bed: bool,
3130    /// Where in X Z is homed, in millimetres.
3131    pub x: f64,
3132    /// Where in Y Z is homed, in millimetres.
3133    pub y: f64,
3134}
3135
3136/// Whether Z is trusted, and since when or why not.
3137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3138pub enum ZTrust {
3139    /// Z is where printobserver last put it, from this instant on: a `home_z`
3140    /// it executed, or a print it started.
3141    #[serde(rename = "trusted_since")]
3142    TrustedSince(Timestamp),
3143    /// Z is not trusted, and why.
3144    #[serde(rename = "untrusted")]
3145    Untrusted(String),
3146}
3147
3148/// One payload type declared under a kind name, by the domain that owns it.
3149///
3150/// The table from kind name to payload type, as the server declares it: every
3151/// kind below has exactly one implementation here, and a kind this client
3152/// does not know flows through `EventRecord` untouched.
3153pub trait EventPayloadKind: serde::de::DeserializeOwned {
3154    /// The name this payload's events are written down under.
3155    const KIND: &'static str;
3156}
3157
3158impl EventPayloadKind for ActionExecutedPayload {
3159    const KIND: &'static str = "action_executed";
3160}
3161
3162impl EventPayloadKind for ActionRejectedPayload {
3163    const KIND: &'static str = "action_rejected";
3164}
3165
3166impl EventPayloadKind for ActionRequestedPayload {
3167    const KIND: &'static str = "action_requested";
3168}
3169
3170impl EventPayloadKind for AgentAssessmentPayload {
3171    const KIND: &'static str = "agent_assessment";
3172}
3173
3174impl EventPayloadKind for CameraDegradedPayload {
3175    const KIND: &'static str = "camera_degraded";
3176}
3177
3178impl EventPayloadKind for CameraLookPayload {
3179    const KIND: &'static str = "camera_look";
3180}
3181
3182impl EventPayloadKind for ChargePendedPayload {
3183    const KIND: &'static str = "charge_pended";
3184}
3185
3186impl EventPayloadKind for DetectorHealthChangedPayload {
3187    const KIND: &'static str = "detector_health_changed";
3188}
3189
3190impl EventPayloadKind for FilamentChangeSignalledPayload {
3191    const KIND: &'static str = "filament_change_signalled";
3192}
3193
3194impl EventPayloadKind for FilamentConsumedPayload {
3195    const KIND: &'static str = "filament_consumed";
3196}
3197
3198impl EventPayloadKind for FileTallyRebaselinedPayload {
3199    const KIND: &'static str = "file_tally_rebaselined";
3200}
3201
3202impl EventPayloadKind for InterventionExpiredPayload {
3203    const KIND: &'static str = "intervention_expired";
3204}
3205
3206impl EventPayloadKind for MalformedExternalEventPayload {
3207    const KIND: &'static str = "malformed_external_event";
3208}
3209
3210impl EventPayloadKind for ObicoFailureAlertPayload {
3211    const KIND: &'static str = "obico_failure_alert";
3212}
3213
3214impl EventPayloadKind for ObicoPrinterNotificationPayload {
3215    const KIND: &'static str = "obico_printer_notification";
3216}
3217
3218impl EventPayloadKind for OperatorAcknowledgementPayload {
3219    const KIND: &'static str = "operator_acknowledgement";
3220}
3221
3222impl EventPayloadKind for PeriodicObservationPayload {
3223    const KIND: &'static str = "periodic_observation";
3224}
3225
3226impl EventPayloadKind for PortFailurePayload {
3227    const KIND: &'static str = "port_failure";
3228}
3229
3230impl EventPayloadKind for PreflightCheckedPayload {
3231    const KIND: &'static str = "preflight_checked";
3232}
3233
3234impl EventPayloadKind for PrintEndedPayload {
3235    const KIND: &'static str = "print_ended";
3236}
3237
3238impl EventPayloadKind for PrintStartedPayload {
3239    const KIND: &'static str = "print_started";
3240}
3241
3242impl EventPayloadKind for PrintUntrackedPayload {
3243    const KIND: &'static str = "print_untracked";
3244}
3245
3246impl EventPayloadKind for PrinterConnectionChangedPayload {
3247    const KIND: &'static str = "printer_connection_changed";
3248}
3249
3250impl EventPayloadKind for PrinterResetPayload {
3251    const KIND: &'static str = "printer_reset";
3252}
3253
3254impl EventPayloadKind for RecoveryExecutedPayload {
3255    const KIND: &'static str = "recovery_executed";
3256}
3257
3258impl EventPayloadKind for RecoveryRejectedPayload {
3259    const KIND: &'static str = "recovery_rejected";
3260}
3261
3262impl EventPayloadKind for RecoveryRequestedPayload {
3263    const KIND: &'static str = "recovery_requested";
3264}
3265
3266impl EventPayloadKind for SpoolAdjustedPayload {
3267    const KIND: &'static str = "spool_adjusted";
3268}
3269
3270impl EventPayloadKind for SpoolCommandRejectedPayload {
3271    const KIND: &'static str = "spool_command_rejected";
3272}
3273
3274impl EventPayloadKind for SpoolLoadedPayload {
3275    const KIND: &'static str = "spool_loaded";
3276}
3277
3278impl EventPayloadKind for SpoolRegisteredPayload {
3279    const KIND: &'static str = "spool_registered";
3280}
3281
3282impl EventPayloadKind for SpoolRetiredPayload {
3283    const KIND: &'static str = "spool_retired";
3284}
3285
3286impl EventPayloadKind for SpoolUnloadedPayload {
3287    const KIND: &'static str = "spool_unloaded";
3288}
3289
3290impl EventPayloadKind for SpoolUnverifiedPayload {
3291    const KIND: &'static str = "spool_unverified";
3292}
3293
3294impl EventPayloadKind for StartupReconciliationPayload {
3295    const KIND: &'static str = "startup_reconciliation";
3296}
3297
3298impl EventPayloadKind for SupervisionSessionClosedPayload {
3299    const KIND: &'static str = "supervision_session_closed";
3300}
3301
3302impl EventPayloadKind for SupervisionSessionOpenedPayload {
3303    const KIND: &'static str = "supervision_session_opened";
3304}
3305
3306impl EventPayloadKind for UnaccountedPrintPayload {
3307    const KIND: &'static str = "unaccounted_print";
3308}
3309
3310impl EventPayloadKind for UnattendedPrinterActivityPayload {
3311    const KIND: &'static str = "unattended_printer_activity";
3312}
3313
3314/// Every kind the server declares a payload type for, in name order.
3315pub const EVENT_KINDS: [&str; 39] = [
3316    "action_executed",
3317    "action_rejected",
3318    "action_requested",
3319    "agent_assessment",
3320    "camera_degraded",
3321    "camera_look",
3322    "charge_pended",
3323    "detector_health_changed",
3324    "filament_change_signalled",
3325    "filament_consumed",
3326    "file_tally_rebaselined",
3327    "intervention_expired",
3328    "malformed_external_event",
3329    "obico_failure_alert",
3330    "obico_printer_notification",
3331    "operator_acknowledgement",
3332    "periodic_observation",
3333    "port_failure",
3334    "preflight_checked",
3335    "print_ended",
3336    "print_started",
3337    "print_untracked",
3338    "printer_connection_changed",
3339    "printer_reset",
3340    "recovery_executed",
3341    "recovery_rejected",
3342    "recovery_requested",
3343    "spool_adjusted",
3344    "spool_command_rejected",
3345    "spool_loaded",
3346    "spool_registered",
3347    "spool_retired",
3348    "spool_unloaded",
3349    "spool_unverified",
3350    "startup_reconciliation",
3351    "supervision_session_closed",
3352    "supervision_session_opened",
3353    "unaccounted_print",
3354    "unattended_printer_activity",
3355];
3356
3357impl EventRecord {
3358    /// The payload as `P`, when this event is under the kind `P` declares.
3359    ///
3360    /// Answers `None` for an event of any other kind — one this client knows
3361    /// or one it does not — and the parse failure for an event under the
3362    /// right kind whose payload is not of the type.
3363    #[must_use]
3364    pub fn payload_as<P: EventPayloadKind>(&self) -> Option<Result<P, serde_json::Error>> {
3365        (self.kind == P::KIND).then(|| serde_json::from_value(self.payload.clone()))
3366    }
3367}
3368
3369impl Client {
3370    /// Call `prints` on the configured supervisor.
3371    ///
3372    /// # Errors
3373    ///
3374    /// Returns `ClientError` when the supervisor could not be reached, when
3375    /// it answered something this client cannot read.
3376    // llmlint: ignore[names_match_behavior] See suppressions.toml.
3377    pub fn prints(&self) -> Result<PrintsAnswer, ClientError> {
3378        let target = String::from("/v1/prints");
3379        let asked: Vec<(String, String)> = Vec::new();
3380        let sending: Option<serde_json::Value> = None;
3381        self.call("GET", &target, &asked, sending.as_ref())
3382    }
3383
3384    /// Call `status` on the configured supervisor.
3385    ///
3386    /// # Errors
3387    ///
3388    /// Returns `ClientError` when the supervisor could not be reached, when
3389    /// it answered something this client cannot read.
3390    pub fn status(&self, print_id: &str) -> Result<StatusAnswer, ClientError> {
3391        let target = format!("/v1/prints/{print_id}/status");
3392        let asked: Vec<(String, String)> = Vec::new();
3393        let sending: Option<serde_json::Value> = None;
3394        self.call("GET", &target, &asked, sending.as_ref())
3395    }
3396
3397    /// Call `context` on the configured supervisor.
3398    ///
3399    /// # Errors
3400    ///
3401    /// Returns `ClientError` when the supervisor could not be reached, when
3402    /// it answered something this client cannot read.
3403    pub fn context(&self, print_id: &str) -> Result<ContextAnswer, ClientError> {
3404        let target = format!("/v1/prints/{print_id}/context");
3405        let asked: Vec<(String, String)> = Vec::new();
3406        let sending: Option<serde_json::Value> = None;
3407        self.call("GET", &target, &asked, sending.as_ref())
3408    }
3409
3410    /// Call `image` on the configured supervisor.
3411    ///
3412    /// # Errors
3413    ///
3414    /// Returns `ClientError` when the supervisor could not be reached, when
3415    /// it answered something this client cannot read.
3416    pub fn image(&self, image_id: &str) -> Result<ImageAnswer, ClientError> {
3417        let target = format!("/v1/images/{image_id}");
3418        let asked: Vec<(String, String)> = Vec::new();
3419        let sending: Option<serde_json::Value> = None;
3420        self.call("GET", &target, &asked, sending.as_ref())
3421    }
3422
3423    /// Call `history` on the configured supervisor.
3424    ///
3425    /// # Errors
3426    ///
3427    /// Returns `ClientError` when the supervisor could not be reached, when
3428    /// it answered something this client cannot read.
3429    pub fn history(
3430        &self,
3431        print_id: &str,
3432        limit: Option<i64>,
3433    ) -> Result<HistoryAnswer, ClientError> {
3434        let target = format!("/v1/prints/{print_id}/history");
3435        let mut asked: Vec<(String, String)> = Vec::new();
3436        if let Some(value) = limit {
3437            asked.push(("limit".to_owned(), value.to_string()));
3438        }
3439        let sending: Option<serde_json::Value> = None;
3440        self.call("GET", &target, &asked, sending.as_ref())
3441    }
3442
3443    /// Call `events` on the configured supervisor.
3444    ///
3445    /// # Errors
3446    ///
3447    /// Returns `ClientError` when the supervisor could not be reached, when
3448    /// it answered something this client cannot read.
3449    pub fn events(
3450        &self,
3451        after: Option<String>,
3452        print_id: Option<String>,
3453        kinds: Option<Vec<String>>,
3454        exclude_kinds: Option<Vec<String>>,
3455        wait_s: Option<i64>,
3456        limit: Option<i64>,
3457    ) -> Result<EventsAnswer, ClientError> {
3458        let target = String::from("/v1/events");
3459        let mut asked: Vec<(String, String)> = Vec::new();
3460        if let Some(value) = after {
3461            asked.push(("after".to_owned(), value));
3462        }
3463        if let Some(value) = print_id {
3464            asked.push(("print_id".to_owned(), value));
3465        }
3466        for value in kinds.into_iter().flatten() {
3467            asked.push(("kinds".to_owned(), value));
3468        }
3469        for value in exclude_kinds.into_iter().flatten() {
3470            asked.push(("exclude_kinds".to_owned(), value));
3471        }
3472        if let Some(value) = wait_s {
3473            asked.push(("wait_s".to_owned(), value.to_string()));
3474        }
3475        if let Some(value) = limit {
3476            asked.push(("limit".to_owned(), value.to_string()));
3477        }
3478        let sending: Option<serde_json::Value> = None;
3479        self.call("GET", &target, &asked, sending.as_ref())
3480    }
3481
3482    /// Call `manifest_get` on the configured supervisor.
3483    ///
3484    /// # Errors
3485    ///
3486    /// Returns `ClientError` when the supervisor could not be reached, when
3487    /// it answered something this client cannot read.
3488    pub fn manifest_get(&self, print_id: &str) -> Result<ManifestAnswer, ClientError> {
3489        let target = format!("/v1/prints/{print_id}/manifest");
3490        let asked: Vec<(String, String)> = Vec::new();
3491        let sending: Option<serde_json::Value> = None;
3492        self.call("GET", &target, &asked, sending.as_ref())
3493    }
3494
3495    /// Call `look` on the configured supervisor.
3496    ///
3497    /// # Errors
3498    ///
3499    /// Returns `ClientError` when the supervisor could not be reached, when
3500    /// it answered something this client cannot read.
3501    pub fn look(&self, print_id: &str, wait_s: Option<i64>) -> Result<Look, ClientError> {
3502        let target = format!("/v1/prints/{print_id}/look");
3503        let mut asked: Vec<(String, String)> = Vec::new();
3504        if let Some(value) = wait_s {
3505            asked.push(("wait_s".to_owned(), value.to_string()));
3506        }
3507        let sending: Option<serde_json::Value> = None;
3508        self.call("GET", &target, &asked, sending.as_ref())
3509    }
3510
3511    /// Call `manifest_set` on the configured supervisor.
3512    ///
3513    /// # Errors
3514    ///
3515    /// Returns `ClientError` when the supervisor could not be reached, when
3516    /// it answered something this client cannot read, when this client
3517    /// refuses the call before making it because the reason is empty, and
3518    /// when the policy refused the action — which arrives as
3519    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3520    /// the range allowed.
3521    pub fn manifest_set(
3522        &self,
3523        print_id: &str,
3524        reason: &str,
3525        manifest: &JobManifest,
3526    ) -> Result<ManifestAnswer, ClientError> {
3527        crate::reason_given(reason)?;
3528        let target = format!("/v1/prints/{print_id}/manifest");
3529        let asked: Vec<(String, String)> = Vec::new();
3530        let mut sending = serde_json::Map::new();
3531        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3532        sending.insert("manifest".to_owned(), crate::as_value(&manifest)?);
3533        let sending = Some(serde_json::Value::Object(sending));
3534        self.call("PUT", &target, &asked, sending.as_ref())
3535    }
3536
3537    /// Call `spools` on the configured supervisor.
3538    ///
3539    /// # Errors
3540    ///
3541    /// Returns `ClientError` when the supervisor could not be reached, when
3542    /// it answered something this client cannot read.
3543    pub fn spools(&self, include_retired: Option<bool>) -> Result<SpoolsAnswer, ClientError> {
3544        let target = String::from("/v1/spools");
3545        let mut asked: Vec<(String, String)> = Vec::new();
3546        if let Some(value) = include_retired {
3547            asked.push(("include_retired".to_owned(), value.to_string()));
3548        }
3549        let sending: Option<serde_json::Value> = None;
3550        self.call("GET", &target, &asked, sending.as_ref())
3551    }
3552
3553    /// Call `spool` on the configured supervisor.
3554    ///
3555    /// # Errors
3556    ///
3557    /// Returns `ClientError` when the supervisor could not be reached, when
3558    /// it answered something this client cannot read.
3559    pub fn spool(&self, spool_id: &str) -> Result<SpoolAnswer, ClientError> {
3560        let target = format!("/v1/spools/{spool_id}");
3561        let asked: Vec<(String, String)> = Vec::new();
3562        let sending: Option<serde_json::Value> = None;
3563        self.call("GET", &target, &asked, sending.as_ref())
3564    }
3565
3566    /// Call `printer_history` on the configured supervisor.
3567    ///
3568    /// # Errors
3569    ///
3570    /// Returns `ClientError` when the supervisor could not be reached, when
3571    /// it answered something this client cannot read.
3572    pub fn printer_history(
3573        &self,
3574        kinds: Option<Vec<String>>,
3575        since: Option<String>,
3576        until: Option<String>,
3577        spool_id: Option<String>,
3578        before: Option<String>,
3579        limit: Option<i64>,
3580    ) -> Result<TimelineAnswer, ClientError> {
3581        let target = String::from("/v1/history");
3582        let mut asked: Vec<(String, String)> = Vec::new();
3583        for value in kinds.into_iter().flatten() {
3584            asked.push(("kinds".to_owned(), value));
3585        }
3586        if let Some(value) = since {
3587            asked.push(("since".to_owned(), value));
3588        }
3589        if let Some(value) = until {
3590            asked.push(("until".to_owned(), value));
3591        }
3592        if let Some(value) = spool_id {
3593            asked.push(("spool_id".to_owned(), value));
3594        }
3595        if let Some(value) = before {
3596            asked.push(("before".to_owned(), value));
3597        }
3598        if let Some(value) = limit {
3599            asked.push(("limit".to_owned(), value.to_string()));
3600        }
3601        let sending: Option<serde_json::Value> = None;
3602        self.call("GET", &target, &asked, sending.as_ref())
3603    }
3604
3605    /// Call `preflight` on the configured supervisor.
3606    ///
3607    /// # Errors
3608    ///
3609    /// Returns `ClientError` when the supervisor could not be reached, when
3610    /// it answered something this client cannot read.
3611    pub fn preflight(&self, file_name: &str) -> Result<PreflightAnswer, ClientError> {
3612        let target = String::from("/v1/preflight");
3613        let asked: Vec<(String, String)> = vec![("file_name".to_owned(), file_name.to_owned())];
3614        let sending: Option<serde_json::Value> = None;
3615        self.call("GET", &target, &asked, sending.as_ref())
3616    }
3617
3618    /// Call `pause` on the configured supervisor.
3619    ///
3620    /// # Errors
3621    ///
3622    /// Returns `ClientError` when the supervisor could not be reached, when
3623    /// it answered something this client cannot read, when this client
3624    /// refuses the call before making it because the reason is empty, and
3625    /// when the policy refused the action — which arrives as
3626    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3627    /// the range allowed.
3628    pub fn pause(&self, print_id: &str, reason: &str) -> Result<ActionAnswer, ClientError> {
3629        crate::reason_given(reason)?;
3630        let target = format!("/v1/prints/{print_id}/actions/pause");
3631        let asked: Vec<(String, String)> = Vec::new();
3632        let mut sending = serde_json::Map::new();
3633        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3634        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3635        let sending = Some(serde_json::Value::Object(sending));
3636        self.call("POST", &target, &asked, sending.as_ref())
3637    }
3638
3639    /// Call `resume` on the configured supervisor.
3640    ///
3641    /// # Errors
3642    ///
3643    /// Returns `ClientError` when the supervisor could not be reached, when
3644    /// it answered something this client cannot read, when this client
3645    /// refuses the call before making it because the reason is empty, and
3646    /// when the policy refused the action — which arrives as
3647    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3648    /// the range allowed.
3649    pub fn resume(&self, print_id: &str, reason: &str) -> Result<ActionAnswer, ClientError> {
3650        crate::reason_given(reason)?;
3651        let target = format!("/v1/prints/{print_id}/actions/resume");
3652        let asked: Vec<(String, String)> = Vec::new();
3653        let mut sending = serde_json::Map::new();
3654        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3655        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3656        let sending = Some(serde_json::Value::Object(sending));
3657        self.call("POST", &target, &asked, sending.as_ref())
3658    }
3659
3660    /// Call `cancel` on the configured supervisor.
3661    ///
3662    /// # Errors
3663    ///
3664    /// Returns `ClientError` when the supervisor could not be reached, when
3665    /// it answered something this client cannot read, when this client
3666    /// refuses the call before making it because the reason is empty, and
3667    /// when the policy refused the action — which arrives as
3668    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3669    /// the range allowed.
3670    pub fn cancel(&self, print_id: &str, reason: &str) -> Result<ActionAnswer, ClientError> {
3671        crate::reason_given(reason)?;
3672        let target = format!("/v1/prints/{print_id}/actions/cancel");
3673        let asked: Vec<(String, String)> = Vec::new();
3674        let mut sending = serde_json::Map::new();
3675        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3676        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3677        let sending = Some(serde_json::Value::Object(sending));
3678        self.call("POST", &target, &asked, sending.as_ref())
3679    }
3680
3681    /// Call `start_print` on the configured supervisor.
3682    ///
3683    /// # Errors
3684    ///
3685    /// Returns `ClientError` when the supervisor could not be reached, when
3686    /// it answered something this client cannot read, when this client
3687    /// refuses the call before making it because the reason is empty, and
3688    /// when the policy refused the action — which arrives as
3689    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3690    /// the range allowed.
3691    pub fn start_print(
3692        &self,
3693        print_id: &str,
3694        file_name: &FileName,
3695        manifest: &JobManifest,
3696        reason: &str,
3697        override_filament_check: Option<bool>,
3698    ) -> Result<ActionAnswer, ClientError> {
3699        crate::reason_given(reason)?;
3700        let target = format!("/v1/prints/{print_id}/actions/start_print");
3701        let asked: Vec<(String, String)> = Vec::new();
3702        let mut sending = serde_json::Map::new();
3703        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3704        sending.insert("file_name".to_owned(), crate::as_value(&file_name)?);
3705        sending.insert("manifest".to_owned(), crate::as_value(&manifest)?);
3706        if let Some(value) = override_filament_check {
3707            sending.insert(
3708                "override_filament_check".to_owned(),
3709                crate::as_value(&value)?,
3710            );
3711        }
3712        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3713        let sending = Some(serde_json::Value::Object(sending));
3714        self.call("POST", &target, &asked, sending.as_ref())
3715    }
3716
3717    /// Call `set_feedrate_factor` on the configured supervisor.
3718    ///
3719    /// # Errors
3720    ///
3721    /// Returns `ClientError` when the supervisor could not be reached, when
3722    /// it answered something this client cannot read, when this client
3723    /// refuses the call before making it because the reason is empty, and
3724    /// when the policy refused the action — which arrives as
3725    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3726    /// the range allowed.
3727    pub fn set_feedrate_factor(
3728        &self,
3729        print_id: &str,
3730        factor: f64,
3731        reason: &str,
3732        duration_s: Option<i64>,
3733    ) -> Result<ActionAnswer, ClientError> {
3734        crate::reason_given(reason)?;
3735        let target = format!("/v1/prints/{print_id}/actions/set_feedrate_factor");
3736        let asked: Vec<(String, String)> = Vec::new();
3737        let mut sending = serde_json::Map::new();
3738        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3739        if let Some(value) = duration_s {
3740            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
3741        }
3742        sending.insert("factor".to_owned(), crate::as_value(&factor)?);
3743        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3744        let sending = Some(serde_json::Value::Object(sending));
3745        self.call("POST", &target, &asked, sending.as_ref())
3746    }
3747
3748    /// Call `set_flowrate_factor` on the configured supervisor.
3749    ///
3750    /// # Errors
3751    ///
3752    /// Returns `ClientError` when the supervisor could not be reached, when
3753    /// it answered something this client cannot read, when this client
3754    /// refuses the call before making it because the reason is empty, and
3755    /// when the policy refused the action — which arrives as
3756    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3757    /// the range allowed.
3758    pub fn set_flowrate_factor(
3759        &self,
3760        print_id: &str,
3761        factor: f64,
3762        reason: &str,
3763        duration_s: Option<i64>,
3764    ) -> Result<ActionAnswer, ClientError> {
3765        crate::reason_given(reason)?;
3766        let target = format!("/v1/prints/{print_id}/actions/set_flowrate_factor");
3767        let asked: Vec<(String, String)> = Vec::new();
3768        let mut sending = serde_json::Map::new();
3769        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3770        if let Some(value) = duration_s {
3771            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
3772        }
3773        sending.insert("factor".to_owned(), crate::as_value(&factor)?);
3774        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3775        let sending = Some(serde_json::Value::Object(sending));
3776        self.call("POST", &target, &asked, sending.as_ref())
3777    }
3778
3779    /// Call `set_tool_target_c` on the configured supervisor.
3780    ///
3781    /// # Errors
3782    ///
3783    /// Returns `ClientError` when the supervisor could not be reached, when
3784    /// it answered something this client cannot read, when this client
3785    /// refuses the call before making it because the reason is empty, and
3786    /// when the policy refused the action — which arrives as
3787    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3788    /// the range allowed.
3789    pub fn set_tool_target_c(
3790        &self,
3791        print_id: &str,
3792        reason: &str,
3793        target_c: f64,
3794        tool: i64,
3795        duration_s: Option<i64>,
3796    ) -> Result<ActionAnswer, ClientError> {
3797        crate::reason_given(reason)?;
3798        let target = format!("/v1/prints/{print_id}/actions/set_tool_target_c");
3799        let asked: Vec<(String, String)> = Vec::new();
3800        let mut sending = serde_json::Map::new();
3801        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3802        if let Some(value) = duration_s {
3803            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
3804        }
3805        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3806        sending.insert("target_c".to_owned(), crate::as_value(&target_c)?);
3807        sending.insert("tool".to_owned(), crate::as_value(&tool)?);
3808        let sending = Some(serde_json::Value::Object(sending));
3809        self.call("POST", &target, &asked, sending.as_ref())
3810    }
3811
3812    /// Call `set_bed_target_c` on the configured supervisor.
3813    ///
3814    /// # Errors
3815    ///
3816    /// Returns `ClientError` when the supervisor could not be reached, when
3817    /// it answered something this client cannot read, when this client
3818    /// refuses the call before making it because the reason is empty, and
3819    /// when the policy refused the action — which arrives as
3820    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3821    /// the range allowed.
3822    pub fn set_bed_target_c(
3823        &self,
3824        print_id: &str,
3825        reason: &str,
3826        target_c: f64,
3827        duration_s: Option<i64>,
3828    ) -> Result<ActionAnswer, ClientError> {
3829        crate::reason_given(reason)?;
3830        let target = format!("/v1/prints/{print_id}/actions/set_bed_target_c");
3831        let asked: Vec<(String, String)> = Vec::new();
3832        let mut sending = serde_json::Map::new();
3833        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3834        if let Some(value) = duration_s {
3835            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
3836        }
3837        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3838        sending.insert("target_c".to_owned(), crate::as_value(&target_c)?);
3839        let sending = Some(serde_json::Value::Object(sending));
3840        self.call("POST", &target, &asked, sending.as_ref())
3841    }
3842
3843    /// Call `set_fan_percent` on the configured supervisor.
3844    ///
3845    /// # Errors
3846    ///
3847    /// Returns `ClientError` when the supervisor could not be reached, when
3848    /// it answered something this client cannot read, when this client
3849    /// refuses the call before making it because the reason is empty, and
3850    /// when the policy refused the action — which arrives as
3851    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3852    /// the range allowed.
3853    pub fn set_fan_percent(
3854        &self,
3855        print_id: &str,
3856        percent: f64,
3857        reason: &str,
3858        duration_s: Option<i64>,
3859    ) -> Result<ActionAnswer, ClientError> {
3860        crate::reason_given(reason)?;
3861        let target = format!("/v1/prints/{print_id}/actions/set_fan_percent");
3862        let asked: Vec<(String, String)> = Vec::new();
3863        let mut sending = serde_json::Map::new();
3864        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3865        if let Some(value) = duration_s {
3866            sending.insert("duration_s".to_owned(), crate::as_value(&value)?);
3867        }
3868        sending.insert("percent".to_owned(), crate::as_value(&percent)?);
3869        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3870        let sending = Some(serde_json::Value::Object(sending));
3871        self.call("POST", &target, &asked, sending.as_ref())
3872    }
3873
3874    /// Call `acknowledge_failure` on the configured supervisor.
3875    ///
3876    /// # Errors
3877    ///
3878    /// Returns `ClientError` when the supervisor could not be reached, when
3879    /// it answered something this client cannot read, when this client
3880    /// refuses the call before making it because the reason is empty, and
3881    /// when the policy refused the action — which arrives as
3882    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3883    /// the range allowed.
3884    pub fn acknowledge_failure(
3885        &self,
3886        print_id: &str,
3887        disposition: &AcknowledgementDisposition,
3888        event_id: &EventId,
3889        reason: &str,
3890    ) -> Result<ActionAnswer, ClientError> {
3891        crate::reason_given(reason)?;
3892        let target = format!("/v1/prints/{print_id}/actions/acknowledge_failure");
3893        let asked: Vec<(String, String)> = Vec::new();
3894        let mut sending = serde_json::Map::new();
3895        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3896        sending.insert("disposition".to_owned(), crate::as_value(&disposition)?);
3897        sending.insert("event_id".to_owned(), crate::as_value(&event_id)?);
3898        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3899        let sending = Some(serde_json::Value::Object(sending));
3900        self.call("POST", &target, &asked, sending.as_ref())
3901    }
3902
3903    /// Call `close_print` on the configured supervisor.
3904    ///
3905    /// # Errors
3906    ///
3907    /// Returns `ClientError` when the supervisor could not be reached, when
3908    /// it answered something this client cannot read, when this client
3909    /// refuses the call before making it because the reason is empty, and
3910    /// when the policy refused the action — which arrives as
3911    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3912    /// the range allowed.
3913    pub fn close_print(
3914        &self,
3915        print_id: &str,
3916        outcome: &PrintOutcome,
3917        reason: &str,
3918    ) -> Result<ActionAnswer, ClientError> {
3919        crate::reason_given(reason)?;
3920        let target = format!("/v1/prints/{print_id}/actions/close_print");
3921        let asked: Vec<(String, String)> = Vec::new();
3922        let mut sending = serde_json::Map::new();
3923        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3924        sending.insert("outcome".to_owned(), crate::as_value(&outcome)?);
3925        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3926        let sending = Some(serde_json::Value::Object(sending));
3927        self.call("POST", &target, &asked, sending.as_ref())
3928    }
3929
3930    /// Call `register_spool` on the configured supervisor.
3931    ///
3932    /// # Errors
3933    ///
3934    /// Returns `ClientError` when the supervisor could not be reached, when
3935    /// it answered something this client cannot read, when this client
3936    /// refuses the call before making it because the reason is empty, and
3937    /// when the policy refused the action — which arrives as
3938    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3939    /// the range allowed.
3940    // One argument per value the operation declares, as every client takes them; suppressions.toml has the reason.
3941    #[expect(clippy::too_many_arguments)]
3942    pub fn register_spool(
3943        &self,
3944        initial_g: f64,
3945        material: &str,
3946        name: &str,
3947        reason: &str,
3948        color: Option<String>,
3949        density_g_cm3: Option<f64>,
3950        diameter_mm: Option<f64>,
3951        remaining_g: Option<f64>,
3952        tare_g: Option<f64>,
3953    ) -> Result<SpoolCommandAnswer, ClientError> {
3954        crate::reason_given(reason)?;
3955        let target = String::from("/v1/spools/actions/register_spool");
3956        let asked: Vec<(String, String)> = Vec::new();
3957        let mut sending = serde_json::Map::new();
3958        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
3959        if let Some(value) = color {
3960            sending.insert("color".to_owned(), crate::as_value(&value)?);
3961        }
3962        if let Some(value) = density_g_cm3 {
3963            sending.insert("density_g_cm3".to_owned(), crate::as_value(&value)?);
3964        }
3965        if let Some(value) = diameter_mm {
3966            sending.insert("diameter_mm".to_owned(), crate::as_value(&value)?);
3967        }
3968        sending.insert("initial_g".to_owned(), crate::as_value(&initial_g)?);
3969        sending.insert("material".to_owned(), crate::as_value(&material)?);
3970        sending.insert("name".to_owned(), crate::as_value(&name)?);
3971        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
3972        if let Some(value) = remaining_g {
3973            sending.insert("remaining_g".to_owned(), crate::as_value(&value)?);
3974        }
3975        if let Some(value) = tare_g {
3976            sending.insert("tare_g".to_owned(), crate::as_value(&value)?);
3977        }
3978        let sending = Some(serde_json::Value::Object(sending));
3979        self.call("POST", &target, &asked, sending.as_ref())
3980    }
3981
3982    /// Call `load_spool` on the configured supervisor.
3983    ///
3984    /// # Errors
3985    ///
3986    /// Returns `ClientError` when the supervisor could not be reached, when
3987    /// it answered something this client cannot read, when this client
3988    /// refuses the call before making it because the reason is empty, and
3989    /// when the policy refused the action — which arrives as
3990    /// `ClientError::Rejected`, carrying the reason, the value asked for and
3991    /// the range allowed.
3992    pub fn load_spool(
3993        &self,
3994        spool_id: &str,
3995        reason: &str,
3996        tool: i64,
3997    ) -> Result<SpoolCommandAnswer, ClientError> {
3998        crate::reason_given(reason)?;
3999        let target = format!("/v1/spools/{spool_id}/actions/load_spool");
4000        let asked: Vec<(String, String)> = Vec::new();
4001        let mut sending = serde_json::Map::new();
4002        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
4003        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
4004        sending.insert("tool".to_owned(), crate::as_value(&tool)?);
4005        let sending = Some(serde_json::Value::Object(sending));
4006        self.call("POST", &target, &asked, sending.as_ref())
4007    }
4008
4009    /// Call `unload_spool` on the configured supervisor.
4010    ///
4011    /// # Errors
4012    ///
4013    /// Returns `ClientError` when the supervisor could not be reached, when
4014    /// it answered something this client cannot read, when this client
4015    /// refuses the call before making it because the reason is empty, and
4016    /// when the policy refused the action — which arrives as
4017    /// `ClientError::Rejected`, carrying the reason, the value asked for and
4018    /// the range allowed.
4019    pub fn unload_spool(
4020        &self,
4021        spool_id: &str,
4022        reason: &str,
4023    ) -> Result<SpoolCommandAnswer, ClientError> {
4024        crate::reason_given(reason)?;
4025        let target = format!("/v1/spools/{spool_id}/actions/unload_spool");
4026        let asked: Vec<(String, String)> = Vec::new();
4027        let mut sending = serde_json::Map::new();
4028        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
4029        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
4030        let sending = Some(serde_json::Value::Object(sending));
4031        self.call("POST", &target, &asked, sending.as_ref())
4032    }
4033
4034    /// Call `adjust_spool` on the configured supervisor.
4035    ///
4036    /// # Errors
4037    ///
4038    /// Returns `ClientError` when the supervisor could not be reached, when
4039    /// it answered something this client cannot read, when this client
4040    /// refuses the call before making it because the reason is empty, and
4041    /// when the policy refused the action — which arrives as
4042    /// `ClientError::Rejected`, carrying the reason, the value asked for and
4043    /// the range allowed.
4044    pub fn adjust_spool(
4045        &self,
4046        spool_id: &str,
4047        reason: &str,
4048        gross_g: Option<f64>,
4049        remaining_g: Option<f64>,
4050    ) -> Result<SpoolCommandAnswer, ClientError> {
4051        crate::reason_given(reason)?;
4052        let target = format!("/v1/spools/{spool_id}/actions/adjust_spool");
4053        let asked: Vec<(String, String)> = Vec::new();
4054        let mut sending = serde_json::Map::new();
4055        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
4056        if let Some(value) = gross_g {
4057            sending.insert("gross_g".to_owned(), crate::as_value(&value)?);
4058        }
4059        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
4060        if let Some(value) = remaining_g {
4061            sending.insert("remaining_g".to_owned(), crate::as_value(&value)?);
4062        }
4063        let sending = Some(serde_json::Value::Object(sending));
4064        self.call("POST", &target, &asked, sending.as_ref())
4065    }
4066
4067    /// Call `retire_spool` on the configured supervisor.
4068    ///
4069    /// # Errors
4070    ///
4071    /// Returns `ClientError` when the supervisor could not be reached, when
4072    /// it answered something this client cannot read, when this client
4073    /// refuses the call before making it because the reason is empty, and
4074    /// when the policy refused the action — which arrives as
4075    /// `ClientError::Rejected`, carrying the reason, the value asked for and
4076    /// the range allowed.
4077    pub fn retire_spool(
4078        &self,
4079        spool_id: &str,
4080        reason: &str,
4081    ) -> Result<SpoolCommandAnswer, ClientError> {
4082        crate::reason_given(reason)?;
4083        let target = format!("/v1/spools/{spool_id}/actions/retire_spool");
4084        let asked: Vec<(String, String)> = Vec::new();
4085        let mut sending = serde_json::Map::new();
4086        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
4087        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
4088        let sending = Some(serde_json::Value::Object(sending));
4089        self.call("POST", &target, &asked, sending.as_ref())
4090    }
4091
4092    /// Call `recover_status` on the configured supervisor.
4093    ///
4094    /// # Errors
4095    ///
4096    /// Returns `ClientError` when the supervisor could not be reached, when
4097    /// it answered something this client cannot read.
4098    pub fn recover_status(&self) -> Result<RecoveryStatus, ClientError> {
4099        let target = String::from("/v1/printer/recovery");
4100        let asked: Vec<(String, String)> = Vec::new();
4101        let sending: Option<serde_json::Value> = None;
4102        self.call("GET", &target, &asked, sending.as_ref())
4103    }
4104
4105    /// Call `recover_lift` on the configured supervisor.
4106    ///
4107    /// # Errors
4108    ///
4109    /// Returns `ClientError` when the supervisor could not be reached, when
4110    /// it answered something this client cannot read, when this client
4111    /// refuses the call before making it because the reason is empty, and
4112    /// when the policy refused the action — which arrives as
4113    /// `ClientError::Rejected`, carrying the reason, the value asked for and
4114    /// the range allowed.
4115    pub fn recover_lift(&self, mm: f64, reason: &str) -> Result<RecoveryAnswer, ClientError> {
4116        crate::reason_given(reason)?;
4117        let target = String::from("/v1/printer/recovery/recover_lift");
4118        let asked: Vec<(String, String)> = Vec::new();
4119        let mut sending = serde_json::Map::new();
4120        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
4121        sending.insert("mm".to_owned(), crate::as_value(&mm)?);
4122        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
4123        let sending = Some(serde_json::Value::Object(sending));
4124        self.call("POST", &target, &asked, sending.as_ref())
4125    }
4126
4127    /// Call `recover_cool` on the configured supervisor.
4128    ///
4129    /// # Errors
4130    ///
4131    /// Returns `ClientError` when the supervisor could not be reached, when
4132    /// it answered something this client cannot read, when this client
4133    /// refuses the call before making it because the reason is empty, and
4134    /// when the policy refused the action — which arrives as
4135    /// `ClientError::Rejected`, carrying the reason, the value asked for and
4136    /// the range allowed.
4137    pub fn recover_cool(&self, reason: &str) -> Result<RecoveryAnswer, ClientError> {
4138        crate::reason_given(reason)?;
4139        let target = String::from("/v1/printer/recovery/recover_cool");
4140        let asked: Vec<(String, String)> = Vec::new();
4141        let mut sending = serde_json::Map::new();
4142        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
4143        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
4144        let sending = Some(serde_json::Value::Object(sending));
4145        self.call("POST", &target, &asked, sending.as_ref())
4146    }
4147
4148    /// Call `recover_fan` on the configured supervisor.
4149    ///
4150    /// # Errors
4151    ///
4152    /// Returns `ClientError` when the supervisor could not be reached, when
4153    /// it answered something this client cannot read, when this client
4154    /// refuses the call before making it because the reason is empty, and
4155    /// when the policy refused the action — which arrives as
4156    /// `ClientError::Rejected`, carrying the reason, the value asked for and
4157    /// the range allowed.
4158    pub fn recover_fan(&self, percent: f64, reason: &str) -> Result<RecoveryAnswer, ClientError> {
4159        crate::reason_given(reason)?;
4160        let target = String::from("/v1/printer/recovery/recover_fan");
4161        let asked: Vec<(String, String)> = Vec::new();
4162        let mut sending = serde_json::Map::new();
4163        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
4164        sending.insert("percent".to_owned(), crate::as_value(&percent)?);
4165        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
4166        let sending = Some(serde_json::Value::Object(sending));
4167        self.call("POST", &target, &asked, sending.as_ref())
4168    }
4169
4170    /// Call `recover_heat` on the configured supervisor.
4171    ///
4172    /// # Errors
4173    ///
4174    /// Returns `ClientError` when the supervisor could not be reached, when
4175    /// it answered something this client cannot read, when this client
4176    /// refuses the call before making it because the reason is empty, and
4177    /// when the policy refused the action — which arrives as
4178    /// `ClientError::Rejected`, carrying the reason, the value asked for and
4179    /// the range allowed.
4180    pub fn recover_heat(
4181        &self,
4182        reason: &str,
4183        target_c: f64,
4184        tool: i64,
4185        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
4186        clearance_asserted: Option<bool>,
4187    ) -> Result<RecoveryAnswer, ClientError> {
4188        crate::reason_given(reason)?;
4189        let target = String::from("/v1/printer/recovery/recover_heat");
4190        let asked: Vec<(String, String)> = Vec::new();
4191        let mut sending = serde_json::Map::new();
4192        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
4193        if let Some(value) = clearance_asserted {
4194            sending.insert("clearance_asserted".to_owned(), crate::as_value(&value)?);
4195        }
4196        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
4197        sending.insert("target_c".to_owned(), crate::as_value(&target_c)?);
4198        sending.insert("tool".to_owned(), crate::as_value(&tool)?);
4199        let sending = Some(serde_json::Value::Object(sending));
4200        self.call("POST", &target, &asked, sending.as_ref())
4201    }
4202
4203    /// Call `recover_park` on the configured supervisor.
4204    ///
4205    /// # Errors
4206    ///
4207    /// Returns `ClientError` when the supervisor could not be reached, when
4208    /// it answered something this client cannot read, when this client
4209    /// refuses the call before making it because the reason is empty, and
4210    /// when the policy refused the action — which arrives as
4211    /// `ClientError::Rejected`, carrying the reason, the value asked for and
4212    /// the range allowed.
4213    pub fn recover_park(
4214        &self,
4215        reason: &str,
4216        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
4217        clearance_asserted: Option<bool>,
4218    ) -> Result<RecoveryAnswer, ClientError> {
4219        crate::reason_given(reason)?;
4220        let target = String::from("/v1/printer/recovery/recover_park");
4221        let asked: Vec<(String, String)> = Vec::new();
4222        let mut sending = serde_json::Map::new();
4223        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
4224        if let Some(value) = clearance_asserted {
4225            sending.insert("clearance_asserted".to_owned(), crate::as_value(&value)?);
4226        }
4227        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
4228        let sending = Some(serde_json::Value::Object(sending));
4229        self.call("POST", &target, &asked, sending.as_ref())
4230    }
4231
4232    /// Call `recover_home_xy` on the configured supervisor.
4233    ///
4234    /// # Errors
4235    ///
4236    /// Returns `ClientError` when the supervisor could not be reached, when
4237    /// it answered something this client cannot read, when this client
4238    /// refuses the call before making it because the reason is empty, and
4239    /// when the policy refused the action — which arrives as
4240    /// `ClientError::Rejected`, carrying the reason, the value asked for and
4241    /// the range allowed.
4242    pub fn recover_home_xy(
4243        &self,
4244        reason: &str,
4245        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
4246        clearance_asserted: Option<bool>,
4247    ) -> Result<RecoveryAnswer, ClientError> {
4248        crate::reason_given(reason)?;
4249        let target = String::from("/v1/printer/recovery/recover_home_xy");
4250        let asked: Vec<(String, String)> = Vec::new();
4251        let mut sending = serde_json::Map::new();
4252        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
4253        if let Some(value) = clearance_asserted {
4254            sending.insert("clearance_asserted".to_owned(), crate::as_value(&value)?);
4255        }
4256        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
4257        let sending = Some(serde_json::Value::Object(sending));
4258        self.call("POST", &target, &asked, sending.as_ref())
4259    }
4260
4261    /// Call `recover_home_z` on the configured supervisor.
4262    ///
4263    /// # Errors
4264    ///
4265    /// Returns `ClientError` when the supervisor could not be reached, when
4266    /// it answered something this client cannot read, when this client
4267    /// refuses the call before making it because the reason is empty, and
4268    /// when the policy refused the action — which arrives as
4269    /// `ClientError::Rejected`, carrying the reason, the value asked for and
4270    /// the range allowed.
4271    pub fn recover_home_z(
4272        &self,
4273        reason: &str,
4274        // llmlint: ignore[invalid_states_unrepresentable] See suppressions.toml.
4275        bed_clear_asserted: Option<bool>,
4276    ) -> Result<RecoveryAnswer, ClientError> {
4277        crate::reason_given(reason)?;
4278        let target = String::from("/v1/printer/recovery/recover_home_z");
4279        let asked: Vec<(String, String)> = Vec::new();
4280        let mut sending = serde_json::Map::new();
4281        sending.insert("actor".to_owned(), crate::as_value(&self.actor())?);
4282        if let Some(value) = bed_clear_asserted {
4283            sending.insert("bed_clear_asserted".to_owned(), crate::as_value(&value)?);
4284        }
4285        sending.insert("reason".to_owned(), crate::as_value(&reason)?);
4286        let sending = Some(serde_json::Value::Object(sending));
4287        self.call("POST", &target, &asked, sending.as_ref())
4288    }
4289}