yog 0.0.76

yog: the standalone server for litany loops — the world, the balls and the conversations, behind one wire
//! **The wire** (REMOTE §9 step 5, bl-b6fa): the mTLS channel a seat reaches
//! the control boundary over, and the transport a seat is made of.
//!
//! REMOTE §1.2 rules that the UI operates entirely via RPC and that even the
//! local, single-machine arrangement does the hard split. This module is the
//! channel that makes that possible: [`server`] is the engine's listener,
//! [`client`] is a seat's transport, [`frame`] is what they say to each other,
//! [`material`] is the operator-provisioned trust the whole thing rests on, and
//! [`seat`] is the first shipped consumer — the terminal seat, `yog seat`.
//!
//! **The wire is a transport for the boundary, not a vocabulary** (REMOTE §3).
//! [`intake`] hands a request straight to the deposit consumer's own context,
//! which decodes with the one codec and runs the one `dispatch`/`answer`. So
//! there is no wire verb, no wire-only capability and no second dispatch
//! implementation (VISION §8) — the listener is a **second intake to the same
//! chokepoints**, exactly as the gestures inbox is, and the inbox remains for
//! the world's own residents (REMOTE §3).
//!
//! **The listener rides the engine, not one face** (bl-b6fa). Both faces run
//! the deposit consumer so a deposit converges whichever face is up (§8.5, I0);
//! a seat wants the identical guarantee, so the listener boots in
//! [`Engine::boot`](crate::engine::Engine::boot) beside it. That is also what
//! keeps **one engine per world**: a windowed yog serves the wire it would
//! otherwise have to start a second engine to reach, and `yog serve` is the
//! same engine with no window.
//!
//! **Absence WAS the off switch, and is not since bl-ae05.** REMOTE §1.2's
//! window is a client of this listener now, so a box with no material would be
//! a window that paints nothing — and REMOTE §8 had already rejected both ways
//! around that. A boot therefore founds its own loopback trust root
//! ([`provision`]), which is the operator's own out-of-channel act performed on
//! the operator's own box — aimed at `127.0.0.1:0`, so no two engines contend
//! for a process-global port (I0, bl-dc14). A *half*-provisioned wire the mint
//! cannot heal still refuses, because silently degrading to no encryption is
//! the one failure this design exists to exclude — and the refusal is a
//! *returned sentence* now, painted by the window it starves
//! (`shell::refusal`) rather than lost on a desktop launch's stderr.

use crate::xdg::Env;
use std::sync::Arc;

pub mod frame;
/// The version preface (REMOTE §3, bl-a670): what each end states about itself
/// before it says anything, and the fail-closed refusal a skew earns.
pub mod hello;
pub mod intake;
pub mod material;
/// The mint (REMOTE §1.4, §8; bl-ae05) — the one `openssl` recipe, spent by the
/// engine's boot and by `yog wire-certs` alike.
pub mod provision;
/// The punched wire's engine end (REMOTE §13, bl-4263): presence, the inbox
/// poll, the punch, and held-connection serving.
pub mod rendezvous;
pub mod server;
pub mod tls;

/// **How often a seat re-asks its standing set** — human cadence (REMOTE §3),
/// and the number REMOTE §10's ask-rate criterion is measured against.
///
/// It is a **protocol** period rather than a client's private knob, which is
/// why it survived the seat's departure (bl-7942) and why it stayed after
/// bl-776a: the §7.3 wound grace is the sum of every leg a fact crosses before
/// a seat can see it
/// ([`Cadence::wound_grace`](crate::app::Cadence::wound_grace)), and the last
/// of those legs is the ask. The engine **spends** that grace itself now — a
/// wound crosses already-judged — so this constant is load-bearing in the
/// stronger sense: a server that did not know the seat's own poll period could
/// not compute the window it waits out, and would raise the alarm the grace
/// exists to prevent.
pub const ASK_PERIOD: std::time::Duration = std::time::Duration::from_millis(500);

