OXYGENHUB
# 01320eefd505 spectra / crates / spectra-core / tests / common / mod.rs
4.3 KiB · Rust · last changed Channels, threads, messages, the inbox and full-text search, shared by every interface (#6) 2 days ago source blame history raw
1
//! A world for scenario tests: an owner, the instance, the default channels,
2
//! two projects with their channels, and agents the owner delegated to, all
3
//! recorded as `spectra init`, `project add` and `agent add` record them.
4
#![allow(dead_code)]
5
6
use oxy_core::durable::{write_atomic, write_atomic_private};
7
use oxy_core::identity::{Delegate, SecretKey};
8
use spectra_core::conversation::{default_channel_events, project_channel_event, Author, Signer};
9
use spectra_core::ids::now_ms;
10
use spectra_core::keys::KeyPair;
11
use spectra_core::ledger::Ledger;
12
use spectra_core::principals::{identity_payload, signed_event, Principal, Principals, DELEGATION_SCOPE};
13
use std::path::PathBuf;
14
15
pub struct World {
16
    pub dir: tempfile::TempDir,
17
    pub owner_home: PathBuf,
18
    pub ledger: Ledger,
19
    pub owner: KeyPair,
20
}
21
22
impl World {
23
    pub async fn new(projects: &[&str]) -> World {
24
        let dir = tempfile::tempdir().unwrap();
25
        let owner_home = dir.path().join("owner");
26
        let owner = KeyPair::create_home(&owner_home, "Andy McCall", "[email protected]").unwrap();
27
        let data = dir.path().join("data");
28
        std::fs::create_dir_all(&data).unwrap();
29
        let instance = KeyPair::instance(&data, "laptop").unwrap();
30
        let ledger = Ledger::open(&data.join("spectra.db"), Some(instance.clone())).await.unwrap();
31
        let mut first = vec![
32
            signed_event(&owner, "human", "identity.owner", identity_payload(&owner.identity)),
33
            signed_event(&instance, "system", "identity.instance", identity_payload(&instance.identity)),
34
        ];
35
        first.extend(default_channel_events());
36
        ledger.append(first).await.unwrap();
37
        for p in projects {
38
            let payload = serde_json::json!({ "name": p, "path": format!("/src/{p}"), "modules": [] });
39
            ledger.append(vec![signed_event(&owner, "human", "project.added", payload), project_channel_event(p)]).await.unwrap();
40
        }
41
        World { dir, owner_home, ledger, owner }
42
    }
43
44
    /// Delegate to a new agent and add it, as `spectra agent add` does.
45
    pub async fn agent(&mut self, name: &str, project: &str) -> KeyPair {
46
        let agent = KeyPair::generate(name, "");
47
        let keys = self.owner_home.join("keys");
48
        let (_, secret) = SecretKey::decode(&std::fs::read(keys.join(format!("{}.secret", self.owner.key.to_hex()))).unwrap()).unwrap();
49
        let mut d = self.owner.identity.delegates.clone();
50
        d.push(Delegate { key: agent.key, scope: DELEGATION_SCOPE, expires: None });
51
        let next = self.owner.identity.successor(d, None, now_ms());
52
        let k = next.hash();
53
        write_atomic_private(&keys.join(format!("{}.secret", k.to_hex())), &secret.encode(&k)).unwrap();
54
        write_atomic(&keys.join(format!("{}.identity", k.to_hex())), &next.encode()).unwrap();
55
        oxy_core::repo::Config::set_user(&self.owner_home.join("config.toml"), "author.key", &k.to_hex()).unwrap();
56
        self.owner = KeyPair::load_home(&self.owner_home).unwrap();
57
        let mut rotated = identity_payload(&self.owner.identity);
58
        rotated["chain"] = serde_json::json!([]);
59
        let mut p = identity_payload(&agent.identity);
60
        p["name"] = name.into();
61
        p["kind"] = "claude-code".into();
62
        p["project"] = project.into();
63
        p["owner_name"] = "Andy McCall".into();
64
        p["scopes"] = spectra_core::principals::DEFAULT_SCOPES.into();
65
        self.ledger
66
            .append(vec![
67
                signed_event(&self.owner, "human", "identity.rotated", rotated),
68
                signed_event(&self.owner, "human", "agent.added", p),
69
            ])
70
            .await
71
            .unwrap();
72
        agent
73
    }
74
75
    pub async fn principals(&self) -> Principals {
76
        Principals::load(&self.ledger).await.unwrap()
77
    }
78
79
    /// An author signing from the command line, as the CLI does.
80
    pub async fn author<'a>(&self, pair: &'a KeyPair) -> Author<'a> {
81
        let principal: Principal = self.principals().await.principal(&pair.key, now_ms()).unwrap();
82
        Author { principal, signer: Signer::Cli(pair) }
83
    }
84
85
    pub async fn verify_clean(&self) {
86
        let v = self.ledger.verify(&mut Principals::default()).await.unwrap();
87
        assert!(v.clean(), "{:?}", v.findings);
88
        assert!(self.ledger.check().await.unwrap().is_empty(), "projections drifted");
89
    }
90
}