use super::*;
use crate::api::Owner;
use crate::api::scripted::ScriptedApi;
use crate::provider::ProviderId;
use crate::provider::claude::paths as claude;
use crate::state::Account;
use crate::store::memory::MemoryHost;
use crate::time::{Clock, FixedClock};
use serde_json::json;
use std::collections::HashSet;
use std::sync::Arc;
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) 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> {
crate::provider::of(self.which)
.read_live(&self.ctx)
.ok()
.flatten()
.map(|credential| credential.raw)
}
pub(crate) fn sign_in(&self, document: &Value) {
let live = crate::provider::of(self.which)
.live(&self.ctx)
.expect("a store to write to");
store::write_raw(&live.chain, &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: crate::store::memory::Fault) {
let (store, service) = self.live_store();
store.fault(&service, fault);
}
pub(crate) fn live_store(&self) -> (Arc<crate::store::memory::MemoryStore>, String) {
let live = crate::provider::of(self.which)
.live(&self.ctx)
.expect("a live store");
let store = match self.which {
ProviderId::Claude => Arc::clone(self.mem.live()),
ProviderId::Codex => self
.mem
.file_at(crate::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 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>);
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, "there").expect("a free name");
let parked = park::store_at(
&ctx,
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)));
state.set_active(ProviderId::Claude, Some("here".into()));
state::save(&ctx, &state).expect("saved");
Machine {
ctx,
mem,
api,
root,
service,
which: ProviderId::Claude,
}
}
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": crate::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 {
crate::provider::jwt::unsigned(&json!({"exp": NOW + 10 * 86_400, "for": refresh}))
}
pub(crate) fn codex_account(label: &str, uuid: &str, parked: Option<Park>) -> Account {
Account {
last_used_at: None,
label: label.into(),
account_uuid: uuid.into(),
email: format!("{uuid}@example.com"),
parked,
detail: crate::state::Detail::Codex {
workspace_id: None,
plan: Some("pro".into()),
},
}
}
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),
account_uuid: None,
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, &codex_id("there")).expect("a free name");
let parked = park::store_at(
&machine.ctx,
ProviderId::Codex,
&parked_service,
&codex_login("there", "there-refresh"),
)
.expect("parked");
let mut state = State::default();
state
.accounts
.push(codex_account("here", &codex_id("here"), None));
state
.accounts
.push(codex_account("there", &codex_id("there"), Some(parked)));
state.set_active(ProviderId::Codex, Some("here".into()));
state::save(&machine.ctx, &state).expect("saved");
machine
}
pub(crate) fn login_of(m: &Machine, who: &str, refresh: &str) -> Value {
match m.which {
ProviderId::Claude => {
m.api.owned_by(&format!("access-{refresh}"), owner(who));
document(refresh)
}
ProviderId::Codex => codex_login(who, refresh),
}
}
pub(crate) fn signed_in(m: &Machine, who: &str, refresh: &str) -> enroll::SignIn {
enroll::planted(&m.ctx, m.which, login_of(m, who, refresh)).expect("a sign-in")
}
pub(crate) fn renews(m: &Machine, refresh: &str, renewed: &str) {
match m.which {
ProviderId::Claude => {
m.api.renews(
refresh,
crate::api::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,
},
);
}
ProviderId::Codex => {
m.api.codex_renews(
refresh,
crate::provider::codex::api::Fresh {
id_token: None,
access_token: Some(codex_access(renewed)),
refresh_token: Some(renewed.into()),
at: Some(NOW),
},
);
}
}
}
pub(crate) fn account(label: &str, uuid: &str, parked: Option<Park>) -> Account {
Account {
last_used_at: None,
label: label.into(),
account_uuid: uuid.into(),
email: format!("{uuid}@example.com"),
parked,
detail: crate::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 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}"));
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(String::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 = crate::provider::of(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, &crate::provider::Credential::new(m.which, document))
.ok()
})
.map(|found| found.account_id);
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 recover(m: &Machine) -> Result<()> {
settle(&m.ctx, None).map(|_| ())
}