yog 0.0.1

yog: a balls-oriented session manager for lernie loops (egui frontend)
Documentation
use super::*;
use crate::cli_outbound::Cli;
use crate::test_support::spawn_guard;
use crate::xdg::Os;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use tempfile::tempdir;

/// Ambient snapshot: `HOME` + the XDG anchors, with the two vars the world
/// overrides deliberately pre-set to *other* values — so a passing derivation
/// proves the world value **replaced** them, not merely filled a gap.
/// `BRAZEN_CONFIG` is pre-set to prove the world *keeps* it (left ambient,
/// §16.2); `USER` rides along to prove a non-overridden var survives the
/// composition.
fn ambient() -> Env {
    Env::from_pairs([
        ("HOME", "/h"),
        ("XDG_DATA_HOME", "/d"),
        ("XDG_CACHE_HOME", "/c"),
        ("LERNIE_HOME", "/ambient/lernie"),
        ("XDG_STATE_HOME", "/ambient/state"),
        ("BRAZEN_CONFIG", "/ambient/brazen.toml"),
        ("USER", "alice"),
    ])
}

/// A fold `fn(&Env) -> PathBuf` and the nested path it must yield through the
/// composed world `Env`.
type Case = (fn(&Env) -> PathBuf, &'static str);

#[test]
fn overrides_win_and_every_substrate_fold_nests() {
    let world = compose(&ambient());
    let cases: &[Case] = &[
        // Each override seen through the fold that reads it — yielding the
        // world value, NOT the ambient one it replaced.
        (Env::lernie_config_root, "/d/yog/world/lernie"),
        (Env::lernie_data_root, "/d/yog/world/lernie"),
        (Env::balls_state_root, "/d/yog/world/state/balls"),
        (Env::balls_clones_dir, "/d/yog/world/state/balls/clones"),
        // yog's own state root moves under the world (§16.2 decision).
        (Env::yog_state_root, "/d/yog/world/state/yog"),
        (Env::yog_stage_root, "/d/yog/world/state/yog/stage"),
    ];
    for &(fold, expect) in cases {
        assert_eq!(fold(&world), PathBuf::from(expect));
    }
    // yog's two artifacts themselves, re-derived through the world Env.
    assert_eq!(
        world.yog_state_root().join("ui.json"),
        PathBuf::from("/d/yog/world/state/yog/ui.json")
    );
    assert_eq!(
        world.yog_state_root().join("ops.jsonl"),
        PathBuf::from("/d/yog/world/state/yog/ops.jsonl")
    );
}

#[test]
fn shared_folds_and_anchor_stay_ambient_under_the_world() {
    let amb = ambient();
    let world = compose(&amb);
    // Brazen's config reads $BRAZEN_CONFIG, left ambient (§16.2 — the shared
    // host bz's config): the world Env yields the SAME path as the ambient Env,
    // the pre-set value un-replaced.
    assert_eq!(world.brazen_config_path(), amb.brazen_config_path());
    assert_eq!(
        world.brazen_config_path(),
        PathBuf::from("/ambient/brazen.toml")
    );
    // Brazen creds + model cache read XDG_DATA_HOME / XDG_CACHE_HOME, both left
    // ambient — so the world Env yields the SAME paths as the ambient Env, on
    // both OS arms.
    for os in [Os::Linux, Os::MacOs] {
        assert_eq!(
            world.brazen_credentials_dir(os),
            amb.brazen_credentials_dir(os)
        );
        assert_eq!(
            world.brazen_models_cache_dir(os),
            amb.brazen_models_cache_dir(os)
        );
    }
    assert_eq!(
        world.brazen_credentials_dir(Os::Linux),
        PathBuf::from("/d/brazen/credentials")
    );
    assert_eq!(
        world.brazen_models_cache_dir(Os::Linux),
        PathBuf::from("/c/brazen/models")
    );
    // The anchor is self-consistent: re-deriving it through the world Env yields
    // the same yog data root, so `layout(world) == layout(ambient)` (§16.2).
    assert_eq!(world.yog_data_root(), amb.yog_data_root());
    assert_eq!(layout(&world).root, layout(&amb).root);
    // A non-overridden var survives the composition untouched.
    assert_eq!(world.user(), Some("alice".to_owned()));
}

#[test]
fn layout_names_the_world_subtree() {
    let l = layout(&ambient());
    assert_eq!(l.root, PathBuf::from("/d/yog/world"));
    assert_eq!(l.lernie, PathBuf::from("/d/yog/world/lernie"));
    assert_eq!(l.state, PathBuf::from("/d/yog/world/state"));
    assert_eq!(l.tools, PathBuf::from("/d/yog/world/tools"));
}

/// The §16.4 agent-correctness invariant: **reads and spawns agree.** yog
/// watches its balls clones dir through the *composed* world `Env` (a read);
/// every child spawns with the *standing* world [`overrides`] (§16.6 W2). A
/// recorder `bl` spawned in the world reports the `XDG_STATE_HOME` it received —
/// which must be the world state dir, so the clones dir it would write
/// (`$XDG_STATE_HOME/balls/clones`) is byte-for-byte the one yog watches. If the
/// two derivations ever drifted, an agent closing a ball would hit a *different*
/// clone than yog renders; this test is the guard.
#[test]
fn watched_clones_dir_equals_the_dir_a_world_spawned_bl_writes() {
    let g = spawn_guard();
    let data = tempdir().unwrap();
    let bin = tempdir().unwrap();
    let log = bin.path().join("state");
    // Ambient env anchored on a real temp data root; the world nests under it.
    let data_s = data.path().to_string_lossy().into_owned();
    let amb = Env::from_pairs([("HOME", "/h"), ("XDG_DATA_HOME", data_s.as_str())]);
    let world = compose(&amb);
    let ov = overrides(&amb);
    // A recorder `bl` that reports the XDG_STATE_HOME it was spawned with (printf
    // builtin only — no fork to race the coverage ptrace engine).
    let path = bin.path().join("bl");
    fs::write(
        &path,
        format!(
            "#!/bin/sh\nprintf '%s' \"$XDG_STATE_HOME\" > '{}'\n",
            log.display()
        ),
    )
    .unwrap();
    let mut perms = fs::metadata(&path).unwrap().permissions();
    perms.set_mode(0o755);
    fs::set_permissions(&path, perms).unwrap();
    // Spawn it in the world (standing overrides), then drain to completion.
    let stream = Cli::new(path).with_env(ov).run(&[]).unwrap();
    drop(g);
    for _ in stream {}
    // The child was spawned with XDG_STATE_HOME = the world state dir …
    let child_state = fs::read_to_string(&log).unwrap();
    assert_eq!(Path::new(&child_state), layout(&amb).state);
    // … so the clones dir it would write is exactly the one yog watches (derived
    // through the composed world `Env`). Reads and spawns agree.
    let spawned_clones = Path::new(&child_state).join("balls").join("clones");
    assert_eq!(spawned_clones, world.balls_clones_dir());
}