liminal-server 0.20.1

Standalone server for the liminal messaging bus
Documentation
//! The production [`SeatStarter`]: a seat is a Lys identity with a pass,
//! started by Lys (ACCESS-003 R1).
//!
//! There is no client registry in liminal. `seats.<name>` names a Lys agent;
//! once the caller's grant on the subject allowed the start, the seat is
//! started through Lys's `POST /agents/{name}/start-command` with the
//! request's operation id, on the machine the operator configured, so Lys
//! renders the command from the agent's reviewed profile, asks that
//! machine's runner, and keeps the start once under the operation: sent
//! again, it answers what it kept and starts nothing twice. Every Lys
//! refusal (an agent it does not hold, a machine without a runner, a profile
//! not reviewed) comes back by its name.

use super::lys_http::{LysHttp, segment};
use super::{SeatStarter, ServiceEffectError};
use std::sync::Arc;

/// Seats started by Lys over [`LysHttp`].
pub struct LysSeatStarter {
    http: Arc<dyn LysHttp>,
    bearer: String,
    machine: String,
}

impl std::fmt::Debug for LysSeatStarter {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("LysSeatStarter")
            .field("http", &self.http)
            .field("machine", &self.machine)
            .finish_non_exhaustive()
    }
}

impl LysSeatStarter {
    /// Starts seats on `machine` (the machine id Lys records) through
    /// `http`, asking with `bearer`, this liminal's own Lys pass, never shown
    /// by `Debug`.
    #[must_use]
    pub const fn new(http: Arc<dyn LysHttp>, bearer: String, machine: String) -> Self {
        Self {
            http,
            bearer,
            machine,
        }
    }
}

impl SeatStarter for LysSeatStarter {
    fn start(&self, seat: &str, operation: &str) -> Result<(), ServiceEffectError> {
        let route = format!("agents/{}/start-command", segment(seat));
        let body = serde_json::json!({
            "machine": self.machine,
            "operation": operation,
        });
        let answer = self.http.post(&self.bearer, &route, &body)?;
        if answer.succeeded() {
            Ok(())
        } else {
            Err(answer.refused(&route))
        }
    }
}