yog 0.0.38

yog: the standalone server for litany loops — the world, the balls and the conversations, behind one wire
Documentation
//! The §11 inspector family's envelope spelling (§8.5, bl-6233; extended
//! bl-13f9) — split from [`super`] at §12's per-file budget on the seam the
//! family itself draws: these are the only queries addressed at a
//! **conversation** rather than a workspace, so the address they share is
//! written once here and every other query's spelling stays where it was.
//!
//! Both directions stay under the §4.8 compile gate: [`super::encode`] names
//! each variant by hand, and [`read`] is chained ahead of the sibling table so
//! an op it does not claim falls through unchanged.

use serde_json::{Map, Value, json};

use super::super::fields::opt_str_of;
use super::super::start::opt_field;
use super::super::str_of;
use crate::boundary::Query;

/// The follow lane's op token (bl-73e7), named once for both directions.
pub(super) const FOLLOW: &str = "follow";

/// The address every one of the family carries: the workspace, and the
/// conversation inside it.
pub(super) fn at(op: &str, workspace: &str, agent: &str) -> Value {
    Value::Object(at_map(op, workspace, agent))
}

/// The same, still open for the two that carry one more key.
fn at_map(op: &str, workspace: &str, agent: &str) -> Map<String, Value> {
    let mut map = Map::new();
    map.insert("op".to_owned(), json!(op));
    map.insert("workspace".to_owned(), json!(workspace));
    map.insert("agent".to_owned(), json!(agent));
    map
}

/// One step's drill-in: the address, plus the sequence name that picks the step.
pub(super) fn step(workspace: &str, agent: &str, seq: &str) -> Value {
    let mut map = at_map("step", workspace, agent);
    map.insert("seq".to_owned(), json!(seq));
    Value::Object(map)
}

/// The Files read: the address, plus the path when one file's bytes are asked
/// for and the commit when a tree other than the worktree is. Absent is the
/// listing of the live worktree — the [`WorkDiff`](Query::WorkDiff) shape.
pub(super) fn files(
    workspace: &str,
    agent: &str,
    path: Option<&String>,
    at: Option<&String>,
) -> Value {
    let mut map = at_map("files", workspace, agent);
    opt_field(&mut map, "path", path);
    opt_field(&mut map, "at", at);
    Value::Object(map)
}

/// The config-frozen-at read (bl-13f9): the address, plus the commit when one
/// other than the agent's own tip is asked about. Absent is the tip — the
/// [`files`] shape at the other tab whose subject is a tree.
pub(super) fn governing(workspace: &str, agent: &str, at: Option<&String>) -> Value {
    let mut map = at_map("governing", workspace, agent);
    opt_field(&mut map, "at", at);
    Value::Object(map)
}

/// Decode one of the family, or `Ok(None)` when `op` names none of them — the
/// signal [`super::read`] chains on before its own table. Strict: the address
/// is required in full on every one of them, because a conversation read that
/// guessed either half would answer about a different chat entirely.
pub(super) fn read(op: &str, o: &Map<String, Value>) -> Result<Option<Query>, String> {
    Ok(Some(match op {
        "transcript" => {
            let (workspace, agent) = address(o)?;
            Query::Transcript { workspace, agent }
        }
        FOLLOW => {
            let (workspace, agent) = address(o)?;
            Query::Follow { workspace, agent }
        }
        "steps" => {
            let (workspace, agent) = address(o)?;
            Query::Steps { workspace, agent }
        }
        "step" => {
            let (workspace, agent) = address(o)?;
            Query::Step {
                workspace,
                agent,
                seq: str_of(o, "seq")?,
            }
        }
        "files" => {
            let (workspace, agent) = address(o)?;
            Query::Files {
                workspace,
                agent,
                path: opt_str_of(o, "path")?,
                at: opt_str_of(o, "at")?,
            }
        }
        "governing" => {
            let (workspace, agent) = address(o)?;
            Query::Governing {
                workspace,
                agent,
                at: opt_str_of(o, "at")?,
            }
        }
        "rail" => {
            let (workspace, agent) = address(o)?;
            Query::Rail { workspace, agent }
        }
        "inbox" => {
            let (workspace, agent) = address(o)?;
            Query::Inbox { workspace, agent }
        }
        "agent" => {
            let (workspace, agent) = address(o)?;
            Query::Agent { workspace, agent }
        }
        _ => return Ok(None),
    }))
}

/// The shared address reader — both halves required.
fn address(o: &Map<String, Value>) -> Result<(String, String), String> {
    Ok((str_of(o, "workspace")?, str_of(o, "agent")?))
}