sprawling-wire 0.0.12

The process boundary of a sprawling city: the frames its clients speak, and the server that answers them.
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Copyright (c) 2026 2youg1 and the sprawling contributors

//! Everything a client may ask the city to do, and the two invariants
//! that live in the type system rather than in a check.
//!
//! - `Command` is generic over the carrier of a secret. `WireCommand`
//!   fixes that carrier to an uninhabited type, so a frame arriving from
//!   a socket cannot be a `PutSecret` — not "is rejected", but has no
//!   representation. Credentials are enrolled on the host machine, and
//!   that constraint is held by construction.
//! - Every state-changing Command owns an `IdemKey` field. There is no
//!   constructor that omits it, so "double-clicking twice opens two
//!   runs" is not reachable from this type.
//!
//! The enum is not `#[non_exhaustive]`: the wire version is the
//! versioning mechanism, so the assembly layer must handle every variant
//! and a new one fails to compile until somebody decides what it does.
//! That is what keeps a button off the client until the city can answer
//! the frame behind it.

//! The wire face: no-secret commands and back.

use kernel::IdemKey;

use super::kind::Command;
use super::no_secret::NoSecret;
use super::step::{
    AgentAdding, AgentLoginStart, CityClosing, CitySettings, DeviceForgetting, DoorAnswer,
    DoorOpening, DoorStep, PolicyChange, ProposalDecisions, RangeWrite, RulesWrite,
    SecretForgetting, SessionNaming,
};

/// The Command set a socket can carry. `PutSecret` is unreachable because
/// `NoSecret` has no values.
pub type WireCommand = Command<NoSecret>;

impl<Secret> Command<Secret> {
    /// The deduplication key. `None` only for the two Commands that change
    /// nothing: `Auth`, and `PutSecret` whose effect is confined to the host
    /// process and whose replay writes the same Vault entry.
    pub fn idem(&self) -> Option<&IdemKey> {
        match *self {
            Self::Dispatch { ref idem, .. }
            | Self::Wake { ref idem, .. }
            | Self::OpenSession { ref idem, .. }
            | Self::ProbeEndpoint { ref idem, .. }
            | Self::ConfigureBuilding { ref idem, .. }
            | Self::CreateBuilding { ref idem, .. }
            | Self::RemoveBuilding { ref idem, .. }
            | Self::Steer { ref idem, .. }
            | Self::Cancel { ref idem, .. }
            | Self::Halt { ref idem, .. }
            | Self::Release { ref idem, .. }
            | Self::BatchByBuilding { ref idem, .. }
            | Self::Approve { ref idem, .. }
            | Self::HandOff { ref idem, .. }
            | Self::SetAutonomy { ref idem, .. }
            | Self::Pursue { ref idem, .. }
            | Self::PutDocument { ref idem, .. }
            | Self::PutIdentity { ref idem, .. }
            | Self::PutRules(RulesWrite { ref idem, .. })
            | Self::ConfigureCity(CitySettings { ref idem, .. })
            | Self::RestoreFile { ref idem, .. }
            | Self::PutGuide { ref idem, .. }
            | Self::PutSpine { ref idem, .. }
            | Self::PutRange(RangeWrite { ref idem, .. })
            | Self::DecideProposals(ProposalDecisions { ref idem, .. })
            | Self::PutPreferences { ref idem, .. }
            | Self::PutShelved { ref idem, .. }
            | Self::AttachEndpoint { ref idem, .. }
            | Self::SelectModel { ref idem, .. }
            | Self::Reveal { ref idem, .. }
            | Self::RestoreDiscard { ref idem, .. }
            | Self::DoctorInstall { ref idem, .. }
            | Self::DoctorRefresh { ref idem, .. }
            | Self::ConnectToolkit { ref idem, .. }
            | Self::NameSession(SessionNaming { ref idem, .. })
            | Self::ChangeRunPolicy(PolicyChange { ref idem, .. })
            | Self::OpenRemoteDoor(DoorOpening { ref idem, .. })
            | Self::ConfirmRemoteDoor(DoorAnswer { ref idem, .. })
            | Self::ReplaceCityKey(DoorStep { ref idem })
            | Self::CloseRemoteDoor(DoorStep { ref idem })
            | Self::PrivacyOperation(crate::PrivacyRequest { ref idem, .. })
            | Self::ForgetSecret(SecretForgetting { ref idem, .. })
            | Self::CloseCity(CityClosing { ref idem, .. })
            | Self::AddAgent(AgentAdding { ref idem, .. })
            | Self::AgentLogin(AgentLoginStart { ref idem, .. })
            | Self::ForgetDevice(DeviceForgetting { ref idem, .. }) => Some(idem),
            Self::PutSecret { .. } | Self::Auth { .. } => None,
        }
    }
}

