use serde::{Deserialize, Serialize};
use crate::actionability::PlanActionability;
use crate::calibration::CalibrationBinding;
use crate::definition::{Definition, RequiredKind};
use crate::descriptor::OutputKind;
use crate::execution::ExecutionPolicy;
use crate::grade::ValueGrade;
use crate::ids::{
ArtifactId, CalibrationId, DefinitionId, DescriptorId, ExecutionPolicyId, PlanId, SourceId,
};
use crate::judgment::{Derivation, Notice};
use crate::limits::PLAN_V1;
use crate::schema;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Plan {
pub schema: String,
pub compiler: String,
pub registry: String,
pub definition_id: DefinitionId,
pub definition: Definition,
pub order: Vec<String>,
pub steps: Vec<PlanStep>,
pub notices: Vec<Notice>,
pub execution: PlanExecution,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sources: Option<Vec<BoundSource>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub grades: Option<Vec<StaticGrade>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub actionability: Option<PlanActionability>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub instantiation: Option<Instantiation>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::screening::present"
)]
#[schemars(with = "crate::screening::PlanScreening")]
pub screening: Option<crate::screening::PlanScreening>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::acquisition_record::list"
)]
#[schemars(
with = "Vec<crate::acquisition_record::PlanAcquisition>",
length(min = 1, max = 4096)
)]
pub acquisitions: Option<Vec<crate::acquisition_record::PlanAcquisition>>,
}
impl crate::Document for Plan {
const SCHEMA: &'static str = schema::PLAN;
const LIMITS: crate::bounded::ParseLimits = PLAN_V1;
fn schema(&self) -> &str {
&self.schema
}
fn parse(bytes: &[u8]) -> Result<Self, crate::DocumentError> {
use crate::DocumentError;
use crate::bounded::{ParseError, parse_bounded};
let doc: Self = parse_bounded(bytes, &Self::LIMITS).map_err(DocumentError::Parse)?;
if doc.schema != schema::PLAN && doc.schema != schema::PLAN_V3 {
return Err(DocumentError::Schema {
expected: "rustev.plan/2 or rustev.plan/3",
found: doc.schema.chars().take(80).collect(),
});
}
if doc.schema != schema::PLAN_V3
&& doc.steps.iter().any(|s| {
matches!(
s.detail,
PlanStepDetail::Generate(_)
| PlanStepDetail::Extract(_)
| PlanStepDetail::AcceptModelDerived(_)
| PlanStepDetail::Rank(_)
| PlanStepDetail::Select(_)
)
})
{
return Err(DocumentError::Parse(ParseError::Schema {
message: "model step details require rustev.plan/3".into(),
}));
}
if doc.schema != schema::PLAN_V3 && doc.acquisitions.is_some() {
return Err(DocumentError::Parse(ParseError::Schema {
message: "acquisitions require rustev.plan/3".into(),
}));
}
if doc.schema != schema::PLAN_V3 && doc.screening.is_some() {
return Err(DocumentError::Parse(ParseError::Schema {
message: "screening is a rustev.plan/3 member".into(),
}));
}
if doc.schema != schema::PLAN_V3
&& serde_json::from_slice::<PlanMemberPresence>(bytes)
.map_err(|e| {
DocumentError::Parse(ParseError::Schema {
message: e.to_string(),
})
})?
.instantiation
{
return Err(DocumentError::Parse(ParseError::Schema {
message: "instantiation is a rustev.plan/3 member".into(),
}));
}
Ok(doc)
}
fn record_digest(&self) -> Result<crate::ids::ContentDigest, crate::canonical::CanonicalError> {
Ok(crate::ids::ContentDigest::from_digest(
crate::canonical::tagged_digest(&self.schema, &self.record_canonical()?),
))
}
}
#[derive(Deserialize)]
struct PlanMemberPresence {
#[serde(default, deserialize_with = "member_present")]
instantiation: bool,
}
fn member_present<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
serde::de::IgnoredAny::deserialize(deserializer).map(|_| true)
}
impl crate::Identified for Plan {
type Id = PlanId;
fn wrap(digest: String) -> PlanId {
PlanId::from_digest(digest)
}
fn id(&self) -> Result<PlanId, crate::canonical::CanonicalError> {
Ok(Self::wrap(crate::canonical::tagged_digest(
&self.schema,
&crate::Document::canonical(self)?,
)))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct BoundSource {
pub name: String,
pub version: String,
pub id: SourceId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct StaticGrade {
pub subject: String,
pub ceiling: ValueGrade,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum PlanExecution {
None,
Declared(Box<DeclaredExecution>),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DeclaredExecution {
pub id: ExecutionPolicyId,
pub policy: ExecutionPolicy,
pub fallbacks: Vec<FallbackBinding>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct FallbackBinding {
pub step: String,
pub target: u32,
pub backend_id: String,
pub artifact: ArtifactId,
pub descriptor: DescriptorId,
pub output: OutputKind,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct PlanStep {
pub id: String,
pub derivation: Derivation,
pub detail: PlanStepDetail,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum PlanStepDetail {
Acquire,
Widen,
Exact {
op: String,
version: u32,
},
Semantic(Box<SemanticBinding>),
Unsupported {
capability: String,
},
Corroborate,
Generate(Box<crate::generation::GenerateBinding>),
Extract(Box<crate::extraction_results::ExtractBinding>),
AcceptModelDerived(Box<crate::extraction::AcceptModelDerived>),
Rank(Box<crate::ranking_result::RankBinding>),
Select(Box<crate::ranking::Select>),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SemanticBinding {
pub backend_id: String,
pub artifact: ArtifactId,
pub descriptor: DescriptorId,
pub output: OutputKind,
pub requires: RequiredKind,
pub normalization: Normalization,
pub calibration: BoundCalibration,
pub fallback_taken: bool,
pub max_requests: u64,
pub max_projection_bytes: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Normalization {
None,
#[serde(rename = "softmax/1")]
Softmax1,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum BoundCalibration {
None,
Bound {
id: CalibrationId,
binding: CalibrationBinding,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Instantiation {
pub template_schema: String,
pub template: serde_json::Value,
pub template_id: DefinitionId,
pub variant: Variant,
pub profile: ProfileChain,
pub parameters: Vec<ResolvedParameter>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum Variant {
None,
Named(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum ProfileChain {
None,
Chain(Vec<ProfileEntry>),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ProfileEntry {
pub profile: String,
pub version: String,
pub id: crate::ids::ProfileId,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ResolvedParameter {
pub name: String,
pub value: serde_json::Value,
pub source: ParameterSource,
pub sites: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum ParameterSource {
Default,
Profile(String),
}