//! Wire types shared between `hive-c0re` and the in-container harness. use serde::{Deserialize, Serialize}; pub mod assets; // ----------------------------------------------------------------------------- // Host admin socket — /run/hyperhive/host.sock // ----------------------------------------------------------------------------- /// Requests on the host admin socket. /// /// Wire format: one JSON object per line. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "cmd", rename_all = "snake_case")] pub enum HostRequest { /// Create and start a sub-agent container directly, bypassing the /// approval queue. Privileged-context only. See /// `docs/approvals.md::Approval kinds (wire shapes)`. Spawn { name: String }, /// Submit a spawn request for the operator to approve. See /// `docs/approvals.md::Approval kinds (wire shapes)` (`Spawn`). RequestSpawn { name: String }, /// Stop a managed container (graceful). Kill { name: String }, /// Tear down a sub-agent container, optionally purging state. /// See `docs/approvals.md::Destroy semantics`. Destroy { name: String, #[serde(default)] purge: bool, }, /// Apply pending config to a managed container. Rebuild { name: String }, /// List managed containers. List, /// List pending approval requests. Pending, /// Approve a pending request by id; the action runs immediately. Approve { id: i64 }, /// Deny a pending request by id. Deny { id: i64 }, /// Move an agent in the topology tree. `new_parent = None` /// promotes the agent to root, `Some(name)` sets a new parent. /// Validation rules + bind-mount caveat documented in /// `docs/agent-hierarchy.md::Current state`. SetParent { child: String, new_parent: Option, }, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HostResponse { pub ok: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub agents: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub approvals: Option>, } /// One row in the approval queue. `commit_ref` is overloaded per /// `kind` — see `docs/approvals.md::Approval kinds (wire shapes)` /// for the encoding table and lifecycle. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Approval { pub id: i64, pub agent: String, #[serde(default)] pub kind: ApprovalKind, /// Kind-specific payload (git sha / inputs array / schedule /// payload / empty). See the Approval struct doc. pub commit_ref: String, /// `ApplyCommit` only: the canonical hive-c0re-vouched sha after /// the proposal fetch, tagged `proposal/`. Stable for the /// approval's lifetime — manager amends in proposed don't change /// what gets built. #[serde(default, skip_serializing_if = "Option::is_none")] pub fetched_sha: Option, pub requested_at: i64, pub status: ApprovalStatus, #[serde(default, skip_serializing_if = "Option::is_none")] pub resolved_at: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub note: Option, /// Free-text description the manager attached at submission time; /// shown on the dashboard approval card. #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, } /// What action the approval, when granted, will trigger. /// Variant-specific payload encoding + flow lives in /// `docs/approvals.md::Approval kinds (wire shapes)`. #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ApprovalKind { /// Apply a manager-proposed config commit. #[default] ApplyCommit, /// Create + start a new sub-agent container with the given name /// (under the default `agent.nix` template). Spawn, /// Seed an agent's proposed config repo for manager customisation /// (step 1 of the two-step spawn flow). InitConfig, /// Run `nix flake update [inputs...]` on the meta flake and commit /// the resulting lock changes. UpdateMetaInputs, /// Add a scheduled prompt to the broker queue. SchedulePrompt, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ApprovalStatus { Pending, Approved, Denied, Failed, /// Manager withdrew the request before the operator acted on it. /// Distinct from `Denied` (operator decision) and `Failed` /// (post-approval lifecycle error). See /// `docs/approvals.md::Withdrawing a pending approval`. Cancelled, } /// Reminder activity statistics for an agent over a time window. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ReminderStats { /// Total reminders scheduled in the window (`created_at` >= cutoff). pub scheduled: u64, /// Reminders that have been delivered in the window (`sent_at` IS NOT NULL). pub delivered: u64, /// Reminders still pending in the window (`sent_at` IS NULL). pub pending: u64, } impl HostResponse { #[must_use] pub fn success() -> Self { Self { ok: true, error: None, agents: None, approvals: None, } } #[must_use] pub fn error(message: impl Into) -> Self { Self { ok: false, error: Some(message.into()), agents: None, approvals: None, } } #[must_use] pub fn list(agents: Vec) -> Self { Self { ok: true, error: None, agents: Some(agents), approvals: None, } } #[must_use] pub fn pending(approvals: Vec) -> Self { Self { ok: true, error: None, agents: None, approvals: Some(approvals), } } } // ----------------------------------------------------------------------------- // Per-agent socket — /run/hyperhive/agents//mcp.sock on the host, // bind-mounted into the container at /run/hive/mcp.sock. // ----------------------------------------------------------------------------- /// A logical message between agents. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message { pub from: String, pub to: String, pub body: String, /// Optional broker row-id of the message this is a reply to. /// Stored in the DB and echoed back on `Recv` so the dashboard can /// render conversation threads. `None` for messages that start a /// new thread. Ignored if the referenced id is unknown or out of /// retention — purely advisory. pub in_reply_to: Option, } /// One row of a broker inbox query — what the dashboard renders in /// its operator-inbox section and what a per-agent web UI returns /// from a `Recent` request. Lives in `hive_sh4re` so it can travel /// over both the dashboard's `/api/state` and the agent socket /// without an internal-to-wire conversion. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InboxRow { pub id: i64, pub from: String, pub body: String, pub at: i64, /// Row-id of the message this is a reply to, if any. #[serde(skip_serializing_if = "Option::is_none")] pub in_reply_to: Option, } /// One delivered message in a `Recv` response. /// See `docs/conventions.md::Broker delivery + ack cycle` for the /// full delivery/ack/requeue story. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DeliveredMessage { pub from: String, pub body: String, /// Broker row id, tracked by the harness for `AckTurn`. Opaque to /// claude. `default` for wire backwards-compat. #[serde(default)] pub id: i64, /// `true` if this row was resurfaced by `RequeueInflight` (previously /// popped, never acked). Formatter prepends a "may already be handled" /// hint when set. #[serde(default)] pub redelivered: bool, /// Row-id of the message this is a reply to, if any. #[serde(skip_serializing_if = "Option::is_none")] pub in_reply_to: Option, } /// Reminder timing: either relative (wait N seconds) or absolute (at unix /// timestamp). #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "timing_type", rename_all = "snake_case")] pub enum ReminderTiming { /// Remind after this many seconds from now. InSeconds { seconds: u64 }, /// Remind at this unix timestamp (seconds since epoch). At { unix_timestamp: i64 }, } /// One row in the response to `GetLooseEnds`. Tagged enum so new /// thread kinds can land without breaking existing handlers. /// Per-flavour scoping + per-variant fields + clock-anomaly /// saturation behaviour live in /// `docs/conventions.md::Loose-ends wire shape`. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum LooseEnd { /// A pending approval row. Approval { id: i64, agent: String, commit_ref: String, #[serde(default, skip_serializing_if = "Option::is_none")] description: Option, age_seconds: u64, }, /// An unanswered question row. Question { id: i64, asker: String, #[serde(default, skip_serializing_if = "Option::is_none")] target: Option, question: String, age_seconds: u64, }, /// A scheduled but un-delivered reminder row. Reminder { id: i64, owner: String, message: String, due_at: i64, age_seconds: u64, }, } /// Kind discriminator for `CancelLooseEnd`. Per-kind store + /// authorisation rules live in /// `docs/conventions.md::Loose-ends wire shape`. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum CancelLooseEndKind { Question, Reminder, /// Withdraw a pending approval (manager surface only). Approval, } /// Unified request enum for both agent and manager sockets. The agent's /// identity is the socket it arrived on. Privileged variants are marked /// `*(privileged)*` — an agent socket returns `Err` for them server-side. /// /// `AgentRequest` and `ManagerRequest` are type aliases for this enum; /// existing callers continue to compile unchanged. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "cmd", rename_all = "snake_case")] pub enum Request { /// Send a message to another agent. Send { to: String, body: String, /// Optional id of the message being replied to. Stored in the /// broker DB and returned on `Recv` so the dashboard can render /// threads. Ignored if the id is unknown or out of retention. #[serde(default, skip_serializing_if = "Option::is_none")] in_reply_to: Option, }, /// Pop pending messages from this agent's inbox. /// Delivery + ack cycle: see /// `docs/conventions.md::Broker delivery + ack cycle`. Recv { #[serde(default)] wait_seconds: Option, #[serde(default)] max: Option, }, /// Non-mutating: how many pending messages are addressed to me? /// Used by the harness to render a status line after each tool call. Status, /// Operator-injected message TO this agent (from this agent's own web /// UI). Recipient is implicit — `from` is `"operator"`. Effectively the /// per-agent equivalent of the old dashboard T4LK form, but scoped to /// the agent whose page the operator is on. OperatorMsg { body: String }, /// Wake-up event injected from inside the container. Recipient is /// implicit (this agent); `from` is caller-chosen. See /// `docs/conventions.md::Wake injection` for the trust model and /// typical callers. Wake { from: String, body: String }, /// Last `limit` messages addressed to this agent, newest-first. /// Non-mutating — pulls from the broker without delivering. The /// per-agent web UI uses this to render its own inbox section. Recent { limit: u64 }, /// Surface a question to either the operator or another agent. /// Routing + shape: see /// `docs/conventions.md::Question routing (Ask / Answer)`. Ask { question: String, #[serde(default)] options: Vec, #[serde(default)] multi: bool, #[serde(default)] ttl_seconds: Option, #[serde(default)] to: Option, }, /// Answer a question previously routed to this agent via /// `HelperEvent::QuestionAsked`. Authorised callers + threading /// back via `HelperEvent::QuestionAnswered`: see /// `docs/conventions.md::Question routing (Ask / Answer)`. Answer { id: i64, answer: String }, /// Schedule a reminder message to be delivered to this agent at a /// future time. The reminder lands in the agent's inbox as an auto-sent /// message from `"reminder"`. Use for agent follow-ups (e.g. check task /// status, retry failed operation). Message length is limited; pass /// `file_path` to store in a file and get a path-reference message /// instead. Remind { message: String, timing: ReminderTiming, #[serde(default)] file_path: Option, }, /// Loose-ends view. On the agent socket, scoped to the calling agent /// (the `agent` field is ignored — agents can only see their own /// loose ends). On the manager socket, `agent = None` scopes to the /// manager itself, `Some("*")` is hive-wide, `Some("")` is /// that agent's loose ends. See /// `docs/conventions.md::Loose-ends wire shape`. GetLooseEnds { #[serde(default, skip_serializing_if = "Option::is_none")] agent: Option, }, /// Count of pending (un-delivered) reminders. On the agent socket /// always scoped to the calling agent. On the manager socket, /// `agent = None` means self, `Some("")` means that agent. /// Used by the harness's per-turn stats sink. CountPendingReminders { #[serde(default, skip_serializing_if = "Option::is_none")] agent: Option, }, /// Reminder statistics: counts of scheduled, delivered, and pending /// reminders over a time window. `since_secs` filters to reminders /// created in the last N seconds (0 = all). On the manager socket /// `agent = None` means self, `Some("")` means that agent. ReminderRollup { /// Only count reminders created in the last N seconds from now. /// Pass 0 to include all reminders. #[serde(default)] since_secs: u64, /// Whose reminders to roll up. `None` = the caller's own. /// Manager socket only: `Some("")` = that agent's. #[serde(default, skip_serializing_if = "Option::is_none")] agent: Option, }, /// Set a free-text status string visible on the dashboard. Persisted /// to `{state_dir}/hyperhive-status` so it survives harness restarts. /// Pass an empty string to clear the status. SetStatus { text: String }, /// Fetch identity + status for an agent. `name = None` = /// self-introspection; `Some()` = target query. See /// `docs/conventions.md::Agent metadata`. GetAgentMeta { #[serde(default, skip_serializing_if = "Option::is_none")] name: Option, }, /// Cancel an open thread the agent owns. Authorisation + /// per-kind semantics in /// `docs/conventions.md::Loose-ends wire shape`. CancelLooseEnd { kind: CancelLooseEndKind, id: i64 }, /// Mark every message popped since the last `AckTurn` as handled. /// Harness↔broker pairing fired after `TurnOutcome::Ok`. See /// `docs/conventions.md::Broker delivery + ack cycle`. AckTurn, /// Requeue every popped-but-unacked message back into the inbox. /// Harness fires this once at boot to recover from /// crashed-mid-turn sessions. See /// `docs/conventions.md::Broker delivery + ack cycle`. RequeueInflight, // ---- privileged (manager socket only for now) --------------------------- /// *(privileged)* Initialise a brand-new agent's proposed config repo /// and queue an approval for the operator to review. RequestInitConfig { name: String, #[serde(default, skip_serializing_if = "Option::is_none")] description: Option, }, /// *(privileged)* Stop a sub-agent (graceful). Kill { name: String }, /// *(privileged)* Start a previously-stopped sub-agent container. Start { name: String }, /// *(privileged)* Restart a sub-agent container (stop + start). Restart { name: String }, /// *(privileged)* Rebuild a sub-agent against the current hyperhive /// flake + agent.nix. No approval required. Update { name: String }, /// *(privileged)* Submit a config commit for the operator to approve. RequestApplyCommit { agent: String, commit_ref: String, #[serde(default, skip_serializing_if = "Option::is_none")] description: Option, }, /// *(privileged)* Fetch recent journal lines for a sub-agent container. GetLogs { agent: String, #[serde(default)] lines: Option, }, /// *(privileged)* Queue an approval to run `nix flake update [inputs...]`. RequestUpdateMetaInputs { #[serde(default)] inputs: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] description: Option, }, /// *(privileged)* Queue an approval to add a scheduled prompt. RequestSchedulePrompt(SchedulePromptPayload), /// *(privileged)* Cancel a scheduled prompt. CancelSchedule { id: i64, #[serde(default, skip_serializing_if = "Option::is_none")] targets: Option>, }, /// *(privileged)* List every schedule in the queue. ListSchedules, /// *(privileged)* Fire a scheduled prompt out of band immediately. FireScheduleNow { id: i64 }, /// *(privileged)* Edit an existing schedule's mutable fields. EditSchedule { id: i64, #[serde(default, skip_serializing_if = "Option::is_none")] body: Option, #[serde(default, skip_serializing_if = "Option::is_none")] description: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] interval_seconds: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] next_fire_at_unix: Option, #[serde(default, skip_serializing_if = "Option::is_none")] targets_add: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] targets_remove: Option>, }, } /// Backwards-compatible aliases. Both sockets now speak the unified `Request` /// / `Response` wire; the server-side privilege gate rejects privileged /// variants on agent sockets with `Err { message: "privileged variant..." }`. pub type AgentRequest = Request; pub type ManagerRequest = Request; /// Unified response enum for both agent and manager sockets. Privileged /// variants (`Logs`, `Schedules`) are never returned on agent sockets. /// /// `AgentResponse` and `ManagerResponse` are type aliases for this enum. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum Response { /// `Send` succeeded. Ok, /// Either `Send` failed or `Recv` errored. Err { message: String }, /// `Recv` result: zero or more messages, FIFO-ordered, never /// longer than the `max` the caller passed. Empty vec = nothing /// pending (the "(empty)" path for the formatter). Per-row `id` + /// `redelivered` carry the broker's row id (opaque to claude; /// tracked by the harness for `AckTurn`) and the "previously /// popped, not acked" flag — see `DeliveredMessage` for details. Messages { messages: Vec }, /// `Status` result: how many pending messages are in this agent's inbox. Status { unread: u64 }, /// `Recent` result: newest-first inbox rows. Recent { rows: Vec }, /// `Ask` result: the queued question id. The answer lands later /// as `HelperEvent::QuestionAnswered` in this agent's inbox. QuestionQueued { id: i64 }, /// `GetLooseEnds` result: list of loose ends pending against /// this agent. Ordered newest-first within each kind. LooseEnds { loose_ends: Vec }, /// `CountPendingReminders` result. PendingRemindersCount { count: u64 }, /// `ReminderRollup` result: reminder activity stats for the agent. ReminderRollup(ReminderStats), /// `GetAgentMeta` result. Per-field semantics + serde defaults /// live in `docs/conventions.md::Agent metadata`. AgentMeta { name: String, role: String, #[serde(default = "default_true")] running: bool, #[serde(default, skip_serializing_if = "Option::is_none")] hyperhive_rev: Option, #[serde(default, skip_serializing_if = "Option::is_none")] status_text: Option, #[serde(default, skip_serializing_if = "Option::is_none")] status_set_at: Option, #[serde(default, skip_serializing_if = "Option::is_none")] hive_name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] swarm_name: Option, }, /// `GetLogs` result: journal lines for the requested container. /// Returned on the manager socket only. Logs { content: String }, /// `ListSchedules` result. Snapshot of every schedule. /// Returned on the manager socket only. Schedules { schedules: Vec }, } /// Backwards-compatible response aliases. pub type AgentResponse = Response; pub type ManagerResponse = Response; /// Serde default for the `running` field; keeps wire backwards-compat /// with pre-running-field payloads. See /// `docs/conventions.md::Agent metadata`. fn default_true() -> bool { true } // ----------------------------------------------------------------------------- // Manager socket — /run/hyperhive/manager/mcp.sock on the host, bind-mounted // into the manager container at /run/hive/mcp.sock. // ----------------------------------------------------------------------------- /// Logical name the broker uses for the manager. pub const MANAGER_AGENT: &str = "manager"; /// Logical name the broker uses for the human operator. Messages with /// `to = OPERATOR_RECIPIENT` accumulate in sqlite and surface on the /// dashboard's inbox view — they are never `recv`'d by an agent harness. pub const OPERATOR_RECIPIENT: &str = "operator"; /// Reserved magic recipient — `send(to: "", ...)` is rewritten /// by hive-c0re at delivery time to whoever `topology::parent_of(sender)` /// returns, or to [`OPERATOR_RECIPIENT`] when the sender is a root agent /// (no parent). Lets agents address their parent without hardcoding the /// label, so runtime reparenting requires no agent-side restart. The /// angle brackets are not valid in agent names (validators reject /// `<`/`>`), so this name can never collide with a real recipient. pub const PARENT_RECIPIENT: &str = ""; /// Reserved magic recipient — `send(to: "", ...)` fans out to /// every agent whose direct parent (per `topology.json`) is the sender. /// Lets a sub-manager nudge its subtree without enumerating labels at /// call-time; topology changes propagate for free. The angle brackets /// are structurally safe — agent name validation rejects `<`/`>`. /// Delivers to an empty set (no-op) for leaf agents that have no children. pub const CHILDREN_RECIPIENT: &str = ""; /// Sender hive-c0re uses for events it pushes into the manager's inbox. /// Manager harness recognises this and parses the body as a `HelperEvent`. pub const SYSTEM_SENDER: &str = "system"; /// Out-of-band events the host-side daemon pushes to the manager's inbox. /// Serialised as JSON in `Message::body` (sender = `SYSTEM_SENDER`). /// Per-variant triggers + the optional `sha`/`tag` semantics live in /// `docs/approvals.md::Helper events to the manager`. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "event", rename_all = "snake_case")] pub enum HelperEvent { /// An approval transitioned to a terminal state. ApprovalResolved { id: i64, agent: String, commit_ref: String, status: ApprovalStatus, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, #[serde(default, skip_serializing_if = "Option::is_none")] sha: Option, #[serde(default, skip_serializing_if = "Option::is_none")] tag: Option, }, /// A new container was spawned. `ok = false` = spawn failed. Spawned { agent: String, ok: bool, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, #[serde(default, skip_serializing_if = "Option::is_none")] sha: Option, }, /// A container was rebuilt (auto-update or manual). Rebuilt { agent: String, ok: bool, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, #[serde(default, skip_serializing_if = "Option::is_none")] sha: Option, #[serde(default, skip_serializing_if = "Option::is_none")] tag: Option, }, /// A new agent's proposed config repo was seeded post-`InitConfig`. ConfigReady { agent: String }, /// A sub-agent's container was stopped (systemd unit down; state kept). Killed { agent: String }, /// A sub-agent's container was torn down (state dirs preserved by default). Destroyed { agent: String }, /// A sub-agent's container has no claude session yet. NeedsLogin { agent: String }, /// A sub-agent just completed claude login. LoggedIn { agent: String }, /// A sub-agent's recorded flake rev is stale relative to hyperhive. NeedsUpdate { agent: String }, /// Container exited without an operator-initiated stop (crash). ContainerCrash { agent: String, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, }, /// A question queued via `Ask` was answered. `id` matches the /// originating `QuestionQueued.id`; `answerer` is `"operator"` / /// a peer agent name / `"ttl-watchdog"` on expiry. QuestionAnswered { id: i64, question: String, answer: String, answerer: String, }, /// A peer (or the manager) asked this agent a question. Recipient /// replies via `Answer { id, answer }`; the answer routes back to /// the asker as `QuestionAnswered`. QuestionAsked { id: i64, asker: String, question: String, #[serde(default)] options: Vec, #[serde(default)] multi: bool, }, } /// Submission payload for `RequestSchedulePrompt`. Lives outside the /// enum so it can also serialize into the approval row's `commit_ref` /// (the dispatcher re-parses it on approve and inserts the schedule). #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct SchedulePromptPayload { /// Names of recipient agents. Operator + `hm1nd` allowed. pub targets: Vec, /// Message body delivered to each target's inbox at fire time. /// Same size budget as `Send.body` — soft cap at the broker level. pub body: String, /// Absolute unix timestamp (seconds) for the FIRST fire. For /// recurring schedules the worker then re-arms in /// `interval_seconds` steps. pub first_fire_at_unix: i64, /// `None` = one-shot. `Some(n > 0)` = recurring every `n` seconds. #[serde(default, skip_serializing_if = "Option::is_none")] pub interval_seconds: Option, /// Optional description shown on the dashboard approval card AND /// stored on the resulting schedule row for the operator's /// "what is this?" reference later. #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, } /// Schedule row shape on the wire — mirror of /// `scheduled_prompts::Schedule` but in the public crate so the /// dashboard and agent surfaces can deserialize without depending /// on hive-c0re-internal types. Kept structurally identical to the /// in-process type; the conversion is field-by-field in /// `manager_server` / `dashboard`. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WireSchedule { pub id: i64, pub owner: String, pub body: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub interval_seconds: Option, pub next_fire_at_unix: i64, pub created_at_unix: i64, pub source: WireScheduleSource, #[serde(default, skip_serializing_if = "Option::is_none")] pub cancelled_at_unix: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, pub targets: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum WireScheduleSource { Operator, Approval { id: i64 }, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WireScheduleTarget { pub target: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub cancelled_at_unix: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub last_fired_at_unix: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub last_result: Option, }