yog 0.0.1

yog: a balls-oriented session manager for lernie loops (egui frontend)
Documentation
//! The nested world yog composes under its own data root (DESIGN §16.2) — the
//! pure `ambient Env → world Env` composition plus the `<yog-data-root>/world/`
//! subtree layout. yog reads the ambient environment once, anchors on
//! `$XDG_DATA_HOME/yog`, and layers a fixed two-var override set over that
//! snapshot; the composed result is itself an [`Env`], so every §5.1 fold
//! re-derives the *nested* location through it (balls state, lernie home, and
//! yog's own `ui.json`/`ops.jsonl`) while the brazen config, credential, and
//! model-cache folds stay ambient (§16.2). This module is the pure composition
//! layer only: it neither materializes the subtree nor wires the world into any
//! spawn (W2/W3).
//!
//! **The overrides, and why exactly these two (§16.2):**
//!
//! | Var | World value | Nests |
//! |---|---|---|
//! | `LERNIE_HOME` | `world/lernie` | lernie config **and** data (the `lernie_home` collapse) |
//! | `XDG_STATE_HOME` | `world/state` | balls clones/worktrees/op-logs **and** yog's `ui.json`/`ops.jsonl` |
//!
//! `XDG_DATA_HOME`, `XDG_CACHE_HOME`, and `BRAZEN_CONFIG` are left ambient by
//! design. `$XDG_DATA_HOME` is the world's **anchor** (overriding it would
//! recurse — re-deriving the anchor through the world `Env` must yield the same
//! path) and carries brazen's **shared** credentials; `$XDG_CACHE_HOME` carries
//! brazen's **shared**, regenerable model cache; `$BRAZEN_CONFIG` (else the
//! `$XDG_CONFIG_HOME` fold, [`Env::brazen_config_path`]) names brazen's
//! **shared** config — phase 1 spawns the one host `bz`, so there is no version
//! skew for a nested config to protect against, and its provider rows are
//! credential-adjacent to the secrets already shared (§16.2 amendment; a nested
//! `BRAZEN_CONFIG` pointed at a file nothing creates and broke auth live).
//! Secrets, cache, and config are reused from the ambient world; everything
//! version-fragile (lernie's home, balls' store layout) is nested.
//!
//! **Design decision — yog's own artifacts move under the world (§16.2, not
//! ambiguous).** `ui.json`/`ops.jsonl` resolve through [`Env::yog_state_root`] =
//! `$XDG_STATE_HOME/yog`, so under the world `Env` they land at
//! `world/state/yog/`. The §16.2 `XDG_STATE_HOME` row ("… **and** yog's own
//! `ui.json`/`ops.jsonl`"), the severability clause ("… and yog's own
//! artifacts"), and §16.6 W1 ("its own two artifacts through the world `Env`")
//! all mandate the move — so yog's artifacts nest with the tools rather than
//! staying at the ambient yog roots, and one `rm -rf $XDG_DATA_HOME/yog` erases
//! the whole world including them.
//!
//! **Task 0 — bl-delivery worktree territory (§16.2 diligence), confirmed from
//! source _and_ empirically.** A child `bl` lands its worktrees under *its own
//! process* `$XDG_STATE_HOME`, so spawning it in the world `Env` (W2) nests
//! every worktree in `world/state`. Source: `balls@main:src/bin/bl-delivery.rs`
//! reads the live env — `Xdg::with(&home, env::var("XDG_CONFIG_HOME")…,
//! env::var("XDG_STATE_HOME")…)` — and `layout.rs::plugin_territory(name) =
//! state_home.join("balls").join("plugins").join(name)` feeds
//! `delivery_path.rs::binding_territory = plugin_territory(plugin).
//! join(invocation_path)`, whose `<id>` child is the worktree. So the worktree
//! is `$XDG_STATE_HOME/balls/plugins/<delivery>/<project-path>/<id>/`, rooted
//! entirely on the child's own `$XDG_STATE_HOME`. Empirically, this task's own
//! `bl claim` (ambient `$XDG_STATE_HOME` = `~/.local/state`) materialized its
//! worktree at `~/.local/state/balls/plugins/bl-delivery/home/mark/dev/yog/
//! bl-c68f`. Env inheritance alone nests the worktrees; W2 threads the override
//! into the spawn.