impl From<WireCommand> for Command {
    /// Widening a wire frame into the in-process Command set. Total: the
    /// `PutSecret` arm is unreachable because its payload cannot exist.
    fn from(wire: WireCommand) -> Self {
        match wire {
            // One line per variant keeps this table inside the function budget.
            #[rustfmt::skip]
            Command::Wake { source, subject, body, idem } => Self::Wake { source, subject, body, idem },
            Command::Dispatch {
                addr,
                task,
                goal,
                policy,
                idem,
                session,
                effort,
                model,
            } => Self::Dispatch {
                addr,
                task,
                goal,
                policy,
                idem,
                session,
                effort,
                model,
            },
            Command::ConfigureBuilding {
                addr,
                sandbox,
                mcp,
                desktop,
                context_second_threshold,
                idem,
            } => Self::ConfigureBuilding {
                addr,
                sandbox,
                mcp,
                desktop,
                context_second_threshold,
                idem,
            },
            Command::ProbeEndpoint {
                name,
                base_url,
                dialect,
                secret,
                auth_header,
                tuning,
                idem,
            } => Self::ProbeEndpoint {
                name,
                base_url,
                dialect,
                secret,
                auth_header,
                tuning,
                idem,
            },
            Command::AttachEndpoint {
                name,
                base_url,
                dialect,
                secret,
                auth_header,
                admit,
                named,
                tuning,
                idem,
            } => Self::AttachEndpoint {
                name,
                base_url,
                dialect,
                secret,
                auth_header,
                admit,
                named,
                tuning,
                idem,
            },
            Command::SelectModel {
                endpoint,
                model,
                tag,
                context_tokens,
                max_output_tokens,
                input,
                idem,
            } => Self::SelectModel {
                endpoint,
                model,
                tag,
                context_tokens,
                max_output_tokens,
                input,
                idem,
            },
            Command::OpenSession {
                addr,
                carry,
                from,
                idem,
            } => Self::OpenSession {
                addr,
                carry,
                from,
                idem,
            },
            // One line per variant keeps this table inside the function budget.
            #[rustfmt::skip]
            Command::CreateBuilding { addr, template, idem } =>
                Self::CreateBuilding { addr, template, idem },
            Command::PutSecret { value, .. } => match value {},
            Command::Steer { run, text, idem } => Self::Steer { run, text, idem },
            Command::Cancel { run, idem } => Self::Cancel { run, idem },
            Command::Halt { scope, idem } => Self::Halt { scope, idem },
            Command::Reveal { at, idem } => Self::Reveal { at, idem },
            Command::RestoreDiscard { restoration, idem } => {
                Self::RestoreDiscard { restoration, idem }
            }
            Command::RemoveBuilding { addr, idem } => Self::RemoveBuilding { addr, idem },
            Command::DoctorInstall { item, idem } => Self::DoctorInstall { item, idem },
            Command::DoctorRefresh { idem } => Self::DoctorRefresh { idem },
            Command::ConnectToolkit { toolkit, idem } => Self::ConnectToolkit { toolkit, idem },
            Command::Release { scope, idem } => Self::Release { scope, idem },
            Command::BatchByBuilding { addr, idem } => Self::BatchByBuilding { addr, idem },
            Command::Approve {
                item,
                verdict,
                idem,
            } => Self::Approve {
                item,
                verdict,
                idem,
            },
            Command::HandOff { item, to, idem } => Self::HandOff { item, to, idem },
            Command::Pursue { addr, step, idem } => Self::Pursue { addr, step, idem },
            Command::SetAutonomy {
                scope,
                autonomy,
                idem,
            } => Self::SetAutonomy {
                scope,
                autonomy,
                idem,
            },
            Command::PutDocument {
                which,
                base,
                body,
                idem,
            } => Self::PutDocument {
                which,
                base,
                body,
                idem,
            },
            Command::PutIdentity { card, base, idem } => Self::PutIdentity { card, base, idem },
            Command::PutRules(write) => Self::PutRules(write),
            Command::RestoreFile { at, point, idem } => Self::RestoreFile { at, point, idem },
            Command::PutGuide { progress, idem } => Self::PutGuide { progress, idem },
            Command::ConfigureCity(settings) => Self::ConfigureCity(settings),
            Command::PutRange(write) => Self::PutRange(write),
            Command::DecideProposals(decisions) => Self::DecideProposals(decisions),
            Command::PutSpine {
                building,
                which,
                base,
                body,
                idem,
            } => Self::PutSpine {
                building,
                which,
                base,
                body,
                idem,
            },
            Command::PutPreferences { patch, idem } => Self::PutPreferences { patch, idem },
            Command::NameSession(naming) => Self::NameSession(naming),
            Command::ChangeRunPolicy(change) => Self::ChangeRunPolicy(change),
            Command::OpenRemoteDoor(opening) => Self::OpenRemoteDoor(opening),
            Command::ReplaceCityKey(step) => Self::ReplaceCityKey(step),
            Command::ConfirmRemoteDoor(answer) => Self::ConfirmRemoteDoor(answer),
            Command::CloseRemoteDoor(step) => Self::CloseRemoteDoor(step),
            Command::PrivacyOperation(request) => Self::PrivacyOperation(request),
            Command::ForgetSecret(forgetting) => Self::ForgetSecret(forgetting),
            Command::CloseCity(closing) => Self::CloseCity(closing),
            Command::AddAgent(adding) => Self::AddAgent(adding),
            Command::AgentLogin(login) => Self::AgentLogin(login),
            Command::ForgetDevice(forgetting) => Self::ForgetDevice(forgetting),
            #[rustfmt::skip]
            Command::PutShelved { shelf, name, text, idem } => Self::PutShelved { shelf, name, text, idem },
            Command::Auth { token } => Self::Auth { token },
        }
    }
}

#[cfg(test)]
#[allow(
    clippy::float_arithmetic,
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::panic,
    clippy::indexing_slicing,
    clippy::string_slice,
    clippy::arithmetic_side_effects,
    reason = "test code"
)]
mod tests {
    use super::*;
    /// The runtime half of "a credential cannot be enrolled over a
    /// connection": the compile-time half is `NoSecret` having no values,
    /// and this is what happens to bytes that try anyway.
    #[test]
    fn a_wire_frame_carrying_put_secret_fails_to_decode() {
        let json = r#"{"put_secret":{"realm":"anthropic","name":"api","value":"sk-nope"}}"#;
        let decoded: Result<WireCommand, _> = serde_json::from_str(json);
        let err = decoded.expect_err("PutSecret has no wire form");
        assert!(
            !err.to_string().contains("sk-nope"),
            "the refusal must not echo the bytes it protects"
        );
    }
}