use std::collections::HashMap;
use std::sync::Arc;
use std::time::SystemTime;
use async_trait::async_trait;
use crate::config::{Config, ConfigError};
use crate::desired_state::pricing::{
EffectiveInstant, InvalidInstant, PriceBooks, PricingError, PricingSnapshot,
};
use crate::desired_state::{DesiredState, LoadedRevision, ResourceRef, RevisionId};
use crate::policy::ActivationRefusal;
use crate::state::{ConfigSnapshot, SnapshotError};
use super::secrets::{MaterialLedger, SecretMaterialization};
pub trait RevisionProjection: Send + Sync {
fn name(&self) -> &'static str;
fn project(
&self,
bootstrap: &Config,
state: &DesiredState,
source: RevisionId,
) -> Result<Config, ProjectionError>;
}
#[derive(Debug, thiserror::Error)]
pub enum ProjectionError {
#[error("desired state does not describe a servable deployment: {detail}")]
Incomplete { detail: String },
#[error("{reference} carries a body this build cannot read: {detail}")]
Body {
reference: ResourceRef,
detail: String,
},
#[error("secret `{reference}` referenced by {holder} could not be resolved: {detail}")]
Secret {
holder: ResourceRef,
reference: String,
detail: String,
},
}
#[derive(Debug, thiserror::Error)]
pub enum CompileError {
#[error("revision {revision} does not project onto a servable configuration: {source}")]
Projection {
revision: RevisionId,
#[source]
source: ProjectionError,
},
#[error("revision {revision} fails the configuration gate boot applies: {source}")]
Validation {
revision: RevisionId,
#[source]
source: ConfigError,
},
#[error("revision {revision} does not carry pricing this build can bill: {source}")]
Pricing {
revision: RevisionId,
#[source]
source: Box<PricingError>,
},
#[error("revision {revision} cannot be priced against this host's clock: {source}")]
Clock {
revision: RevisionId,
#[source]
source: InvalidInstant,
},
#[error("revision {revision} could not be compiled into a runtime snapshot: {source}")]
Snapshot {
revision: RevisionId,
#[source]
source: SnapshotError,
},
#[error("revision {revision} cannot activate its policy: {source}")]
Activation {
revision: RevisionId,
#[source]
source: ActivationRefusal,
},
}
impl CompileError {
pub const REASONS: &'static [&'static str] = &[
"secret",
"projection",
"validation",
"pricing",
"clock",
"snapshot",
"unsupported",
"migration",
"refused",
"withdrawn",
"ungoverned",
"invalid_policy",
];
pub const fn revision(&self) -> RevisionId {
match self {
Self::Projection { revision, .. }
| Self::Validation { revision, .. }
| Self::Pricing { revision, .. }
| Self::Clock { revision, .. }
| Self::Snapshot { revision, .. }
| Self::Activation { revision, .. } => *revision,
}
}
pub const fn reason(&self) -> &'static str {
match self {
Self::Projection {
source: ProjectionError::Secret { .. },
..
} => "secret",
Self::Projection { .. } => "projection",
Self::Validation { .. } => "validation",
Self::Pricing { .. } => "pricing",
Self::Clock { .. } => "clock",
Self::Snapshot { .. } => "snapshot",
Self::Activation { source, .. } => source.reason(),
}
}
}
#[async_trait]
pub trait CandidateCompiler: Send + Sync {
async fn compile(
&self,
revision: &LoadedRevision,
generation: u64,
) -> Result<ConfigSnapshot, CompileError>;
}
pub struct RevisionCompiler<P> {
bootstrap: Config,
env: HashMap<String, String>,
projection: P,
secrets: Arc<SecretMaterialization>,
clock: fn() -> SystemTime,
}
impl<P: RevisionProjection> RevisionCompiler<P> {
pub fn new(bootstrap: Config, env: HashMap<String, String>, projection: P) -> Self {
Self::with_secrets(
bootstrap,
env,
projection,
Arc::new(SecretMaterialization::stateless(MaterialLedger::new())),
)
}
pub const fn with_secrets(
bootstrap: Config,
env: HashMap<String, String>,
projection: P,
secrets: Arc<SecretMaterialization>,
) -> Self {
Self {
bootstrap,
env,
projection,
secrets,
clock: SystemTime::now,
}
}
#[must_use]
pub const fn with_clock(mut self, clock: fn() -> SystemTime) -> Self {
self.clock = clock;
self
}
pub fn projection_name(&self) -> &'static str {
self.projection.name()
}
pub fn secrets(&self) -> &Arc<SecretMaterialization> {
&self.secrets
}
fn pricing(&self, revision: &LoadedRevision) -> Result<Option<PricingSnapshot>, CompileError> {
let id = revision.id();
let books = PriceBooks::of(revision.state()).map_err(|source| CompileError::Pricing {
revision: id,
source: Box::new(source),
})?;
if books.book().is_none() {
return Ok(None);
}
let at = EffectiveInstant::of((self.clock)()).map_err(|source| CompileError::Clock {
revision: id,
source,
})?;
Ok(books.snapshot_at(at))
}
}
#[async_trait]
impl<P: RevisionProjection> CandidateCompiler for RevisionCompiler<P> {
async fn compile(
&self,
revision: &LoadedRevision,
generation: u64,
) -> Result<ConfigSnapshot, CompileError> {
let id = revision.id();
let config = self
.projection
.project(&self.bootstrap, revision.state(), id)
.map_err(|source| CompileError::Projection {
revision: id,
source,
})?;
config
.validate_compiled()
.map_err(|source| CompileError::Validation {
revision: id,
source,
})?;
let pricing = self.pricing(revision)?;
let secrets = self
.secrets
.resolve(revision.state())
.await
.map_err(|source| CompileError::Projection {
revision: id,
source,
})?;
let snapshot = ConfigSnapshot::build_with(config, &self.env, generation, secrets).map_err(
|source| CompileError::Snapshot {
revision: id,
source,
},
)?;
Ok(match pricing {
None => snapshot,
Some(pricing) => snapshot.with_pricing(pricing),
})
}
}
#[cfg(test)]
pub(crate) mod testing {
use super::*;
use crate::desired_state::fixtures;
pub(crate) struct AliasProjection {
pub(crate) provider: &'static str,
}
impl RevisionProjection for AliasProjection {
fn name(&self) -> &'static str {
"test-alias"
}
fn project(
&self,
bootstrap: &Config,
state: &DesiredState,
_source: RevisionId,
) -> Result<Config, ProjectionError> {
let mut config = bootstrap.clone();
for resource in state.resources() {
if resource.reference.kind != crate::desired_state::ResourceKind::Alias {
continue;
}
config.model.push(crate::config::Model {
name: resource.slug.as_str().to_owned(),
targets: vec![crate::config::Target {
provider: self.provider.to_owned(),
model: "gpt-4o".to_owned(),
price: gateway_core::catalog::ModelPrice {
input_microdollars_per_million: 1,
output_microdollars_per_million: 1,
reasoning_microdollars_per_million: None,
cache_read_microdollars_per_million: None,
cache_write_microdollars_per_million: None,
},
}],
});
}
Ok(config)
}
}
pub(crate) struct RefusingProjection;
impl RevisionProjection for RefusingProjection {
fn name(&self) -> &'static str {
"test-refusing"
}
fn project(
&self,
_: &Config,
_: &DesiredState,
_: RevisionId,
) -> Result<Config, ProjectionError> {
Err(ProjectionError::Incomplete {
detail: "no tenant is enabled".to_owned(),
})
}
}
pub(crate) fn bootstrap() -> Config {
Config::from_toml_str(
r#"
[[namespace]]
id = "platform"
default = true
[[provider]]
id = "openai"
kind = "openai"
base_url = "https://api.openai.com/v1"
[[gateway_key]]
env = "AXOND_KEY"
namespace = "platform"
"#,
)
.expect("a valid bootstrap config")
}
pub(crate) fn env() -> HashMap<String, String> {
HashMap::from([("AXOND_KEY".to_owned(), "inbound-secret".to_owned())])
}
pub(crate) fn revision() -> LoadedRevision {
revision_with(fixtures::state())
}
pub(crate) fn revision_with(state: DesiredState) -> LoadedRevision {
let candidate = fixtures::candidate(
crate::desired_state::ExpectedRevision::Empty,
"first",
state,
);
let manifest = crate::desired_state::RevisionManifest::of(
fixtures::revision_id(9),
None,
std::time::SystemTime::UNIX_EPOCH,
&candidate,
)
.expect("a valid manifest");
LoadedRevision::assemble(manifest, candidate.state).expect("a consistent revision")
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::testing::{
AliasProjection, RefusingProjection, bootstrap, env, revision, revision_with,
};
use super::*;
use crate::backends::catalog::ProviderId;
use crate::desired_state::fixtures;
use crate::desired_state::pricing::{
Approval, EffectiveInterval, PriceBookBody, RulePrecedence,
};
#[tokio::test]
async fn a_projected_revision_compiles_into_a_snapshot_at_the_requested_generation() {
let compiler = RevisionCompiler::with_secrets(
bootstrap(),
env(),
AliasProjection { provider: "openai" },
crate::convergence::secrets::testing::permissive(),
);
let snapshot = compiler
.compile(&revision(), 7)
.await
.expect("the projected config is servable");
assert_eq!(snapshot.generation, 7);
assert!(
snapshot
.config
.model
.iter()
.any(|model| model.name == "fast")
);
}
#[tokio::test]
async fn a_revision_whose_alias_targets_an_undefined_provider_is_refused_by_the_boot_gate() {
let compiler = RevisionCompiler::with_secrets(
bootstrap(),
env(),
AliasProjection {
provider: "nonexistent",
},
crate::convergence::secrets::testing::permissive(),
);
let error = compiler
.compile(&revision(), 1)
.await
.err()
.expect("an undefined target cannot be served");
assert_eq!(error.reason(), "validation");
assert!(error.to_string().contains("undefined provider"), "{error}");
}
#[tokio::test]
async fn an_unreadable_revision_is_refused_before_any_configuration_is_built() {
let compiler = RevisionCompiler::with_secrets(
bootstrap(),
env(),
RefusingProjection,
crate::convergence::secrets::testing::permissive(),
);
let error = compiler
.compile(&revision(), 1)
.await
.err()
.expect("the projection refuses");
assert_eq!(error.reason(), "projection");
assert_eq!(error.revision(), fixtures::revision_id(9));
}
#[tokio::test]
async fn an_approved_book_is_published_in_the_same_snapshot_as_the_routing_config() {
let body = fixtures::approved_price_book();
let compiler = RevisionCompiler::with_secrets(
bootstrap(),
env(),
AliasProjection { provider: "openai" },
crate::convergence::secrets::testing::permissive(),
);
let snapshot = compiler
.compile(&revision_with(fixtures::state_with_price_book(&body)), 3)
.await
.expect("an approved book is servable");
let pricing = snapshot.pricing().expect("the revision carries pricing");
assert!(pricing.is_approved());
assert_eq!(pricing.catalog(), fixtures::catalog_content_id());
assert_eq!(
pricing
.price(&ProviderId::parse("openai").expect("id"), "gpt-4o")
.expect("the book prices the routed target")
.input_microdollars_per_million,
2_500
);
assert_eq!(snapshot.generation, 3);
assert!(
snapshot
.config
.model
.iter()
.any(|model| model.name == "fast")
);
}
#[tokio::test]
async fn a_revision_without_a_price_book_compiles_without_pricing() {
let compiler = RevisionCompiler::with_secrets(
bootstrap(),
env(),
AliasProjection { provider: "openai" },
crate::convergence::secrets::testing::permissive(),
);
let snapshot = compiler
.compile(&revision(), 1)
.await
.expect("pricing is not a precondition for routing");
assert!(snapshot.pricing().is_none());
assert!(
snapshot
.config
.model
.iter()
.any(|model| model.name == "fast")
);
}
#[tokio::test]
async fn a_draft_book_publishes_its_identity_and_no_prices() {
let body = PriceBookBody::new(fixtures::catalog_content_id(), Approval::Draft).with_rule(
fixtures::price_rule(
fixtures::priced_target("openai", "gpt-4o"),
RulePrecedence::Baseline,
EffectiveInterval::from(EffectiveInstant::EPOCH),
1_000,
1_000,
),
);
let compiler = RevisionCompiler::with_secrets(
bootstrap(),
env(),
AliasProjection { provider: "openai" },
crate::convergence::secrets::testing::permissive(),
);
let snapshot = compiler
.compile(&revision_with(fixtures::state_with_price_book(&body)), 1)
.await
.expect("a draft book does not refuse the candidate");
let pricing = snapshot.pricing().expect("the identity is published");
assert!(!pricing.is_approved());
assert!(pricing.is_empty());
}
#[tokio::test]
async fn a_host_clock_before_the_epoch_refuses_the_candidate_rather_than_guessing() {
let body = fixtures::approved_price_book();
let compiler = RevisionCompiler::with_secrets(
bootstrap(),
env(),
AliasProjection { provider: "openai" },
crate::convergence::secrets::testing::permissive(),
)
.with_clock(|| SystemTime::UNIX_EPOCH - Duration::from_secs(1));
let error = compiler
.compile(&revision_with(fixtures::state_with_price_book(&body)), 1)
.await
.err()
.expect("an instant off the timeline cannot price a revision");
assert_eq!(error.reason(), "clock");
assert_eq!(error.revision(), fixtures::revision_id(9));
}
#[tokio::test]
async fn unresolvable_secret_material_refuses_the_candidate_without_disclosing_it() {
let compiler = RevisionCompiler::with_secrets(
bootstrap(),
HashMap::new(),
AliasProjection { provider: "openai" },
crate::convergence::secrets::testing::permissive(),
);
let error = compiler
.compile(&revision(), 1)
.await
.err()
.expect("an unresolvable gateway key cannot be published");
assert_eq!(error.reason(), "snapshot");
assert!(error.to_string().contains("AXOND_KEY"), "{error}");
}
#[test]
fn every_activation_refusal_is_a_compile_reason() {
for reason in ActivationRefusal::REASONS {
assert!(
CompileError::REASONS.contains(reason),
"`{reason}` is forwarded by `CompileError::Activation` and has to be catalogued \
with the rest"
);
}
}
}