use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::decimal::Decimal;
use crate::definition::{ParamSource, Predicate, ProvenanceClass, TypeDecl};
use crate::grade::Grade;
use crate::ids::ContentDigest;
use crate::time::{DurationMs, Timestamp};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Actionability {
pub caller_controlled: Vec<ProvenanceClass>,
pub remediations: Vec<RemediationDecl>,
pub questions: Vec<QuestionDecl>,
pub constraint_groups: Vec<ConstraintGroup>,
pub checks: Vec<CheckDecl>,
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum Party {
None,
Caller,
Host,
Upstream,
}
impl Party {
pub fn combine<I: IntoIterator<Item = Party>>(parties: I) -> Party {
parties.into_iter().max().unwrap_or(Party::None)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct RemediationDecl {
pub code: String,
#[serde(rename = "for")]
pub for_party: Party,
pub params: Vec<RemediationParam>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct RemediationParam {
pub name: String,
#[serde(rename = "type")]
pub ty: TypeDecl,
}
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, schemars::JsonSchema,
)]
#[serde(deny_unknown_fields)]
pub struct QuestionDecl {
pub id: String,
pub fields: Vec<String>,
pub text: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ConstraintGroup {
pub name: String,
pub checks: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct CheckDecl {
pub id: String,
pub subject: String,
pub test: Predicate,
pub on_fail: OnFail,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct OnFail {
pub by_provenance: BTreeMap<ProvenanceClass, Consequence>,
pub default: DefaultConsequence,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DefaultConsequence {
None,
#[serde(untagged)]
Consequence(Consequence),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum Consequence {
Refuse {
remediation: RemediationUse,
#[serde(default, skip_serializing_if = "Address::is_computed")]
address: Address,
},
DropField {
notice: String,
#[serde(default, skip_serializing_if = "Address::is_computed")]
address: Address,
},
Relax {
group: String,
notice: String,
#[serde(default, skip_serializing_if = "Address::is_computed")]
address: Address,
},
DowngradeGrade {
to: Grade,
notice: String,
#[serde(default, skip_serializing_if = "Address::is_computed")]
address: Address,
},
Abstain,
Escalate {
reason: String,
},
ContinueWithNotice {
notice: String,
#[serde(default, skip_serializing_if = "Address::is_computed")]
address: Address,
},
}
impl Consequence {
pub fn is_terminal(&self) -> bool {
matches!(
self,
Consequence::Refuse { .. } | Consequence::Abstain | Consequence::Escalate { .. }
)
}
pub fn kind(&self) -> &'static str {
match self {
Consequence::Refuse { .. } => "refuse",
Consequence::DropField { .. } => "drop_field",
Consequence::Relax { .. } => "relax",
Consequence::DowngradeGrade { .. } => "downgrade_grade",
Consequence::Abstain => "abstain",
Consequence::Escalate { .. } => "escalate",
Consequence::ContinueWithNotice { .. } => "continue_with_notice",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Address {
#[default]
Computed,
#[serde(untagged)]
Override(AddressOverride),
}
impl Address {
pub fn is_computed(&self) -> bool {
matches!(self, Address::Computed)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct AddressOverride {
pub to: Party,
pub reason: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(untagged)]
pub enum RemediationUse {
Code(String),
WithParams(RemediationCall),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct RemediationCall {
pub code: String,
pub params: BTreeMap<String, ParamSource>,
}
impl RemediationUse {
pub fn code(&self) -> &str {
match self {
RemediationUse::Code(c) => c,
RemediationUse::WithParams(c) => &c.code,
}
}
}
pub const BUILTIN_REMEDIATIONS: &[&str] = &[
"provide_fields",
"refresh_fields",
"correct_fields",
"resolve_conflict",
"improve_evidence",
"retry",
"report_backend",
"raise_budget",
"provide_capability",
"retry_after",
];
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum RemediationValue {
Bool(bool),
Integer(i64),
Decimal(Decimal),
Text(String),
Enum(String),
Timestamp(Timestamp),
Duration(DurationMs),
List(Vec<RemediationValue>),
}
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum Remediation {
None,
#[serde(untagged)]
Some {
code: String,
params: BTreeMap<String, RemediationValue>,
},
}
impl Remediation {
pub fn code(code: &str) -> Remediation {
Remediation::Some {
code: code.into(),
params: BTreeMap::new(),
}
}
pub fn with(
code: &str,
params: impl IntoIterator<Item = (String, RemediationValue)>,
) -> Remediation {
Remediation::Some {
code: code.into(),
params: params.into_iter().collect(),
}
}
pub fn texts<I: IntoIterator<Item = String>>(items: I) -> RemediationValue {
RemediationValue::List(items.into_iter().map(RemediationValue::Text).collect())
}
}
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum CauseProvenance {
Observed { class: ProvenanceClass },
Accepted { classes: Vec<ProvenanceClass> },
}
#[derive(
Debug,
Clone,
Copy,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum Origin {
#[default]
Upstream,
Host,
}
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum Cause {
Input {
field: String,
provenance: CauseProvenance,
},
Backend {
step: String,
origin: Origin,
},
Budget {
resource: String,
},
Deadline,
Capability {
name: String,
},
Policy {
site: String,
},
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum OverrideBasis {
Declared,
Remediation,
}
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum Override {
None,
#[serde(untagged)]
Applied {
to: Party,
basis: OverrideBasis,
reason: String,
},
}
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, schemars::JsonSchema,
)]
#[serde(deny_unknown_fields)]
pub struct Action {
pub addressed_to: Party,
pub computed: Party,
#[serde(rename = "override")]
pub override_: Override,
pub remediation: Remediation,
pub caused_by: Vec<Cause>,
pub transient: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Refusal {
pub check: String,
pub subject: String,
pub action: Action,
}
pub type Question = QuestionDecl;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct NeedsInput {
pub fields: Vec<String>,
pub questions: Vec<Question>,
pub continuation: ContentDigest,
pub action: Action,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ContinuationInputs<'a> {
pub plan_id: &'a str,
pub snapshot_id: &'a str,
pub evaluation_time_ms: i64,
pub site: &'a str,
pub fields: &'a [String],
pub questions: &'a [String],
}
pub const CONTINUATION_TAG: &str = "rustev.continuation/1";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum CheckStatus {
Passed,
Failed {
class: ProvenanceClass,
consequence: String,
},
NotEvaluated {
reason: crate::judgment::Unresolved,
},
Unevaluable {
reason: crate::judgment::Unresolved,
},
Relaxed {
by: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct CheckRecord {
pub check: String,
pub subject: String,
pub status: CheckStatus,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct PlanActionability {
pub checks: Vec<StaticCheck>,
pub sites: Vec<StaticSite>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct StaticCheck {
pub check: String,
pub subject: String,
pub branches: Vec<StaticBranch>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct StaticBranch {
pub classes: Vec<ProvenanceClass>,
pub consequence: String,
pub causes: Vec<Cause>,
pub party: Party,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct StaticSite {
pub site: String,
pub causes: Vec<Cause>,
pub party: Party,
}
pub const CALLER_CONTROLLABLE: [ProvenanceClass; 3] = [
ProvenanceClass::UserSupplied,
ProvenanceClass::AuthenticatedAppField,
ProvenanceClass::AttributedClaim,
];
pub fn is_code(s: &str) -> bool {
let b = s.as_bytes();
!b.is_empty()
&& b.len() <= 64
&& b[0].is_ascii_lowercase()
&& b.iter()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || *c == b'_')
}