From 1361cc17663b4c1000f37f172d7d20f63726840b Mon Sep 17 00:00:00 2001 From: 0thernet <894119+0thernet@users.noreply.github.com> Date: Mon, 28 Sep 2026 02:22:41 -0400 Subject: [PATCH] Project account custody onto the shared host contracts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pin the frozen algal host-contract revision and add Store::host_lifecycle, a read-only algal.host-lifecycle.v1 projection of one account's custody slot: the exclusive lease, sessions still marked working that do not hold it, held authority labels, the newest terminal receipt, and subscription-quota usage (never API dollars). A lease whose owner can no longer be proven alive projects as uncertain with pendingIntent bound to the exact run digest xcb recover requires — never settled or failed — and the projection runs on open_read_only, so building it cannot mutate state, retry a provider call, or change admission. Also ship the committed algal.host-profile.v1 fixture for the context-recipe host: the managed-program executor profile under the pinned evaluator, the real recipe and managed-call limits, reconcile-required uncertain-effect policy, and honest probes — only the committed offline replay carries passed evidence. The quota window vocabulary now has one shared definition so a window that can block a lease is the same window the projection reports. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- Cargo.lock | 11 +- crates/xcb-core/src/usage.rs | 28 +- crates/xcb-runtime/Cargo.toml | 2 +- crates/xcb-runtime/src/host_contract.rs | 334 +++++++++++++++ crates/xcb-runtime/src/host_contract_tests.rs | 384 ++++++++++++++++++ crates/xcb-runtime/src/managed_program.rs | 3 +- crates/xcb-runtime/src/store.rs | 3 + .../fixtures/context-recipe-host-profile.json | 66 +++ 8 files changed, 814 insertions(+), 17 deletions(-) create mode 100644 crates/xcb-runtime/src/host_contract.rs create mode 100644 crates/xcb-runtime/src/host_contract_tests.rs create mode 100644 crates/xcb-runtime/tests/fixtures/context-recipe-host-profile.json diff --git a/Cargo.lock b/Cargo.lock index 54d6fded..12cd0cc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,12 +85,13 @@ dependencies = [ [[package]] name = "algal" version = "0.2.0" -source = "git+https://github.com/hraness/algal?rev=2c7f86a257761dc1a2adaf064adc18dfcdf35925#2c7f86a257761dc1a2adaf064adc18dfcdf35925" +source = "git+https://github.com/hraness/algal?rev=9922202a2da45bb1f7e0db82c0be5a1c6770c21b#9922202a2da45bb1f7e0db82c0be5a1c6770c21b" dependencies = [ "algal-expr", "apple-foundation", "clap", "getrandom 0.3.4", + "hraness-cli-kit", "libc", "reqwest", "rusqlite", @@ -104,7 +105,7 @@ dependencies = [ [[package]] name = "algal-expr" version = "0.2.0" -source = "git+https://github.com/hraness/algal?rev=2c7f86a257761dc1a2adaf064adc18dfcdf35925#2c7f86a257761dc1a2adaf064adc18dfcdf35925" +source = "git+https://github.com/hraness/algal?rev=9922202a2da45bb1f7e0db82c0be5a1c6770c21b#9922202a2da45bb1f7e0db82c0be5a1c6770c21b" dependencies = [ "ryu-js", "serde_json", @@ -174,8 +175,8 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "apple-foundation" -version = "0.1.3" -source = "git+https://github.com/hraness/apple-foundation?tag=v0.1.3#18f82c61338623a95310a829d589cdf0a067ccd4" +version = "0.2.0" +source = "git+https://github.com/hraness/apple-foundation?tag=v0.2.0#135458de495cd6edd583c04222b3bc42c2e1c8e8" dependencies = [ "serde", "serde_json", @@ -3403,7 +3404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/crates/xcb-core/src/usage.rs b/crates/xcb-core/src/usage.rs index 5e673cde..e38d234e 100644 --- a/crates/xcb-core/src/usage.rs +++ b/crates/xcb-core/src/usage.rs @@ -64,24 +64,32 @@ impl QuotaPoint { } } +/// The windows that carry account-scope quota for each provider. Claude uses +/// `five_hour`/`seven_day`; Codex's account-level ChatGPT windows arrive as +/// `codex.primary`/`codex.secondary`; Devin has no account-scope meter. The +/// same vocabulary gates admission (`quota_blocked_until`) and feeds the +/// read-only usage projection so a window that can block a lease is the same +/// window a projection reports. +pub fn account_windows(provider: Provider) -> &'static [&'static str] { + match provider { + Provider::Claude => &["five_hour", "seven_day"], + Provider::Codex => &["codex.primary", "codex.secondary"], + Provider::Devin => &[], + } +} + /// The latest observation in each known account-wide window is authoritative /// until its reported reset, even after percentage telemetry becomes stale. -/// Claude uses `five_hour`/`seven_day`; Codex's account-level ChatGPT windows -/// arrive as `codex.primary`/`codex.secondary`. Callers must bind `pool` to the -/// current account credential generation; model-specific and other-provider -/// windows are deliberately not inferred to have account scope. +/// Callers must bind `pool` to the current account credential generation; +/// model-specific and other-provider windows are deliberately not inferred +/// to have account scope. pub fn quota_blocked_until( points: &[QuotaPoint], pool: &Id, provider: Provider, now: u64, ) -> Option { - let windows: &[&str] = match provider { - Provider::Claude => &["five_hour", "seven_day"], - Provider::Codex => &["codex.primary", "codex.secondary"], - Provider::Devin => &[], - }; - windows + account_windows(provider) .iter() .copied() .filter_map(|window| { diff --git a/crates/xcb-runtime/Cargo.toml b/crates/xcb-runtime/Cargo.toml index caee3b02..d0143afb 100644 --- a/crates/xcb-runtime/Cargo.toml +++ b/crates/xcb-runtime/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] aes-gcm = "=0.11.1" aicharts-core = { git = "https://github.com/hraness/aicharts", rev = "091433796f7253386c3e3186c67c275fb273b704", version = "0.1.0" } -algal = { git = "https://github.com/hraness/algal", rev = "2c7f86a257761dc1a2adaf064adc18dfcdf35925", version = "0.2.0" } +algal = { git = "https://github.com/hraness/algal", rev = "9922202a2da45bb1f7e0db82c0be5a1c6770c21b", version = "0.2.0" } base64 = "=0.23.1" convex = "=0.10.4" getrandom = "=0.4.3" diff --git a/crates/xcb-runtime/src/host_contract.rs b/crates/xcb-runtime/src/host_contract.rs new file mode 100644 index 00000000..828b1fdf --- /dev/null +++ b/crates/xcb-runtime/src/host_contract.rs @@ -0,0 +1,334 @@ +//! `algal.host-lifecycle.v1` projections of xcb account custody and the +//! committed `algal.host-profile.v1` record for the context-recipe host. +//! +//! These records are data-only projections. Building or reading one is a +//! pure read: no transaction, recovery, or admission path runs, no provider +//! is contacted, and nothing is retried, so the projection is also safe on +//! `Store::open_read_only`. An unsettled run whose owning process can no +//! longer be proven alive projects as `uncertain` with `pendingIntent` bound +//! to the exact stored run record — the same digest `xcb recover` requires — +//! never as `settled` or `failed`, and the account lease stays held. + +use serde_json::{Value, json}; + +use super::*; + +/// `usage.unit` in lifecycle records and `usageUnits.name` in the host +/// profile: the worst provider-reported share of a live subscription quota +/// window. Deliberately distinct from token counters and never API dollars. +const QUOTA_UNIT: &str = "subscription-quota"; + +/// The evaluator revision the context-recipe host pins; the fixture test +/// asserts it stays in lockstep with the `algal` dependency in +/// `crates/xcb-runtime/Cargo.toml`. +const ALGAL_EVALUATOR_REV: &str = "9922202a2da45bb1f7e0db82c0be5a1c6770c21b"; + +/// The contract's integer ceiling (`algal` rejects larger values). +const RECORD_INT_MAX: u64 = 4_294_967_295; + +/// Canonical digest of `examples/context-recipes/coordination/replay.json`, +/// the committed offline replay evidence; the fixture test recomputes it. +const REPLAY_EVIDENCE: &str = + "sha256:b026bbd49c3c559164c3f2bcd82a24e8ee532fb43562e3d9be0aee9d0181cf8b"; + +fn record_digest(value: &Value) -> Result { + algal::canonical::digest(value) + .map_err(|_| xcb_core::Error::Invalid("host contract record").into()) +} + +/// The worst provider-reported share of a live account-scope window, using +/// the same window vocabulary and credential-generation binding as +/// `blocked_until_from`: a rotated or absent Claude credential projects no +/// measured share rather than another identity's meter, and windows that +/// cannot gate admission never feed the meter. +fn used_percent(db: &Connection, root: &Path, account: &Account, now: u64) -> Result { + let windows = xcb_core::usage::account_windows(account.provider); + if windows.is_empty() { + return Ok(0.0); + } + let pool = if account.provider == Provider::Claude { + match generation_pool(root, account)? { + Some(pool) => pool, + None => return Ok(0.0), + } + } else { + account.quota_pool.clone() + }; + if pool != account.quota_pool { + return Ok(0.0); + } + let points = quotas_from(db, &pool)?; + Ok(windows + .iter() + .filter_map(|window| { + points + .iter() + .filter(|point| { + point.window.as_str() == *window + && point.validate().is_ok() + && point.observed_at_ms <= now + && now < point.resets_at_ms + }) + .max_by_key(|point| point.observed_at_ms) + .map(|point| point.used_percent) + }) + .fold(0.0, f64::max)) +} + +impl Store { + /// A read-only `algal.host-lifecycle.v1` projection of one account's + /// custody slot. `owner` is the account's stable system-derived identity + /// and `generation` counts its recorded custody acquisitions. + /// + /// `backlog.active` is at most one because the lease is exclusive; + /// `backlog.queued` counts sessions still marked `working` that do not + /// hold custody — xcb refuses a second turn at admission rather than + /// queueing it, so the value is ordinarily zero. `pendingIntent` binds + /// the exact stored run record (the digest `xcb recover` requires) and + /// `receipt` binds the newest terminal outcome row. `usage` reports the + /// worst live subscription-window share in `subscription-quota` units. + /// A lease whose owner is no longer provably alive — or any unsettled + /// record outside the single-lease invariant — projects as `uncertain`, + /// never as `settled` or `failed`. + pub fn host_lifecycle(&self, account: &Id, now: u64) -> Result { + let db = self.db()?; + let payload: String = db + .query_row( + "SELECT payload FROM accounts WHERE id=?1", + [account.as_str()], + |row| row.get(0), + ) + .optional()? + .ok_or(Error::Unavailable("account not found"))?; + let record: Account = decode(&payload)?; + record.validate()?; + if record.id != *account { + return Err(Error::Conflict("account identity changed")); + } + // Custody is the lease row committed with the run. A second unsettled + // run, or one without its lease, means custody is unproven: project + // `uncertain` rather than silently picking one record. + let mut unsettled: Vec<(String, RunRecord, bool)> = Vec::new(); + { + let mut query = db.prepare( + "SELECT r.payload, EXISTS(SELECT 1 FROM leases l WHERE l.run=r.id AND l.account=r.account) + FROM runs r WHERE r.account=?1 AND r.phase!='settled' ORDER BY r.id LIMIT 2", + )?; + let rows = query.query_map([account.as_str()], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, bool>(1)?)) + })?; + for row in rows { + let (payload, held) = row?; + let run: RunRecord = decode(&payload)?; + run.validate()?; + if run.account != record.id || run.phase == "settled" { + return Err(Error::Conflict("run account or phase changed")); + } + unsettled.push((payload, run, held)); + } + } + let open = unsettled.first(); + let custody_proven = unsettled.len() == 1 && unsettled[0].2; + + let mut queued = 0_u64; + { + let mut query = db.prepare("SELECT payload FROM sessions WHERE account=?1 LIMIT ?2")?; + let rows = query.query_map(params![account.as_str(), MAX_SESSIONS], |row| { + row.get::<_, String>(0) + })?; + for row in rows { + let Ok(session) = decode::(&row?) else { + continue; + }; + if session.validate().is_err() + || session.state != State::Working + || open.is_some_and(|(_, run, _)| run.session.as_ref() == Some(&session.id)) + { + continue; + } + queued += 1; + } + } + + let generation: u64 = db + .query_row( + "SELECT count(*) FROM runs WHERE account=?1", + [account.as_str()], + |row| row.get::<_, i64>(0), + )? + .try_into() + .unwrap_or(0); + + let receipt = if db.query_row::( + "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type='table' AND name='run_outcomes')", + [], + |row| row.get(0), + )? { + db.query_row( + "SELECT o.payload FROM run_outcomes o JOIN runs r ON r.id=o.run + WHERE r.account=?1 ORDER BY o.rowid DESC LIMIT 1", + [account.as_str()], + |row| row.get::<_, String>(0), + ) + .optional()? + .map(|payload| algal::canonical::digest_bytes(payload.as_bytes())) + } else { + None + }; + + let used = used_percent(&db, &self.root, &record, now)?; + + let mut held = vec!["subscription-account".to_owned()]; + let mut intent = None; + // Custody takes precedence over the enabled flag: a lease can never + // outlive a disable, but an unproven record projects `uncertain` + // rather than `stopped` while custody is unclear. + let (state, actions) = if let Some((payload, run, _)) = open { + held.push("account-lease".into()); + if run.pid.is_some() { + held.push("provider-process-group".into()); + } + if run.command_custody.is_some() { + held.push("guest-command".into()); + } + intent = Some(algal::canonical::digest_bytes(payload.as_bytes())); + if custody_proven && run.owner.as_ref().is_some_and(RunOwner::alive) { + ("running", vec!["inspect", "stop"]) + } else { + ("uncertain", vec!["inspect", "reconcile", "stop"]) + } + } else if !record.enabled { + ("stopped", vec!["inspect", "resume"]) + } else { + ("ready", vec!["inspect", "stop"]) + }; + held.sort_unstable(); + held.dedup(); + + let mut lifecycle = json!({ + "contract": algal::host_contract::HOST_LIFECYCLE_CONTRACT, + "owner": record.fixed_name(), + "generation": generation.min(RECORD_INT_MAX), + "state": state, + "pendingIntent": intent, + "backlog": { + "queued": queued.min(RECORD_INT_MAX), + "active": u64::from(open.is_some()), + }, + "heldAuthority": held, + "usage": {"units": used, "charges": used, "unit": QUOTA_UNIT}, + "receipt": receipt, + "permittedOperatorActions": actions, + }); + lifecycle["digest"] = json!(record_digest(&lifecycle)?); + algal::host_contract::parse_host_lifecycle(&lifecycle) + .map_err(|_| xcb_core::Error::Invalid("host lifecycle projection"))?; + Ok(lifecycle) + } +} + +/// The committed `algal.host-profile.v1` record for the context-recipe host: +/// `context_recipe` compiles a bounded `xcb.context-recipe.v1` program onto +/// the managed-program executor profile and dispatches its agent cells +/// through ordinary managed-task provider selection under the project's +/// grant. +/// +/// Identity digests bind descriptors rather than copied state: the runtime +/// digest names the managed-program executor profile, the evaluator digest +/// the pinned `algal` revision, and the route digests the managed-task +/// admission route and its project-workspace scope. The record claims no +/// provider authority and grants none. The only `passed` probe carries the +/// canonical digest of the committed offline replay +/// (`examples/context-recipes/coordination/replay.json`); live provider +/// dispatch and custody reconciliation are `not-run`, never fabricated. +pub fn context_recipe_host_profile() -> Result { + let mut profile = json!({ + "contract": algal::host_contract::HOST_PROFILE_CONTRACT, + "host": { + "id": "xcb-context-recipe", + "kind": "subscription-cli", + "version": "xcb.context-recipe.v1", + }, + "runtime": { + "runtimeDigest": record_digest(&json!({ + "contract": "xcb.managed-program-executor.v1", + "profile": crate::managed_program::EXECUTOR_PROFILE, + }))?, + "evaluatorDigest": record_digest(&json!({ + "contract": "xcb.evaluator.v1", + "evaluator": "algal", + "rev": ALGAL_EVALUATOR_REV, + }))?, + "supportedContracts": [ + "algal.effect.v1", + "algal.host-lifecycle.v1", + "algal.host-profile.v1", + "algal.organism.v1", + "algal.run.v1", + "xcb.context-recipe.v1", + ], + }, + "route": { + "profileDigest": record_digest(&json!({ + "contract": "xcb.route-profile.v1", + "profile": "xcb-managed-task", + "providers": ["claude", "codex", "devin"], + "selection": "project-grant", + }))?, + "scopeDigest": record_digest(&json!({ + "contract": "xcb.route-scope.v1", + "scope": "project-workspace", + "recipe": "xcb.context-recipe.v1", + }))?, + "accountScopeDigest": null, + }, + "limits": { + "maxConcurrent": 1, + "maxQueue": crate::managed_program::MAX_MANAGED_CALLS, + "maxInputBytes": crate::context_recipe::MAX_RECIPE_BYTES, + "maxOutputBytes": crate::managed_program::MAX_SUMMARY_BYTES, + "maxWork": crate::managed_program::MAX_MANAGED_CALLS, + }, + "usageUnits": { + "name": QUOTA_UNIT, + "semantics": "Provider-reported share of a live subscription quota window consumed by the leased account; subscription allowance only, never API dollars or token counters.", + }, + "resultRetention": { + "mode": "original", + "maxBytes": crate::context_recipe::MAX_RECIPE_BYTES, + "originalRetrieval": true, + }, + "uncertainEffectPolicy": "reconcile-required", + "probes": [ + { + "id": "live-provider-dispatch", + "status": "not-run", + "evidence": null, + }, + { + "id": "offline-replay", + "status": "passed", + "evidence": REPLAY_EVIDENCE, + }, + { + "id": "uncertain-custody-recovery", + "status": "not-run", + "evidence": null, + }, + ], + "absentCapabilities": [ + "api-dollar-metering", + "credential-material", + "host-messaging", + "uncertain-auto-retry", + ], + }); + profile["digest"] = json!(record_digest(&profile)?); + algal::host_contract::parse_host_profile(&profile) + .map_err(|_| xcb_core::Error::Invalid("host profile record"))?; + Ok(profile) +} + +#[cfg(test)] +#[path = "host_contract_tests.rs"] +mod tests; diff --git a/crates/xcb-runtime/src/host_contract_tests.rs b/crates/xcb-runtime/src/host_contract_tests.rs new file mode 100644 index 00000000..0014c025 --- /dev/null +++ b/crates/xcb-runtime/src/host_contract_tests.rs @@ -0,0 +1,384 @@ +use super::*; +use std::path::PathBuf; +use xcb_core::{ + models::{Mode, ModelChoice}, + session::State, + usage::QuotaPoint, +}; + +const NOW: u64 = 1_000_000; +const FIXTURE: &str = include_str!("../tests/fixtures/context-recipe-host-profile.json"); +const REPLAY: &str = include_str!("../../../examples/context-recipes/coordination/replay.json"); + +struct Fixture { + _dir: tempfile::TempDir, + state: PathBuf, + store: Store, + account: Account, + session: Session, +} + +fn fixture() -> Fixture { + let dir = tempfile::tempdir().unwrap(); + let base = dir.path().canonicalize().unwrap(); + let workspace = private::directory(&base.join("work")).unwrap(); + let state = base.join("state"); + let store = Store::open(&state).unwrap(); + let account = store + .add_account(Provider::Codex, "Synthetic", 1, None) + .unwrap(); + let model = ModelChoice { + provider: Provider::Codex, + id: Id::new("fixture-model").unwrap(), + label: "Fixture".into(), + mode: Mode::Fixed, + resolved: None, + effort: None, + observed_at_ms: 1, + }; + let session = store + .create_session(&account.id, model, &workspace, 2) + .unwrap(); + Fixture { + _dir: dir, + state, + store, + account, + session, + } +} + +/// Replaces the run's recorded owner with a provably absent pid — the same +/// state `store::tests::orphaned` builds for recovery tests. +fn orphaned(store: &Store, run: &RunRecord) -> RunRecord { + let mut run = run.clone(); + run.owner.as_mut().unwrap().pid = i32::MAX as u32; + store + .db() + .unwrap() + .execute( + "UPDATE runs SET payload=?1 WHERE id=?2", + params![serde_json::to_string(&run).unwrap(), run.id.as_str()], + ) + .unwrap(); + run +} + +fn intent(store: &Store, run: &Id) -> String { + let (_, run_digest) = store.recovery_candidate(run).unwrap().unwrap(); + format!("sha256:{run_digest}") +} + +/// Every mutable row, as stored, so a projection can prove it wrote nothing. +fn snapshot(store: &Store) -> Vec<(String, String)> { + let db = store.db().unwrap(); + let mut rows = Vec::new(); + for select in [ + "SELECT id, payload FROM accounts ORDER BY id", + "SELECT id, payload FROM sessions ORDER BY id", + "SELECT id, payload FROM runs ORDER BY id", + "SELECT account, run FROM leases ORDER BY account", + "SELECT pool || '|' || window || '|' || observed_at, payload FROM quotas ORDER BY 1", + "SELECT run, payload FROM run_outcomes ORDER BY run", + ] { + let mut query = db.prepare(select).unwrap(); + let listed = query + .query_map([], |row| { + Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)) + }) + .unwrap(); + for row in listed { + rows.push(row.unwrap()); + } + } + rows +} + +#[test] +fn ready_slot_projects_a_valid_signed_record() { + let f = fixture(); + let record = f.store.host_lifecycle(&f.account.id, NOW).unwrap(); + algal::host_contract::parse_host_lifecycle(&record).unwrap(); + assert_eq!(record["contract"], "algal.host-lifecycle.v1"); + assert_eq!(record["owner"], f.account.fixed_name()); + assert_eq!(record["generation"], 0); + assert_eq!(record["state"], "ready"); + assert_eq!(record["pendingIntent"], Value::Null); + assert_eq!(record["backlog"], json!({"queued": 0, "active": 0})); + assert_eq!(record["heldAuthority"], json!(["subscription-account"])); + assert_eq!( + record["usage"], + json!({"units": 0.0, "charges": 0.0, "unit": "subscription-quota"}) + ); + assert_eq!(record["receipt"], Value::Null); + assert_eq!( + record["permittedOperatorActions"], + json!(["inspect", "stop"]) + ); +} + +#[test] +fn leased_running_slot_projects_held_custody_and_pending_intent() { + let f = fixture(); + let run = f + .store + .prepare_run(&f.session.id, f.session.revision, 3) + .unwrap(); + let started = f.store.mark_spawned(&run, 42).unwrap(); + let record = f.store.host_lifecycle(&f.account.id, NOW).unwrap(); + algal::host_contract::parse_host_lifecycle(&record).unwrap(); + assert_eq!(record["state"], "running"); + assert_eq!( + record["pendingIntent"], + json!(intent(&f.store, &started.id)) + ); + assert_eq!(record["generation"], 1); + assert_eq!(record["backlog"], json!({"queued": 0, "active": 1})); + assert_eq!( + record["heldAuthority"], + json!([ + "account-lease", + "provider-process-group", + "subscription-account" + ]) + ); + assert_eq!( + record["permittedOperatorActions"], + json!(["inspect", "stop"]) + ); +} + +#[test] +fn unproven_lease_projects_uncertain_and_retains_pending_intent() { + let f = fixture(); + let run = f + .store + .prepare_run(&f.session.id, f.session.revision, 3) + .unwrap(); + let started = f.store.mark_spawned(&run, 42).unwrap(); + orphaned(&f.store, &started); + let record = f.store.host_lifecycle(&f.account.id, NOW).unwrap(); + algal::host_contract::parse_host_lifecycle(&record).unwrap(); + // The uncertain provider effect is reconciled, never retried: custody + // stays held and the pending intent keeps binding the exact run record. + assert_eq!(record["state"], "uncertain"); + assert_ne!(record["state"], json!("failed")); + assert_ne!(record["state"], json!("settled")); + assert_eq!( + record["pendingIntent"], + json!(intent(&f.store, &started.id)) + ); + assert_eq!( + record["permittedOperatorActions"], + json!(["inspect", "reconcile", "stop"]) + ); + assert_eq!(f.store.unsettled_runs().unwrap().len(), 1); + + // A prepared run never spawned is just as unproven once its owner dies. + let f = fixture(); + let run = f + .store + .prepare_run(&f.session.id, f.session.revision, 3) + .unwrap(); + orphaned(&f.store, &run); + let record = f.store.host_lifecycle(&f.account.id, NOW).unwrap(); + assert_eq!(record["state"], "uncertain"); + assert_eq!(record["pendingIntent"], json!(intent(&f.store, &run.id))); +} + +#[test] +fn disabled_slot_projects_stopped_and_a_settled_run_binds_a_receipt() { + let f = fixture(); + f.store.set_account_enabled(&f.account.id, false).unwrap(); + let record = f.store.host_lifecycle(&f.account.id, NOW).unwrap(); + algal::host_contract::parse_host_lifecycle(&record).unwrap(); + assert_eq!(record["state"], "stopped"); + assert_eq!(record["pendingIntent"], Value::Null); + assert_eq!( + record["permittedOperatorActions"], + json!(["inspect", "resume"]) + ); + + // A settled turn leaves custody free and `ready` again; the newest + // terminal outcome binds as the receipt. + let f = fixture(); + let run = f + .store + .prepare_run(&f.session.id, f.session.revision, 3) + .unwrap(); + let started = f.store.mark_spawned(&run, 42).unwrap(); + f.store.settle(&started, State::Idle, 4).unwrap(); + let outcome = json!({"version": 1, "run": started.id.as_str()}); + { + let db = f.store.db().unwrap(); + db.execute( + "INSERT INTO run_outcomes(run,session,input_sequence,payload) VALUES(?1,?2,0,?3)", + params![ + started.id.as_str(), + f.session.id.as_str(), + serde_json::to_string(&outcome).unwrap() + ], + ) + .unwrap(); + } + let record = f.store.host_lifecycle(&f.account.id, NOW).unwrap(); + algal::host_contract::parse_host_lifecycle(&record).unwrap(); + assert_eq!(record["state"], "ready"); + assert_eq!(record["backlog"], json!({"queued": 0, "active": 0})); + assert_eq!( + record["receipt"], + json!(algal::canonical::digest_bytes( + serde_json::to_string(&outcome).unwrap().as_bytes() + )) + ); + assert_eq!(record["generation"], 1); +} + +#[test] +fn usage_reports_the_live_subscription_window_share_never_dollars() { + let f = fixture(); + f.store + .record_quota(&QuotaPoint { + pool: f.account.quota_pool.clone(), + window: Id::new("codex.primary").unwrap(), + used_percent: 41.5, + resets_at_ms: NOW + 60_000, + observed_at_ms: NOW - 1_000, + }) + .unwrap(); + // A window outside the account-scope vocabulary never feeds the meter, + // and a window past its reset does not count as consumed. + f.store + .record_quota(&QuotaPoint { + pool: f.account.quota_pool.clone(), + window: Id::new("codex.model").unwrap(), + used_percent: 99.0, + resets_at_ms: NOW + 60_000, + observed_at_ms: NOW - 1_000, + }) + .unwrap(); + f.store + .record_quota(&QuotaPoint { + pool: f.account.quota_pool.clone(), + window: Id::new("codex.secondary").unwrap(), + used_percent: 88.0, + resets_at_ms: NOW - 1, + observed_at_ms: NOW - 120_000, + }) + .unwrap(); + let record = f.store.host_lifecycle(&f.account.id, NOW).unwrap(); + algal::host_contract::parse_host_lifecycle(&record).unwrap(); + assert_eq!( + record["usage"], + json!({"units": 41.5, "charges": 41.5, "unit": "subscription-quota"}) + ); +} + +#[test] +fn projection_never_mutates_state_and_reads_through_open_read_only() { + let f = fixture(); + let run = f + .store + .prepare_run(&f.session.id, f.session.revision, 3) + .unwrap(); + let started = f.store.mark_spawned(&run, 42).unwrap(); + orphaned(&f.store, &started); + let before = snapshot(&f.store); + let record = f.store.host_lifecycle(&f.account.id, NOW).unwrap(); + assert_eq!(before, snapshot(&f.store)); + + // `open_read_only` pins `query_only`: any write inside the projection + // would fail, so an identical record is itself the non-mutation proof. + let reader = Store::open_read_only(&f.state).unwrap(); + let projected = reader.host_lifecycle(&f.account.id, NOW).unwrap(); + assert_eq!(record, projected); +} + +#[test] +fn tampered_or_denormalized_records_fail_digest_or_shape_checks() { + let f = fixture(); + let run = f + .store + .prepare_run(&f.session.id, f.session.revision, 3) + .unwrap(); + let started = f.store.mark_spawned(&run, 42).unwrap(); + orphaned(&f.store, &started); + let record = f.store.host_lifecycle(&f.account.id, NOW).unwrap(); + + // Any byte change breaks the digest binding. + let mut tampered = record.clone(); + tampered["owner"] = json!("other"); + assert!(algal::host_contract::parse_host_lifecycle(&tampered).is_err()); + + // An uncertain record cannot drop its pending intent. + let mut dropped = record.clone(); + dropped["pendingIntent"] = Value::Null; + assert!(algal::host_contract::parse_host_lifecycle(&dropped).is_err()); + + // Uncertainty never projects as settled or failed — and the contract + // rejects pendingIntent on terminal states outright. + for state in ["settled", "failed"] { + let mut collapsed = record.clone(); + collapsed["state"] = json!(state); + assert!(algal::host_contract::parse_host_lifecycle(&collapsed).is_err()); + } + + // Set-valued lists must stay sorted and unique. + let mut unsorted = record.clone(); + unsorted["heldAuthority"] = json!(["subscription-account", "account-lease"]); + assert!(algal::host_contract::parse_host_lifecycle(&unsorted).is_err()); +} + +#[test] +fn committed_host_profile_matches_the_builder_and_passes_parse() { + let built = context_recipe_host_profile().unwrap(); + algal::host_contract::parse_host_profile(&built).unwrap(); + let fixture: Value = serde_json::from_str(FIXTURE).unwrap(); + algal::host_contract::parse_host_profile(&fixture).unwrap(); + assert_eq!(fixture, built); + assert_eq!( + algal::canonical::canonical(&fixture).unwrap(), + algal::canonical::canonical(&built).unwrap() + ); + assert_eq!(built["uncertainEffectPolicy"], "reconcile-required"); + assert_eq!(built["usageUnits"]["name"], "subscription-quota"); +} + +#[test] +fn host_profile_evidence_and_evaluator_bind_committed_facts() { + let built = context_recipe_host_profile().unwrap(); + let probes = built["probes"].as_array().unwrap(); + let replay: Value = serde_json::from_str(REPLAY).unwrap(); + let replay_digest = algal::canonical::digest(&replay).unwrap(); + let offline = probes + .iter() + .find(|probe| probe["id"] == "offline-replay") + .unwrap(); + assert_eq!(offline["status"], "passed"); + assert_eq!(offline["evidence"], json!(replay_digest)); + for probe in probes { + if probe["id"] != "offline-replay" { + assert_eq!(probe["status"], "not-run"); + assert_eq!(probe["evidence"], Value::Null); + } + } + + // The evaluator digest names the exact pinned `algal` revision. + let manifest = + std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml")).unwrap(); + assert!(manifest.contains(ALGAL_EVALUATOR_REV)); + let expected = record_digest(&json!({ + "contract": "xcb.evaluator.v1", + "evaluator": "algal", + "rev": ALGAL_EVALUATOR_REV, + })) + .unwrap(); + assert_eq!(built["runtime"]["evaluatorDigest"], json!(expected)); +} + +#[test] +fn replay_evidence_digest_is_current() { + let replay: Value = serde_json::from_str(REPLAY).unwrap(); + let digest = algal::canonical::digest(&replay).unwrap(); + assert_eq!(digest, REPLAY_EVIDENCE, "regenerate REPLAY_EVIDENCE"); +} diff --git a/crates/xcb-runtime/src/managed_program.rs b/crates/xcb-runtime/src/managed_program.rs index 41ffd273..94d2e854 100644 --- a/crates/xcb-runtime/src/managed_program.rs +++ b/crates/xcb-runtime/src/managed_program.rs @@ -30,7 +30,8 @@ pub const MAX_MANAGED_CALLS: u8 = 8; pub const MAX_CHECKPOINT_BYTES: usize = 512 * 1024; const MAX_RUN_TIME: Duration = Duration::from_secs(5); const EXECUTOR_NAME: &str = "xcb-managed-agent-v1"; -const EXECUTOR_PROFILE: &str = "xcb-managed-agent-v1:text:8192:lookup-or-suspend:no-retry"; +pub(crate) const EXECUTOR_PROFILE: &str = + "xcb-managed-agent-v1:text:8192:lookup-or-suspend:no-retry"; fn is_zero(value: &u8) -> bool { *value == 0 diff --git a/crates/xcb-runtime/src/store.rs b/crates/xcb-runtime/src/store.rs index 3acd85f7..72e9295e 100644 --- a/crates/xcb-runtime/src/store.rs +++ b/crates/xcb-runtime/src/store.rs @@ -25,6 +25,9 @@ pub(crate) const AUTHENTICATION_REQUIRED: &str = #[path = "store_overview.rs"] mod overview; +#[path = "host_contract.rs"] +pub mod host_contract; + fn authentication_required_from(db: &Connection, account: &Id) -> Result { let available: bool = db.query_row( "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type='table' AND name='account_auth_failures')", diff --git a/crates/xcb-runtime/tests/fixtures/context-recipe-host-profile.json b/crates/xcb-runtime/tests/fixtures/context-recipe-host-profile.json new file mode 100644 index 00000000..0372f91f --- /dev/null +++ b/crates/xcb-runtime/tests/fixtures/context-recipe-host-profile.json @@ -0,0 +1,66 @@ +{ + "contract": "algal.host-profile.v1", + "host": { + "id": "xcb-context-recipe", + "kind": "subscription-cli", + "version": "xcb.context-recipe.v1" + }, + "runtime": { + "runtimeDigest": "sha256:905d9c070c0d41ef3886cd01ac4f9c904aeecbc98de5e3431eecf61613b4a686", + "evaluatorDigest": "sha256:377a9b31edaa14cab3a5fbbf52305d864a957d62c6995dfaae291a7d279bd135", + "supportedContracts": [ + "algal.effect.v1", + "algal.host-lifecycle.v1", + "algal.host-profile.v1", + "algal.organism.v1", + "algal.run.v1", + "xcb.context-recipe.v1" + ] + }, + "route": { + "profileDigest": "sha256:46129ba2cecde7ec24f056b2eb8ee6da6c82f443db06dcdd32c1dff041e7cca1", + "scopeDigest": "sha256:1d4f2401312784d8782ee7d8b35a5160ffded7847fc0fb01eefb8c92e3f4f9e7", + "accountScopeDigest": null + }, + "limits": { + "maxConcurrent": 1, + "maxQueue": 8, + "maxInputBytes": 67108864, + "maxOutputBytes": 8192, + "maxWork": 8 + }, + "usageUnits": { + "name": "subscription-quota", + "semantics": "Provider-reported share of a live subscription quota window consumed by the leased account; subscription allowance only, never API dollars or token counters." + }, + "resultRetention": { + "mode": "original", + "maxBytes": 67108864, + "originalRetrieval": true + }, + "uncertainEffectPolicy": "reconcile-required", + "probes": [ + { + "id": "live-provider-dispatch", + "status": "not-run", + "evidence": null + }, + { + "id": "offline-replay", + "status": "passed", + "evidence": "sha256:b026bbd49c3c559164c3f2bcd82a24e8ee532fb43562e3d9be0aee9d0181cf8b" + }, + { + "id": "uncertain-custody-recovery", + "status": "not-run", + "evidence": null + } + ], + "absentCapabilities": [ + "api-dollar-metering", + "credential-material", + "host-messaging", + "uncertain-auto-retry" + ], + "digest": "sha256:ffb5392bb95eed2b912f495ea7e853ed3fcad35d14240752e73e107c6a8b9ef1" +}