use std::path::{Path, PathBuf};

use crate::xdg::Env;

pub mod marks;

/// The two world escape hatches `yog env` / `yog exec` (§8.4, §16.6 W6) — the
/// human counterpart to §16.4's agent tools. Pure argv → plan; `main.rs`
/// dispatches (print, or spawn-and-exit) before eframe.
pub mod hatch;

/// The phase-1 toolchain version gate (§16.4, §16.6 W5) — deleted by phase 2.
pub mod toolgate;

/// The `<yog-data-root>/world/` subtree (§16.2). Every path is computed from the
/// ambient anchor; nothing is stored. `root` and `tools` back materialization
/// (W3) and the phase-2 `lernie-tool-bl` shim territory (§16.4); the other two
/// are the override anchors [`compose`] layers into the world `Env`.
pub struct Layout {
    /// `<yog-data-root>/world` — the subtree root; one `rm -rf` severs the world.
    pub root: PathBuf,
    /// `world/lernie` → `LERNIE_HOME` (lernie config **and** data).
    pub lernie: PathBuf,
    /// `world/state` → `XDG_STATE_HOME` (balls state **and** yog's artifacts).
    pub state: PathBuf,
    /// `world/tools` — the phase-2 `lernie-tool-bl` territory (§16.4); no env var
    /// points here, so it nests by seeding (W3), not by an override.
    pub tools: PathBuf,
}

/// Compute the world layout from the ambient env's data-root anchor
/// ([`Env::yog_data_root`] = `$XDG_DATA_HOME/yog`). Pure; no IO. Delegates to
/// [`layout_under`], the `Env`-free core.
pub fn layout(ambient: &Env) -> Layout {
    layout_under(&ambient.yog_data_root())
}

/// The [`Env`]-free core [`layout`] delegates to: the world subtree under a
/// yog data-root anchor path (§16.2), pure path algebra. Seeding (W3) and the
/// start flow derive the world layout straight from `PlanInputs::yog_data_root`
/// through here, without re-snapshotting the process env into an [`Env`].
pub fn layout_under(yog_data_root: &Path) -> Layout {
    let root = yog_data_root.join("world");
    Layout {
        lernie: root.join("lernie"),
        state: root.join("state"),
        tools: root.join("tools"),
        root,
    }
}

/// `LERNIE_HOME` — nests lernie config **and** data onto [`Layout::lernie`].
const LERNIE_HOME: &str = "LERNIE_HOME";
/// `XDG_STATE_HOME` — nests balls state **and** yog's artifacts onto [`Layout::state`].
const XDG_STATE_HOME: &str = "XDG_STATE_HOME";

/// The world's fixed override set (§16.2) as `(var, nested-value)` pairs — the
/// **single source of truth** for which two vars nest and to what, consumed
/// both by [`compose`] (folded into the world `Env` so every §5.1 read nests)
/// and by every world spawn (layered onto each child through
/// [`Cli::resolve_in_world`](crate::cli_outbound::Cli::resolve_in_world), W2).
/// Reads-derive-through-compose and spawns-inherit-these are therefore one fact:
/// the dir yog watches and the dir a spawned `bl` writes are the same path.
/// `BRAZEN_CONFIG` is deliberately absent — brazen's config stays ambient,
/// shared like its credentials (§16.2).
pub fn overrides(ambient: &Env) -> Vec<(String, String)> {
    let l = layout(ambient);
    vec![
        (
            LERNIE_HOME.to_owned(),
            l.lernie.to_string_lossy().into_owned(),
        ),
        (
            XDG_STATE_HOME.to_owned(),
            l.state.to_string_lossy().into_owned(),
        ),
    ]
}

/// Compose the world `Env`: the ambient snapshot plus the two nesting
/// [`overrides`] (§16.2). `XDG_DATA_HOME`/`XDG_CACHE_HOME`/`BRAZEN_CONFIG` are
/// left ambient — the anchor and the brazen config/creds/cache share. The
/// result is itself an [`Env`]; pass it to any §5.1 fold to derive the nested
/// location.
pub fn compose(ambient: &Env) -> Env {
    let ov = overrides(ambient);
    let slice: Vec<(&str, &str)> = ov.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
    ambient.with_overrides(&slice)
}

pub mod seed;

#[cfg(test)]
mod tests;