/// Bring the engine's listener up, or explain why there is none.
///
/// **It founds its own trust root first** (REMOTE §8 as amended, bl-ae05).
/// Absence of material used to be the off switch; it cannot be any more,
/// because REMOTE §1.2 makes the window a client of this listener and a window
/// with no listener paints nothing. So [`provision::ensure`] performs the
/// out-of-channel mint — on this box, before anything is dialled — and what it
/// writes is aimed at loopback. Wider listening is still the operator's own
/// act: `address` is one fact with one home, and only an operator ever writes
/// a host that is not loopback into it.
///
/// A refusal is *returned* rather than said here, and the caller owns both the
/// saying and the consequence (bl-dc14): the engine's boot hands it up, and
/// `Engine::serve` prints it and **exits non-zero** (bl-1d9b). It used not to
/// be fatal — a box with no `openssl` got the engine yog had always been — on
/// the argument that only a seat was shut out; the window that made that true
/// left with bl-7942, so a yog with no listener answers nobody while still
/// draining the world's gesture inbox, which is the half-engine bl-1d9b is
/// about.
pub fn listen(
    world: &Env,
    answerer: Arc<dyn server::Answerer>,
    presence: crate::registry::presence::Presence,
) -> Result<server::Listener, String> {
    let minted = provision::ensure(&material::dir(world));
    let material = match material::read(world, material::Role::Server) {
        Ok(Some(m)) => m,
        // Nothing readable at all is exactly a box whose mint failed —
        // `ensure` writes `address` on every success — so the mint's own words
        // are the refusal. The half-provisioned read (`Err`) speaks for itself.
        Ok(None) => return Err(minted.err().unwrap_or_default()),
        Err(e) => return Err(e),
    };
    // A mint failure that still left readable material is a warning, not a
    // refusal: the wire the box already had is the wire it keeps.
    if let Err(e) = minted {
        eprintln!("yog: wire: {e}");
    }
    server::Listener::bind(&material, answerer, presence)
}

/// **What this process's listener bound** (REMOTE §8, bl-28f4) — a handle, set
/// once by the boot that bound it and read by the one gesture that asks whether
/// this box is wired (`/doctor`).
///
/// **It is not a second home for the address.** `wire/address` is the fact's one
/// home and it holds a *request*; a `:0` there is answered by the kernel, and
/// only the listener ever learns the answer. §8 says that answer is "said once,
/// on stderr, by the boot that bound it" — which is true and is also why an
/// operator whose engine is under a supervisor cannot get it back. This is the
/// same sentence, kept in RAM so it can be *asked for*: never written, never
/// derived from, and empty in every process that did not bind (a test, the §4.3
/// pilot, an engine whose bind was refused), which is the honest reading.
///
/// **It carries the punched wire's counters too** (REMOTE §13.4, bl-355c) —
/// the second door's facts about this process, shared with the loop by handle
/// for the same reason, and all zero where no loop started.
#[derive(Clone, Default)]
pub struct Listening {
    bound: Arc<std::sync::OnceLock<String>>,
    rendezvous: Arc<rendezvous::Stats>,
}

impl Listening {
    /// Record what was bound. Once, by the boot: a second listener in one
    /// process is the thing DESIGN I0 says never happens, and `OnceLock` says
    /// so rather than trusting the caller.
    pub fn state(&self, address: &str) {
        let _ = self.bound.set(address.to_owned());
    }

    /// What was bound, or `None` where nothing did.
    pub fn address(&self) -> Option<String> {
        self.bound.get().cloned()
    }

    /// The handle the rendezvous loop counts into.
    pub(crate) fn rendezvous(&self) -> Arc<rendezvous::Stats> {
        Arc::clone(&self.rendezvous)
    }

    /// The loop's standing as `/doctor` hands it over — `None` where nothing
    /// bound, since only an engine has a loop to speak of.
    pub(crate) fn standing(&self) -> Option<rendezvous::Standing> {
        self.address().map(|_| self.rendezvous.standing())
    }
}

#[cfg(test)]
mod tests;