use super::*;
use crate::api::scripted::ScriptedApi;
use crate::provider::Owner;
use crate::provider::ProviderId;
use crate::provider::claude::api::Renewed;
use crate::provider::claude::paths as claude;
use crate::service::Permit;
use crate::host::memory::MemoryHost;
use crate::state::Account;
use crate::time::{Clock, FixedClock};
use serde_json::json;
use std::collections::HashSet;
use std::sync::Arc;
use std::sync::mpsc::RecvTimeoutError;
pub(crate) const NOW: i64 = 1_760_000_000;
pub(crate) const POINTS: [&str; 6] = [
"switch.journal_written",
"switch.park_stored",
"switch.park_recorded",
"switch.installed",
"switch.recorded",
"switch.config_updated",
];
pub(crate) const FOLDER_POINTS: [&str; 30] = [
"leave.recorded",
"leave.keys_kept",
"leave.keys_out",
"leave.jar_out",
"leave.item_out",
"leave.verified",
"leave.done",
"arrive.recorded",
"arrive.aside_kept",
"arrive.keys_cleared",
"arrive.item_aside",
"arrive.item_in",
"arrive.jar_in",
"arrive.keys_in",
"arrive.verified",
"arrive.done",
"settings.temp_written",
"settle.keys_back",
"settle.aside_named",
"settle.aside_kept",
"settle.keys_cleared",
"settle.item_back",
"settle.item_aside",
"settle.saved",
"give_up.aside_named",
"give_up.item_aside",
"give_up.done",
"start.aside_kept",
"start.keys_cleared",
"start.saved",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Tool {
Claude,
Codex,
}
impl Tool {
pub(crate) fn of(which: ProviderId) -> Tool {
match which {
ProviderId::Claude => Tool::Claude,
ProviderId::Codex => Tool::Codex,
ProviderId::Desktop => panic!("{which} keeps no login document"),
}
}
}
pub(crate) struct Machine {
pub(crate) ctx: Context,
pub(crate) mem: Arc<MemoryHost>,
pub(crate) api: Arc<ScriptedApi>,
root: PathBuf,
pub(crate) service: String,
pub(crate) which: ProviderId,
}
impl Machine {
pub(crate) fn ctx_home(&self) -> PathBuf {
self.root.clone()
}
pub(crate) fn live(&self) -> Option<Value> {
provider::document(self.which)
.read_live(&self.ctx)
.ok()
.flatten()
.map(|credential| credential.raw)
}
pub(crate) fn sign_in(&self, document: &Value) {
let live = provider::document(self.which)
.live(&self.ctx)
.expect("a store to write to");
store::write_raw(
&live.chain,
Permit::for_a_test(),
&live.service,
&document.to_string(),
)
.expect("the live login is written");
}
pub(crate) fn key(&self, label: &str) -> Key {
Key::new(self.which, label)
}
pub(crate) fn fault_live(&self, fault: store::memory::Fault) {
let (store, service) = self.live_store();
store.fault(&service, fault);
}
pub(crate) fn live_store(&self) -> (Arc<store::memory::MemoryStore>, String) {
let live = provider::document(self.which)
.live(&self.ctx)
.expect("a live store");
let store = match Tool::of(self.which) {
Tool::Claude => Arc::clone(self.mem.live()),
Tool::Codex => self
.mem
.file_at(provider::codex::paths::auth_file(&self.ctx)),
};
(store, live.service)
}
}
impl Drop for Machine {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.root);
}
}
pub(crate) fn oauth(refresh: &str, expires_in_days: i64) -> Value {
json!({
"accessToken": format!("access-{refresh}"),
"refreshToken": refresh,
"expiresAt": (NOW + 3600) * 1000,
"refreshTokenExpiresAt": (NOW + expires_in_days * 86_400) * 1000,
"scopes": ["user:profile", "user:inference"],
})
}
pub(crate) fn document(refresh: &str) -> Value {
json!({
"claudeAiOauth": oauth(refresh, 30),
"organizationUuid": "org-of-the-outgoing-account",
"mcpOAuth": {"some-server": {"token": "unrelated"}},
})
}
pub(crate) fn lapsed(refresh: &str) -> Value {
let mut login = document(refresh);
login["claudeAiOauth"]["expiresAt"] = json!((NOW - 60) * 1000);
login
}
pub(crate) fn owner(uuid: &str) -> Owner {
Owner {
account_uuid: uuid.into(),
email: format!("{uuid}@example.com"),
organization_uuid: format!("org-{uuid}"),
}
}
pub(crate) fn machine(name: &str) -> Machine {
let root = std::env::temp_dir().join(format!(
"pitboard-crash-{name}-{}-{:?}",
std::process::id(),
std::thread::current().id()
));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).expect("a scratch home");
let mem = MemoryHost::new();
let api = ScriptedApi::new();
let ctx = Context::new(root.clone())
.with_pitboard_home(root.join(".pitboard"))
.with_memory_stores(Arc::clone(&mem))
.with_scripted_api(Arc::clone(&api))
.with_clock(Arc::new(FixedClock::at(NOW)) as Arc<dyn Clock>)
.with_lock_waiting(1, std::time::Duration::from_millis(1));
let service = claude::live_service(&ctx);
mem.live()
.plant(&service, &document("here-refresh").to_string());
std::fs::write(
root.join(".claude.json"),
json!({
"oauthAccount": {
"accountUuid": "here",
"emailAddress": "here@example.com",
"organizationUuid": "org-here",
},
"cachedArtifactRoster": {"org": "org-here"},
"numStartups": 7,
})
.to_string(),
)
.expect("a config file");
api.owned_by("access-here-refresh", owner("here"));
api.owned_by("access-there-refresh", owner("there"));
std::fs::create_dir_all(root.join(".pitboard")).expect("a Pitboard home");
let parked_service = park::reserve(&ctx, Permit::for_a_test(), "there").expect("a free name");
let parked = park::store_at(
&ctx,
Permit::for_a_test(),
provider::document(crate::provider::ProviderId::Claude),
&parked_service,
&oauth("there-refresh", 30),
)
.expect("parked");
let mut state = State::default();
state.accounts.push(account("here", "here", None));
state.accounts.push(account("there", "there", Some(parked)));
record_in_use(
&ctx,
&mut state,
ProviderId::Claude,
&document("here-refresh"),
);
state::save(&ctx, Permit::for_a_test(), &state).expect("saved");
Machine {
ctx,
mem,
api,
root,
service,
which: ProviderId::Claude,
}
}
pub(crate) fn signed_in_outside(name: &str) -> Machine {
let m = machine(name);
m.api
.owned_by("access-elsewhere-refresh", owner("elsewhere"));
let mut state = state::load(&m.ctx).expect("state");
state.accounts.push(account("elsewhere", "elsewhere", None));
state::save(&m.ctx, Permit::for_a_test(), &state).expect("saved");
m.sign_in(&document("elsewhere-refresh"));
config_names(&m, "elsewhere");
m
}
pub(crate) fn config_names(m: &Machine, who: &str) {
let path = m.root.join(".claude.json");
let mut config: Value =
serde_json::from_str(&std::fs::read_to_string(&path).expect("a config")).expect("JSON");
let owner = owner(who);
config["oauthAccount"] = json!({
"accountUuid": owner.account_uuid,
"emailAddress": owner.email,
"organizationUuid": owner.organization_uuid,
});
std::fs::write(&path, config.to_string()).expect("the config is written");
}
pub(crate) fn audit_lines(m: &Machine, verb: &str) -> Vec<(String, String)> {
crate::audit::read(&m.ctx, 100)
.into_iter()
.filter(|entry| entry.verb == verb)
.map(|entry| (entry.subject, entry.outcome))
.collect()
}
pub(crate) fn state_file(m: &Machine) -> (Vec<u8>, std::time::SystemTime) {
let path = home::dir(&m.ctx).join("state.json");
let written = std::fs::metadata(&path)
.and_then(|meta| meta.modified())
.expect("a state file");
(std::fs::read(&path).expect("a state file"), written)
}
const OPENAI: &str = "https://api.openai.com/auth";
pub(crate) fn codex_login(who: &str, refresh: &str) -> Value {
json!({
"auth_mode": "chatgpt",
"OPENAI_API_KEY": null,
"tokens": {
"id_token": provider::jwt::unsigned(&json!({
"email": format!("{who}@example.com"),
"exp": NOW + 3600,
OPENAI: {
"chatgpt_account_id": who,
"chatgpt_user_id": format!("user-{who}"),
"chatgpt_plan_type": "pro",
},
})),
"access_token": codex_access(refresh),
"refresh_token": refresh,
"account_id": who,
},
"last_refresh": "2025-10-09T08:00:00Z",
})
}
pub(crate) fn codex_id(who: &str) -> String {
format!("{who}_user-{who}")
}
pub(crate) fn codex_access(refresh: &str) -> String {
provider::jwt::unsigned(&json!({"exp": NOW + 10 * 86_400, "for": refresh}))
}
pub(crate) fn codex_account(label: &str, who: &str, parked: Option<Park>) -> Account {
Account {
last_used_at: None,
replaced_at: None,
plan: Some("Pro".into()),
label: label.into(),
id: codex_id(who),
account_uuid: codex_id(who),
email: format!("{who}@example.com"),
parked,
detail: state::Detail::Codex {
workspace_id: Some(who.into()),
},
}
}
pub(crate) use crate::desktop_testing::{HOME, WORK};
pub(crate) const STRANGER: crate::desktop_testing::Person = crate::desktop_testing::Person {
uuid: "0b7e2c44-2222-4d1f-8a35-91c0de77aa01",
session: "stranger-session",
};
pub(crate) fn data(m: &Machine) -> PathBuf {
m.ctx_home().join("claude/data")
}
pub(crate) fn settled(m: &Machine) -> Settled {
settle(&m.ctx, Permit::for_a_test(), Some(ProviderId::Desktop))
.expect("nothing of a document to recover")
.0
}
pub(crate) fn tree(root: &std::path::Path) -> Vec<(PathBuf, Option<Vec<u8>>)> {
fn walk(
root: &std::path::Path,
dir: &std::path::Path,
out: &mut Vec<(PathBuf, Option<Vec<u8>>)>,
) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
let within = path
.strip_prefix(root)
.expect("under the root")
.to_path_buf();
let kind = std::fs::symlink_metadata(&path).expect("metadata");
if kind.is_symlink() {
let target = std::fs::read_link(&path).expect("a link");
out.push((within, Some(target.to_string_lossy().as_bytes().to_vec())));
} else if kind.is_dir() {
out.push((within, None));
walk(root, &path, out);
} else {
out.push((within, Some(std::fs::read(&path).expect("a file"))));
}
}
}
let mut found = Vec::new();
walk(root, root, &mut found);
found.sort();
found
}
pub(crate) type Stat = (PathBuf, Option<u64>, u64, Option<std::time::SystemTime>);
pub(crate) type Snapshot = (
Vec<u8>,
Vec<u8>,
Vec<(PathBuf, Option<Vec<u8>>)>,
Vec<String>,
Vec<Stat>,
);
pub(crate) fn stats(root: &std::path::Path) -> Vec<Stat> {
let stat = |within: PathBuf| {
let found = std::fs::symlink_metadata(root.join(&within)).ok();
let inode = crate::host::fs::testing::inode(&root.join(&within)).ok();
let size = found.as_ref().map_or(0, std::fs::Metadata::len);
(
within,
inode,
size,
found.and_then(|kind| kind.modified().ok()),
)
};
let mut found = vec![stat(PathBuf::new())];
found.extend(tree(root).into_iter().map(|(within, _)| stat(within)));
found
}
pub(crate) fn snapshot(m: &Machine) -> Snapshot {
let home = home::dir(&m.ctx);
(
std::fs::read(home.join("state.json")).unwrap_or_default(),
std::fs::read(home.join("pending")).unwrap_or_default(),
tree(&data(m)),
store::folder::list(&m.ctx)
.expect("parks")
.unwrap_or_default(),
stats(&data(m)),
)
}
pub(crate) fn unchanged(m: &Machine, before: &Snapshot, why: &str) {
let now = snapshot(m);
assert!(now.0 == before.0, "{why}: state.json changed");
assert!(now.1 == before.1, "{why}: `pending` changed");
assert!(now.2 == before.2, "{why}: Claude's folder changed");
assert!(now.3 == before.3, "{why}: `parks` changed");
assert!(
now.4 == before.4,
"{why}: an entry of Claude's folder was touched"
);
}
pub(crate) fn desktop_machine(name: &str) -> Machine {
let root = std::env::temp_dir().join(format!(
"pitboard-desktop-{name}-{}-{:?}",
std::process::id(),
std::thread::current().id()
));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).expect("a scratch home");
let mem = MemoryHost::new();
let api = ScriptedApi::new();
let ctx = Context::new(root.clone())
.with_pitboard_home(root.join(".pitboard"))
.with_memory_stores(Arc::clone(&mem))
.with_scripted_api(Arc::clone(&api))
.with_clock(Arc::new(FixedClock::at(NOW)) as Arc<dyn Clock>)
.with_awaited_every(std::time::Duration::from_millis(2))
.with_scratch_claude(root.join("claude"));
let m = Machine {
ctx,
mem,
api,
root,
service: String::new(),
which: ProviderId::Desktop,
};
m.claude().signed_in_as(&WORK);
let (settled, _) = settle(&m.ctx, Permit::for_a_test(), Some(ProviderId::Desktop))
.expect("nothing to recover");
enroll(settled, &m.key("work"), None).expect("`work` enrolled");
m
}
pub(crate) fn desktop_pair(name: &str) -> Machine {
let m = desktop_machine(name);
sign_in_inside(&m, "home", &HOME).expect("`home` added, `work` parked");
let (settled, _) = settle(&m.ctx, Permit::for_a_test(), Some(ProviderId::Desktop))
.expect("nothing to recover");
switch(settled, &m.key("work")).expect("back to `work`");
m
}
pub(crate) fn sign_in_inside(
m: &Machine,
label: &str,
who: &crate::desktop_testing::Person,
) -> Result<(Enrolled, Vec<Warning>)> {
let typed = format!("desktop/{label}");
let pitboard = crate::service::Pitboard::new(m.ctx.clone());
let watched = pitboard
.sign_in_watched(&typed)
.map_err(|failed| failed.error)?;
let said = watched.said();
let claude = m.claude();
claude.opens();
let wait = std::time::Duration::from_secs(10);
loop {
match said.within(wait) {
Ok(line) if line == "Claude is open.\n" => break,
Ok(_) => {}
Err(why) => panic!("the wait never said Claude is open: {why}"),
}
}
claude.signs_in(who);
claude.reports();
claude.quits();
loop {
match said.within(wait) {
Ok(_) => {}
Err(RecvTimeoutError::Disconnected) => break,
Err(RecvTimeoutError::Timeout) => panic!("the wait never ended once Claude quit"),
}
}
let login = watched.finish()?;
pitboard
.enroll_signed_in(&typed, login)
.map(|done| (done.value, done.warnings))
.map_err(|failed| failed.error)
}
pub(crate) fn as_a_dead_runs(m: &Machine) {
let data = m.root.join("claude/data");
for entry in std::fs::read_dir(&data).into_iter().flatten().flatten() {
let name = entry.file_name().to_string_lossy().into_owned();
if crate::atomic::temp_owner(&name) == Some(std::process::id()) {
let dead = name.replace(
&format!(".{}.", std::process::id()),
&format!(".{}.", i32::MAX - 1),
);
std::fs::rename(entry.path(), data.join(dead)).expect("renamed");
}
}
}
pub(crate) fn whereabouts(m: &Machine, who: &crate::desktop_testing::Person) -> Vec<String> {
sessions(m)
.into_iter()
.filter(|(_, login)| *login == who.login())
.map(|(place, _)| place)
.collect()
}
fn sessions(m: &Machine) -> Vec<(String, String)> {
let LoginKind::Folder(app) = provider::of(m.which) else {
return Vec::new();
};
let mut places = vec![("Claude".to_string(), m.root.join("claude/data/Cookies"))];
for name in store::folder::list(&m.ctx)
.ok()
.flatten()
.unwrap_or_default()
{
let jar = store::folder::item(&m.ctx, &name, app.items()[0]);
places.push((name, jar));
}
places
.into_iter()
.filter(|(_, jar)| jar.exists())
.filter_map(|(place, jar)| Some((place, app.session(&m.ctx, &jar).ok()??.login)))
.collect()
}
pub(crate) fn rested(m: &Machine, after: &str) {
let LoginKind::Folder(app) = provider::of(m.which) else {
panic!("{after}: a folder tool");
};
recover(m).unwrap_or_else(|e| panic!("{after}: {e}"));
let state = state::load(&m.ctx).expect("state");
assert!(
state.moving(m.which).is_none(),
"{after}: a move still waits"
);
let mut seen = HashSet::new();
for (place, login) in sessions(m) {
assert!(
seen.insert(login),
"{after}: the session in {place} is also somewhere else"
);
}
let data = m.root.join("claude/data");
for entry in std::fs::read_dir(&data).into_iter().flatten().flatten() {
let name = entry.file_name().to_string_lossy().into_owned();
assert!(
crate::atomic::temp_owner(&name).is_none(),
"{after}: {name} was left in Claude's folder"
);
}
let pending = std::fs::read_to_string(home::dir(&m.ctx).join("pending")).unwrap_or_default();
for line in pending.lines() {
let name = line.strip_prefix("folder ").unwrap_or(line);
assert!(!state.names(name), "{after}: `pending` still lists {name}");
}
if !data.exists() {
return;
}
let settings = app.settings(&data).expect("settings");
let jar = data.join(app.items()[0]);
if !jar.exists() {
let left: Vec<String> = app
.items()
.iter()
.filter(|item| data.join(item).exists())
.map(|item| (*item).to_string())
.collect();
let listed = store::folder::list(&m.ctx)
.ok()
.flatten()
.unwrap_or_default()
.into_iter()
.filter_map(|name| store::folder::manifest(&m.ctx, &name).ok().flatten())
.any(|manifest| !left.is_empty() && manifest.left_in_folder == left);
assert!(
left.is_empty() && settings.account_keys(app).is_empty() || listed,
"{after}: Claude's folder has no jar and holds {left:?}"
);
return;
}
let Some(session) = app.session(&m.ctx, &jar).expect("the jar reads") else {
return;
};
let named = settings.named(app);
for account in state.accounts.iter().filter(|a| a.provider() == m.which) {
let holds_it = state
.in_use(m.which)
.is_some_and(|record| record.login == session.login)
&& state
.account_in_use(m.which)
.is_some_and(|a| a.id == account.id);
if holds_it {
assert_eq!(
named.as_deref(),
Some(account.account_uuid.as_str()),
"{after}: {}'s session beside another name",
account.label
);
}
}
}
impl Machine {
pub(crate) fn claude(&self) -> crate::desktop_testing::FakeClaude {
crate::desktop_testing::FakeClaude::on(
Arc::clone(&self.mem),
&self.ctx,
crate::desktop_testing::JarAs::StandIn,
)
}
}
pub(crate) fn codex_machine(name: &str) -> Machine {
let root = std::env::temp_dir().join(format!(
"pitboard-crash-codex-{name}-{}-{:?}",
std::process::id(),
std::thread::current().id()
));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(root.join(".codex")).expect("a scratch codex home");
let mem = MemoryHost::new();
let api = ScriptedApi::new();
let ctx = Context::new(root.clone())
.with_pitboard_home(root.join(".pitboard"))
.with_codex_home(root.join(".codex").to_string_lossy().into_owned())
.with_memory_stores(Arc::clone(&mem))
.with_scripted_api(Arc::clone(&api))
.with_clock(Arc::new(FixedClock::at(NOW)) as Arc<dyn Clock>);
let machine = Machine {
ctx,
mem,
api,
root,
service: String::new(),
which: ProviderId::Codex,
};
machine.sign_in(&codex_login("here", "here-refresh"));
for refresh in ["here-refresh", "there-refresh"] {
machine.api.using(
&codex_access(refresh),
crate::usage::Snapshot {
windows: Vec::new(),
observed_at: Some(NOW),
answered_at: Some(NOW),
lists_every_limit: false,
source: crate::usage::Source::Live,
},
);
}
std::fs::create_dir_all(machine.root.join(".pitboard")).expect("a Pitboard home");
let parked_service =
park::reserve(&machine.ctx, Permit::for_a_test(), &codex_id("there")).expect("a free name");
let parked = park::store_at(
&machine.ctx,
Permit::for_a_test(),
provider::document(ProviderId::Codex),
&parked_service,
&codex_login("there", "there-refresh"),
)
.expect("parked");
let mut state = State::default();
state.accounts.push(codex_account("here", "here", None));
state
.accounts
.push(codex_account("there", "there", Some(parked)));
record_in_use(
&machine.ctx,
&mut state,
ProviderId::Codex,
&codex_login("here", "here-refresh"),
);
state::save(&machine.ctx, Permit::for_a_test(), &state).expect("saved");
machine
}
pub(crate) fn record_in_use(ctx: &Context, state: &mut State, which: ProviderId, login: &Value) {
let here = state
.get(&Key::new(which, "here"))
.expect("`here` is enrolled");
let found = InUse {
owner: Some(here.owner()),
login: provider::document(which).fingerprint(login),
known_at: NOW,
named: in_use::named(ctx, which),
};
state.in_use.insert(which.code().to_string(), found);
}
pub(crate) fn login_of(m: &Machine, who: &str, refresh: &str) -> Value {
match Tool::of(m.which) {
Tool::Claude => {
m.api.owned_by(&format!("access-{refresh}"), owner(who));
document(refresh)
}
Tool::Codex => codex_login(who, refresh),
}
}
pub(crate) fn signed_in(m: &Machine, who: &str, refresh: &str) -> SignIn {
planted(
&m.ctx,
Permit::for_a_test(),
provider::document(m.which),
login_of(m, who, refresh),
)
.expect("a sign-in")
}
pub(crate) fn renews(m: &Machine, refresh: &str, renewed: &str) {
match Tool::of(m.which) {
Tool::Claude => {
m.api.renews(
refresh,
Renewed {
access_token: format!("access-{renewed}"),
refresh_token: Some(renewed.into()),
expires_in: 3600,
refresh_token_expires_in: Some(30 * 86_400),
scopes: None,
at: None,
},
);
}
Tool::Codex => {
m.api.codex_renews(
refresh,
provider::codex::api::Fresh {
id_token: None,
access_token: Some(codex_access(renewed)),
refresh_token: Some(renewed.into()),
at: Some(NOW),
},
);
}
}
}
#[derive(Default)]
pub(crate) struct Session {
pub(crate) waited: std::cell::Cell<bool>,
saving: std::cell::RefCell<Option<(String, Value)>>,
}
impl Session {
pub(crate) fn new() -> std::rc::Rc<Session> {
std::rc::Rc::new(Session::default())
}
}
pub(crate) fn lock_dir(target: &std::path::Path) -> PathBuf {
PathBuf::from(format!("{}.lock", target.display()))
}
pub(crate) fn write_target(m: &Machine) -> PathBuf {
provider::document(ProviderId::Claude)
.write_lock(&m.ctx)
.expect("Claude Code takes one")
}
pub(crate) fn takes_the_write_lock(m: &Machine) -> impl FnOnce() + 'static {
let writing = lock_dir(&write_target(m));
move || {
let parent = writing.parent().expect("the storage directory");
std::fs::create_dir_all(parent).expect("the storage directory is made");
std::fs::create_dir(&writing).expect("the write lock is free");
}
}
pub(crate) fn lets_go_of_the_write_lock(m: &Machine) -> impl FnOnce() + 'static {
let writing = lock_dir(&write_target(m));
move || std::fs::remove_dir(&writing).expect("the session lets go")
}
pub(crate) fn renews_meanwhile(
m: &Machine,
store: Arc<store::memory::MemoryStore>,
session: &std::rc::Rc<Session>,
) -> impl FnOnce() + 'static {
let (service, api) = (m.service.clone(), Arc::clone(&m.api));
let refreshing = lock_dir(&claude::refresh_lock(&m.ctx));
let writing = lock_dir(&write_target(m));
let session = std::rc::Rc::clone(session);
move || {
let parent = refreshing.parent().expect("the storage directory");
std::fs::create_dir_all(parent).expect("the storage directory is made");
match std::fs::create_dir(&refreshing) {
Ok(()) => {}
Err(held) if held.kind() == std::io::ErrorKind::AlreadyExists => {
session.waited.set(true);
return;
}
Err(error) => panic!("the refresh lock: {error}"),
}
let held = store.peek(&service).expect("a login is stored");
let mut login: Value = serde_json::from_str(&held).expect("JSON");
let sent = login["claudeAiOauth"]["refreshToken"]
.as_str()
.expect("a refresh token")
.to_string();
api.renew_trouble(&sent, crate::api::scripted::Trouble::InvalidGrant);
login["claudeAiOauth"]["refreshToken"] = json!(format!("{sent}-by-a-session"));
login["claudeAiOauth"]["accessToken"] = json!(format!("access-{sent}-by-a-session"));
login["claudeAiOauth"]["expiresAt"] = json!((NOW + 3600) * 1000);
*session.saving.borrow_mut() = Some((sent, login));
if std::fs::create_dir(&writing).is_ok() {
save(&store, &service, &session);
let _ = std::fs::remove_dir(&writing);
let _ = std::fs::remove_dir(&refreshing);
}
}
}
pub(crate) fn saves(m: &Machine, store: &store::memory::MemoryStore, session: &Session) {
if session.saving.borrow().is_none() {
return;
}
save(store, &m.service, session);
let _ = std::fs::remove_dir(lock_dir(&claude::refresh_lock(&m.ctx)));
}
fn save(store: &store::memory::MemoryStore, service: &str, session: &Session) {
let Some((sent, login)) = session.saving.borrow_mut().take() else {
return;
};
let holds = store
.peek(service)
.and_then(|held| serde_json::from_str::<Value>(&held).ok())
.is_some_and(|held| held["claudeAiOauth"]["refreshToken"] == sent.as_str());
if holds {
store.plant(service, &login.to_string());
}
}
pub(crate) fn window(kind: &str, percent: f64) -> crate::usage::Window {
crate::usage::Window {
kind: kind.into(),
scope: None,
percent,
resets_at: Some(NOW + 3600),
is_active: true,
severity: None,
length_seconds: crate::usage::anthropic_window_length(kind),
}
}
pub(crate) fn usage_answer(percent: f64) -> Value {
let resets_at = jiff::Timestamp::from_second(NOW + 3600).expect("a time");
json!({"limits": [{"kind": "session", "percent": percent, "resets_at": resets_at.to_string()}]})
}
pub(crate) fn cache_usage(m: &Machine, answer: Value) {
let path = m.root.join(".claude.json");
let mut config: Value =
serde_json::from_str(&std::fs::read_to_string(&path).expect("a config")).expect("JSON");
config["cachedUsageUtilization"] = json!({
"fetchedAtMs": NOW * 1000,
"accountUuid": "here",
"utilization": answer,
});
std::fs::write(&path, config.to_string()).expect("the config is written");
}
pub(crate) fn account(label: &str, uuid: &str, parked: Option<Park>) -> Account {
Account {
last_used_at: None,
replaced_at: None,
plan: None,
label: label.into(),
id: uuid.into(),
account_uuid: uuid.into(),
email: format!("{uuid}@example.com"),
parked,
detail: state::Detail::Claude {
organization_uuid: format!("org-{uuid}"),
oauth_account: json!({
"accountUuid": uuid,
"emailAddress": format!("{uuid}@example.com"),
"organizationUuid": format!("org-{uuid}"),
}),
},
}
}
pub(crate) fn in_organisation(label: &str, who: &str, org: &str, parked: Option<Park>) -> Account {
let owner = Owner {
organization_uuid: org.into(),
..owner(who)
};
Account {
last_used_at: None,
replaced_at: None,
plan: None,
label: label.into(),
id: state::new_id(ProviderId::Claude, &owner),
account_uuid: owner.account_uuid,
email: owner.email,
parked,
detail: state::Detail::Claude {
organization_uuid: owner.organization_uuid,
oauth_account: json!({"accountUuid": who, "organizationUuid": org}),
},
}
}
pub(crate) fn hold(m: &Machine, after: &str) {
let state = state::load(&m.ctx)
.unwrap_or_else(|e| panic!("{after}: the state file must still parse, got {e}"));
match provider::of(m.which) {
LoginKind::Document(_) => {}
LoginKind::Folder(app) => {
return hold_folder(m, after, &state, app, Holds::all());
}
}
let named: HashSet<&str> = state
.accounts
.iter()
.filter_map(|a| a.parked.as_ref())
.map(|p| p.service.as_str())
.chain(state.discarded.iter().map(|d| d.service.as_str()))
.collect();
for service in m.mem.vault().services() {
assert!(
named.contains(service.as_str()),
"{after}: {service} holds a login nothing on this machine names"
);
}
let tool = provider::document(m.which);
let mut seen: HashSet<String> = HashSet::new();
let mut fingerprints = Vec::new();
for service in m.mem.vault().services() {
let raw = m.mem.vault().peek(&service).expect("just listed");
let value: Value = serde_json::from_str(&raw).expect("a park is JSON");
fingerprints.push((service, tool.fingerprint(&value)));
}
let live = m.live();
if let Some(document) = &live {
fingerprints.push(("the live slot".into(), tool.fingerprint(document)));
}
for (place, fingerprint) in fingerprints {
assert!(
seen.insert(fingerprint.clone()),
"{after}: the login in {place} is also somewhere else"
);
}
let live_uuid = live
.and_then(|document| {
tool.identify(&m.ctx, &provider::Credential::new(m.which, document))
.ok()
})
.map(|found| found.account_uuid);
for a in &state.accounts {
if Some(&a.account_uuid) == live_uuid.as_ref() {
continue;
}
if let Some(park) = &a.parked {
assert!(
park.restorable_at(NOW),
"{after}: {} holds a login that can no longer be restored",
a.label
);
assert!(
m.mem.vault().peek(&park.service).is_some(),
"{after}: {} names a park that is not in the vault",
a.label
);
}
}
}
pub(crate) fn hold_where_deleted(m: &Machine, after: &str) {
let state = state::load(&m.ctx)
.unwrap_or_else(|e| panic!("{after}: the state file must still parse, got {e}"));
let LoginKind::Folder(app) = provider::of(m.which) else {
panic!("{after}: a folder tool");
};
let holds = Holds {
nothing_deleted: false,
..Holds::all()
};
hold_folder(m, after, &state, app, holds);
}
pub(crate) fn hold_where_twice(m: &Machine, after: &str) {
let state = state::load(&m.ctx)
.unwrap_or_else(|e| panic!("{after}: the state file must still parse, got {e}"));
let LoginKind::Folder(app) = provider::of(m.which) else {
panic!("{after}: a folder tool");
};
let holds = Holds {
one_place: false,
beside_its_name: false,
..Holds::all()
};
hold_folder(m, after, &state, app, holds);
}
#[derive(Clone, Copy)]
struct Holds {
one_place: bool,
beside_its_name: bool,
nothing_deleted: bool,
}
impl Holds {
fn all() -> Holds {
Holds {
one_place: true,
beside_its_name: true,
nothing_deleted: true,
}
}
}
fn hold_folder(
m: &Machine,
after: &str,
state: &State,
app: &'static dyn provider::folder::Folder,
holds: Holds,
) {
let parks = store::folder::list(&m.ctx)
.expect("parks listed")
.unwrap_or_default();
let pending = std::fs::read_to_string(home::dir(&m.ctx).join("pending")).unwrap_or_default();
for name in &parks {
let named =
state.names(name) || pending.lines().any(|line| line == format!("folder {name}"));
assert!(named, "{after}: {name} under parks is named by nothing");
}
for dropped in &state.discarded {
if parks.contains(&dropped.service) {
assert_eq!(
dropped.kind,
ParkKind::Folder,
"{after}: {}",
dropped.service
);
}
}
for touch in m.mem.trail() {
assert!(
!(touch.store == "vault"
&& (touch.service.starts_with("pitboard-park-desktop_")
|| touch.service.starts_with("pitboard-aside-"))),
"{after}: {touch:?} asked the vault of a folder"
);
}
let in_use = state.account_in_use(m.which).map(Account::key);
for account in state.accounts.iter().filter(|a| a.provider() == m.which) {
let Some(park) = &account.parked else {
continue;
};
if in_use.as_ref() == Some(&account.key()) {
continue;
}
if state
.moving(m.which)
.is_some_and(|moving| moving.park == park.service)
{
continue;
}
let manifest = store::folder::manifest(&m.ctx, &park.service)
.ok()
.flatten()
.unwrap_or_else(|| panic!("{after}: {} names a park with no manifest", account.label));
assert_eq!(
manifest.login.as_deref(),
Some(park.login.as_str()),
"{after}"
);
let jar = store::folder::item(&m.ctx, &park.service, app.items()[0]);
let session = app.session(&m.ctx, &jar).expect("its jar reads");
assert_eq!(
session.map(|s| s.login),
Some(park.login.clone()),
"{after}"
);
}
if holds.one_place {
let mut seen = HashSet::new();
for (place, login) in sessions(m) {
assert!(
seen.insert(login),
"{after}: the session in {place} is also somewhere else"
);
}
}
let data = m.root.join("claude/data");
if holds.nothing_deleted {
for (store, file) in crate::desktop_testing::STORES {
let within = std::path::Path::new(store).join(file);
let places = std::iter::once(data.join(&within)).chain(
parks
.iter()
.map(|name| store::folder::item(&m.ctx, name, store).join(file)),
);
let found = places
.filter(|place| {
std::fs::read(place).is_ok_and(|held| held == WORK.session.as_bytes())
})
.count();
assert_eq!(
found,
1,
"{after}: `work`'s {} is in {found} places",
within.display()
);
}
}
if !data.exists() {
return;
}
let config = data.join("config.json");
assert!(config.exists(), "{after}: config.json is gone");
let settings = app
.settings(&data)
.unwrap_or_else(|e| panic!("{after}: config.json: {e:?}"));
let jar = data.join(app.items()[0]);
let session = jar
.exists()
.then(|| app.session(&m.ctx, &jar).ok().flatten())
.flatten();
if holds.beside_its_name
&& let (Some(session), Some(named)) = (session, settings.named(app))
{
let tool = m.which;
let in_use = state
.in_use(tool)
.filter(|record| record.login == session.login)
.and_then(|_| state.account_in_use(tool));
let parked = state.accounts.iter().filter(|a| {
a.provider() == tool
&& a.parked
.as_ref()
.is_some_and(|park| park.login == session.login)
});
let moving = state
.moving(tool)
.filter(|moving| moving.login == session.login)
.and_then(|moving| state.by_id(tool, &moving.id));
for account in in_use.into_iter().chain(parked).chain(moving) {
assert_eq!(
account.account_uuid, named,
"{after}: {}'s session beside another name",
account.label
);
}
}
let others: Vec<(&str, &str)> = settings
.body
.iter()
.filter(|(key, _)| !app.keys().contains(&key.as_str()))
.filter(|(key, _)| key != "updaterRunningInstance")
.map(|(key, value)| (key.as_str(), value.get()))
.collect();
assert_eq!(
others,
[("locale", "\"en-US\""), ("userThemeMode", "\"dark\"")],
"{after}"
);
assert!(
crate::host::fs::testing::is_private(&config).expect("its mode"),
"{after}: config.json's mode changed"
);
for (stays, held) in [
("Preferences", "{}"),
("Cache/Cache_Data/index", WORK.session),
] {
assert_eq!(
std::fs::read_to_string(data.join(stays)).ok().as_deref(),
Some(held),
"{after}: {stays}"
);
}
}
pub(crate) fn recover(m: &Machine) -> Result<()> {
settle(&m.ctx, Permit::for_a_test(), None).map(|_| ())
}