| 1 |
|
| 2 |
|
| 3 |
|
| 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 |
|
| 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 |
|
| 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 |
} |