feat(tui): provider-accepted Computer meter receipts (#5781)

* feat(tui): add provider-accepted Computer meter receipts

Bind v3 Computer admission (profile, 1x/2x/4x multiplier, account,
quote, expiry) before dispatch. Mint immutable receipts only for
provider-accepted active seconds at per-second granularity. Reject
wall-clock-if-idle, queued/stopped/teardown time, and allocations
that do not exactly match standard-8/16/32.

Tests use hermetic Daytona-shaped fixtures. No live provider call,
no charge, no deploy.

Signed-off-by: CodeWhale Bot <[email protected]>

* docs(changelog): record Pod public surface receipt (#5776)

Signed-off-by: CodeWhale Bot <[email protected]>

---------

Signed-off-by: CodeWhale Bot <[email protected]>
Co-authored-by: CodeWhale Bot <[email protected]>
This commit is contained in:
Hunter BownandCodeWhale Bot authored and GitHub committed 2026-08-31 21:51:56 +00:00
1 parent 9a24bdc627
commit 7db8b47f3f
5 files changed
+1539

No files matched your search

+117
View File
@@ -11,6 +11,10 @@
//! - missing Daytona credentials fail closed; success is never faked
//!
//! Watch/cancel of a live sandbox and auto-decide heuristics are leftover.
//!
//! A sandbox create receipt is infrastructure identity, not Computer
//! entitlement. Metering requires [`crate::computer_meter`] admission plus a
//! provider-accepted active observation.
use std::fs;
use std::io::Write;
@@ -23,6 +27,11 @@ use codewhale_paths::codewhale_home;
use codewhale_secrets::Secrets;
use serde::{Deserialize, Serialize};
use crate::computer_meter::{
ComputerAdmission, ComputerMeterError, ComputerMeterReceipt, ProviderObservation,
issue_computer_meter_receipt,
};
const MAX_PROMPT_CHARS: usize = 4_000;
const MAX_REMOTE_BYTES: usize = 4 * 1024;
const JOB_KIND: &str = "cloud";
@@ -652,11 +661,35 @@ pub trait DaytonaLauncher {
}
/// Provider receipt. `pr_url` is intentionally omitted from this slice.
///
/// This is the infrastructure id of a created sandbox. It is not Computer
/// entitlement and must not be treated as provider-accepted active seconds.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SandboxReceipt {
pub sandbox_id: String,
}
/// Meter one closed interval on a dispatched cloud job.
///
/// The job's sandbox id must match the provider observation. Wall-clock after
/// create is not enough: the observation has to be provider-accepted active
/// time bound to the immutable admission.
pub fn meter_cloud_job(
job: &CloudJob,
admission: &ComputerAdmission,
observation: ProviderObservation,
) -> Result<ComputerMeterReceipt, ComputerMeterError> {
match job.sandbox_id.as_deref() {
Some(sandbox_id) if sandbox_id == observation.provider_sandbox_id => {
issue_computer_meter_receipt(admission, observation)
}
_ => Err(ComputerMeterError::AllocationMismatch {
message: "Cloud job sandbox id does not match the provider allocation snapshot."
.to_string(),
}),
}
}
/// Real Daytona HTTP create. Fails closed; never invents a PR URL.
pub struct LiveDaytonaLauncher;
@@ -1112,4 +1145,88 @@ mod tests {
assert!(!message.contains("sk-"));
assert!(!message.contains("Bearer"));
}
#[test]
fn sandbox_create_is_not_computer_entitlement() {
use crate::computer_meter::{
ComputerAdmissionRequest, MeterBasis, bind_computer_admission,
};
let temp = tempfile::tempdir().unwrap();
let store = CloudJobStore::from_path(temp.path().join("jobs"));
let plan = plan_dispatch(
&remotes(&[("github", "https://github.com/org/repo.git")]),
"meter honesty",
Some(Forge::Github),
Some("codewhale/cloud-meter"),
)
.unwrap();
let launcher = RecordingLauncher {
sandbox_id: "sbox_std8_a".to_string(),
fail: false,
};
let outcome = execute_dispatch(
&store,
plan,
true,
&CredentialState::Present {
source: CredentialSource::Keyring,
},
&launcher,
)
.unwrap();
let DispatchOutcome::Accepted(job) = outcome else {
panic!("expected accept");
};
let admission = bind_computer_admission(ComputerAdmissionRequest {
admission_id: "adm_dispatch".to_string(),
account_id: "acct_demo".to_string(),
computer_id: "cmp_dispatch".to_string(),
run_id: job.id.clone(),
provider: "daytona".to_string(),
profile_id: "standard-8".to_string(),
funding_authority: "coding_membership_included".to_string(),
quote_id: "quote_dispatch".to_string(),
expires_at: "2026-08-31T18:00:00.000Z".to_string(),
meter_revision: String::new(),
catalog_revision: String::new(),
})
.unwrap();
let idle = ProviderObservation {
provider: "daytona".to_string(),
provider_sandbox_id: "sbox_std8_a".to_string(),
provider_event_ref: "daytona:sbox_std8_a:idle".to_string(),
state: "running".to_string(),
idle: true,
provider_accepted: true,
meter_basis: MeterBasis::WallClock,
cpu: 2,
memory_gib: 8,
disk_gib: 8,
started_at: "2026-08-31T12:00:00.000Z".to_string(),
ended_at: "2026-08-31T13:00:00.000Z".to_string(),
};
assert_eq!(
meter_cloud_job(&job, &admission, idle).unwrap_err().code(),
"computer_meter_wall_clock_idle"
);
let accepted = ProviderObservation {
provider: "daytona".to_string(),
provider_sandbox_id: "sbox_std8_a".to_string(),
provider_event_ref: "daytona:sbox_std8_a:running".to_string(),
state: "running".to_string(),
idle: false,
provider_accepted: true,
meter_basis: MeterBasis::ProviderAcceptedActive,
cpu: 2,
memory_gib: 8,
disk_gib: 8,
started_at: "2026-08-31T12:00:00.000Z".to_string(),
ended_at: "2026-08-31T12:10:00.000Z".to_string(),
};
let receipt = meter_cloud_job(&job, &admission, accepted).unwrap();
assert_eq!(receipt.accepted_seconds, 600);
assert_eq!(receipt.standard_equivalent_seconds, 600);
assert_eq!(receipt.admission_id, admission.admission_id);
}
}
+747
View File
@@ -0,0 +1,747 @@
//! Provider-accepted Computer active-second receipts.
//!
//! Codewhale is the billing authority. Daytona (or a future adapter) supplies
//! infrastructure only. Entitlement moves solely for provider-accepted *active*
//! seconds, per second, multiplied by the selected profile's 1x/2x/4x rate.
//!
//! Wall-clock-if-idle, requested, queued, rejected, stopped, suspended,
//! archived, failed-before-acceptance, and teardown-tail time cannot mint a
//! receipt. Provider-observed CPU/RAM/disk must equal the admitted profile.
//! Corrections are append-only and preserve the original receipt.
use chrono::{DateTime, SecondsFormat, Utc};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::hashing::sha256_hex;
/// Pinned v3 meter revision. Bump only with a catalog owner change.
pub const COMPUTER_METER_REVISION: &str = "computer-meter-v3.20260831";
/// Pinned v3 profile catalog revision.
pub const COMPUTER_CATALOG_REVISION: &str = "computer-profiles-v3.20260831";
/// Admission record schema.
pub const COMPUTER_ADMISSION_SCHEMA: &str = "codewhale.computer-admission/v1";
/// Meter receipt schema.
pub const COMPUTER_METER_RECEIPT_SCHEMA: &str = "codewhale.computer-meter-receipt/v1";
/// Receipt kind for one accepted active interval.
pub const COMPUTER_METER_RECEIPT_KIND: &str = "computer.meter.active_seconds";
const ADMISSION_DIGEST_NS: &str = "codewhale/computer-admission/v1";
const RECEIPT_DIGEST_NS: &str = "codewhale/computer-meter-receipt/v1";
const MAX_REF_CHARS: usize = 240;
/// Ratified v3 launch profiles. Historic `standard`/`large`/`xl` decode only.
pub const COMPUTER_PROFILES: [ComputerProfile; 3] = [
ComputerProfile {
id: ComputerProfileId::Standard8,
label: "8 GB",
cpu: 2,
memory_gib: 8,
disk_gib: 8,
multiplier: 1,
},
ComputerProfile {
id: ComputerProfileId::Standard16,
label: "16 GB",
cpu: 4,
memory_gib: 16,
disk_gib: 16,
multiplier: 2,
},
ComputerProfile {
id: ComputerProfileId::Standard32,
label: "32 GB",
cpu: 8,
memory_gib: 32,
disk_gib: 32,
multiplier: 4,
},
];
/// Selectable, quotable, creatable v3 Computer profile.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ComputerProfileId {
/// 2 vCPU / 8 GiB RAM / 8 GiB disk at 1x.
#[serde(rename = "standard-8")]
Standard8,
/// 4 vCPU / 16 GiB RAM / 16 GiB disk at 2x.
#[serde(rename = "standard-16")]
Standard16,
/// 8 vCPU / 32 GiB RAM / 32 GiB disk at 4x.
#[serde(rename = "standard-32")]
Standard32,
}
impl ComputerProfileId {
/// Stable catalog id.
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Standard8 => "standard-8",
Self::Standard16 => "standard-16",
Self::Standard32 => "standard-32",
}
}
}
/// Fixed resource envelope and allowance multiplier.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ComputerProfile {
/// Catalog id.
pub id: ComputerProfileId,
/// Customer label.
pub label: &'static str,
/// vCPU count.
pub cpu: u32,
/// RAM in GiB.
#[serde(rename = "memoryGiB")]
pub memory_gib: u32,
/// Disk in GiB.
#[serde(rename = "diskGiB")]
pub disk_gib: u32,
/// Standard-equivalent multiplier (1, 2, or 4).
pub multiplier: u32,
}
/// How an interval claims to have been measured.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MeterBasis {
/// Provider confirmed the allocation was actively running.
ProviderAcceptedActive,
/// Wall-clock elapsed time. Never entitlement.
WallClock,
}
/// Immutable pre-dispatch binding. CWC remains the commercial owner; Engine
/// refuses to meter anything that is not this record.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ComputerAdmission {
/// Schema id.
pub schema_id: String,
/// Caller-supplied admission identity.
pub admission_id: String,
/// Account that funds the Computer.
pub account_id: String,
/// Durable Computer id.
pub computer_id: String,
/// Optional run this admission authorizes.
#[serde(default)]
pub run_id: String,
/// Infrastructure provider (currently `daytona`).
pub provider: String,
/// Selected v3 profile.
pub profile_id: ComputerProfileId,
/// Bound vCPU.
pub cpu: u32,
/// Bound RAM GiB.
#[serde(rename = "memoryGiB")]
pub memory_gib: u32,
/// Bound disk GiB.
#[serde(rename = "diskGiB")]
pub disk_gib: u32,
/// Bound multiplier.
pub multiplier: u32,
/// Meter revision at bind time.
pub meter_revision: String,
/// Catalog revision at bind time.
pub catalog_revision: String,
/// Funding authority (membership included seconds or a time pack).
pub funding_authority: String,
/// Quote identity bound before dispatch.
pub quote_id: String,
/// Admission expiry (inclusive bound is refused).
pub expires_at: String,
/// Digest of the bound fields.
pub binding_digest: String,
}
/// Provider-observed allocation at one instant.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProviderAllocation {
/// Provider name.
pub provider: String,
/// Provider sandbox / allocation id.
pub provider_sandbox_id: String,
/// Observed vCPU.
pub cpu: u32,
/// Observed RAM GiB.
#[serde(rename = "memoryGiB", alias = "memoryGb")]
pub memory_gib: u32,
/// Observed disk GiB.
#[serde(rename = "diskGiB", alias = "diskGb")]
pub disk_gib: u32,
/// Provider lifecycle state.
pub state: String,
/// Whether the provider accepted this as a live allocation.
pub accepted: bool,
}
/// One closed provider observation used to mint a receipt.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProviderObservation {
/// Infrastructure provider.
pub provider: String,
/// Provider sandbox / allocation id.
pub provider_sandbox_id: String,
/// Idempotent provider event / interval reference.
pub provider_event_ref: String,
/// Provider lifecycle state.
pub state: String,
/// True when the allocation is allocated but idle.
#[serde(default)]
pub idle: bool,
/// True only after the provider accepted the live allocation.
#[serde(default)]
pub provider_accepted: bool,
/// Measurement basis. Wall-clock is never entitlement.
pub meter_basis: MeterBasis,
/// Observed vCPU.
pub cpu: u32,
/// Observed RAM GiB.
#[serde(rename = "memoryGiB", alias = "memoryGb")]
pub memory_gib: u32,
/// Observed disk GiB.
#[serde(rename = "diskGiB", alias = "diskGb")]
pub disk_gib: u32,
/// Interval start (inclusive).
pub started_at: String,
/// Interval end (exclusive).
pub ended_at: String,
}
/// Immutable receipt for one provider-accepted active interval.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ComputerMeterReceipt {
/// Schema id.
pub schema_id: String,
/// Receipt identity derived from the bound work.
pub receipt_id: String,
/// Receipt kind.
pub kind: String,
/// Funding account.
pub account_id: String,
/// Run, when the interval is run-scoped.
#[serde(default)]
pub run_id: String,
/// Computer id.
pub computer_id: String,
/// Admission this interval was authorized under.
pub admission_id: String,
/// Provider name.
pub provider: String,
/// Profile billed at.
pub profile_id: ComputerProfileId,
/// Multiplier billed at.
pub multiplier: u32,
/// Bound vCPU.
pub cpu: u32,
/// Bound RAM GiB.
#[serde(rename = "memoryGiB")]
pub memory_gib: u32,
/// Bound disk GiB.
#[serde(rename = "diskGiB")]
pub disk_gib: u32,
/// Meter revision.
pub meter_revision: String,
/// Catalog revision.
pub catalog_revision: String,
/// Funding authority copied from admission.
pub funding_authority: String,
/// Quote identity copied from admission.
pub quote_id: String,
/// Interval start.
pub started_at: String,
/// Interval end.
pub ended_at: String,
/// Provider-accepted active whole seconds.
pub accepted_seconds: u64,
/// `accepted_seconds * multiplier`.
pub standard_equivalent_seconds: u64,
/// Allocation snapshot the provider accepted.
pub provider_allocation: ProviderAllocation,
/// Provider event / interval reference.
pub provider_event_ref: String,
/// Prior receipt this exact replay matched.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub replay_of: Option<String>,
/// Original receipt this append-only correction restates.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub correction_of: Option<String>,
/// Prior receipt ids in this lineage.
#[serde(default)]
pub lineage: Vec<String>,
/// Digest of the bound receipt fields.
pub binding_digest: String,
}
/// Inputs required to bind an admission before dispatch.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ComputerAdmissionRequest {
/// Caller-supplied admission identity.
pub admission_id: String,
/// Account that funds the Computer.
pub account_id: String,
/// Durable Computer id.
pub computer_id: String,
/// Optional run.
#[serde(default)]
pub run_id: String,
/// Infrastructure provider.
pub provider: String,
/// Selected profile id (`standard-8` / `standard-16` / `standard-32`).
pub profile_id: String,
/// Funding authority.
pub funding_authority: String,
/// Quote identity.
pub quote_id: String,
/// Expiry timestamp.
pub expires_at: String,
/// Optional meter revision; must match v3 when supplied.
#[serde(default)]
pub meter_revision: String,
/// Optional catalog revision; must match v3 when supplied.
#[serde(default)]
pub catalog_revision: String,
}
/// Fail-closed Computer meter errors.
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum ComputerMeterError {
/// Profile id is not a v3 launch profile.
#[error("{message}")]
ProfileUnknown { message: String },
/// Historic Large/XL/Auto/standard aliases cannot admit new work.
#[error("{message}")]
HistoricProfileNotAdmissible { message: String },
/// Required identity field is missing or hostile.
#[error("{message}")]
ReferenceInvalid { message: String },
/// Timestamp is not RFC 3339.
#[error("{message}")]
TimestampInvalid { message: String },
/// Interval ends before it starts.
#[error("A Computer meter interval cannot end before it starts.")]
IntervalReversed,
/// Admission has expired before the interval started.
#[error("The Computer admission expired before this interval.")]
AdmissionExpired,
/// Meter or catalog revision is not the pinned v3 revision.
#[error("{message}")]
RevisionMismatch { message: String },
/// Caller asked to meter wall-clock, including idle wall-clock.
#[error(
"Computer entitlement meters provider-accepted active seconds only, never wall-clock-if-idle."
)]
WallClockIdle,
/// Interval is not a provider-accepted active state.
#[error("{message}")]
NotProviderAcceptedActive { message: String },
/// Observed allocation is not exactly the admitted profile.
#[error("{message}")]
AllocationMismatch { message: String },
/// Replay disagrees with the original bound receipt.
#[error("This Computer meter receipt is already bound to different terms.")]
ReplayConflict,
}
impl ComputerMeterError {
/// Stable error code for tests and CWC.
#[must_use]
pub fn code(&self) -> &'static str {
match self {
Self::ProfileUnknown { .. } => "computer_profile_unknown",
Self::HistoricProfileNotAdmissible { .. } => "computer_profile_historic_not_admissible",
Self::ReferenceInvalid { .. } => "computer_meter_reference_invalid",
Self::TimestampInvalid { .. } => "computer_meter_timestamp_invalid",
Self::IntervalReversed => "computer_meter_interval_reversed",
Self::AdmissionExpired => "computer_admission_expired",
Self::RevisionMismatch { .. } => "computer_meter_revision_mismatch",
Self::WallClockIdle => "computer_meter_wall_clock_idle",
Self::NotProviderAcceptedActive { .. } => "computer_meter_not_provider_accepted_active",
Self::AllocationMismatch { .. } => "computer_meter_allocation_mismatch",
Self::ReplayConflict => "computer_meter_receipt_replay_conflict",
}
}
}
/// Decode a profile for reading historic or v3 ids. Does not authorize admission.
pub fn decode_computer_profile(input: &str) -> Result<ComputerProfile, ComputerMeterError> {
match normalize_token(input).as_str() {
"standard-8" | "standard" | "8" | "8gb" | "8gib" => Ok(COMPUTER_PROFILES[0]),
"standard-16" | "large" | "16" | "16gb" | "16gib" => Ok(COMPUTER_PROFILES[1]),
"standard-32" | "xl" | "32" | "32gb" | "32gib" => Ok(COMPUTER_PROFILES[2]),
other => Err(ComputerMeterError::ProfileUnknown {
message: format!("Unknown Computer profile: {other}."),
}),
}
}
/// Resolve a profile that may be used to create, quote, resume, or bill v3 work.
pub fn admit_computer_profile(input: &str) -> Result<ComputerProfile, ComputerMeterError> {
let normalized = normalize_token(input);
match normalized.as_str() {
"standard-8" | "standard-16" | "standard-32" => decode_computer_profile(&normalized),
"standard" | "large" | "xl" | "auto" => {
Err(ComputerMeterError::HistoricProfileNotAdmissible {
message: format!(
"Historic Computer profile `{normalized}` remains readable but cannot admit, resume, or meter new v3 work."
),
})
}
other => Err(ComputerMeterError::ProfileUnknown {
message: format!("Unknown Computer profile: {other}."),
}),
}
}
/// Bind provider, profile, resources, multiplier, revisions, account, funding,
/// quote, and expiry before dispatch.
pub fn bind_computer_admission(
request: ComputerAdmissionRequest,
) -> Result<ComputerAdmission, ComputerMeterError> {
let profile = admit_computer_profile(&request.profile_id)?;
let admission_id = require_ref(&request.admission_id, "admissionId")?;
let account_id = require_ref(&request.account_id, "accountId")?;
let computer_id = require_ref(&request.computer_id, "computerId")?;
let run_id = optional_ref(&request.run_id, "runId")?;
let provider = require_ref(&request.provider, "provider")?;
let funding_authority = require_ref(&request.funding_authority, "fundingAuthority")?;
let quote_id = require_ref(&request.quote_id, "quoteId")?;
let expires_at = normalize_timestamp(&request.expires_at, "expiresAt")?;
let meter_revision = require_revision(&request.meter_revision, COMPUTER_METER_REVISION)?;
let catalog_revision = require_revision(&request.catalog_revision, COMPUTER_CATALOG_REVISION)?;
let bound = ComputerAdmission {
schema_id: COMPUTER_ADMISSION_SCHEMA.to_string(),
admission_id,
account_id,
computer_id,
run_id,
provider,
profile_id: profile.id,
cpu: profile.cpu,
memory_gib: profile.memory_gib,
disk_gib: profile.disk_gib,
multiplier: profile.multiplier,
meter_revision,
catalog_revision,
funding_authority,
quote_id,
expires_at,
binding_digest: String::new(),
};
let binding_digest = admission_binding_digest(&bound);
Ok(ComputerAdmission {
binding_digest,
..bound
})
}
/// Mint an immutable receipt for one provider-accepted active interval.
pub fn issue_computer_meter_receipt(
admission: &ComputerAdmission,
observation: ProviderObservation,
) -> Result<ComputerMeterReceipt, ComputerMeterError> {
assert_active_observation(&observation)?;
assert_allocation_matches(admission, &observation)?;
let started_at = normalize_timestamp(&observation.started_at, "startedAt")?;
let ended_at = normalize_timestamp(&observation.ended_at, "endedAt")?;
let started = parse_timestamp(&started_at, "startedAt")?;
let ended = parse_timestamp(&ended_at, "endedAt")?;
if ended < started {
return Err(ComputerMeterError::IntervalReversed);
}
let expires = parse_timestamp(&admission.expires_at, "expiresAt")?;
if started >= expires {
return Err(ComputerMeterError::AdmissionExpired);
}
let accepted_seconds = elapsed_whole_seconds(started, ended);
let standard_equivalent_seconds =
accepted_seconds.saturating_mul(u64::from(admission.multiplier));
let provider = require_ref(&observation.provider, "provider")?;
if provider != admission.provider {
return Err(ComputerMeterError::AllocationMismatch {
message: format!(
"Provider `{}` does not match admitted provider `{}`.",
provider, admission.provider
),
});
}
let provider_sandbox_id = require_ref(&observation.provider_sandbox_id, "providerSandboxId")?;
let provider_event_ref = require_ref(&observation.provider_event_ref, "providerEventRef")?;
let allocation = ProviderAllocation {
provider: provider.clone(),
provider_sandbox_id: provider_sandbox_id.clone(),
cpu: observation.cpu,
memory_gib: observation.memory_gib,
disk_gib: observation.disk_gib,
state: normalize_token(&observation.state),
accepted: true,
};
let mut receipt = ComputerMeterReceipt {
schema_id: COMPUTER_METER_RECEIPT_SCHEMA.to_string(),
receipt_id: String::new(),
kind: COMPUTER_METER_RECEIPT_KIND.to_string(),
account_id: admission.account_id.clone(),
run_id: admission.run_id.clone(),
computer_id: admission.computer_id.clone(),
admission_id: admission.admission_id.clone(),
provider,
profile_id: admission.profile_id,
multiplier: admission.multiplier,
cpu: admission.cpu,
memory_gib: admission.memory_gib,
disk_gib: admission.disk_gib,
meter_revision: admission.meter_revision.clone(),
catalog_revision: admission.catalog_revision.clone(),
funding_authority: admission.funding_authority.clone(),
quote_id: admission.quote_id.clone(),
started_at,
ended_at,
accepted_seconds,
standard_equivalent_seconds,
provider_allocation: allocation,
provider_event_ref,
replay_of: None,
correction_of: None,
lineage: Vec::new(),
binding_digest: String::new(),
};
receipt.binding_digest = receipt_binding_digest(&receipt);
receipt.receipt_id = receipt_id_for(&receipt);
Ok(receipt)
}
/// Exact replay of an existing receipt. Identity and digest must match.
pub fn assert_computer_meter_receipt_replay(
existing: &ComputerMeterReceipt,
incoming: &ComputerMeterReceipt,
) -> Result<(), ComputerMeterError> {
if existing.receipt_id == incoming.receipt_id
&& existing.binding_digest == incoming.binding_digest
&& existing.admission_id == incoming.admission_id
&& existing.account_id == incoming.account_id
&& existing.provider_event_ref == incoming.provider_event_ref
&& existing.accepted_seconds == incoming.accepted_seconds
&& existing.standard_equivalent_seconds == incoming.standard_equivalent_seconds
&& existing.profile_id == incoming.profile_id
&& existing.multiplier == incoming.multiplier
{
return Ok(());
}
Err(ComputerMeterError::ReplayConflict)
}
/// Append-only correction. The original receipt is not mutated.
pub fn correct_computer_meter_receipt(
original: &ComputerMeterReceipt,
admission: &ComputerAdmission,
observation: ProviderObservation,
) -> Result<ComputerMeterReceipt, ComputerMeterError> {
if original.admission_id != admission.admission_id
|| original.account_id != admission.account_id
{
return Err(ComputerMeterError::ReplayConflict);
}
let mut correction = issue_computer_meter_receipt(admission, observation)?;
if correction.profile_id != original.profile_id || correction.multiplier != original.multiplier
{
return Err(ComputerMeterError::ReplayConflict);
}
correction.correction_of = Some(original.receipt_id.clone());
correction.lineage = {
let mut lineage = original.lineage.clone();
lineage.push(original.receipt_id.clone());
lineage
};
correction.binding_digest = receipt_binding_digest(&correction);
correction.receipt_id = receipt_id_for(&correction);
Ok(correction)
}
/// Sum Standard-equivalent seconds across independent Computer receipts.
#[must_use]
pub fn sum_standard_equivalent_seconds(receipts: &[ComputerMeterReceipt]) -> u64 {
receipts
.iter()
.map(|receipt| receipt.standard_equivalent_seconds)
.fold(0, u64::saturating_add)
}
fn assert_active_observation(observation: &ProviderObservation) -> Result<(), ComputerMeterError> {
if observation.meter_basis != MeterBasis::ProviderAcceptedActive || observation.idle {
return Err(ComputerMeterError::WallClockIdle);
}
if !observation.provider_accepted {
return Err(ComputerMeterError::NotProviderAcceptedActive {
message: "A Computer meter receipt requires a provider-accepted live allocation."
.to_string(),
});
}
let state = normalize_token(&observation.state);
if !matches!(state.as_str(), "running" | "started" | "active") {
return Err(ComputerMeterError::NotProviderAcceptedActive {
message: format!(
"Computer entitlement does not accrue in `{state}` (requested, queued, rejected, stopped, suspended, archived, failed-before-acceptance, and teardown-tail are excluded)."
),
});
}
Ok(())
}
fn assert_allocation_matches(
admission: &ComputerAdmission,
observation: &ProviderObservation,
) -> Result<(), ComputerMeterError> {
if observation.cpu == admission.cpu
&& observation.memory_gib == admission.memory_gib
&& observation.disk_gib == admission.disk_gib
{
return Ok(());
}
Err(ComputerMeterError::AllocationMismatch {
message: format!(
"Provider allocation {} vCPU / {} GiB RAM / {} GiB disk does not equal admitted profile {} ({} / {} / {}). Smaller is not a cost-saving substitution and larger is not an upgrade.",
observation.cpu,
observation.memory_gib,
observation.disk_gib,
admission.profile_id.as_str(),
admission.cpu,
admission.memory_gib,
admission.disk_gib
),
})
}
fn admission_binding_digest(admission: &ComputerAdmission) -> String {
sha256_hex(
[
ADMISSION_DIGEST_NS,
admission.admission_id.as_str(),
admission.account_id.as_str(),
admission.computer_id.as_str(),
admission.run_id.as_str(),
admission.provider.as_str(),
admission.profile_id.as_str(),
&admission.cpu.to_string(),
&admission.memory_gib.to_string(),
&admission.disk_gib.to_string(),
&admission.multiplier.to_string(),
admission.meter_revision.as_str(),
admission.catalog_revision.as_str(),
admission.funding_authority.as_str(),
admission.quote_id.as_str(),
admission.expires_at.as_str(),
]
.join("\0"),
)
}
fn receipt_binding_digest(receipt: &ComputerMeterReceipt) -> String {
sha256_hex(
[
RECEIPT_DIGEST_NS,
receipt.account_id.as_str(),
receipt.run_id.as_str(),
receipt.computer_id.as_str(),
receipt.admission_id.as_str(),
receipt.provider.as_str(),
receipt.profile_id.as_str(),
&receipt.multiplier.to_string(),
&receipt.cpu.to_string(),
&receipt.memory_gib.to_string(),
&receipt.disk_gib.to_string(),
receipt.meter_revision.as_str(),
receipt.catalog_revision.as_str(),
receipt.funding_authority.as_str(),
receipt.quote_id.as_str(),
receipt.started_at.as_str(),
receipt.ended_at.as_str(),
&receipt.accepted_seconds.to_string(),
&receipt.standard_equivalent_seconds.to_string(),
receipt.provider_event_ref.as_str(),
receipt.provider_allocation.provider_sandbox_id.as_str(),
&receipt.provider_allocation.cpu.to_string(),
&receipt.provider_allocation.memory_gib.to_string(),
&receipt.provider_allocation.disk_gib.to_string(),
receipt.provider_allocation.state.as_str(),
receipt.correction_of.as_deref().unwrap_or(""),
&receipt.lineage.join(","),
]
.join("\0"),
)
}
fn receipt_id_for(receipt: &ComputerMeterReceipt) -> String {
format!("cmr_{}", &receipt.binding_digest[..32])
}
fn elapsed_whole_seconds(started: DateTime<Utc>, ended: DateTime<Utc>) -> u64 {
ended
.signed_duration_since(started)
.num_milliseconds()
.max(0)
.unsigned_abs()
/ 1000
}
fn require_revision(value: &str, expected: &str) -> Result<String, ComputerMeterError> {
let normalized = value.trim();
if normalized.is_empty() {
return Ok(expected.to_string());
}
if normalized == expected {
return Ok(expected.to_string());
}
Err(ComputerMeterError::RevisionMismatch {
message: format!("Computer meter revision `{normalized}` is not {expected}."),
})
}
fn require_ref(value: &str, field: &str) -> Result<String, ComputerMeterError> {
let normalized = value.trim();
if normalized.is_empty()
|| normalized.len() > MAX_REF_CHARS
|| normalized.chars().any(|ch| ch.is_control())
{
return Err(ComputerMeterError::ReferenceInvalid {
message: format!(
"Computer meter field `{field}` is required and must be a bounded token."
),
});
}
Ok(normalized.to_string())
}
fn optional_ref(value: &str, field: &str) -> Result<String, ComputerMeterError> {
let normalized = value.trim();
if normalized.is_empty() {
return Ok(String::new());
}
require_ref(normalized, field)
}
fn normalize_token(value: &str) -> String {
value.trim().to_ascii_lowercase()
}
fn normalize_timestamp(value: &str, field: &str) -> Result<String, ComputerMeterError> {
Ok(parse_timestamp(value, field)?.to_rfc3339_opts(SecondsFormat::Millis, true))
}
fn parse_timestamp(value: &str, field: &str) -> Result<DateTime<Utc>, ComputerMeterError> {
DateTime::parse_from_rfc3339(value.trim())
.map(|parsed| parsed.with_timezone(&Utc))
.map_err(|_| ComputerMeterError::TimestampInvalid {
message: format!("Computer meter field `{field}` must be an RFC 3339 timestamp."),
})
}
#[cfg(test)]
mod tests;
@@ -0,0 +1,429 @@
{
"schema": "codewhale.computer-meter-fixtures/v1",
"note": "Hermetic Daytona-shaped observations. No live provider calls.",
"cases": [
{
"id": "accepted-standard-8",
"expect": "receipt",
"acceptedSeconds": 600,
"standardEquivalentSeconds": 600,
"admission": {
"admissionId": "adm_std8_001",
"accountId": "acct_demo",
"computerId": "cmp_8gb_a",
"runId": "run_demo_1",
"provider": "daytona",
"profileId": "standard-8",
"fundingAuthority": "coding_membership_included",
"quoteId": "quote_std8_001",
"expiresAt": "2026-08-31T18:00:00.000Z"
},
"observation": {
"provider": "daytona",
"providerSandboxId": "sbox_std8_a",
"providerEventRef": "daytona:sbox_std8_a:running:2026-08-31T12:00:00.000Z",
"state": "running",
"idle": false,
"providerAccepted": true,
"meterBasis": "provider_accepted_active",
"cpu": 2,
"memoryGiB": 8,
"diskGiB": 8,
"startedAt": "2026-08-31T12:00:00.000Z",
"endedAt": "2026-08-31T12:10:00.000Z"
}
},
{
"id": "accepted-standard-16",
"expect": "receipt",
"acceptedSeconds": 600,
"standardEquivalentSeconds": 1200,
"admission": {
"admissionId": "adm_std16_001",
"accountId": "acct_demo",
"computerId": "cmp_16gb_a",
"runId": "run_demo_2",
"provider": "daytona",
"profileId": "standard-16",
"fundingAuthority": "coding_membership_included",
"quoteId": "quote_std16_001",
"expiresAt": "2026-08-31T18:00:00.000Z"
},
"observation": {
"provider": "daytona",
"providerSandboxId": "sbox_std16_a",
"providerEventRef": "daytona:sbox_std16_a:running:2026-08-31T12:00:00.000Z",
"state": "running",
"idle": false,
"providerAccepted": true,
"meterBasis": "provider_accepted_active",
"cpu": 4,
"memoryGiB": 16,
"diskGiB": 16,
"startedAt": "2026-08-31T12:00:00.000Z",
"endedAt": "2026-08-31T12:10:00.000Z"
}
},
{
"id": "accepted-standard-32",
"expect": "receipt",
"acceptedSeconds": 600,
"standardEquivalentSeconds": 2400,
"admission": {
"admissionId": "adm_std32_001",
"accountId": "acct_demo",
"computerId": "cmp_32gb_a",
"runId": "run_demo_3",
"provider": "daytona",
"profileId": "standard-32",
"fundingAuthority": "computer_time_pack",
"quoteId": "quote_std32_001",
"expiresAt": "2026-08-31T18:00:00.000Z"
},
"observation": {
"provider": "daytona",
"providerSandboxId": "sbox_std32_a",
"providerEventRef": "daytona:sbox_std32_a:running:2026-08-31T12:00:00.000Z",
"state": "running",
"idle": false,
"providerAccepted": true,
"meterBasis": "provider_accepted_active",
"cpu": 8,
"memoryGiB": 32,
"diskGiB": 32,
"startedAt": "2026-08-31T12:00:00.000Z",
"endedAt": "2026-08-31T12:10:00.000Z"
}
},
{
"id": "per-second-no-minute-floor",
"expect": "receipt",
"acceptedSeconds": 11,
"standardEquivalentSeconds": 11,
"admission": {
"admissionId": "adm_std8_subminute",
"accountId": "acct_demo",
"computerId": "cmp_8gb_b",
"runId": "run_demo_4",
"provider": "daytona",
"profileId": "standard-8",
"fundingAuthority": "coding_membership_included",
"quoteId": "quote_std8_subminute",
"expiresAt": "2026-08-31T18:00:00.000Z"
},
"observation": {
"provider": "daytona",
"providerSandboxId": "sbox_std8_b",
"providerEventRef": "daytona:sbox_std8_b:running:2026-08-31T12:00:00.000Z",
"state": "running",
"idle": false,
"providerAccepted": true,
"meterBasis": "provider_accepted_active",
"cpu": 2,
"memoryGiB": 8,
"diskGiB": 8,
"startedAt": "2026-08-31T12:00:00.000Z",
"endedAt": "2026-08-31T12:00:11.400Z"
}
},
{
"id": "idle-wall-clock",
"expect": "error",
"code": "computer_meter_wall_clock_idle",
"admission": {
"admissionId": "adm_std8_idle",
"accountId": "acct_demo",
"computerId": "cmp_8gb_idle",
"runId": "run_demo_idle",
"provider": "daytona",
"profileId": "standard-8",
"fundingAuthority": "coding_membership_included",
"quoteId": "quote_std8_idle",
"expiresAt": "2026-08-31T18:00:00.000Z"
},
"observation": {
"provider": "daytona",
"providerSandboxId": "sbox_idle",
"providerEventRef": "daytona:sbox_idle:wall-clock:2026-08-31T12:00:00.000Z",
"state": "running",
"idle": true,
"providerAccepted": true,
"meterBasis": "wall_clock",
"cpu": 2,
"memoryGiB": 8,
"diskGiB": 8,
"startedAt": "2026-08-31T12:00:00.000Z",
"endedAt": "2026-08-31T13:00:00.000Z"
}
},
{
"id": "queued-not-accepted",
"expect": "error",
"code": "computer_meter_not_provider_accepted_active",
"admission": {
"admissionId": "adm_std8_queued",
"accountId": "acct_demo",
"computerId": "cmp_8gb_queued",
"runId": "run_demo_queued",
"provider": "daytona",
"profileId": "standard-8",
"fundingAuthority": "coding_membership_included",
"quoteId": "quote_std8_queued",
"expiresAt": "2026-08-31T18:00:00.000Z"
},
"observation": {
"provider": "daytona",
"providerSandboxId": "sbox_queued",
"providerEventRef": "daytona:sbox_queued:queued:2026-08-31T12:00:00.000Z",
"state": "queued",
"idle": false,
"providerAccepted": false,
"meterBasis": "provider_accepted_active",
"cpu": 2,
"memoryGiB": 8,
"diskGiB": 8,
"startedAt": "2026-08-31T12:00:00.000Z",
"endedAt": "2026-08-31T12:02:00.000Z"
}
},
{
"id": "stopped-storage-tail",
"expect": "error",
"code": "computer_meter_not_provider_accepted_active",
"admission": {
"admissionId": "adm_std8_stopped",
"accountId": "acct_demo",
"computerId": "cmp_8gb_stopped",
"runId": "run_demo_stopped",
"provider": "daytona",
"profileId": "standard-8",
"fundingAuthority": "coding_membership_included",
"quoteId": "quote_std8_stopped",
"expiresAt": "2026-08-31T18:00:00.000Z"
},
"observation": {
"provider": "daytona",
"providerSandboxId": "sbox_stopped",
"providerEventRef": "daytona:sbox_stopped:stopped:2026-08-31T12:10:00.000Z",
"state": "stopped",
"idle": false,
"providerAccepted": true,
"meterBasis": "provider_accepted_active",
"cpu": 2,
"memoryGiB": 8,
"diskGiB": 8,
"startedAt": "2026-08-31T12:10:00.000Z",
"endedAt": "2026-08-31T13:10:00.000Z"
}
},
{
"id": "teardown-tail",
"expect": "error",
"code": "computer_meter_not_provider_accepted_active",
"admission": {
"admissionId": "adm_std8_teardown",
"accountId": "acct_demo",
"computerId": "cmp_8gb_teardown",
"runId": "run_demo_teardown",
"provider": "daytona",
"profileId": "standard-8",
"fundingAuthority": "coding_membership_included",
"quoteId": "quote_std8_teardown",
"expiresAt": "2026-08-31T18:00:00.000Z"
},
"observation": {
"provider": "daytona",
"providerSandboxId": "sbox_teardown",
"providerEventRef": "daytona:sbox_teardown:teardown:2026-08-31T12:10:00.000Z",
"state": "teardown",
"idle": false,
"providerAccepted": true,
"meterBasis": "provider_accepted_active",
"cpu": 2,
"memoryGiB": 8,
"diskGiB": 8,
"startedAt": "2026-08-31T12:10:00.000Z",
"endedAt": "2026-08-31T12:10:45.000Z"
}
},
{
"id": "undersized-rollback-small",
"expect": "error",
"code": "computer_meter_allocation_mismatch",
"admission": {
"admissionId": "adm_std8_undersize",
"accountId": "acct_demo",
"computerId": "cmp_8gb_undersize",
"runId": "run_demo_undersize",
"provider": "daytona",
"profileId": "standard-8",
"fundingAuthority": "coding_membership_included",
"quoteId": "quote_std8_undersize",
"expiresAt": "2026-08-31T18:00:00.000Z"
},
"observation": {
"provider": "daytona",
"providerSandboxId": "sbox_undersize",
"providerEventRef": "daytona:sbox_undersize:running:2026-08-31T12:00:00.000Z",
"state": "running",
"idle": false,
"providerAccepted": true,
"meterBasis": "provider_accepted_active",
"cpu": 2,
"memoryGiB": 4,
"diskGiB": 8,
"startedAt": "2026-08-31T12:00:00.000Z",
"endedAt": "2026-08-31T12:10:00.000Z"
}
},
{
"id": "oversize-not-an-upgrade",
"expect": "error",
"code": "computer_meter_allocation_mismatch",
"admission": {
"admissionId": "adm_std8_oversize",
"accountId": "acct_demo",
"computerId": "cmp_8gb_oversize",
"runId": "run_demo_oversize",
"provider": "daytona",
"profileId": "standard-8",
"fundingAuthority": "coding_membership_included",
"quoteId": "quote_std8_oversize",
"expiresAt": "2026-08-31T18:00:00.000Z"
},
"observation": {
"provider": "daytona",
"providerSandboxId": "sbox_oversize",
"providerEventRef": "daytona:sbox_oversize:running:2026-08-31T12:00:00.000Z",
"state": "running",
"idle": false,
"providerAccepted": true,
"meterBasis": "provider_accepted_active",
"cpu": 4,
"memoryGiB": 16,
"diskGiB": 16,
"startedAt": "2026-08-31T12:00:00.000Z",
"endedAt": "2026-08-31T12:10:00.000Z"
}
},
{
"id": "concurrent-two-standard-8",
"expect": "sum",
"standardEquivalentSeconds": 1200,
"intervals": [
{
"admission": {
"admissionId": "adm_std8_conc_a",
"accountId": "acct_demo",
"computerId": "cmp_8gb_conc_a",
"runId": "run_conc_a",
"provider": "daytona",
"profileId": "standard-8",
"fundingAuthority": "coding_membership_included",
"quoteId": "quote_conc_a",
"expiresAt": "2026-08-31T18:00:00.000Z"
},
"observation": {
"provider": "daytona",
"providerSandboxId": "sbox_conc_a",
"providerEventRef": "daytona:sbox_conc_a:running:2026-08-31T12:00:00.000Z",
"state": "running",
"idle": false,
"providerAccepted": true,
"meterBasis": "provider_accepted_active",
"cpu": 2,
"memoryGiB": 8,
"diskGiB": 8,
"startedAt": "2026-08-31T12:00:00.000Z",
"endedAt": "2026-08-31T12:10:00.000Z"
}
},
{
"admission": {
"admissionId": "adm_std8_conc_b",
"accountId": "acct_demo",
"computerId": "cmp_8gb_conc_b",
"runId": "run_conc_b",
"provider": "daytona",
"profileId": "standard-8",
"fundingAuthority": "coding_membership_included",
"quoteId": "quote_conc_b",
"expiresAt": "2026-08-31T18:00:00.000Z"
},
"observation": {
"provider": "daytona",
"providerSandboxId": "sbox_conc_b",
"providerEventRef": "daytona:sbox_conc_b:running:2026-08-31T12:00:00.000Z",
"state": "running",
"idle": false,
"providerAccepted": true,
"meterBasis": "provider_accepted_active",
"cpu": 2,
"memoryGiB": 8,
"diskGiB": 8,
"startedAt": "2026-08-31T12:00:00.000Z",
"endedAt": "2026-08-31T12:10:00.000Z"
}
}
]
},
{
"id": "historic-large-readable",
"expect": "decode",
"profileInput": "large",
"profileId": "standard-16",
"multiplier": 2
},
{
"id": "historic-large-not-admissible",
"expect": "admit_error",
"profileInput": "large",
"code": "computer_profile_historic_not_admissible"
},
{
"id": "append-only-correction",
"expect": "correction",
"acceptedSeconds": 590,
"standardEquivalentSeconds": 590,
"admission": {
"admissionId": "adm_std8_corr",
"accountId": "acct_demo",
"computerId": "cmp_8gb_corr",
"runId": "run_demo_corr",
"provider": "daytona",
"profileId": "standard-8",
"fundingAuthority": "coding_membership_included",
"quoteId": "quote_std8_corr",
"expiresAt": "2026-08-31T18:00:00.000Z"
},
"observation": {
"provider": "daytona",
"providerSandboxId": "sbox_corr",
"providerEventRef": "daytona:sbox_corr:running:2026-08-31T12:00:00.000Z",
"state": "running",
"idle": false,
"providerAccepted": true,
"meterBasis": "provider_accepted_active",
"cpu": 2,
"memoryGiB": 8,
"diskGiB": 8,
"startedAt": "2026-08-31T12:00:00.000Z",
"endedAt": "2026-08-31T12:10:00.000Z"
},
"restatedObservation": {
"provider": "daytona",
"providerSandboxId": "sbox_corr",
"providerEventRef": "daytona:sbox_corr:running:2026-08-31T12:00:00.000Z:restated",
"state": "running",
"idle": false,
"providerAccepted": true,
"meterBasis": "provider_accepted_active",
"cpu": 2,
"memoryGiB": 8,
"diskGiB": 8,
"startedAt": "2026-08-31T12:00:00.000Z",
"endedAt": "2026-08-31T12:09:50.000Z"
}
}
]
}
+245
View File
@@ -0,0 +1,245 @@
use super::*;
const FIXTURES: &str = include_str!("fixtures/cases.json");
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct FixtureFile {
cases: Vec<FixtureCase>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct FixtureCase {
id: String,
expect: String,
#[serde(default)]
code: Option<String>,
#[serde(default)]
accepted_seconds: Option<u64>,
#[serde(default)]
standard_equivalent_seconds: Option<u64>,
#[serde(default)]
admission: Option<ComputerAdmissionRequest>,
#[serde(default)]
observation: Option<ProviderObservation>,
#[serde(default)]
restated_observation: Option<ProviderObservation>,
#[serde(default)]
intervals: Vec<FixtureInterval>,
#[serde(default)]
profile_input: Option<String>,
#[serde(default)]
profile_id: Option<String>,
#[serde(default)]
multiplier: Option<u32>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct FixtureInterval {
admission: ComputerAdmissionRequest,
observation: ProviderObservation,
}
fn fixtures() -> Vec<FixtureCase> {
serde_json::from_str::<FixtureFile>(FIXTURES)
.expect("computer meter fixtures must parse")
.cases
}
fn issue_case(case: &FixtureCase) -> Result<ComputerMeterReceipt, ComputerMeterError> {
let admission = bind_computer_admission(
case.admission
.clone()
.expect("receipt fixture requires admission"),
)?;
issue_computer_meter_receipt(
&admission,
case.observation
.clone()
.expect("receipt fixture requires observation"),
)
}
#[test]
fn fixtures_cover_provider_accepted_active_seconds_only() {
let cases = fixtures();
assert!(
cases.len() >= 12,
"fixture pack must cover accept, idle, queued, stopped, teardown, mismatch, concurrent, historic, correction"
);
for case in &cases {
match case.expect.as_str() {
"receipt" => {
let receipt = issue_case(case).unwrap_or_else(|error| {
panic!("{} should mint a receipt, got {}", case.id, error.code())
});
assert_eq!(receipt.schema_id, COMPUTER_METER_RECEIPT_SCHEMA);
assert_eq!(receipt.kind, COMPUTER_METER_RECEIPT_KIND);
assert_eq!(receipt.accepted_seconds, case.accepted_seconds.unwrap());
assert_eq!(
receipt.standard_equivalent_seconds,
case.standard_equivalent_seconds.unwrap()
);
assert_eq!(
receipt.standard_equivalent_seconds,
receipt.accepted_seconds * u64::from(receipt.multiplier)
);
let admission = bind_computer_admission(case.admission.clone().unwrap()).unwrap();
assert_eq!(receipt.admission_id, admission.admission_id);
assert_eq!(receipt.account_id, admission.account_id);
assert_eq!(receipt.profile_id, admission.profile_id);
assert_eq!(receipt.multiplier, admission.multiplier);
assert_eq!(receipt.provider_allocation.cpu, admission.cpu);
assert_eq!(receipt.provider_allocation.memory_gib, admission.memory_gib);
assert_eq!(receipt.provider_allocation.disk_gib, admission.disk_gib);
assert!(receipt.provider_allocation.accepted);
assert_eq!(receipt.meter_revision, COMPUTER_METER_REVISION);
assert_eq!(receipt.catalog_revision, COMPUTER_CATALOG_REVISION);
assert_ne!(receipt.binding_digest, admission.binding_digest);
}
"error" => {
let error = issue_case(case).expect_err(&case.id);
assert_eq!(error.code(), case.code.as_deref().unwrap(), "{}", case.id);
}
"sum" => {
let receipts: Vec<_> = case
.intervals
.iter()
.map(|interval| {
let admission = bind_computer_admission(interval.admission.clone())
.unwrap_or_else(|error| panic!("{}: {}", case.id, error));
issue_computer_meter_receipt(&admission, interval.observation.clone())
.unwrap_or_else(|error| panic!("{}: {}", case.id, error))
})
.collect();
assert_eq!(
sum_standard_equivalent_seconds(&receipts),
case.standard_equivalent_seconds.unwrap()
);
}
"decode" => {
let profile = decode_computer_profile(case.profile_input.as_deref().unwrap())
.unwrap_or_else(|error| panic!("{}: {}", case.id, error));
assert_eq!(profile.id.as_str(), case.profile_id.as_deref().unwrap());
assert_eq!(profile.multiplier, case.multiplier.unwrap());
}
"admit_error" => {
let error = admit_computer_profile(case.profile_input.as_deref().unwrap())
.expect_err(&case.id);
assert_eq!(error.code(), case.code.as_deref().unwrap());
}
"correction" => {
let admission = bind_computer_admission(case.admission.clone().unwrap()).unwrap();
let original =
issue_computer_meter_receipt(&admission, case.observation.clone().unwrap())
.unwrap();
let original_clone = original.clone();
let correction = correct_computer_meter_receipt(
&original,
&admission,
case.restated_observation.clone().unwrap(),
)
.unwrap();
assert_eq!(
original, original_clone,
"corrections must not mutate the original receipt"
);
assert_eq!(
correction.correction_of.as_deref(),
Some(original.receipt_id.as_str())
);
assert!(correction.lineage.contains(&original.receipt_id));
assert_ne!(correction.receipt_id, original.receipt_id);
assert_eq!(correction.accepted_seconds, case.accepted_seconds.unwrap());
assert_eq!(
correction.standard_equivalent_seconds,
case.standard_equivalent_seconds.unwrap()
);
assert_eq!(correction.account_id, original.account_id);
assert_eq!(correction.admission_id, original.admission_id);
assert_eq!(correction.profile_id, original.profile_id);
}
other => panic!("unknown fixture expect `{other}`"),
}
}
}
#[test]
fn exact_replay_is_idempotent_and_a_mutated_binding_conflicts() {
let case = fixtures()
.into_iter()
.find(|case| case.id == "accepted-standard-8")
.unwrap();
let first = issue_case(&case).unwrap();
let replay = issue_case(&case).unwrap();
assert_eq!(first.receipt_id, replay.receipt_id);
assert_eq!(first.binding_digest, replay.binding_digest);
assert_computer_meter_receipt_replay(&first, &replay).unwrap();
let mut mutated = replay.clone();
mutated.accepted_seconds = first.accepted_seconds + 1;
mutated.standard_equivalent_seconds = mutated.accepted_seconds;
assert_eq!(
assert_computer_meter_receipt_replay(&first, &mutated)
.unwrap_err()
.code(),
"computer_meter_receipt_replay_conflict"
);
}
#[test]
fn admission_binds_profile_multiplier_account_and_expiry_before_dispatch() {
let admission = bind_computer_admission(ComputerAdmissionRequest {
admission_id: "adm_bind".to_string(),
account_id: "acct_demo".to_string(),
computer_id: "cmp_bind".to_string(),
run_id: "run_bind".to_string(),
provider: "daytona".to_string(),
profile_id: "standard-16".to_string(),
funding_authority: "coding_membership_included".to_string(),
quote_id: "quote_bind".to_string(),
expires_at: "2026-08-31T18:00:00.000Z".to_string(),
meter_revision: String::new(),
catalog_revision: String::new(),
})
.unwrap();
assert_eq!(admission.schema_id, COMPUTER_ADMISSION_SCHEMA);
assert_eq!(admission.profile_id, ComputerProfileId::Standard16);
assert_eq!(admission.cpu, 4);
assert_eq!(admission.memory_gib, 16);
assert_eq!(admission.disk_gib, 16);
assert_eq!(admission.multiplier, 2);
assert_eq!(admission.account_id, "acct_demo");
assert_eq!(admission.meter_revision, COMPUTER_METER_REVISION);
assert!(!admission.binding_digest.is_empty());
}
#[test]
fn one_standard_16_matches_two_standard_8_over_the_same_ten_minutes() {
let cases = fixtures();
let two_eights = cases
.iter()
.find(|case| case.id == "concurrent-two-standard-8")
.unwrap();
let sixteen = cases
.iter()
.find(|case| case.id == "accepted-standard-16")
.unwrap();
let eight_receipts: Vec<_> = two_eights
.intervals
.iter()
.map(|interval| {
let admission = bind_computer_admission(interval.admission.clone()).unwrap();
issue_computer_meter_receipt(&admission, interval.observation.clone()).unwrap()
})
.collect();
let sixteen_receipt = issue_case(sixteen).unwrap();
assert_eq!(
sum_standard_equivalent_seconds(&eight_receipts),
sixteen_receipt.standard_equivalent_seconds
);
assert_eq!(sum_standard_equivalent_seconds(&eight_receipts), 1200);
}
+1
View File
@@ -36,6 +36,7 @@ mod commands;
mod compaction;
mod composer_history;
mod composer_stash;
pub mod computer_meter;
mod config;
mod config_persistence;
mod config_ui;