use std::collections::BTreeMap;
use std::fmt;
use super::canonical::{
Canonical, CanonicalError, CanonicalValue, Checksum, InvalidChecksum, SerializerVersion,
};
use super::ids::{InvalidId, ProjectId, ResourceId, Slug, TenantId};
use super::record::{
BodyError, IdentifiedBody, PROJECT_ID_FIELD, Record, SCHEMA_FIELD, TENANT_ID_FIELD,
};
use super::resource::{
BlobKind, ResourceBody, ResourceKind, ResourceRef, ResourceScope, ResourceVersion,
ResourceVersionNumber,
};
use super::revision::DesiredState;
pub const MODEL_ENABLEMENT_SCHEMA: &str = "axond.model-enablement.v1";
pub const MODEL_ALIAS_SCHEMA: &str = "axond.model-alias.v1";
const ENABLEMENT_ID_FIELD: &str = "enablement_id";
const ALIAS_ID_FIELD: &str = "alias_id";
const OFFERING_ID_FIELD: &str = "offering_id";
const CATALOG_SNAPSHOT_FIELD: &str = "catalog_snapshot";
const WIRE_FAMILY_FIELD: &str = "wire_family";
const STATE_FIELD: &str = "state";
const OBSERVED_PRICE_FIELD: &str = "observed_price";
const APPROVED_PRICE_FIELD: &str = "approved_price";
const TARGETS_FIELD: &str = "targets";
const INPUT_MICROS_FIELD: &str = "input_micros_per_million";
const OUTPUT_MICROS_FIELD: &str = "output_micros_per_million";
const PRICE_ID_FIELD: &str = "price_id";
const VERSION_FIELD: &str = "version";
const OBSERVED_PRICE_FIELDS: &[&str] = &[INPUT_MICROS_FIELD, OUTPUT_MICROS_FIELD];
const APPROVED_PRICE_FIELDS: &[&str] = &[PRICE_ID_FIELD, VERSION_FIELD];
const ALIAS_TARGET_FIELDS: &[&str] = &[ENABLEMENT_ID_FIELD, VERSION_FIELD];
const OBSERVED_INPUT_PATH: &str = "observed_price.input_micros_per_million";
const OBSERVED_OUTPUT_PATH: &str = "observed_price.output_micros_per_million";
const APPROVED_PRICE_ID_PATH: &str = "approved_price.price_id";
const APPROVED_VERSION_PATH: &str = "approved_price.version";
const TARGET_ENABLEMENT_ID_PATH: &str = "targets.enablement_id";
const TARGET_VERSION_PATH: &str = "targets.version";
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum InvalidOfferingId {
#[error("offering id `{name}` is not prefixed `{}`", OfferingId::PREFIX)]
Prefix { name: String },
#[error("offering id `{name}` is not 64 lowercase hex digits")]
Digits { name: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OfferingId(Checksum);
impl OfferingId {
pub const PREFIX: &'static str = "off_";
pub fn of(provider: &str, model: &str) -> Result<Self, CanonicalError> {
let key = CanonicalValue::map([
("provider", CanonicalValue::string(provider)),
("model", CanonicalValue::string(model)),
]);
Ok(Self(Checksum::of(&SerializerVersion::V1.encode(&key)?)))
}
pub fn parse(text: &str) -> Result<Self, InvalidOfferingId> {
let digits = text
.strip_prefix(Self::PREFIX)
.ok_or_else(|| InvalidOfferingId::Prefix {
name: text.to_owned(),
})?;
Checksum::parse(&format!("sha256:{digits}"))
.map(Self)
.map_err(|_| InvalidOfferingId::Digits {
name: text.to_owned(),
})
}
}
impl fmt::Display for OfferingId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(Self::PREFIX)?;
for byte in self.0.as_bytes() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CatalogOffering {
pub offering: OfferingId,
pub snapshot: Checksum,
}
impl CatalogOffering {
pub const fn new(offering: OfferingId, snapshot: Checksum) -> Self {
Self { offering, snapshot }
}
pub fn is_pinned_to(self, digest: Checksum) -> bool {
self.snapshot == digest
}
}
impl fmt::Display for CatalogOffering {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}@{}", self.offering, self.snapshot)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum WireFamily {
OpenaiChat,
AnthropicMessages,
}
impl WireFamily {
pub const ALL: &'static [Self] = &[Self::OpenaiChat, Self::AnthropicMessages];
pub const fn as_str(self) -> &'static str {
match self {
Self::OpenaiChat => "openai-chat",
Self::AnthropicMessages => "anthropic-messages",
}
}
pub fn parse(input: &str) -> Option<Self> {
Self::ALL
.iter()
.copied()
.find(|family| family.as_str() == input)
}
}
impl fmt::Display for WireFamily {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum ModelLifecycle {
#[default]
Enabled,
Disabled,
}
impl ModelLifecycle {
pub const ALL: &'static [Self] = &[Self::Enabled, Self::Disabled];
pub const fn as_str(self) -> &'static str {
match self {
Self::Enabled => "enabled",
Self::Disabled => "disabled",
}
}
pub fn parse(input: &str) -> Option<Self> {
Self::ALL
.iter()
.copied()
.find(|state| state.as_str() == input)
}
pub const fn is_enabled(self) -> bool {
matches!(self, Self::Enabled)
}
pub fn transition_to(self, next: Self) -> LifecycleChange {
if self == next {
LifecycleChange::Unchanged(self)
} else {
LifecycleChange::Moved {
from: self,
to: next,
}
}
}
}
impl fmt::Display for ModelLifecycle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LifecycleChange {
Unchanged(ModelLifecycle),
Moved {
from: ModelLifecycle,
to: ModelLifecycle,
},
}
impl LifecycleChange {
pub const fn state(self) -> ModelLifecycle {
match self {
Self::Unchanged(state) => state,
Self::Moved { to, .. } => to,
}
}
pub const fn changed(self) -> bool {
matches!(self, Self::Moved { .. })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ModelInvariant {
Identity,
Owner,
Offering,
Snapshot,
WireFamily,
}
impl ModelInvariant {
pub const fn as_str(self) -> &'static str {
match self {
Self::Identity => "identity",
Self::Owner => "owner",
Self::Offering => "catalogue offering",
Self::Snapshot => "catalogue snapshot",
Self::WireFamily => "wire family",
}
}
}
impl fmt::Display for ModelInvariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("a new version may not change the {invariant} of a model resource")]
pub struct ForbiddenModelTransition {
pub invariant: ModelInvariant,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ModelOwner {
pub tenant: TenantId,
pub project: Option<ProjectId>,
}
impl ModelOwner {
pub const fn tenant(tenant: TenantId) -> Self {
Self {
tenant,
project: None,
}
}
pub const fn project(tenant: TenantId, project: ProjectId) -> Self {
Self {
tenant,
project: Some(project),
}
}
pub const fn from_scope(scope: &ResourceScope) -> Option<Self> {
match scope {
ResourceScope::Deployment => None,
ResourceScope::Tenant(tenant) => Some(Self::tenant(*tenant)),
ResourceScope::Project { tenant, project } => Some(Self::project(*tenant, *project)),
}
}
pub const fn scope(self) -> ResourceScope {
match self.project {
None => ResourceScope::Tenant(self.tenant),
Some(project) => ResourceScope::Project {
tenant: self.tenant,
project,
},
}
}
pub fn reaches(self, other: Self) -> bool {
self.tenant == other.tenant && (other.project.is_none() || other.project == self.project)
}
}
impl fmt::Display for ModelOwner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.project {
None => write!(f, "{}", self.tenant),
Some(project) => write!(f, "{}/{project}", self.tenant),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ObservedPrice {
pub input_micros_per_million: u64,
pub output_micros_per_million: u64,
}
impl ObservedPrice {
pub const fn new(input_micros_per_million: u64, output_micros_per_million: u64) -> Self {
Self {
input_micros_per_million,
output_micros_per_million,
}
}
}
impl fmt::Display for ObservedPrice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}/{} µ$ per 1M tokens (observed)",
self.input_micros_per_million, self.output_micros_per_million
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ApprovedPrice(ResourceRef);
impl ApprovedPrice {
pub fn of(reference: ResourceRef) -> Option<Self> {
(reference.kind == ResourceKind::Price).then_some(Self(reference))
}
pub fn version(price: ResourceId, version: ResourceVersionNumber) -> Self {
Self(ResourceRef::new(ResourceKind::Price, price, version))
}
pub const fn reference(self) -> ResourceRef {
self.0
}
}
impl fmt::Display for ApprovedPrice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AliasTarget {
pub enablement: ResourceId,
pub version: ResourceVersionNumber,
}
impl AliasTarget {
pub const fn new(enablement: ResourceId, version: ResourceVersionNumber) -> Self {
Self {
enablement,
version,
}
}
pub const fn first(enablement: ResourceId) -> Self {
Self::new(enablement, ResourceVersionNumber::FIRST)
}
pub const fn reference(self) -> ResourceRef {
ResourceRef::new(ResourceKind::ModelEnablement, self.enablement, self.version)
}
fn canonical(self) -> CanonicalValue {
CanonicalValue::map([
(
ENABLEMENT_ID_FIELD,
CanonicalValue::string(self.enablement.to_string()),
),
(
VERSION_FIELD,
CanonicalValue::integer(i128::from(self.version.get())),
),
])
}
}
impl fmt::Display for AliasTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.reference())
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ModelError {
#[error("{reference} is a {} resource, not a {}", found.as_str(), expected.as_str())]
Kind {
reference: ResourceRef,
expected: ResourceKind,
found: ResourceKind,
},
#[error("{reference} is a blob body; a model record is inline")]
NotInline { reference: ResourceRef },
#[error("{reference} is not a record")]
NotARecord { reference: ResourceRef },
#[error(
"{reference} declares schema `{found}`, which this build does not read (expected `{expected}`)"
)]
Schema {
reference: ResourceRef,
expected: &'static str,
found: String,
},
#[error(
"{reference} carries a `schema` that is not an identifier, which no release wrote; \
restore the row or republish the resource rather than changing build"
)]
DamagedSchema { reference: ResourceRef },
#[error("{reference} has no `{field}`")]
MissingField {
reference: ResourceRef,
field: &'static str,
},
#[error("{reference} carries `{field}`, which `{schema}` does not define")]
UnknownField {
reference: ResourceRef,
schema: &'static str,
field: String,
},
#[error("{reference} field `{field}` is not the type its schema defines")]
FieldType {
reference: ResourceRef,
field: &'static str,
},
#[error("{reference} field `{field}` is not an id: {source}")]
MalformedId {
reference: ResourceRef,
field: &'static str,
#[source]
source: InvalidId,
},
#[error("{reference} field `{field}` is not an offering id: {source}")]
MalformedOffering {
reference: ResourceRef,
field: &'static str,
#[source]
source: InvalidOfferingId,
},
#[error("{reference} field `{field}` is not a checksum: {source}")]
MalformedChecksum {
reference: ResourceRef,
field: &'static str,
#[source]
source: InvalidChecksum,
},
#[error("{reference} field `{field}` is {found}, which is not a rate this build can read")]
PriceRange {
reference: ResourceRef,
field: &'static str,
found: i128,
},
#[error("{reference} names version {found} of a resource; versions start at 1")]
VersionZero { reference: ResourceRef, found: i128 },
#[error("{reference} declares wire family `{found}`, which this build does not know")]
UnknownWireFamily {
reference: ResourceRef,
found: String,
},
#[error("{reference} declares state `{found}`, which this build does not know")]
UnknownLifecycle {
reference: ResourceRef,
found: String,
},
#[error("{reference} carries {declared}, but its resource identity is {identity}")]
IdentityMismatch {
reference: ResourceRef,
declared: String,
identity: ResourceId,
},
#[error("{reference} declares owner {declared}, which is not the scope it is filed under")]
OwnerMismatch {
reference: ResourceRef,
declared: ModelOwner,
},
#[error("{reference} is an alias filed outside a project")]
NotProjectScoped { reference: ResourceRef },
#[error("{reference} pins catalogue snapshot {snapshot}, which this revision does not declare")]
UnpinnedSnapshot {
reference: ResourceRef,
snapshot: Checksum,
},
#[error("{reference} enables {offering} at a scope {conflicting} already enables it at")]
DuplicateOffering {
reference: ResourceRef,
offering: OfferingId,
conflicting: ResourceRef,
},
#[error("{reference} names {target}, which its envelope does not declare as a dependency")]
UndeclaredTarget {
reference: ResourceRef,
target: ResourceRef,
},
#[error("{reference} names {target}, which this revision does not declare")]
DanglingTarget {
reference: ResourceRef,
target: ResourceRef,
},
#[error("{reference} names {target}, which its owner cannot reach")]
ForeignTarget {
reference: ResourceRef,
target: ResourceRef,
},
#[error("{reference} is an alias with no targets")]
NoTargets { reference: ResourceRef },
#[error("{reference} names {target} more than once")]
DuplicateTarget {
reference: ResourceRef,
target: ResourceRef,
},
#[error("{reference} speaks {alias}, but {target} speaks {found}")]
WireFamilyMismatch {
reference: ResourceRef,
target: ResourceRef,
alias: WireFamily,
found: WireFamily,
},
}
impl ModelError {
pub fn is_incompatible(&self) -> bool {
match self {
Self::Schema { .. }
| Self::UnknownField { .. }
| Self::UnknownWireFamily { .. }
| Self::UnknownLifecycle { .. } => true,
Self::MissingField { field, .. } => *field == SCHEMA_FIELD,
Self::FieldType { .. }
| Self::DamagedSchema { .. }
| Self::Kind { .. }
| Self::NotInline { .. }
| Self::NotARecord { .. }
| Self::MalformedId { .. }
| Self::MalformedOffering { .. }
| Self::MalformedChecksum { .. }
| Self::PriceRange { .. }
| Self::VersionZero { .. }
| Self::IdentityMismatch { .. }
| Self::OwnerMismatch { .. }
| Self::NotProjectScoped { .. }
| Self::UnpinnedSnapshot { .. }
| Self::DuplicateOffering { .. }
| Self::UndeclaredTarget { .. }
| Self::DanglingTarget { .. }
| Self::ForeignTarget { .. }
| Self::NoTargets { .. }
| Self::DuplicateTarget { .. }
| Self::WireFamilyMismatch { .. } => false,
}
}
pub const fn reference(&self) -> ResourceRef {
match self {
Self::Kind { reference, .. }
| Self::NotInline { reference }
| Self::NotARecord { reference }
| Self::Schema { reference, .. }
| Self::DamagedSchema { reference }
| Self::MissingField { reference, .. }
| Self::UnknownField { reference, .. }
| Self::FieldType { reference, .. }
| Self::MalformedId { reference, .. }
| Self::MalformedOffering { reference, .. }
| Self::MalformedChecksum { reference, .. }
| Self::PriceRange { reference, .. }
| Self::VersionZero { reference, .. }
| Self::UnknownWireFamily { reference, .. }
| Self::UnknownLifecycle { reference, .. }
| Self::IdentityMismatch { reference, .. }
| Self::OwnerMismatch { reference, .. }
| Self::NotProjectScoped { reference }
| Self::UnpinnedSnapshot { reference, .. }
| Self::DuplicateOffering { reference, .. }
| Self::UndeclaredTarget { reference, .. }
| Self::DanglingTarget { reference, .. }
| Self::ForeignTarget { reference, .. }
| Self::NoTargets { reference }
| Self::DuplicateTarget { reference, .. }
| Self::WireFamilyMismatch { reference, .. } => *reference,
}
}
}
impl BodyError for ModelError {
fn kind(reference: ResourceRef, expected: ResourceKind, found: ResourceKind) -> Self {
Self::Kind {
reference,
expected,
found,
}
}
fn not_inline(reference: ResourceRef) -> Self {
Self::NotInline { reference }
}
fn not_a_record(reference: ResourceRef) -> Self {
Self::NotARecord { reference }
}
fn schema(reference: ResourceRef, expected: &'static str, found: String) -> Self {
Self::Schema {
reference,
expected,
found,
}
}
fn damaged_schema(reference: ResourceRef) -> Self {
Self::DamagedSchema { reference }
}
fn missing_field(reference: ResourceRef, field: &'static str) -> Self {
Self::MissingField { reference, field }
}
fn unknown_field(reference: ResourceRef, schema: &'static str, field: String) -> Self {
Self::UnknownField {
reference,
schema,
field,
}
}
fn field_type(reference: ResourceRef, field: &'static str) -> Self {
Self::FieldType { reference, field }
}
}
impl IdentifiedBody for ModelError {
fn malformed_id(reference: ResourceRef, field: &'static str, source: InvalidId) -> Self {
Self::MalformedId {
reference,
field,
source,
}
}
fn identity_mismatch(reference: ResourceRef, declared: String, identity: ResourceId) -> Self {
Self::IdentityMismatch {
reference,
declared,
identity,
}
}
}
type ModelRecord<'a> = Record<'a, ModelError>;
fn wire_family(record: &ModelRecord<'_>) -> Result<WireFamily, ModelError> {
let declared = record.string(WIRE_FAMILY_FIELD)?;
WireFamily::parse(declared).ok_or_else(|| ModelError::UnknownWireFamily {
reference: record.reference(),
found: declared.to_owned(),
})
}
fn lifecycle(record: &ModelRecord<'_>) -> Result<ModelLifecycle, ModelError> {
let declared = record.string(STATE_FIELD)?;
ModelLifecycle::parse(declared).ok_or_else(|| ModelError::UnknownLifecycle {
reference: record.reference(),
found: declared.to_owned(),
})
}
fn nested<'a>(
sub: &ModelRecord<'a>,
name: &'static str,
path: &'static str,
) -> Result<&'a CanonicalValue, ModelError> {
sub.optional_value(name).ok_or(ModelError::MissingField {
reference: sub.reference(),
field: path,
})
}
fn integer(
record: &ModelRecord<'_>,
value: &CanonicalValue,
field: &'static str,
) -> Result<i128, ModelError> {
match value {
CanonicalValue::Integer(number) => Ok(*number),
_ => Err(ModelError::FieldType {
reference: record.reference(),
field,
}),
}
}
fn version_number(
record: &ModelRecord<'_>,
value: &CanonicalValue,
field: &'static str,
) -> Result<ResourceVersionNumber, ModelError> {
let number = integer(record, value, field)?;
u64::try_from(number)
.ok()
.and_then(ResourceVersionNumber::new)
.ok_or(ModelError::VersionZero {
reference: record.reference(),
found: number,
})
}
fn micros(
record: &ModelRecord<'_>,
value: &CanonicalValue,
field: &'static str,
) -> Result<u64, ModelError> {
let number = integer(record, value, field)?;
u64::try_from(number).map_err(|_| ModelError::PriceRange {
reference: record.reference(),
field,
found: number,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModelEnablementBody {
enablement: ResourceId,
owner: ModelOwner,
offering: CatalogOffering,
wire_family: WireFamily,
state: ModelLifecycle,
observed: Option<ObservedPrice>,
approved: Option<ApprovedPrice>,
}
impl ModelEnablementBody {
pub const SCHEMA: &'static str = MODEL_ENABLEMENT_SCHEMA;
const KNOWN_FIELDS: &'static [&'static str] = &[
ENABLEMENT_ID_FIELD,
TENANT_ID_FIELD,
PROJECT_ID_FIELD,
OFFERING_ID_FIELD,
CATALOG_SNAPSHOT_FIELD,
WIRE_FAMILY_FIELD,
STATE_FIELD,
OBSERVED_PRICE_FIELD,
APPROVED_PRICE_FIELD,
];
pub const fn new(
enablement: ResourceId,
owner: ModelOwner,
offering: CatalogOffering,
wire_family: WireFamily,
) -> Self {
Self {
enablement,
owner,
offering,
wire_family,
state: ModelLifecycle::Enabled,
observed: None,
approved: None,
}
}
#[must_use]
pub fn observing(mut self, observed: ObservedPrice) -> Self {
self.observed = Some(observed);
self
}
#[must_use]
pub fn approving(mut self, approved: ApprovedPrice) -> Self {
self.approved = Some(approved);
self
}
#[must_use]
pub fn transitioned(mut self, state: ModelLifecycle) -> Self {
self.state = state;
self
}
pub const fn enablement(&self) -> ResourceId {
self.enablement
}
pub const fn owner(&self) -> ModelOwner {
self.owner
}
pub const fn offering(&self) -> CatalogOffering {
self.offering
}
pub const fn wire_family(&self) -> WireFamily {
self.wire_family
}
pub const fn state(&self) -> ModelLifecycle {
self.state
}
pub const fn is_enabled(&self) -> bool {
self.state.is_enabled()
}
pub const fn observed_price(&self) -> Option<ObservedPrice> {
self.observed
}
pub const fn billable_price(&self) -> Option<ApprovedPrice> {
self.approved
}
pub const fn resource_id(&self) -> ResourceId {
self.enablement
}
pub const fn scope(&self) -> ResourceScope {
self.owner.scope()
}
pub fn body(&self) -> ResourceBody {
ResourceBody::Inline(self.canonical())
}
pub fn version(&self, slug: Slug, catalog: ResourceRef) -> ResourceVersion {
self.version_at(slug, ResourceVersionNumber::FIRST, catalog)
}
pub fn version_at(
&self,
slug: Slug,
version: ResourceVersionNumber,
catalog: ResourceRef,
) -> ResourceVersion {
let dependencies = std::iter::once(catalog)
.chain(self.approved.map(ApprovedPrice::reference))
.collect::<Vec<_>>();
ResourceVersion::new(
ResourceRef::new(ResourceKind::ModelEnablement, self.resource_id(), version),
self.scope(),
slug,
self.body(),
)
.depending_on(dependencies)
}
pub fn transition_from(
&self,
previous: &Self,
) -> Result<LifecycleChange, ForbiddenModelTransition> {
for (invariant, unchanged) in [
(
ModelInvariant::Identity,
previous.enablement == self.enablement,
),
(ModelInvariant::Owner, previous.owner == self.owner),
(
ModelInvariant::Offering,
previous.offering.offering == self.offering.offering,
),
(
ModelInvariant::Snapshot,
previous.offering.snapshot == self.offering.snapshot,
),
(
ModelInvariant::WireFamily,
previous.wire_family == self.wire_family,
),
] {
if !unchanged {
return Err(ForbiddenModelTransition { invariant });
}
}
Ok(previous.state.transition_to(self.state))
}
pub fn read(resource: &ResourceVersion) -> Result<Self, ModelError> {
let record = ModelRecord::open(
resource,
ResourceKind::ModelEnablement,
Self::SCHEMA,
Self::KNOWN_FIELDS,
)?;
let enablement = record.typed_id(ENABLEMENT_ID_FIELD, ResourceId::parse)?;
record.identity(enablement, enablement)?;
let owner = ModelOwner {
tenant: record.tenant()?,
project: record.optional_project()?,
};
if resource.scope != owner.scope() {
return Err(ModelError::OwnerMismatch {
reference: resource.reference,
declared: owner,
});
}
let offering = OfferingId::parse(record.string(OFFERING_ID_FIELD)?).map_err(|source| {
ModelError::MalformedOffering {
reference: resource.reference,
field: OFFERING_ID_FIELD,
source,
}
})?;
let snapshot =
Checksum::parse(record.string(CATALOG_SNAPSHOT_FIELD)?).map_err(|source| {
ModelError::MalformedChecksum {
reference: resource.reference,
field: CATALOG_SNAPSHOT_FIELD,
source,
}
})?;
let observed = match record.optional_value(OBSERVED_PRICE_FIELD) {
None => None,
Some(value) => {
let sub = record.sub_record(
value,
OBSERVED_PRICE_FIELD,
MODEL_ENABLEMENT_SCHEMA,
OBSERVED_PRICE_FIELDS,
)?;
let input = nested(&sub, INPUT_MICROS_FIELD, OBSERVED_INPUT_PATH)?;
let output = nested(&sub, OUTPUT_MICROS_FIELD, OBSERVED_OUTPUT_PATH)?;
Some(ObservedPrice::new(
micros(&record, input, OBSERVED_INPUT_PATH)?,
micros(&record, output, OBSERVED_OUTPUT_PATH)?,
))
}
};
let approved = match record.optional_value(APPROVED_PRICE_FIELD) {
None => None,
Some(value) => {
let sub = record.sub_record(
value,
APPROVED_PRICE_FIELD,
MODEL_ENABLEMENT_SCHEMA,
APPROVED_PRICE_FIELDS,
)?;
let price = nested(&sub, PRICE_ID_FIELD, APPROVED_PRICE_ID_PATH)?;
let version = nested(&sub, VERSION_FIELD, APPROVED_VERSION_PATH)?;
let CanonicalValue::String(text) = price else {
return Err(ModelError::FieldType {
reference: resource.reference,
field: APPROVED_PRICE_ID_PATH,
});
};
let price = ResourceId::parse(text).map_err(|source| ModelError::MalformedId {
reference: resource.reference,
field: APPROVED_PRICE_ID_PATH,
source,
})?;
Some(ApprovedPrice::version(
price,
version_number(&record, version, APPROVED_VERSION_PATH)?,
))
}
};
Ok(Self {
enablement,
owner,
offering: CatalogOffering::new(offering, snapshot),
wire_family: wire_family(&record)?,
state: lifecycle(&record)?,
observed,
approved,
})
}
}
impl Canonical for ModelEnablementBody {
fn canonical(&self) -> CanonicalValue {
let mut fields = vec![
(SCHEMA_FIELD, CanonicalValue::string(Self::SCHEMA)),
(
ENABLEMENT_ID_FIELD,
CanonicalValue::string(self.enablement.to_string()),
),
(
TENANT_ID_FIELD,
CanonicalValue::string(self.owner.tenant.to_string()),
),
(
OFFERING_ID_FIELD,
CanonicalValue::string(self.offering.offering.to_string()),
),
(
CATALOG_SNAPSHOT_FIELD,
CanonicalValue::string(self.offering.snapshot.to_string()),
),
(
WIRE_FAMILY_FIELD,
CanonicalValue::string(self.wire_family.as_str()),
),
(STATE_FIELD, CanonicalValue::string(self.state.as_str())),
];
if let Some(project) = self.owner.project {
fields.push((
PROJECT_ID_FIELD,
CanonicalValue::string(project.to_string()),
));
}
if let Some(observed) = self.observed {
fields.push((
OBSERVED_PRICE_FIELD,
CanonicalValue::map([
(
INPUT_MICROS_FIELD,
CanonicalValue::integer(i128::from(observed.input_micros_per_million)),
),
(
OUTPUT_MICROS_FIELD,
CanonicalValue::integer(i128::from(observed.output_micros_per_million)),
),
]),
));
}
if let Some(approved) = self.approved {
let reference = approved.reference();
fields.push((
APPROVED_PRICE_FIELD,
CanonicalValue::map([
(
PRICE_ID_FIELD,
CanonicalValue::string(reference.id.to_string()),
),
(
VERSION_FIELD,
CanonicalValue::integer(i128::from(reference.version.get())),
),
]),
));
}
CanonicalValue::map(fields)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModelAliasBody {
alias: ResourceId,
tenant: TenantId,
project: ProjectId,
wire_family: WireFamily,
state: ModelLifecycle,
targets: Vec<AliasTarget>,
}
impl ModelAliasBody {
pub const SCHEMA: &'static str = MODEL_ALIAS_SCHEMA;
const KNOWN_FIELDS: &'static [&'static str] = &[
ALIAS_ID_FIELD,
TENANT_ID_FIELD,
PROJECT_ID_FIELD,
WIRE_FAMILY_FIELD,
STATE_FIELD,
TARGETS_FIELD,
];
pub fn new(
alias: ResourceId,
tenant: TenantId,
project: ProjectId,
wire_family: WireFamily,
targets: impl IntoIterator<Item = AliasTarget>,
) -> Self {
Self {
alias,
tenant,
project,
wire_family,
state: ModelLifecycle::Enabled,
targets: targets.into_iter().collect(),
}
}
#[must_use]
pub fn transitioned(mut self, state: ModelLifecycle) -> Self {
self.state = state;
self
}
#[must_use]
pub fn retargeted(mut self, targets: impl IntoIterator<Item = AliasTarget>) -> Self {
self.targets = targets.into_iter().collect();
self
}
pub const fn alias(&self) -> ResourceId {
self.alias
}
pub const fn tenant(&self) -> TenantId {
self.tenant
}
pub const fn project(&self) -> ProjectId {
self.project
}
pub const fn owner(&self) -> ModelOwner {
ModelOwner::project(self.tenant, self.project)
}
pub const fn wire_family(&self) -> WireFamily {
self.wire_family
}
pub const fn state(&self) -> ModelLifecycle {
self.state
}
pub const fn is_enabled(&self) -> bool {
self.state.is_enabled()
}
pub fn targets(&self) -> &[AliasTarget] {
&self.targets
}
pub fn primary(&self) -> Option<AliasTarget> {
self.targets.first().copied()
}
pub const fn resource_id(&self) -> ResourceId {
self.alias
}
pub const fn scope(&self) -> ResourceScope {
ResourceScope::Project {
tenant: self.tenant,
project: self.project,
}
}
pub fn body(&self) -> ResourceBody {
ResourceBody::Inline(self.canonical())
}
pub fn version(&self, slug: Slug) -> ResourceVersion {
self.version_at(slug, ResourceVersionNumber::FIRST)
}
pub fn version_at(&self, slug: Slug, version: ResourceVersionNumber) -> ResourceVersion {
ResourceVersion::new(
ResourceRef::new(ResourceKind::Alias, self.resource_id(), version),
self.scope(),
slug,
self.body(),
)
.depending_on(self.targets.iter().map(|target| target.reference()))
}
pub fn transition_from(
&self,
previous: &Self,
) -> Result<LifecycleChange, ForbiddenModelTransition> {
for (invariant, unchanged) in [
(ModelInvariant::Identity, previous.alias == self.alias),
(ModelInvariant::Owner, previous.owner() == self.owner()),
(
ModelInvariant::WireFamily,
previous.wire_family == self.wire_family,
),
] {
if !unchanged {
return Err(ForbiddenModelTransition { invariant });
}
}
Ok(previous.state.transition_to(self.state))
}
pub fn read(resource: &ResourceVersion) -> Result<Self, ModelError> {
let record = ModelRecord::open(
resource,
ResourceKind::Alias,
Self::SCHEMA,
Self::KNOWN_FIELDS,
)?;
let alias = record.typed_id(ALIAS_ID_FIELD, ResourceId::parse)?;
record.identity(alias, alias)?;
let tenant = record.tenant()?;
let project = record.project()?;
match &resource.scope {
ResourceScope::Project { .. } => {}
_ => {
return Err(ModelError::NotProjectScoped {
reference: resource.reference,
});
}
}
if resource.scope != (ResourceScope::Project { tenant, project }) {
return Err(ModelError::OwnerMismatch {
reference: resource.reference,
declared: ModelOwner::project(tenant, project),
});
}
let CanonicalValue::List(targets) = record.value(TARGETS_FIELD)? else {
return Err(ModelError::FieldType {
reference: resource.reference,
field: TARGETS_FIELD,
});
};
let targets = targets
.iter()
.map(|target| {
let sub = record.sub_record(
target,
TARGETS_FIELD,
MODEL_ALIAS_SCHEMA,
ALIAS_TARGET_FIELDS,
)?;
let enablement = nested(&sub, ENABLEMENT_ID_FIELD, TARGET_ENABLEMENT_ID_PATH)?;
let version = nested(&sub, VERSION_FIELD, TARGET_VERSION_PATH)?;
let CanonicalValue::String(text) = enablement else {
return Err(ModelError::FieldType {
reference: resource.reference,
field: TARGET_ENABLEMENT_ID_PATH,
});
};
let enablement =
ResourceId::parse(text).map_err(|source| ModelError::MalformedId {
reference: resource.reference,
field: TARGET_ENABLEMENT_ID_PATH,
source,
})?;
Ok(AliasTarget::new(
enablement,
version_number(&record, version, TARGET_VERSION_PATH)?,
))
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Self {
alias,
tenant,
project,
wire_family: wire_family(&record)?,
state: lifecycle(&record)?,
targets,
})
}
}
impl Canonical for ModelAliasBody {
fn canonical(&self) -> CanonicalValue {
CanonicalValue::map([
(SCHEMA_FIELD, CanonicalValue::string(Self::SCHEMA)),
(
ALIAS_ID_FIELD,
CanonicalValue::string(self.alias.to_string()),
),
(
TENANT_ID_FIELD,
CanonicalValue::string(self.tenant.to_string()),
),
(
PROJECT_ID_FIELD,
CanonicalValue::string(self.project.to_string()),
),
(
WIRE_FAMILY_FIELD,
CanonicalValue::string(self.wire_family.as_str()),
),
(STATE_FIELD, CanonicalValue::string(self.state.as_str())),
(
TARGETS_FIELD,
CanonicalValue::List(
self.targets
.iter()
.map(|target| target.canonical())
.collect(),
),
),
])
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModelEnablement {
pub reference: ResourceRef,
pub slug: Slug,
pub body: ModelEnablementBody,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModelAlias {
pub reference: ResourceRef,
pub slug: Slug,
pub body: ModelAliasBody,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Models {
enablements: BTreeMap<ResourceId, ModelEnablement>,
aliases: BTreeMap<ResourceId, ModelAlias>,
}
impl Models {
pub fn of(state: &DesiredState) -> Result<Self, ModelError> {
let mut models = Self::default();
for resource in state.resources() {
match resource.reference.kind {
ResourceKind::ModelEnablement => {
let body = ModelEnablementBody::read(resource)?;
models.enablements.insert(
body.enablement(),
ModelEnablement {
reference: resource.reference,
slug: resource.slug.clone(),
body,
},
);
}
ResourceKind::Alias if is_typed(resource) => {
let body = ModelAliasBody::read(resource)?;
models.aliases.insert(
body.alias(),
ModelAlias {
reference: resource.reference,
slug: resource.slug.clone(),
body,
},
);
}
_ => {}
}
}
let mut offerings: BTreeMap<(ModelOwner, OfferingId), ResourceRef> = BTreeMap::new();
for enablement in models.enablements.values() {
let resource = state
.get(&enablement.reference)
.expect("the enablement was read from this state");
check_snapshot_pin(state, resource, &enablement.body)?;
if let Some(approved) = enablement.body.billable_price() {
check_reference(
state,
resource,
approved.reference(),
enablement.body.owner(),
)?;
}
if !enablement.body.is_enabled() {
continue;
}
let owner = enablement.body.owner();
let offering = enablement.body.offering().offering;
if let Some(conflicting) = offerings.insert((owner, offering), enablement.reference) {
return Err(ModelError::DuplicateOffering {
reference: enablement.reference,
offering,
conflicting,
});
}
}
for alias in models.aliases.values() {
let resource = state
.get(&alias.reference)
.expect("the alias was read from this state");
models.check_targets(state, resource, &alias.body)?;
}
Ok(models)
}
fn check_targets(
&self,
state: &DesiredState,
resource: &ResourceVersion,
body: &ModelAliasBody,
) -> Result<(), ModelError> {
if body.targets().is_empty() {
return Err(ModelError::NoTargets {
reference: resource.reference,
});
}
let mut seen: Vec<ResourceId> = Vec::with_capacity(body.targets().len());
for target in body.targets() {
if seen.contains(&target.enablement) {
return Err(ModelError::DuplicateTarget {
reference: resource.reference,
target: target.reference(),
});
}
seen.push(target.enablement);
check_reference(state, resource, target.reference(), body.owner())?;
if let Some(enabled) = self.enablements.get(&target.enablement)
&& enabled.body.wire_family() != body.wire_family()
{
return Err(ModelError::WireFamilyMismatch {
reference: resource.reference,
target: target.reference(),
alias: body.wire_family(),
found: enabled.body.wire_family(),
});
}
}
Ok(())
}
pub fn enablements(&self) -> impl ExactSizeIterator<Item = &ModelEnablement> {
self.enablements.values()
}
pub fn aliases(&self) -> impl ExactSizeIterator<Item = &ModelAlias> {
self.aliases.values()
}
pub fn enablement(&self, id: ResourceId) -> Option<&ModelEnablement> {
self.enablements.get(&id)
}
pub fn alias(&self, id: ResourceId) -> Option<&ModelAlias> {
self.aliases.get(&id)
}
pub fn aliases_of(&self, project: ProjectId) -> impl Iterator<Item = &ModelAlias> {
self.aliases
.values()
.filter(move |alias| alias.body.project() == project)
}
pub fn default_for(&self, tenant: TenantId, offering: OfferingId) -> Option<&ModelEnablement> {
self.enablement_at(ModelOwner::tenant(tenant), offering)
}
pub fn override_for(
&self,
tenant: TenantId,
project: ProjectId,
offering: OfferingId,
) -> Option<&ModelEnablement> {
self.enablement_at(ModelOwner::project(tenant, project), offering)
}
pub fn effective_for(
&self,
tenant: TenantId,
project: ProjectId,
offering: OfferingId,
) -> Option<&ModelEnablement> {
self.override_for(tenant, project, offering)
.or_else(|| self.default_for(tenant, offering))
}
fn enablement_at(&self, owner: ModelOwner, offering: OfferingId) -> Option<&ModelEnablement> {
self.enablements.values().find(|enablement| {
enablement.body.owner() == owner && enablement.body.offering().offering == offering
})
}
}
fn is_typed(resource: &ResourceVersion) -> bool {
let ResourceBody::Inline(CanonicalValue::Map(fields)) = &resource.body else {
return false;
};
fields.iter().any(|(field, _)| field == SCHEMA_FIELD)
}
fn check_snapshot_pin(
state: &DesiredState,
resource: &ResourceVersion,
body: &ModelEnablementBody,
) -> Result<(), ModelError> {
let snapshot = body.offering().snapshot;
let pinned = resource
.depends_on
.iter()
.filter(|dependency| dependency.kind == ResourceKind::CatalogModel)
.filter_map(|dependency| state.get(dependency))
.filter_map(|catalog| catalog.body.blob())
.any(|blob| blob.kind == BlobKind::CatalogSnapshot && blob.digest == snapshot);
if pinned {
Ok(())
} else {
Err(ModelError::UnpinnedSnapshot {
reference: resource.reference,
snapshot,
})
}
}
fn check_reference(
state: &DesiredState,
resource: &ResourceVersion,
target: ResourceRef,
owner: ModelOwner,
) -> Result<(), ModelError> {
if !resource.depends_on.contains(&target) {
return Err(ModelError::UndeclaredTarget {
reference: resource.reference,
target,
});
}
let Some(referenced) = state.get(&target) else {
return Err(ModelError::DanglingTarget {
reference: resource.reference,
target,
});
};
let reachable = ModelOwner::from_scope(&referenced.scope)
.is_some_and(|referenced| owner.reaches(referenced));
if reachable {
Ok(())
} else {
Err(ModelError::ForeignTarget {
reference: resource.reference,
target,
})
}
}
#[cfg(test)]
mod tests {
use super::super::canonical::{Canonical as _, SerializerVersion};
use super::super::fixtures::{
alias_body, approved_price, blob_backed_catalog, candidate, catalog_offering,
catalog_reference, catalog_snapshot, enablement_body, observed_price, offering_id, price,
project, project_enablement, project_id, resource_id, revision_id,
second_blob_backed_catalog, state, state_with_models, tenant, tenant_enablement, tenant_id,
typed_alias,
};
use super::super::mutation::ExpectedRevision;
use super::super::revision::{
BodySkew, IntegrityError, LoadedRevision, RevisionManifest, ValidationError,
};
use super::*;
use std::time::SystemTime;
fn model_error(error: &ValidationError) -> Option<&ModelError> {
match error {
ValidationError::Model(model) => Some(model),
_ => None,
}
}
fn owner_tenant() -> ModelOwner {
ModelOwner::tenant(tenant_id(1))
}
fn owner_project() -> ModelOwner {
ModelOwner::project(tenant_id(1), project_id(2))
}
fn with_fields(
resource: &ResourceVersion,
edit: impl FnOnce(&mut Vec<(String, CanonicalValue)>),
) -> ResourceVersion {
let ResourceBody::Inline(CanonicalValue::Map(fields)) = &resource.body else {
panic!("a model fixture body is an inline record");
};
let mut fields = fields.clone();
edit(&mut fields);
ResourceVersion {
body: ResourceBody::Inline(CanonicalValue::Map(fields)),
..resource.clone()
}
}
fn set(fields: &mut Vec<(String, CanonicalValue)>, field: &str, value: CanonicalValue) {
fields.retain(|(name, _)| name != field);
fields.push((field.to_owned(), value));
}
fn state_replacing(resource: ResourceVersion) -> DesiredState {
let mut state = DesiredState::new();
for blob in state_with_models().blobs() {
state.declare_blob(*blob);
}
for existing in state_with_models().resources() {
let existing = if existing.reference.same_resource(&resource.reference) {
resource.clone()
} else {
existing.clone()
};
state.insert(existing).expect("distinct references");
}
state
}
#[test]
fn an_offering_identity_is_opaque_stable_and_pinned_to_a_snapshot() {
let offering = OfferingId::of("openai", "gpt-4o").unwrap();
assert_eq!(
offering,
OfferingId::of("openai", "gpt-4o").unwrap(),
"the same offering derives the same id, so a catalogue refresh does \
not rewrite an enablement"
);
assert_ne!(offering, OfferingId::of("openai", "gpt-4o-mini").unwrap());
assert_ne!(
offering,
OfferingId::of("azure", "gpt-4o").unwrap(),
"one model name under two providers is two offerings"
);
assert_ne!(
OfferingId::of("openai", "gpt-4o").unwrap(),
OfferingId::of("openaigpt", "-4o").unwrap()
);
let text = offering.to_string();
assert!(text.starts_with(OfferingId::PREFIX));
assert_eq!(text.len(), OfferingId::PREFIX.len() + 64);
assert_eq!(OfferingId::parse(&text).unwrap(), offering);
assert!(
!text.contains("gpt") && !text.contains("openai"),
"an id carries no upstream vocabulary: {text}"
);
assert!(matches!(
OfferingId::parse(&text.replace("off_", "sha256:")),
Err(InvalidOfferingId::Prefix { .. })
));
assert!(matches!(
OfferingId::parse(&text[..text.len() - 1]),
Err(InvalidOfferingId::Digits { .. })
));
let pinned = CatalogOffering::new(offering, catalog_snapshot());
assert!(pinned.is_pinned_to(catalog_snapshot()));
assert!(
!pinned.is_pinned_to(other_snapshot()),
"a pin is to one snapshot, so a refreshed catalogue does not satisfy it"
);
assert_eq!(
pinned.to_string(),
format!("{offering}@{}", catalog_snapshot())
);
}
#[test]
fn a_body_round_trips_through_its_envelope_and_its_canonical_bytes() {
let body = enablement_body(30, owner_tenant(), "gpt-4o")
.observing(observed_price())
.approving(approved_price(40));
let resource = body.version(Slug::parse("gpt-4o").unwrap(), catalog_reference());
assert_eq!(ModelEnablementBody::read(&resource).unwrap(), body);
assert_eq!(resource.reference.id, resource_id(30));
assert_eq!(resource.scope, ResourceScope::Tenant(tenant_id(1)));
assert!(
resource.depends_on.contains(&catalog_reference())
&& resource
.depends_on
.contains(&approved_price(40).reference()),
"an authored enablement declares the snapshot it pins and the price it \
bills against"
);
let alias = alias_body(
&tenant_id(1),
&project_id(2),
32,
&[reference_of(30), reference_of(31)],
);
let resource = alias.version(Slug::parse("fast").unwrap());
assert_eq!(ModelAliasBody::read(&resource).unwrap(), alias);
let bytes = SerializerVersion::V1.encode(&alias.canonical()).unwrap();
let decoded = SerializerVersion::V1
.decode(&bytes)
.expect("a model body is canonical, so storage returns what it took");
assert_eq!(SerializerVersion::V1.encode(&decoded).unwrap(), bytes);
assert_eq!(
ModelAliasBody::read(&ResourceVersion {
body: ResourceBody::Inline(decoded),
..resource
})
.unwrap(),
alias,
"and reads back as the same body"
);
assert!(String::from_utf8_lossy(&bytes).contains(MODEL_ALIAS_SCHEMA));
}
fn other_snapshot() -> Checksum {
second_blob_backed_catalog(6)
.body
.blob()
.expect("a blob body")
.digest
}
fn reference_of(seed: u64) -> ResourceRef {
ResourceRef::new(
ResourceKind::ModelEnablement,
resource_id(seed),
ResourceVersionNumber::FIRST,
)
}
#[test]
fn target_order_is_priority_and_a_reordering_is_a_different_state() {
let first = alias_body(
&tenant_id(1),
&project_id(2),
32,
&[reference_of(31), reference_of(30)],
);
let flipped = first
.clone()
.retargeted(first.targets().iter().rev().copied().collect::<Vec<_>>());
assert_eq!(first.primary(), Some(AliasTarget::first(resource_id(31))));
assert_eq!(flipped.primary(), Some(AliasTarget::first(resource_id(30))));
assert_ne!(
first.checksum().unwrap(),
flipped.checksum().unwrap(),
"priority is content: reordering targets is a different revision"
);
assert_eq!(
first.targets(),
&[
AliasTarget::first(resource_id(31)),
AliasTarget::first(resource_id(30))
],
"the authored order is preserved exactly, not sorted"
);
}
#[test]
fn tenant_defaults_and_project_overrides_resolve_by_scope() {
let state = state_with_models();
state.validate().expect("the fixture revision is valid");
let models = Models::of(&state).unwrap();
let offering = offering_id("gpt-4o");
let default = models
.default_for(tenant_id(1), offering)
.expect("the tenant default");
assert_eq!(default.body.owner(), owner_tenant());
let over = models
.override_for(tenant_id(1), project_id(2), offering)
.expect("the project override");
assert_eq!(over.body.owner(), owner_project());
assert_eq!(
models
.effective_for(tenant_id(1), project_id(2), offering)
.map(|enablement| enablement.reference),
Some(over.reference),
"a project's own enablement replaces its tenant's default"
);
assert_eq!(
models
.effective_for(tenant_id(1), project_id(99), offering)
.map(|enablement| enablement.reference),
Some(default.reference),
"and a project without one inherits the default"
);
assert_eq!(models.aliases_of(project_id(2)).count(), 1);
assert_eq!(models.aliases_of(project_id(99)).count(), 0);
let withdrawn = enablement_body(31, owner_project(), "gpt-4o")
.transitioned(ModelLifecycle::Disabled)
.version_at(
Slug::parse("gpt-4o").unwrap(),
ResourceVersionNumber::FIRST,
catalog_reference(),
);
let state = state_replacing(withdrawn);
state
.validate()
.expect("a disabled enablement is valid desired state");
let models = Models::of(&state).unwrap();
assert!(
!models
.effective_for(tenant_id(1), project_id(2), offering)
.unwrap()
.body
.is_enabled()
);
assert!(
models
.default_for(tenant_id(1), offering)
.unwrap()
.body
.is_enabled()
);
}
#[test]
fn one_offering_is_enabled_once_per_scope() {
let duplicate = enablement_body(33, owner_tenant(), "gpt-4o")
.version(Slug::parse("gpt-4o-again").unwrap(), catalog_reference());
let mut state = state_with_models();
state.insert(duplicate).expect("a distinct reference");
let error = state
.validate()
.expect_err("two tenant defaults for one offering are ambiguous");
assert!(
matches!(
model_error(&error),
Some(ModelError::DuplicateOffering { .. })
),
"{error}"
);
}
#[test]
fn a_disabled_enablement_does_not_hold_the_offering_that_replaces_it() {
let replacement = enablement_body(33, owner_tenant(), "gpt-4o").version(
Slug::parse("gpt-4o-refreshed").unwrap(),
catalog_reference(),
);
let mut state = state_with_models();
let retired = state
.resources()
.find(|resource| {
ModelEnablementBody::read(resource)
.is_ok_and(|body| body.offering().offering == offering_id("gpt-4o"))
})
.cloned()
.expect("the state enables gpt-4o");
let body = ModelEnablementBody::read(&retired)
.expect("an enablement body")
.transitioned(ModelLifecycle::Disabled);
let disabled = body.version_at(
retired.slug.clone(),
retired.reference.version.next(),
catalog_reference(),
);
let disabled_reference = disabled.reference;
state
.supersede(disabled)
.expect("disabling advances the enablement");
let dependents: Vec<ResourceVersion> = state
.resources()
.filter(|resource| resource.depends_on.contains(&retired.reference))
.cloned()
.collect();
for dependent in dependents {
let alias = ModelAliasBody::read(&dependent).expect("an alias body");
let targets: Vec<AliasTarget> = alias
.targets()
.iter()
.map(|target| {
if target.enablement == disabled_reference.id {
AliasTarget::new(target.enablement, disabled_reference.version)
} else {
*target
}
})
.collect();
state
.supersede(
alias
.retargeted(targets)
.version_at(dependent.slug.clone(), dependent.reference.version.next()),
)
.expect("the alias follows its target");
}
state.insert(replacement).expect("a distinct reference");
state
.validate()
.expect("only what resolves can be ambiguous");
}
#[test]
fn an_enablement_is_pinned_to_a_snapshot_the_revision_declares() {
let unpinned = ModelEnablementBody::new(
resource_id(30),
owner_tenant(),
CatalogOffering::new(offering_id("gpt-4o"), other_snapshot()),
WireFamily::OpenaiChat,
)
.version(Slug::parse("gpt-4o").unwrap(), catalog_reference());
let error = state_replacing(unpinned)
.validate()
.expect_err("a pin naming an undeclared snapshot must be refused");
assert!(
matches!(
model_error(&error),
Some(ModelError::UnpinnedSnapshot { .. })
),
"{error}"
);
let undeclared = ResourceVersion::new(
reference_of(30),
ResourceScope::Tenant(tenant_id(1)),
Slug::parse("gpt-4o").unwrap(),
enablement_body(30, owner_tenant(), "gpt-4o").body(),
);
let error = state_replacing(undeclared)
.validate()
.expect_err("an unpinned enablement must be refused");
assert!(
matches!(
model_error(&error),
Some(ModelError::UnpinnedSnapshot { .. })
),
"{error}"
);
}
#[test]
fn an_observed_catalogue_rate_is_not_an_approved_price() {
let observed = enablement_body(30, owner_tenant(), "gpt-4o").observing(observed_price());
assert_eq!(observed.observed_price(), Some(observed_price()));
assert_eq!(
observed.billable_price(),
None,
"what a catalogue publishes is not what a deployment charges"
);
let approved = observed.clone().approving(approved_price(40));
assert_eq!(approved.billable_price(), Some(approved_price(40)));
assert_ne!(
observed.checksum().unwrap(),
approved.checksum().unwrap(),
"approving a price is a change to desired state"
);
assert_eq!(
ApprovedPrice::of(catalog_reference()),
None,
"only a price resource can be an approved price"
);
let enablement = enablement_body(30, owner_tenant(), "gpt-4o")
.approving(approved_price(40))
.version(Slug::parse("gpt-4o").unwrap(), catalog_reference());
let mut state = state_replacing(enablement.clone());
state
.insert(price(&tenant_id(1), 40, "gpt-4o-rate"))
.expect("a distinct reference");
state
.validate()
.expect("an approved price of the same tenant is reachable");
let mut state = state_replacing(enablement);
state
.insert(tenant(11, "globex"))
.and_then(|state| state.insert(price(&tenant_id(11), 40, "foreign-rate")))
.expect("a second tenant");
let error = state
.validate()
.expect_err("a price belonging to another tenant is unreachable");
assert!(
matches!(error, ValidationError::CrossTenantReference { .. })
|| matches!(model_error(&error), Some(ModelError::ForeignTarget { .. })),
"{error}"
);
}
#[test]
fn an_alias_resolves_in_order_within_its_own_reach_and_one_wire_family() {
let (tenant, project) = (tenant_id(1), project_id(2));
let dangling = typed_alias(
&tenant,
&project,
32,
"fast",
&[reference_of(31), reference_of(77)],
);
let error = state_replacing(dangling)
.validate()
.expect_err("an alias cannot resolve to a row that is not here");
assert!(
matches!(error, ValidationError::DanglingResourceReference { .. })
|| matches!(model_error(&error), Some(ModelError::DanglingTarget { .. })),
"{error}"
);
let duplicated = typed_alias(
&tenant,
&project,
32,
"fast",
&[reference_of(31), reference_of(31)],
);
let error = state_replacing(duplicated)
.validate()
.expect_err("a priority list may not repeat a target");
assert!(
matches!(
model_error(&error),
Some(ModelError::DuplicateTarget { .. })
),
"{error}"
);
let sibling = project_enablement(&tenant, &project_id(9), 34, "gpt-4o-mini");
let reaching = typed_alias(
&tenant,
&project,
32,
"fast",
&[reference_of(31), sibling.reference],
);
let mut state = state_replacing(reaching);
state
.insert(super::super::fixtures::project(&tenant, 9, "other"))
.and_then(|state| state.insert(sibling))
.expect("a second project of the same tenant");
let error = state
.validate()
.expect_err("an alias does not reach a sibling project's enablement");
assert!(
matches!(model_error(&error), Some(ModelError::ForeignTarget { .. })),
"{error}"
);
let anthropic = ModelEnablementBody::new(
resource_id(35),
ModelOwner::tenant(tenant),
catalog_offering("claude-sonnet"),
WireFamily::AnthropicMessages,
)
.version(Slug::parse("claude-sonnet").unwrap(), catalog_reference());
let mixed = typed_alias(
&tenant,
&project,
32,
"fast",
&[reference_of(31), anthropic.reference],
);
let mut state = state_replacing(mixed);
state.insert(anthropic).expect("a distinct reference");
let error = state
.validate()
.expect_err("one name cannot mean two request shapes");
assert!(
matches!(
model_error(&error),
Some(ModelError::WireFamilyMismatch { .. })
),
"{error}"
);
let empty = typed_alias(&tenant, &project, 32, "fast", &[]);
let error = state_replacing(empty)
.validate()
.expect_err("an alias with no targets resolves to nothing");
assert!(
matches!(model_error(&error), Some(ModelError::NoTargets { .. })),
"{error}"
);
}
#[test]
fn an_alias_is_a_project_scoped_name_unique_within_its_project() {
let tenant = tenant_id(1);
let sibling_project = project(&tenant, 9, "other");
let sibling_enablement = project_enablement(&tenant, &project_id(9), 34, "gpt-4o");
let sibling_alias = typed_alias(
&tenant,
&project_id(9),
36,
"fast",
&[sibling_enablement.reference],
);
let mut state = state_with_models();
state
.insert(sibling_project)
.and_then(|state| state.insert(sibling_enablement))
.and_then(|state| state.insert(sibling_alias))
.expect("distinct references");
state
.validate()
.expect("`fast` in two projects is two aliases, not a collision");
let second = typed_alias(&tenant, &project_id(2), 37, "fast", &[reference_of(31)]);
let mut state = state_with_models();
state.insert(second).expect("a distinct reference");
let error = state
.validate()
.expect_err("one project cannot publish one name twice");
assert!(
matches!(error, ValidationError::DuplicateSlug { .. }),
"{error}"
);
let body = alias_body(&tenant, &project_id(2), 32, &[reference_of(31)]);
let outside = ResourceVersion::new(
ResourceRef::new(
ResourceKind::Alias,
resource_id(32),
ResourceVersionNumber::FIRST,
),
ResourceScope::Tenant(tenant),
Slug::parse("fast").unwrap(),
body.body(),
);
assert_eq!(
ModelAliasBody::read(&outside),
Err(ModelError::NotProjectScoped {
reference: outside.reference
})
);
}
#[test]
fn a_body_is_bound_to_the_envelope_it_is_filed_under() {
let enablement = tenant_enablement(&tenant_id(1), 30, "gpt-4o");
let renamed = ResourceVersion {
reference: reference_of(99),
..enablement.clone()
};
assert!(matches!(
ModelEnablementBody::read(&renamed),
Err(ModelError::IdentityMismatch { .. })
));
let misfiled = ResourceVersion {
scope: ResourceScope::Project {
tenant: tenant_id(1),
project: project_id(2),
},
..enablement
};
assert_eq!(
ModelEnablementBody::read(&misfiled),
Err(ModelError::OwnerMismatch {
reference: misfiled.reference,
declared: owner_tenant()
}),
"a tenant default filed inside a project would be an override nobody \
authored"
);
}
#[test]
fn a_lifecycle_move_is_total_and_a_version_may_not_change_what_a_model_is() {
for from in ModelLifecycle::ALL.iter().copied() {
for to in ModelLifecycle::ALL.iter().copied() {
let change = from.transition_to(to);
assert_eq!(change.state(), to);
assert_eq!(change.changed(), from != to);
assert_eq!(
ModelLifecycle::parse(to.as_str()),
Some(to),
"every state has one identifier"
);
}
}
assert_eq!(ModelLifecycle::parse("retired"), None);
assert_eq!(WireFamily::parse("openai-responses"), None);
let enabled = enablement_body(30, owner_tenant(), "gpt-4o");
let disabled = enabled.clone().transitioned(ModelLifecycle::Disabled);
assert_eq!(
disabled.transition_from(&enabled),
Ok(LifecycleChange::Moved {
from: ModelLifecycle::Enabled,
to: ModelLifecycle::Disabled
})
);
assert_eq!(
enabled.transition_from(&enabled),
Ok(LifecycleChange::Unchanged(ModelLifecycle::Enabled)),
"a retried administrative call is an answer, not a conflict"
);
assert!(
disabled
.clone()
.approving(approved_price(40))
.transition_from(&enabled)
.is_ok()
);
for (next, invariant) in [
(
enablement_body(30, owner_project(), "gpt-4o"),
ModelInvariant::Owner,
),
(
enablement_body(30, owner_tenant(), "gpt-4o-mini"),
ModelInvariant::Offering,
),
(
enablement_body(31, owner_tenant(), "gpt-4o"),
ModelInvariant::Identity,
),
] {
assert_eq!(
next.transition_from(&enabled),
Err(ForbiddenModelTransition { invariant }),
"{invariant} is durable across versions"
);
}
let repinned = ModelEnablementBody::new(
resource_id(30),
owner_tenant(),
CatalogOffering::new(offering_id("gpt-4o"), other_snapshot()),
WireFamily::OpenaiChat,
);
assert_eq!(
repinned.transition_from(&enabled),
Err(ForbiddenModelTransition {
invariant: ModelInvariant::Snapshot
}),
"a refreshed catalogue is a new enablement, not a re-pinned one"
);
let alias = alias_body(&tenant_id(1), &project_id(2), 32, &[reference_of(31)]);
assert!(
alias
.clone()
.retargeted([AliasTarget::first(resource_id(30))])
.transition_from(&alias)
.is_ok(),
"re-prioritizing is what an operator does to a published name"
);
let other_family = ModelAliasBody::new(
resource_id(32),
tenant_id(1),
project_id(2),
WireFamily::AnthropicMessages,
[AliasTarget::first(resource_id(31))],
);
assert_eq!(
other_family.transition_from(&alias),
Err(ForbiddenModelTransition {
invariant: ModelInvariant::WireFamily
}),
"callers of a name were written against the shape it promised"
);
}
#[test]
fn a_schema_this_build_does_not_read_is_refused_rather_than_guessed_at() {
let resource = tenant_enablement(&tenant_id(1), 30, "gpt-4o");
let newer = with_fields(&resource, |fields| {
set(
fields,
SCHEMA_FIELD,
CanonicalValue::string("axond.model-enablement.v2"),
);
});
assert_eq!(
ModelEnablementBody::read(&newer),
Err(ModelError::Schema {
reference: newer.reference,
expected: MODEL_ENABLEMENT_SCHEMA,
found: "axond.model-enablement.v2".to_owned()
})
);
let extended = with_fields(&resource, |fields| {
set(fields, "residency", CanonicalValue::string("eu"));
});
assert_eq!(
ModelEnablementBody::read(&extended),
Err(ModelError::UnknownField {
reference: extended.reference,
schema: MODEL_ENABLEMENT_SCHEMA,
field: "residency".to_owned()
})
);
for error in [
ModelEnablementBody::read(&newer).unwrap_err(),
ModelEnablementBody::read(&extended).unwrap_err(),
ModelEnablementBody::read(&with_fields(&resource, |fields| {
set(fields, STATE_FIELD, CanonicalValue::string("retired"));
}))
.unwrap_err(),
ModelEnablementBody::read(&with_fields(&resource, |fields| {
set(
fields,
WIRE_FAMILY_FIELD,
CanonicalValue::string("openai-responses"),
);
}))
.unwrap_err(),
] {
assert!(
error.is_incompatible(),
"a body a newer release wrote is a compatibility refusal: {error}"
);
assert_eq!(error.reference(), resource.reference);
}
let damaged = with_fields(&resource, |fields| {
fields.retain(|(name, _)| name != WIRE_FAMILY_FIELD);
});
let error = ModelEnablementBody::read(&damaged).unwrap_err();
assert_eq!(
error,
ModelError::MissingField {
reference: damaged.reference,
field: WIRE_FAMILY_FIELD
}
);
assert!(!error.is_incompatible());
for (field, value, expected) in [
(
OFFERING_ID_FIELD,
CanonicalValue::string("gpt-4o"),
"offering",
),
(
CATALOG_SNAPSHOT_FIELD,
CanonicalValue::string("sha512:0"),
"checksum",
),
(TENANT_ID_FIELD, CanonicalValue::string("acme"), "id"),
(STATE_FIELD, CanonicalValue::integer(1), "type"),
] {
let broken = with_fields(&resource, |fields| set(fields, field, value));
let error = ModelEnablementBody::read(&broken)
.expect_err("a malformed field is a typed refusal");
assert!(
error.to_string().contains(expected),
"{error} should say what {field} is not"
);
}
}
fn extend_nested(resource: &ResourceVersion, outer: &str, field: &str) -> ResourceVersion {
with_fields(resource, |fields| {
let (_, value) = fields
.iter_mut()
.find(|(name, _)| name == outer)
.expect("the fixture body carries the nested field");
let CanonicalValue::Map(nested) = value else {
panic!("{outer} is a nested record");
};
nested.push((field.to_owned(), CanonicalValue::string("later")));
})
}
#[test]
fn a_field_a_newer_release_added_inside_a_nested_record_is_refused_too() {
let enablement = enablement_body(30, owner_tenant(), "gpt-4o")
.observing(observed_price())
.approving(approved_price(40))
.version(Slug::parse("gpt-4o").unwrap(), catalog_reference());
for (outer, field, schema) in [
(
OBSERVED_PRICE_FIELD,
"cached_input_micros_per_million",
MODEL_ENABLEMENT_SCHEMA,
),
(
APPROVED_PRICE_FIELD,
"effective_from",
MODEL_ENABLEMENT_SCHEMA,
),
] {
let extended = extend_nested(&enablement, outer, field);
let error = ModelEnablementBody::read(&extended).expect_err("an extended sub-record");
assert_eq!(
error,
ModelError::UnknownField {
reference: extended.reference,
schema,
field: format!("{outer}.{field}")
}
);
assert!(
error.is_incompatible(),
"a body a newer release wrote is a compatibility refusal: {error}"
);
}
let alias = typed_alias(
&tenant_id(1),
&project_id(2),
32,
"fast",
&[reference_of(30)],
);
let extended = with_first_target(&alias, |target| {
target.push(("weight".to_owned(), CanonicalValue::integer(1)));
});
let error = ModelAliasBody::read(&extended).expect_err("an extended target");
assert_eq!(
error,
ModelError::UnknownField {
reference: extended.reference,
schema: MODEL_ALIAS_SCHEMA,
field: format!("{TARGETS_FIELD}.weight")
}
);
assert!(error.is_incompatible());
let error = state_replacing(extended)
.validate()
.expect_err("a revision carrying an extended sub-record is not valid");
assert!(
matches!(model_error(&error), Some(ModelError::UnknownField { .. })),
"{error}"
);
}
fn with_first_target(
resource: &ResourceVersion,
mutate: impl FnOnce(&mut Vec<(String, CanonicalValue)>),
) -> ResourceVersion {
with_fields(resource, |fields| {
let (_, value) = fields
.iter_mut()
.find(|(name, _)| name == TARGETS_FIELD)
.expect("a typed alias carries targets");
let CanonicalValue::List(targets) = value else {
panic!("targets is a list");
};
let CanonicalValue::Map(target) = &mut targets[0] else {
panic!("a target is a nested record");
};
mutate(target);
})
}
#[test]
fn a_value_missing_inside_a_nested_record_is_named_by_its_path() {
let enablement = enablement_body(30, owner_tenant(), "gpt-4o")
.observing(observed_price())
.approving(approved_price(40))
.version(Slug::parse("gpt-4o").unwrap(), catalog_reference());
for (outer, field, path) in [
(
OBSERVED_PRICE_FIELD,
INPUT_MICROS_FIELD,
OBSERVED_INPUT_PATH,
),
(
OBSERVED_PRICE_FIELD,
OUTPUT_MICROS_FIELD,
OBSERVED_OUTPUT_PATH,
),
(APPROVED_PRICE_FIELD, PRICE_ID_FIELD, APPROVED_PRICE_ID_PATH),
(APPROVED_PRICE_FIELD, VERSION_FIELD, APPROVED_VERSION_PATH),
] {
let damaged = with_fields(&enablement, |fields| {
let (_, value) = fields
.iter_mut()
.find(|(name, _)| name == outer)
.expect("the fixture body carries the nested record");
let CanonicalValue::Map(nested) = value else {
panic!("{outer} is a nested record");
};
nested.retain(|(name, _)| name != field);
});
let error = ModelEnablementBody::read(&damaged)
.expect_err("a sub-record missing a value is refused");
assert_eq!(
error,
ModelError::MissingField {
reference: damaged.reference,
field: path
}
);
assert!(
!error.is_incompatible(),
"a value this build reads is damage, not skew: {error}"
);
}
let damaged = with_fields(&enablement, |fields| {
let (_, value) = fields
.iter_mut()
.find(|(name, _)| name == APPROVED_PRICE_FIELD)
.expect("the fixture body carries an approved price");
let CanonicalValue::Map(nested) = value else {
panic!("approved_price is a nested record");
};
set(nested, PRICE_ID_FIELD, CanonicalValue::integer(1));
});
assert_eq!(
ModelEnablementBody::read(&damaged).expect_err("a price id that is not text"),
ModelError::FieldType {
reference: damaged.reference,
field: APPROVED_PRICE_ID_PATH
}
);
let alias = typed_alias(
&tenant_id(1),
&project_id(2),
32,
"fast",
&[reference_of(30)],
);
for (field, path) in [
(ENABLEMENT_ID_FIELD, TARGET_ENABLEMENT_ID_PATH),
(VERSION_FIELD, TARGET_VERSION_PATH),
] {
let damaged = with_first_target(&alias, |target| {
target.retain(|(name, _)| name != field);
});
assert_eq!(
ModelAliasBody::read(&damaged).expect_err("a target missing a value is refused"),
ModelError::MissingField {
reference: damaged.reference,
field: path
}
);
}
let damaged = with_first_target(&alias, |target| {
set(target, ENABLEMENT_ID_FIELD, CanonicalValue::integer(1));
});
assert_eq!(
ModelAliasBody::read(&damaged).expect_err("a target id that is not text"),
ModelError::FieldType {
reference: damaged.reference,
field: TARGET_ENABLEMENT_ID_PATH
}
);
}
#[test]
fn an_alias_whose_schema_marker_is_damaged_is_refused_rather_than_skipped() {
let alias = typed_alias(
&tenant_id(1),
&project_id(2),
32,
"fast",
&[reference_of(30)],
);
for marker in [
CanonicalValue::integer(1),
CanonicalValue::List(vec![CanonicalValue::string(MODEL_ALIAS_SCHEMA)]),
CanonicalValue::map([(SCHEMA_FIELD, CanonicalValue::string(MODEL_ALIAS_SCHEMA))]),
] {
let damaged = with_fields(&alias, |fields| {
set(fields, SCHEMA_FIELD, marker.clone());
});
let state = state_replacing(damaged.clone());
let error = Models::of(&state).expect_err("a damaged schema marker is refused");
assert_eq!(
error,
ModelError::DamagedSchema {
reference: damaged.reference
}
);
assert!(
!error.is_incompatible(),
"a marker no release wrote is corruption, not skew: {error}"
);
let detail = error.to_string();
assert!(
detail.contains("no release wrote") && detail.contains("restore"),
"a corruption refusal must say what to do: {detail}"
);
let error = state
.validate()
.expect_err("a revision carrying a damaged alias is not valid");
assert!(
matches!(model_error(&error), Some(ModelError::DamagedSchema { .. })),
"{error}"
);
}
}
#[test]
fn a_damaged_alias_marker_hydrates_as_damage_not_skew() {
let candidate = candidate(ExpectedRevision::Empty, "models", state_with_models());
let manifest =
RevisionManifest::of(revision_id(1), None, SystemTime::UNIX_EPOCH, &candidate)
.expect("the fixture state is publishable");
let mut damaged = DesiredState::new();
for blob in candidate.state.blobs() {
damaged.declare_blob(*blob);
}
for resource in candidate.state.resources() {
let resource = if resource.reference.kind == ResourceKind::Alias {
with_fields(resource, |fields| {
set(fields, SCHEMA_FIELD, CanonicalValue::integer(1));
})
} else {
resource.clone()
};
damaged.insert(resource).expect("distinct references");
}
let error = LoadedRevision::assemble(manifest, damaged)
.expect_err("a damaged alias marker must not hydrate");
assert!(
matches!(
error,
IntegrityError::Invalid(ValidationError::Model(ref refusal))
if matches!(**refusal, ModelError::DamagedSchema { .. })
),
"{error}"
);
assert!(
!error.is_incompatible(),
"damaged storage is not a build to roll forward: {error}"
);
assert!(
error.to_string().contains("restore the row"),
"the alert must name the repair: {error}"
);
}
#[test]
fn alias_rows_published_before_this_slice_still_load() {
let state = state();
state
.validate()
.expect("an untyped alias body is not this build's to read");
let models = Models::of(&state).unwrap();
assert_eq!(models.aliases().len(), 0);
assert_eq!(models.enablements().len(), 0);
}
#[test]
fn an_untyped_enablement_is_refused_rather_than_skipped() {
let typed = state_with_models();
let mut state = DesiredState::new();
for blob in typed.blobs() {
state.declare_blob(*blob);
}
for resource in typed.resources() {
let resource = if resource.reference.kind == ResourceKind::ModelEnablement {
with_fields(resource, |fields| {
fields.retain(|(field, _)| field != SCHEMA_FIELD);
})
} else {
resource.clone()
};
state.insert(resource).expect("distinct references");
}
let error = Models::of(&state).expect_err("an untyped enablement is refused");
assert!(
matches!(
error,
ModelError::MissingField {
field: SCHEMA_FIELD,
..
}
),
"{error} should name the missing schema"
);
assert!(
error.is_incompatible(),
"a body with no schema is a compatibility refusal, not corruption"
);
}
#[test]
fn a_body_this_build_cannot_read_hydrates_as_an_incompatibility_not_corruption() {
let candidate = candidate(ExpectedRevision::Empty, "models", state_with_models());
let manifest =
RevisionManifest::of(revision_id(1), None, SystemTime::UNIX_EPOCH, &candidate)
.expect("the fixture state is publishable");
let mut newer = DesiredState::new();
for blob in candidate.state.blobs() {
newer.declare_blob(*blob);
}
for resource in candidate.state.resources() {
let resource = if resource.reference.kind == ResourceKind::Alias {
with_fields(resource, |fields| {
set(
fields,
SCHEMA_FIELD,
CanonicalValue::string("axond.model-alias.v2"),
);
})
} else {
resource.clone()
};
newer.insert(resource).expect("distinct references");
}
let error = LoadedRevision::assemble(manifest, newer)
.expect_err("a newer alias schema must not hydrate");
assert!(
matches!(
error,
IntegrityError::Incompatible(BodySkew::Model(ref skew))
if matches!(**skew, ModelError::Schema { .. })
),
"{error}"
);
assert!(error.is_incompatible());
assert!(
!error.to_string().contains("unreadable"),
"intact storage must not be described as unreadable: {error}"
);
assert!(
!ModelError::DuplicateOffering {
reference: reference_of(30),
offering: offering_id("gpt-4o"),
conflicting: reference_of(31),
}
.is_incompatible()
);
}
#[test]
fn an_owner_is_the_scope_it_came_from_and_reaches_only_what_it_owns() {
let tenant = tenant_id(1);
let project = project_id(2);
assert_eq!(
ModelOwner::from_scope(&ResourceScope::Tenant(tenant)),
Some(ModelOwner::tenant(tenant))
);
assert_eq!(
ModelOwner::from_scope(&ResourceScope::Project { tenant, project }),
Some(ModelOwner::project(tenant, project))
);
assert_eq!(ModelOwner::from_scope(&ResourceScope::Deployment), None);
for owner in [
ModelOwner::tenant(tenant),
ModelOwner::project(tenant, project),
] {
assert_eq!(ModelOwner::from_scope(&owner.scope()), Some(owner));
assert!(owner.reaches(ModelOwner::tenant(tenant)));
assert!(!owner.reaches(ModelOwner::tenant(tenant_id(11))));
assert!(!owner.reaches(ModelOwner::project(tenant, project_id(9))));
}
assert!(
!ModelOwner::tenant(tenant).reaches(ModelOwner::project(tenant, project)),
"a tenant default does not reach into one of its projects"
);
assert_eq!(
ModelOwner::project(tenant, project).to_string(),
format!("{tenant}/{project}")
);
assert_eq!(ModelOwner::from_scope(&blob_backed_catalog(5).scope), None);
}
}