liminal-cli 0.3.1

The liminal command: serve the network, maintain its stores, or serve its discovery MCP over stdio
Documentation
//! Both channel streams and publishes use the configured seat's own proof.

use crate::config::{Config, Credential};
use crate::lys::LysAgent;
use liminal_sdk::{SdkError, lys_pass_credential};
use zeroize::Zeroizing;

enum Proof {
    Fixed(Zeroizing<Vec<u8>>),
    Lys(LysAgent),
}

pub(super) struct Source {
    pub address: String,
    pub record_bytes: usize,
    pub timeout: std::time::Duration,
    proof: Proof,
    revoke: Option<super::channel_lifecycle::Revoke>,
}

impl std::fmt::Debug for Source {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ChannelSource")
            .field("address", &self.address)
            .field("record_bytes", &self.record_bytes)
            .finish_non_exhaustive()
    }
}

impl Source {
    pub fn new(config: &Config) -> Result<Self, String> {
        let revoke = super::channel_lifecycle::Revoke::new(config)?;
        let proof = match &config.credential {
            Credential::Token(token) => {
                Proof::Fixed(Zeroizing::new(lys_pass_credential(token.as_str())))
            }
            Credential::Lys(table) => Proof::Lys(
                LysAgent::new(table, config.limits.reply_timeout()).map_err(str::to_owned)?,
            ),
            // ACCESS-010 R3: an open seat presents nothing; the channel's
            // own tools still refuse it, as agents need Lys.
            Credential::Open => Proof::Fixed(Zeroizing::new(Vec::new())),
        };
        Ok(Self {
            address: config.server.clone(),
            record_bytes: config.limits.record_bytes,
            timeout: config.limits.reply_timeout(),
            proof,
            revoke,
        })
    }

    pub fn credential(&self) -> Result<Zeroizing<Vec<u8>>, String> {
        match &self.proof {
            Proof::Fixed(bytes) => Ok(Zeroizing::new(bytes.to_vec())),
            Proof::Lys(agent) => agent
                .pass()
                .map(|pass| Zeroizing::new(lys_pass_credential(&pass)))
                .map_err(|error| error.0),
        }
    }

    pub fn revoke(&self) -> Result<(), String> {
        self.revoke
            .as_ref()
            .map_or(Ok(()), super::channel_lifecycle::Revoke::send)
    }
}

pub(super) fn words(error: SdkError) -> String {
    let detail = match error {
        SdkError::Protocol { description } | SdkError::Connection { description } => description,
        error => return error.to_string(),
    };
    // The SDK's setup and terminal constructors add this fixed prefix to the
    // server's detail. Only that exact constructor shape is unwrapped.
    for prefix in [
        "server rejected subscribe (reason ",
        "server ended the subscription (reason ",
        "server rejected subscription connection (reason ",
    ] {
        if let Some(rest) = detail.strip_prefix(prefix)
            && let Some((code, words)) = rest.split_once("): ")
            && code.parse::<u16>().is_ok()
        {
            return words.to_owned();
        }
    }
    detail
}